React电商实战项目 - 1

一、项目介绍


1、界面

在这里插入图片描述


2、功能业务

  • 登录、注册
  • 商品检索:模糊搜索、属性筛选(多选)、价格区间筛选
  • 购物流程:购物车 - 订单 - 支付 - 查看订单状态
  • 管理员:创建分类、创建商品、订单列表

3、技术栈

⑴、 客户端

  • 脚本:TypeScript
  • 前端框架:React
  • 路由管理:React-router-dom
  • 用户界面:Antd
  • 全局状态管理:Redux
  • 异步状态更新:redux-saga
  • 路由状态同步:connected-react-router
  • 网络请求:Axios
  • 调试工具:redux-devtools-extension

⑵、 服务端

  • 脚本:Node.js
  • 数据库:Mongodb
  • 数据库可视化:Robo 3T



二、基础配置


1、安装 mongodb 数据库(Mac)


⑴、安装 homebrew

Homebrew 是mac系统中的软件包管理器

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

推荐:国内的镜像地址

$ /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
// 镜像选择推荐选择清华大学TUNA镜像源

⑵、添加 mongodb 仓库源

$ brew tap mongodb/brew

⑶、安装 mongodb

安装前确保系统已经安装 xcode 命令行编译开发工具

$ brew install mongodb-community

$ xcode-select --install 

⑷、启动 mongodb

$ brew services run mongodb-community

⑸、启动 mongodb

brew services stop mongodb-community

⑹、文件位置

  • 数据库配置文件:/usr/local/etc/mongod.conf
  • 数据库文件默认存放位置:/usr/local/var/mongodb
  • 日志存放位置:/usr/local/var/log/mongodb/mongo.log

2、安装 数据库可视化 Robo 3T


3、安装 谷歌浏览器(Chrome) 扩展插件

  1. React Developer Tools - React开发调试工具
    在这里插入图片描述

  1. Redux DevTools
    在这里插入图片描述



三、创建项目


1、打开终端、创建项目

$ npx create-react-app ecommerce-front --template typescript

// 安装依赖
$ npm install antd axios moment redux react-redux react-router-dom redux-saga connected-react-router redux-devtools-extension @types/react-redux @types/react-router-dom

2、用VScode将项目打开


3、将antd的CSS 使用 CDN引入

public文件下的index.html文件

   <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
   // !!!下面添加
   
    <!-- 引用css样式 -->
    <link
      rel="stylesheet"
      href="https://cdn.bootcdn.net/ajax/libs/antd/4.8.3/antd.min.css"
    />

4、初始化目录结构

⑴、删除多余文件

在这里插入图片描述

⑵、src文件夹下的 App.tsx

import React from 'react';

function App() {
  return <div>App works</div>
}

export default App;

⑶、src文件夹下的 index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'))

5、打开新终端(VScode)、运行项目

$ npm start

6、界面效果

在这里插入图片描述



四、配置基础环境


1、配置服务器端API请求地址

⑴、在项目的根目录下新建 .env 文件:

// 生产环境的服务器端 API 地址
REACT_APP_PRODUCTION_API_URL=http://fullstack.net.cn/api

// 开发环境的服务器端 API 地址
REACT_APP_DEVLOPMENT_API_URL=http://localhost/api

在项目中可以通过 process.env.REACT_APP_DEVLOPMENT_API_URL方式进行访问,但是这样会有弊端,其一是代码过长写起来不方便,其二是如果在代码中将环境写死,当切换环境时改起来也不方便。

解决方案就是将 API 地址写入配置中,根据环境决定使用哪个 API 地址

⑵、在src目录下新建 config.ts 文件:

// 根据环境变量决定使用哪一个值
export let API: string

if (process.env.NODE_ENV === "development") {
  API = process.env.REACT_APP_DEVLOPMENT_API_URL!
} else {
  API = process.env.REACT_APP_PRODUCTION_API_URL!
}

⑶、src文件夹下的 index.tsx

测试是否配置成功

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// 引入API
import { API } from './config'

// 测试 配置服务器端API请求地址 是否配置成功
console.log(API)

ReactDOM.render(<App />, document.getElementById('root'))

⑷、打印结果(开发环境)

在这里插入图片描述

⑸、测试生产环境

Ctrl + C 打断项目

// 构建项目
$ npm run build

// 启动构建好的项目
$ serve -s build

检查
在这里插入图片描述



2、页面组件初始化和路由初始化

  1. 在src目录下新建 components 文件夹, 用来放置公共组件
  2. 在components 文件夹下, 新建 core 文件夹, 用来放置 前台核心组件
  3. 在components 文件夹下, 新建 admin 文件夹, 用来放置 和管理员相关的页面

⑴、核心组件 - 在core文件夹下创建

生成函数组件的快捷键

// 快捷键生成函数
// 插件 ES7 React/Redux/GraphQL/React-Native snippets
// fafce => 

import React from 'react'

