1.创建项目
npx create-react-app my-app --template typescript
2.改造目录结构
src
api
components
layout
store
router
utils
views
App.tsx
index.tsx
打开文件发现jsx代码处都有问题,修改 tsconfig.json
- jsx: 'react-jsx'
+ jsx: 'react'
3.安装一些必须的模块
状态管理器
cnpm i redux react-redux redux-saga immutable redux-immutable -S
cnpm i @types/redux-immutable -D
cnpm i redux react-redux redux-thunk immutable redux-immutable -S
cnpm i @types/redux-immutable -D
cnpm i redux react-redux redux-saga -S
cnpm i redux react-redux redux-thunk -S
cnpm i mobx mobx-react -S
本项目选择第一个
路由
2021年11月4日 发布了 react-router-dom的v6.0.0版本:https://reactrouter.com/
本项目采用 V5版本:https://v5.reactrouter.com/web/guides/quick-start
cnpm i react-router-dom@5 -S
cnpm i @types/react-router-dom@5 -D
数据验证
思考,有没有必要安装 prop-types ?
cnpm i prop-types -S
数据请求
cnpm i axios -S
以前版本中 cnpm i @types/axios -S
ui库
cnpm i antd -S
// src/App.css
@import '~antd/dist/antd.css';
4.创建主布局文件
src/layout/main/Index.tsx 座位后台管理系统的主页面布局(包含左侧的菜单栏,顶部,底部等)
https://ant.design/components/layout-cn/#components-layout-demo-custom-trigger
// src/layout/main/Index.ts
import React from 'react'
import { Layout, Menu } from 'antd';
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
UserOutlined,
VideoCameraOutlined,
UploadOutlined,
} from '@ant-design/icons';
const { Header, Sider, Content } = Layout;
class Index extends React.Component {
state = {
collapsed: false,
};
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
};
render() {
return (
<Layout id="components-layout-demo-custom-trigger">
<Sider trigger={null} collapsible collapsed={this.state.collapsed}>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1" icon={<UserOutlined />}>
nav 1
</Menu.Item>
<Menu.Item key="2" icon={<VideoCameraOutlined />}>
nav 2
</Menu.Item>
<Menu.Item key="3" icon={<UploadOutlined />}>
nav 3
</Menu.Item>
</Menu>
</Sider>
<Layout className="site-layout">
<Header className="site-layout-background" style={
{ padding: 0 }}>
{React.createElement(this.state.collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: this.toggle,
})}
</Header>
<Content
className="site-layout-background"
style={
{
margin: '24px 16px',
padding: 24,
minHeight: 280,
}}
>
Content
</Content>
</Layout>
</Layout>
);
}
}
export default Index
// src/App.tsx
import React from 'react'
import Index from './layout/main/Index'
import './App.css'
const App = () => {
return (
<>
<Index />
</>
)
}
export default App
// src/App.css
@import '~antd/dist/antd.css';
#root, #components-layout-demo-custom-trigger {
height: 100%;
}
#components-layout-demo-custom-trigger .trigger {
padding: 0 24px;
font-size: 18px;
line-height: 64px;
cursor: pointer;
transition: color 0.3s;
}
#components-layout-demo-custom-trigger .trigger:hover {
color: #1890ff;
}
#components-layout-demo-custom-trigger .logo {
height: 32px;
margin: 16px;
background: rgba(255, 255, 255, 0.3);
}
.site-layout .site-layout-background {
background: #fff;
}
5.拆分主界面
// src/layout/main/components/SideBar.tsx
import React, { FC, useState } from 'react';
import { Layout, Menu } from 'antd';
import {
UserOutlined,
VideoCameraOutlined,
UploadOutlined,
} from '@ant-design/icons';
const { Sider } = Layout;
type Props = {};
const SideBar: FC<Props> = (props) => {
const [ collapsed, setCollapsed ] = useState(false)
return (
<Sider trigger={null} collapsible collapsed={collapsed}>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1" icon={<UserOutlined />}>
nav 1
</Menu.Item>
<Menu.Item key="2" icon={<VideoCameraOutlined />}>
nav 2
</Menu.Item>
<Menu.Item key="3" icon={<UploadOutlined />}>
nav 3
</Menu.Item>
</Menu>
</Sider>
)
}
export default SideBar;
// src/layout/main/components/Appheader.tsx
import React, { FC, useState } from 'react';
import { Layout } from 'antd';
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
} from '@ant-design/icons';
const { Header } = Layout;
type Props = {};
const AppHeader: FC<Props> = (props) => {
const [ collapsed, setCollapsed ] = useState(false)
const toggle = () => {
setCollapsed(!collapsed)
}
return (
<Header className="site-layout-background" style={
{ padding: 0 }}>
{React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: toggle,
})}
</Header>
)
}
export default AppHeader;
// src/layout/main/components/AppMain
import React, { FC } from 'react';
import { Layout } from 'antd';
const { Content } = Layout;
type Props = {};
const AppMain: FC<Props> = (props) => {
return (
<Content
className="site-layout-background"
style={
{
margin: '24px 16px',
padding: 24,
minHeight: 280,
}}
>
Content
</Content>
)
}
export default AppMain;
// src/layout/main/components/index.tsx
export { default as SideBar } from './SideBar'
export { default as AppMain } from './AppMain'
export { default as AppHeader } from './AppHeader'
// src/layout/main/Index.tsx
import React from 'react'
import {
Layout } from 'antd';
import {
SideBar, AppHeader, AppMain } from './components'
class Index extends React.Component {
render() {
return (
<Layout id="components-layout-demo-custom-trigger">
<SideBar/>
<Layout className="site-layout">
<AppHeader />
<AppMain />
</Layout>
</Layout>
);
}
}
export default Index
6.状态管理器配置-saga
菜单的收缩封装到一个叫做 app 的模块
使用不可变的数据结构 immutable,如果初始值为对象,使用 Map,如果初始值为数组,使用 List
创建reducer的app模块
// src/store/modules/app.ts
import {
Map } from 'immutable'
const initialState = Map({
collapsed: localStorage.getItem('collapsed') === 'true'
})
export default (state = initialState, {
type }: {
type: string; payload?: any }) => {
switch (type) {
case 'CHANGE_COLLAPSED':
// { ...state, collapsed: !state.collapsed }
localStorage.setItem('collapsed', String(!state.get('collapsed')))
return state.set('collapsed', !state.get('collapsed'))
default:
return state
}
};
创建sagas中app模块
Redux-saga 结合es6中的 generator 函数
// src/store/sagas/app.ts
import {
put } from 'redux-saga/effects'
// call 方法用来触发异步的请求
// put 相当于以前的dispatch,触发的是reducer中的 aciton.type
export function * changeCollapsedAction () {
yield put({
type: 'CHANGE_COLLAPSED'
})
}
创建mySaga分配器
// src/store/mySaga.ts
import {
takeLatest } from 'redux-saga/effects'
import {
changeCollapsedAction } from './sagas/app'
function * mySaga () {
yield takeLatest('REQUEST_CHANGE_COLLAPSED', changeCollapsedAction) // 如果有人触发key,立马执行value的函数
}
export default mySaga
创建状态管理器store
// src/store/index.ts
import {
createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
// import thunk from 'redux-thunk'
import {
combineReducers } from 'redux-immutable' // 组合reducer 一定要使用这个
import mySaga from './mySaga' // 必须存在
import app from './modules/app'
const reducer = combineReducers({
app
})
const sagaMiddleware = createSagaMiddleware() // 生成saga中间件
const store = createStore(reducer, applyMiddleware(sagaMiddleware))
// const store = createStore(reducer, applyMiddleware(thunk))
// 一定要在生成store之后运行
sagaMiddleware.run(mySaga)
export default store
入口文件引入状态状态管理器
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './store'
ReactDOM.render(
<React.StrictMode>
<Provider store = { store }>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
左侧菜单栏调用状态
高阶组件
// src/layout/main/components/SideBar.tsx
import React, { FC } from 'react';
import { Layout, Menu } from 'antd';
import {
UserOutlined,
VideoCameraOutlined,
UploadOutlined,
} from '@ant-design/icons';
import { connect } from 'react-redux'
const { Sider } = Layout;
type Props = {
collapsed: boolean
};
const SideBar: FC<Props> = ({ collapsed }) => {
// const [ collapsed, setCollapsed ] = useState(false)
return (
<Sider trigger={null} collapsible collapsed={collapsed}>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1" icon={<UserOutlined />}>
nav 1
</Menu.Item>
<Menu.Item key="2" icon={<VideoCameraOutlined />}>
nav 2
</Menu.Item>
<Menu.Item key="3" icon={<UploadOutlined />}>
nav 3
</Menu.Item>
</Menu>
</Sider>
)
}
export default connect(
(state: any) => {
console.log(state)
return {
collapsed: state.getIn(['app', 'collapsed'])
}
}
)(SideBar);
头部组件调用以及更改状态
// src/layout/main/components/AppHeader.tsx
import React, { FC } from 'react';
import { Layout } from 'antd';
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
} from '@ant-design/icons';
import { connect } from 'react-redux'
const { Header } = Layout;
type Props = {
collapsed: boolean;
toggleCollapsed: Function
};
const AppHeader: FC<Props> = ({ collapsed, toggleCollapsed }) => {
const toggle = () => {
toggleCollapsed()
}
return (
<Header className="site-layout-background" style={
{ padding: 0 }}>
{React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: toggle,
})}
</Header>
)
}
// connect(mapStateToProps, mapDispatchToProps)()
export default connect(
(state: any) => {
return {
collapsed: state.getIn(['app', 'collapsed'])
}
},
(dispatch) => {
return {
toggleCollapsed () {
dispatch({
type: 'REQUEST_CHANGE_COLLAPSED'
})
}
}
}
)(AppHeader);
7.状态管理配置-thunk
创建reducer的app模块
// src/store/modules/app.ts
import {
Map } from 'immutable'
const initialState = Map({
collapsed: localStorage.getItem('collapsed') === 'true'
})
export default (state = initialState, {
type }: {
type: string; payload?: any }) => {
switch (type) {
case 'CHANGE_COLLAPSED':
// { ...state, collapsed: !state.collapsed }
localStorage.setItem('collapsed', String(!state.get('collapsed')))
return state.set('collapsed', !state.get('collapsed'))
default:
return state
}
};
创建状态管理器
// src/store/index.ts
import {
createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import {
combineReducers } from 'redux-immutable'
import app from './modules/app'
const reducer = combineReducers({
app
})
const store = createStore(reducer, applyMiddleware(thunk))
export default store
入口文件引入状态状态管理器
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './store'
ReactDOM.render(
<React.StrictMode>
<Provider store = { store }>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
左侧菜单栏调用状态
高阶组件
// src/layout/main/components/SideBar.tsx
import React, { FC } from 'react';
import { Layout, Menu } from 'antd';
import {
UserOutlined,
VideoCameraOutlined,
UploadOutlined,
} from '@ant-design/icons';
import { connect } from 'react-redux'
const { Sider } = Layout;
type Props = {
collapsed: boolean
};
const SideBar: FC<Props> = ({ collapsed }) => {
// const [ collapsed, setCollapsed ] = useState(false)
return (
<Sider trigger={null} collapsible collapsed={collapsed}>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1" icon={<UserOutlined />}>
nav 1
</Menu.Item>
<Menu.Item key="2" icon={<VideoCameraOutlined />}>
nav 2
</Menu.Item>
<Menu.Item key="3" icon={<UploadOutlined />}>
nav 3
</Menu.Item>
</Menu>
</Sider>
)
}
export default connect(
(state: any) => {
console.log(state)
return {
collapsed: state.getIn(['app', 'collapsed'])
}
}
)(SideBar);
创建app模块的异步行为
// src/store/actions/app.ts
const actions = {
changeCollapsedAction () {
return (dispatch: any) =>{
// 异步操作
dispatch({
// 触发reducer
type: 'CHANGE_COLLAPSED'
})
}
}
}
export default actions
头部组件调用以及更改状态
// src/layout/main/components/AppHeader.tsx
import React, { FC } from 'react';
import { Layout } from 'antd';
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
} from '@ant-design/icons';
import { connect } from 'react-redux'
import action from './../../../store/actions/app'
const { Header } = Layout;
type Props = {
collapsed: boolean;
toggleCollapsed: Function
};
const AppHeader: FC<Props> = ({ collapsed, toggleCollapsed }) => {
const toggle = () => {
toggleCollapsed()
}
return (
<Header className="site-layout-background" style={
{ padding: 0 }}>
{React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: toggle,
})}
</Header>
)
}
// connect(mapStateToProps, mapDispatchToProps)()
export default connect(
(state: any) => {
return {
collapsed: state.getIn(['app', 'collapsed'])
}
},
(dispatch: any) => {
return {
toggleCollapsed () {
// 加不加() 取决于你在定义acitons时的写法
// 如果你是函数返回一个函数(且返回的函数有默认参数dispatch),需要加
// 如果函数直接写业务逻辑,且默认参数为dispatch,不要加
dispatch(action.changeCollapsedAction())
}
}
}
)(AppHeader);
8.左侧菜单栏
1.设计左侧菜单栏的数据
// src/router/menus.ts
import {
HomeOutlined
} from '@ant-design/icons';
const menus = [
{
key: '0-0', // 树形控件时需要
title: '系统首页',
path: '/',
icon: HomeOutlined
},
{
key: '0-1',
title: '轮播图管理',
path: '/banner',
icon: HomeOutlined,
children: [
{
key: '0-1-0',
title: '轮播图列表',
path: '/banner/list',
icon: HomeOutlined
},
{
key: '0-1-1',
title: '添加轮播图',
path: '/banner/add',
icon: HomeOutlined,
hidden: true
}
]
},
{
key: '0-2',
title: '产品管理',
path: '/pro',
icon: HomeOutlined,
children: [
{
key: '0-2-0',
title: '产品列表',
path: '/pro/list',
icon: HomeOutlined
},
{
key: '0-2-1',
title: '秒杀列表',
path: '/pro/seckill',
icon: HomeOutlined
},
{
key: '0-2-2',
title: '推荐列表',
path: '/pro/recommend',
icon: HomeOutlined
},
{
key: '0-2-3',
title: '筛选列表',
path: '/pro/search',
icon: HomeOutlined
},
]
},
{
key: '0-3',
title: '账户管理',
path: '/user',
icon: HomeOutlined,
children: [
{
key: '0-3-0',
title: '用户管理',
path: '/user/list',
icon: HomeOutlined
},
{
key: '0-3-1',
title: '管理员管理',
path: '/user/admin',
icon: HomeOutlined
}
]
},
{
key: '0-4',
title: '设置',
path: '/setting',
icon: HomeOutlined,
hidden: true
}
]
export default menus
2.渲染左侧菜单栏
带有hidden属性不渲染在左侧菜单栏
// src/layout/main/components/SideBar.tsx
import React, { FC } from 'react';
import { Layout, Menu } from 'antd';
import { connect } from 'react-redux'
import menus, { IRoute } from './../../../router/menus'
const { Sider } = Layout;
const { SubMenu } = Menu
type Props = {
collapsed: boolean
};
const SideBar: FC<Props> = ({ collapsed }) => {
// const [ collapsed, setCollapsed ] = useState(false)
const renderMenu = (menus: IRoute[]) => {
return menus.map(item => {
if (item.children) {
return (
<SubMenu key={item.path} icon={<item.icon />} title={ item.title }>
{
renderMenu(item.children)
}
</SubMenu>
)
} else {
return (
item.hidden ? null : <Menu.Item key={item.path} icon={<item.icon />}>
{ item.title }
</Menu.Item>
)
}
})
}
return (
<Sider trigger={null} collap