目的:实现一个react的商城demo,实现含有购物车,登录,注册,商城等功能
源码地址:
使用到的技术栈:
- 脚本:TypeScript
- 前端框架:React
- 路由管理:React-router-dom
- 用户界面:Antd
- 全局状态管理:Redux
- 异步状态更新:redux-saga
- 路由状态同步:connected-react-router
- 网络请求:Axios
- 调试工具:redux-devtools-extension
服务端:
- 脚本:Node.js
- 数据库:Mongodb
- 数据库可视化:Robo 3T
搭建开发环境 (服务端)
2.1 安装 mongodb 数据库 (Mac)
-
安装 homebrew
Homebrew 是mac系统中的软件包管理器
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
-
添加 mongodb 仓库源
brew tap mongodb/brew
-
安装 mongodb
安装前确保系统已经安装 xcode 命令行编译开发工具
xcode-select --install
brew install mongodb-community
-
启动 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.2 安装 mongodb 数据库 (Windows)
2.3 数据库可视化 Robo 3T
2.4 启动服务器端应用程序
- Mac 用户将服务器端应用程序文件夹拖拽到终端中,windows 用户打开服务器端应用程序文件夹,按住 shift 同时单击鼠标右键,选择在此处打开命令行工具 (cmd 或者 powershell)
- 执行
npm install
命令安装程序依赖文件 - 执行
npm start
命令启动服务器端应用程序,服务器端应用程序默认监听 80 端口
3. 搭建开发环境 (客户端)
3.1 创建项目并安装依赖
-
使用 create-react-app 脚手架创建 react 项目
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
-
antd CSS 使用 CDN
https://cdn.bootcdn.net/ajax/libs/antd/4.8.3/antd.min.css
3.2 配置服务器端 API 请求地址
在项目的根目录下新建 .env 文件,并在文件中添加以下内容:
REACT_APP_PRODUCTION_API_URL=http://fullstack.net.cn/api
REACT_APP_DEVLOPMENT_API_URL=http://localhost/api
create-react-app 脚手架中内置了 dotenv,允许我们在 React 项目中配置环境变量,但环境变量的名字必须以 REACT_APP_ 开头。
REACT_APP_PRODUCTION_API_URL: 生产环境的服务器端 API 地址
REACT_APP_DEVLOPMENT_API_URL:开发环境的服务器端 API 地址
在项目中可以通过 process.env.REACT_APP_DEVLOPMENT_API_URL
方式进行访问,但是这样会有弊端,其一是代码过长写起来不方便,其二是如果在代码中将环境写死,当切换环境时改起来也不方便。
解决方案就是将 API 地址写入配置中,根据环境决定使用哪个 API 地址
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!
}
3.3 安装 chrome 扩展
React Developer Tools:检查React组件层次结构,在页面上显示React组件。
Redux DevTools:监测 Store 中状态的变化
import { composeWithDevTools } from "redux-devtools-extension"
export const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(...middlewares))
)
进行页面和路由初始化
src下新建component放置组件,新建admin,core放置公共代码,header,layout,shop部分
Layout.tsx
import {
PageHeader } from "antd"
import React, {
FC } from "react"
import Navigation from "./Navigation"
interface Props {
children: React.ReactNode
title: string
subTitle: string
}
const Layout: FC<Props> = ({
children, title, subTitle }) => {
return (
<div>
<Navigation />
<PageHeader className="jumbotron" title={
title} subTitle={
subTitle} />
<div style={
{
width: "85%", margin: "0 auto" }}>{
children}</div>
</div>
)
}
export default Layout
Routes.tsx
import React from "react"
import {
HashRouter, Route, Switch } from "react-router-dom"
import Home from "./components/core/Home"
import Shop from "./components/core/Shop"
import Signin from "./components/core/Signin"
import Signup from "./components/core/Signup"
import Dashboard from "./components/admin/Dashboard"
import PrivateRoute from "./components/admin/PrivateRoute"
import AdminDashboard from "./components/admin/AdminDashboard"
import AdminRoute from "./components/admin/AdminRoute"
import AddCategory from "./components/admin/AddCategory"
import AddProduct from "./components/admin/AddProduct"
import Product from "./components/core/Product"
import Cart from "./components/core/Cart"
import Success from "./components/core/Success"
import Orders from "./components/admin/Orders"
const Routes = () => {
return (
<HashRouter>
<Switch>
<Route path="/" component={
Home} exact />
<Route path="/shop" component={
Shop} />
<Route path="/signin" component={
Signin} />
<Route path="/signup" component={
Signup} />
<Route path="/cart" component={
Cart} />
<Route path="/paysuccess" component={
Success} />
<PrivateRoute path="/user/dashboard" component={
Dashboard} />
<AdminRoute path="/admin/dashboard" component={
AdminDashboard} />
<AdminRoute path="/create/category" component={
AddCategory} />
<AdminRoute path="/create/product" component={
AddProduct} />
<AdminRoute path="/admin/orders" component={
Orders} />
<Route path="/product/:productId" component={
Product} />
</Switch>
</HashRouter>
)
}
export default Routes
创建store文件夹放置reducers和actions,saga等
sotre/index.ts
import {
applyMiddleware, createStore } from "redux"
import createRootReducer from "./reducers/index"
import {
createHashHistory } from "history"
import {
routerMiddleware } from "connected-react-router"
import createSagaMiddleware from "redux-saga"
import rootSaga from "./sagas/index"
import {
composeWithDevTools } from "redux-devtools-extension"
export const history = createHashHistory()
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
createRootReducer(history),
composeWithDevTools(
applyMiddleware(routerMiddleware(history), sagaMiddleware)
)
)
sagaMiddleware.run(rootSaga)
export default store
使用connected-react-router监听路由的变化
store/reducers/index.ts
import {
applyMiddleware, createStore } from "redux"
import createRootReducer from "./reducers/index"
import {
createHashHistory } from "history"
import {
routerMiddleware } from "connected-react-router"
import createSagaMiddleware from "redux-saga"
import rootSaga from "./sagas/index"
import {
composeWithDevTools } from "redux-devtools-extension"
export const history = createHashHistory()
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
createRootReducer(history),
composeWithDevTools(
applyMiddleware(routerMiddleware(history), sagaMiddleware)
)
)
sagaMiddleware.run(rootSaga)
export default store
登录注册及首页
在antd中使用导航组件,menu菜单,在Layout中引用
Navigation.tsx
import {
Badge, Menu } from "antd"
import React, {
useContext, useEffect } from "react"
import {
useSelector } from "react-redux"
import {
Link } from "react-router-dom"
import {
AppState } from "../../store/reducers/index"
import {
RouterState } from "connected-react-router"
import {
isAuth } from "../../helpers/auth"
import {
Jwt } from "../../store/models/auth"
import {
TotalContext } from "../../anotherStore"
import {
itemCount } from "../../helpers/cart"
function useActive(currentPath: string, path: string): string {
return currentPath === path ? "ant-menu-item-selected" : ""
}
const Navigation = () => {
const router = useSelector<AppState, RouterState>(state => state.router)
const pathname = router.location.pathname
const isHome = useActive(pathname, "/")
const isShop =