本文主要是介绍React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 01 基础配置,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
页面预览
该实战项目是(不怎么严谨的)电子商务网站。
首页
商品列表页面
登录页面
注册页面
购物车列表
支付完成页面
Dashboard 页面
普通用户页面
购买历史页面
资料更新页面
管理员页面
创建分类页面
创建商品页面
订单列表页面
显示所有用户的订单
客户端技术栈介绍
- 脚本:TypeScript
- 前端框架:React
- 路由管理:react-router-dom
- 用户界面:Ant Design
- 全局状态管理:Redux
- 一部状态更新:redux-saga
- 路由状态同步:connected-react-router
- 网络请求:Axios
- 日期处理工具:Moment
- 调试工具:redux-devtools-extension
创建客户端项目
创建使用 TypeScript 的项目
npx create-react-app ecommerce-front --template typescript
cd ecommerce-front
# 启动项目
npm start
安装项目依赖
npm i @types/react react-router-dom @types/react-router-dom antd redux react-redux @types/react-redux redux-saga connected-react-router axios redux-devtools-extension moment
引入 Antd 样式表
可以引入项目本地模块中的样式表文件,也可以从 CDN 平台引入。
本地文件方式
在 src/index.tsx
中引入
import 'antd/dist/antd.css'
CDN 方式
以 cdnjs 平台为例,修改 public/index.html
,添加 link
标签:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.16.8/antd.min.css" />
删除不需要的文件
├─ src
│ ├─ App.css
│ ├─ App.test.tsx
│ ├─ index.css
│ ├─ logo.svg
│ ├─ reportWebVitals.ts
│ └─ setupTests.ts
删除不需要的代码
// src\index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import '~antd/dist/antd.css'
import App from './App'ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById('root')
)
// src\App.tsx
function App() {return <div>App works</div>
}export default App
配置服务器端 API 请求地址
create-react-app 脚手架内置了 dotenv,允许开发者在 React 项目中配置环境变量,环境变量的名称要求以 REACT_APP_
开头,在项目中通过 process.env.REACT_APP_<name>
访问。
在项目根目录下新建 .env
文件:
# .env
# 以 `#` 开头的行被视为注释
# 生产环境的服务器端 API 地址 应该在 `npm run build` 构建项目时使用
REACT_APP_PRODUCTION_API_URL = http://xxx.com/api
# 开发环境的服务器端 API 地址 应该在 `npm start` 启动项目时使用
REACT_APP_DEVELOPMENT_API_URL = http://localhost/api
直接使用 process.env
访问 API 地址会将环境写死,为了使其根据环境决定使用哪个 API 地址,可以将 API 地址写入配置中。
新建 src/config.ts
文件:
// src\config.ts
export let API: stringif (process.env.NODE_ENV === 'development') {// 在值后添加 `!` 后缀断言值不会是 `null` 或 `undefined` 省略检查// https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#non-null-assertion-operator-postfix-API = process.env.REACT_APP_DEVELOPMENT_API_URL!
} else {API = process.env.REACT_APP_PRODUCTION_API_URL!
}
安装 chrome 扩展
安装扩展
- React Developer Tools:检查 React 组件层次结构、props、hooks 等信息,在页面上显示 React 组件
- Redux DevTools:监测 Store 中状态的变化
Chrome 网上应用店访问受限,可以使用 Microsoft Edge 浏览器,不需要翻墙。
React Developer Tools
Redux DevTools
配置 Redux DevTools
安装完扩展,还需要修改代码,在创建 store 时用 Redux DevTools 的 composeWithDevTools
包裹下 applyMiddleware
,该方法来自安装的 redux-devtools-extension
模块。
import { composeWithDevTools } from 'redux-devtools-extension'export const store = createStore(rootReducer,composeWithDevTools(applyMiddleware(...middlewares))
)
页面组件初始化和路由初始化
创建文件和文件夹
文件命名规范建议(参考 Taro):
- 普通 TS 文件以
.ts
作为后缀 - 组件文件以
.tsx
作为后缀
在 src
目录下添加:
├─ components
│ ├─ admin # 存放登录后访问页面的文件夹
│ └─ core # 存放前台页面组件的文件夹
│ ├─ Layout.tsx # 布局组件
│ ├─ Home.tsx # 首页
│ └─ Shop.tsx # 商品列表页
└─ Routes.tsx # 路由组件
布局组件
// src\components\core\Layout.tsx
import React, { FC } from 'react'// 定义 Layout 组件参数类型的接口
interface Props {children: React.ReactNode
}// FC 表示函数型组件类型
const Layout: FC<Props> = ({ children }) => {return <div>Layout {children}</div>
}export default Layout
首页
// src\components\core\Home.tsx
import Layout from './Layout'const Home = () => {return <Layout>Home</Layout>
}export default Home
商品列表页
// src\components\core\Shop.tsx
import Layout from './Layout'const Shop = () => {return <Layout>Shop</Layout>
}export default Shop
路由组件
// src\Routes.tsx
import { HashRouter, Route, Switch } from 'react-router-dom'
import Home from './components/core/Home'
import Shop from './components/core/Shop'const Routes = () => {return (<HashRouter><Switch><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 'antd/dist/antd.css'
import Routes from './Routes'ReactDOM.render(<React.StrictMode><Routes /></React.StrictMode>,document.getElementById('root')
)
访问页面
npm start
运行,访问:
http://localhost:3000/
http://localhost:3000/#/shop
全局 Store 初始化
创建文件和文件夹
在 src
下添加:
├─ store
│ ├─ reducers
│ │ ├─ index.ts
│ │ └─ test.reducer.ts # 测试 reducer
│ └─ index.ts
// src\store\reducers\test.reducer.ts
export default function testReducer(state: number = 0) {return state
}
// src\store\reducers\index.ts
import { combineReducers } from 'redux'
import testReducer from './test.reducer'const rootReducer = combineReducers({test: testReducer
})export default rootReducer
// src\store\index.ts
import { createStore } from 'redux'
import rootReducer from './reducers'const store = createStore(rootReducer)export default store
注入全局
// src\index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import 'antd/dist/antd.css'
import Routes from './Routes'
import { Provider } from 'react-redux'
import store from './store'ReactDOM.render(<React.StrictMode><Provider store={store}><Routes /></Provider></React.StrictMode>,document.getElementById('root')
)
测试
// src\components\core\Home.tsx
import { useSelector } from 'react-redux'
import Layout from './Layout'const Home = () => {const state = useSelector(state => state)return <Layout>Home {JSON.stringify(state)}</Layout>
}export default Home
访问首页显示:Layout Home {"test":0}
将路由状态同步到全局 Store
connected-react-router 文档
connected-react-router 用于将路由状态同步到 Store。
第一步
在 Root Reducer 文件中,
- 将 rootReducer 更改为创建 rootReducer 的函数(createRootReducer),将历史记录(history)作为参数接收。
- 内部通过向
connectRouter
函数传递 history 实例对象创建 routerReducer ,并添加到返回的 rootReducer 中。 - key 必须是
router
// reducers.js
import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'// 导出的是一个创建 rootReducer 的函数
const createRootReducer = (history) => combineReducers({// connectRouter 返回一个 routerReducerrouter: connectRouter(history),// 其余的 reducers... // rest of your reducers
})
export default createRootReducer
第二步
当创建 Store 时,
- 创建一个 history 实例对象,并导出
- 通过调用
createBrowserHistory/createHashHistory
方法创建 history 实例对象 createBrowserHistory/createHashHistory
是 history 模块提供的 API。- history 模块是 react-router-dom (除了 React 本身)仅有的两个主要依赖项之一
- 它提供了用于在 JavaScript 中各种环境下管理 history 的实现。
- 官方文档:ReactTraining/history
- React Router 文档:history
- 通过调用
- 向
createRootReducer
函数传递 history 对象,创建的 rootReducer 传递给createStore
方法 - 配置用于派发 history actions 的中间件
routerMiddleware()
- 该中间件通过调用
routerMiddleware
生成,方法来自connected-react-router
- 传递 history 对象
- 中间件的作用是监听路由状态,当路由状态更改的时候 dispatch 一个 action
- 该中间件通过调用
// 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(// 第一步编写的追加了 routerReducer 的 rootReducercreateRootReducer(history), // root reducer with router statepreloadedState,compose(applyMiddleware(// 用于派发历史记录操作的中间件routerMiddleware(history), // for dispatching history actions// ... 其它中间件 ...),),)return store
}
第三步
- 用
ConnectedRouter
组件包裹根组件,并将第二步创建的 history 对象传递给组件- 该组件用于让内部组件可以获取路由状态
- 记得删除
BrowserRouter
、HashRouter
或NativeRouter
。 - 将
ConnectedRouter
组件作为 react-redux 的Provider
子级放置 - 注意:如果进行服务器端渲染,仍然应该使用
StaticRouter
// 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')
)
注意:提供给 routerReducer、routerMiddleware 和 ConnectedRouter 组件的 history 对象必须是同一个对象。
修改代码
第一步
// src\store\reducers\index.ts
import { connectRouter } from 'connected-react-router'
import { History } from 'history'
import { combineReducers } from 'redux'
// import testReducer from './test.reducer'// 定义一个包含 router 的 store 类型接口 供外部使用
export interface AppState {router: RouterState
}const createRootReducer = (history: History) =>combineReducers({// test: testReducer,router: connectRouter(history)})export default createRootReducer
第二步
// src\store\index.ts
import { applyMiddleware, createStore } from 'redux'
import createRootReducer from './reducers'
import { createHashHistory } from 'history'
import { routerMiddleware } from 'connected-react-router'export const history = createHashHistory()const store = createStore(createRootReducer(history), applyMiddleware(routerMiddleware(history)))export default store
第三步
// src\index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import 'antd/dist/antd.css'
import Routes from './Routes'
import { Provider } from 'react-redux'
import store, { history } from './store'
import { ConnectedRouter } from 'connected-react-router'ReactDOM.render(<React.StrictMode><Provider store={store}><ConnectedRouter history={history}><Routes /></ConnectedRouter></Provider></React.StrictMode>,document.getElementById('root')
)
测试
在 Shop 页面也输入 Redux 状态:
// src\components\core\Shop.tsx
import { useSelector } from 'react-redux'
import Layout from './Layout'const Shop = () => {const state = useSelector(state => state)return <Layout>Shop {JSON.stringify(state)}</Layout>
}export default Shop
# 访问 `http://localhost:3000/` 输出:
Layout Home {"test":0,"router":{"location":{"pathname":"/","search":"","hash":"","query":{}},"action":"POP"}}# 访问 `http://localhost:3000/#/shop?id=1` 输出:
Layout Shop {"test":0,"router":{"location":{"pathname":"/shop","search":"?id=1","hash":"","query":{"id":"1"}},"action":"POP"}}
配置 Redux DevTools 插件
Redux DevTools 插件需要使用composeWithDevTools
包裹 applyMiddleware
:
// src\store\index.ts
import { applyMiddleware, createStore } from 'redux'
import createRootReducer from './reducers'
import { createHashHistory } from 'history'
import { routerMiddleware } from 'connected-react-router'
import { composeWithDevTools } from 'redux-devtools-extension'export const history = createHashHistory()const store = createStore(createRootReducer(history), composeWithDevTools(applyMiddleware(routerMiddleware(history))))export default store
这篇关于React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 01 基础配置的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!