const test = () => {
    return (
        <div>
            
        </div>
    )
}

export default test

①、Home.tsx - 主页组件
// 引入Layout组件
import Layout from './Layout'

const Home = () => {
    return <Layout>Home</Layout>
}

export default Home

②、Shop.tsx - 商品列表页面组件
// 引入Layout组件
import Layout from './Layout'

const Shop = () => {
    return <Layout>Shop</Layout>
}

export default Shop

③、Layout.tsx - 布局组件

其他页面的组件要作为Layout 的 children 属性存在, 例如:菜单栏

import React, { FC } from 'react'

// Props需要自己定义
interface Props {
    // children为react元素
    children: React.ReactNode
}

// const layout = ({children}) => {
// Layout是函数性组件, 用 FC表示, 其后跟的是Props的数据类型
const layout: FC<Props> = ({children}) => {
    return (
        <div>
            {/* 标识一下Layout */}
            Layout
            {children}
        </div>
    )
}

export default layout

⑵、初始化路由

在 src 文件夹下新建 Routes.tsx

import React from 'react'
import { HashRouter, Switch, Route } from "react-router-dom"

// 引入components组件
import Home from './components/core/Home'
import Shop from './components/core/Shop'

const Routes = () => {
    return (
        // 调用HashRouter
        <HashRouter>
            {/* 调用Switch组件 */}
            <Switch>
                {/* 配置路由规则 */}

                {/* 通过Route配置路由, exact为精准匹配 */}
                <Route path="/" component={ Home } exact/>
                <Route path="/shop" component={ Shop }/>
                
            </Switch>
        </HashRouter>
    )
}

export default Routes

⑶、更改默认渲染的组件 - 主页

编辑在 src 文件夹下的 index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
// import App from './App';
import Routes from "./Routes"

// 引入API
// import { API } from './config'

// 测试 配置服务器端API请求地址 是否配置成功
// console.log(API)

ReactDOM.render(<Routes />, document.getElementById('root'))

⑷、页面效果

npm start运行项目
在这里插入图片描述
在这里插入图片描述

⑸、运行可能缺少的依赖

npm start运行项目

$ npm i --save-dev @types/react-router-dom

$ npm install --save react-redux

$ npm add connected-react-router


2、全局store初始化

  1. 在src目录下新建 store 文件夹
  2. 在store 文件夹下, 新建 index.ts 文件 和 reducers 文件夹
  3. 在 reducers 文件夹下, 新建 index.ts 和 test.reducer.ts 文件

⑴、test.reducer.ts - 测试的ruducer

// 默认导出一个名为 estReducer 的reducer函数
// reducer有一个参数state, 类型是number, 初始值是0
export default function testReducer(state: number = 0) {
    // 将state返回
    return state
}

⑵、index.ts(reducers文件夹下) - root reducer

import { combineReducers } from 'redux';
import testReducer from './test.reducer';

// 使用const这个关键词 声明 rootReducer这个常量, 它的值是combineReducers调用的结果
const rootReducer = combineReducers({

    // 在调用combineReducers的时候,需要传入一个对象,这个对象有一个属性叫test, test的值是testReducer
    test: testReducer,
})

// 通过export default关键字 导出 rootReducer
export default createRootReducer

⑶、index.ts(store文件夹下) - store

import { createStore } from 'redux';
import rootReducer from './reducers/index';

// 这个store就是createStore方法的返回结果
// 在调用createStore方法的时候,需要传入rooReducer
const store = createStore(rootReducer)

// 初始化完毕,导出store
export default store

⑷、src文件夹下的 index.tsx

让其他组件能从store中获取状态

import React from 'react';
import ReactDOM from 'react-dom';
// import App from './App';
import Routes from "./Routes"

// 引入API
// import { API } from './config'

// 测试 配置服务器端API请求地址 是否配置成功
// console.log(API)


// ReactDOM.render(<App />, document.getElementById('root'))
// 改为以Routes为首页
ReactDOM.render(
    // 调用Provider组件,其他组件才能从store中获取状态
    // 将刚刚创建的store赋值给这个store
    <Provider store={store}>
        <Routes/>
    </Provider>
, document.getElementById('root'))

⑸、Home.tsx - src文件夹 下的components文件夹 下的core文件夹

测试能否获取状态

// 首页组件

// import React from 'react'
import { useSelector } from "react-redux"

// 引入Layout
import Layout from './Layout'

const Home = () => {

    // 通过钩子函数获取store的状态,直接返回
    const state = useSelector(state => state)
    return (
        <Layout>
            Home
            {/* 状态是一个对象,不能直接显示,需要转化成字符串 */}
            { JSON.stringify(state) }
        </Layout>
    )
}

export default Home

⑹、页面展示

在这里插入图片描述



3、将路由状态同步到全局store

⑴、connected-react-router的使用方法

①、在您的root reducer文件中
  • 创建一个history以参数为参数并返回根减速器的函数
  • router传递history给,将减速器添加到根减速器中connectRouter
  • 注意:密钥必须为router
// reducers.js
import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'

const createRootReducer = (history) => combineReducers({
  router: connectRouter(history),
  ... // rest of your reducers
})
export default createRootReducer

②、创建Redux存储区时
  • 创建一个history对象
  • 将创建的提供history给root reducer创建者
  • 如果你想要调度历史动作(例如用push(’/path/to/somewhere’)来改变URL),可以使用routerMiddleware(history)
// configureStore.js
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
...
export const history = createBrowserHistory()

export default function configureStore(preloadedState) {
  const store = createStore(
    createRootReducer(history), // root reducer with router state
    preloadedState,
    compose(
      applyMiddleware(
        routerMiddleware(history), // for dispatching history actions
        // ... other middlewares ...
      ),
    ),
  )

  return store
}

③、第三步
  • 将您的react-router v4 / v5路由包装起来ConnectedRouter,并将该history对象作为道具传递。切记删除的任何用法BrowserRouter或将NativeRouter其保留会导致 同步状态时出现问题
  • 将ConnectedRouter作为react-redux的Provider的子节点
  • 注意:如果执行服务器端渲染,则仍应在服务器上使用StaticRouterfrom react-router
// index.js
...
import { Provider } from 'react-redux'
import { Route, Switch } from 'react-router' // react-router v4/v5
import { ConnectedRouter } from 'connected-react-router'
import configureStore, { history } from './configureStore'
...
const store = configureStore(/* provide initial state if any */)

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
      <> { /* your usual react-router v4/v5 routing */ }
        <Switch>
          <Route exact path="/" render={() => (<div>Match</div>)} />
          <Route render={() => (<div>Miss</div>)} />
        </Switch>
      </>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('react-root')
)

⑵、index.ts(reducers文件夹下)

import { connectRouter } from 'connected-react-router';
import { combineReducers } from 'redux';
import testReducer from './test.reducer';
import { History } from 'history';

// 使用const这个关键词 声明 rootReducer这个常量, 它的值是combineReducers调用的结果
// const rootReducer = combineReducers({

// 这个history需要传递过来, 当前的值会变成一个方法, 这个方法会接收一个名为history的参数, 参数的类型就是History
const createRootReducer = (history: History) =>
combineReducers({
    // 在调用combineReducers的时候,需要传入一个对象,这个对象有一个属性叫test, test的值是testReducer
    test: testReducer,
    
    // 添加属性touter, 它的值是connectRouter方法的调用
    // 调用这个方法的时候需要传入一个history
    router: connectRouter(history)
})

// 通过export default关键字 导出 rootReducer
export default createRootReducer

⑶、index.ts(store文件夹下)

// store
import { createStore, applyMiddleware } from 'redux';
// import rootReducer from './reducers/index';
import createRootReducer from './reducers';
import { createHashHistory } from 'history';
import { routerMiddleware } from 'connected-react-router';

//通过export const关键字 导入 history, 它的值就是createHashHistory方法的调用
export const history = createHashHistory()

// 这个store就是createStore方法的返回结果
// 在调用createStore方法的时候,需要传入rooReducer
// 传入参数history
const store = createStore(
    createRootReducer(history),
    // 传入applyMiddleware
    // routerMiddleware的作用就是监听路由状态, 当路由状态更改的时候, 去description一个action
    applyMiddleware(routerMiddleware(history))
)

// 初始化完毕,导出store
export default store

⑷、index.tsx(src文件夹下)

// import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from "react-redux"
import Routes from "./Routes"
import store from "./store/index"
import { ConnectedRouter } from "connected-react-router"
import { history } from './store'
// import App from './App';

// 引入API
import { API } from './config'

console.log(API)

// ReactDOM.render(<App />, document.getElementById('root'))
// 改为以Routes为首页
ReactDOM.render(
    // 调用Provider组件,其他组件才能从store中获取状态
    // 将刚刚创建的store赋值给这个store
    <Provider store={store}>
        {/* 添加一个组件, 在组件中传入history */}
        <ConnectedRouter history={history}>
            <Routes/>
        </ConnectedRouter>
    </Provider>
, document.getElementById('root'))

⑸、页面展示

在这里插入图片描述

⑹、测试 - 改变请求头

http://localhost:3000/#/ => http://localhost:3000/#/?name=zhangsan

在这里插入图片描述

⑺、路由发生改变能否更新

Shop.tsx - core文件夹下

// import React from 'react'
import { useSelector } from 'react-redux'

// 引入Layout组件
import Layout from './Layout'

const Shop = () => {

    // 测试路由切换的时候,状态是不是能更新
    const state = useSelector(state => state)

    return (
        <Layout>
            Shop
            
            {/* 状态是一个对象,不能直接显示,需要转化成字符串 */}
            { JSON.stringify(state) }
        </Layout>
    )
}

export default Shop
  • 8
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后海 0_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值