| dva
| redux
| redux-saga
| redux-thunk
| Ant Design Pro
状态管理框架
https://cn.mobx.js.org/
// redux-saga 中间件
https://redux-saga-in-chinese.js.org/
// dva
https://dvajs.com/guide/#%E7%89%B9%E6%80%A7 dva
redux-saga
redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action),Sagas是通过Generator函数来创建
Reducers 负责处理 action 的 state 更新
Sagas 负责协调那些复杂或异步的操作
一、安装
$ npm install --save redux-saga
二、使用
sagas.js // 创建一saga.js文件
main.js
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// 创建saga中间件
const sagaMiddleware = createSagaMiddleware()
// 加到createStore中
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// 运行 saga
sagaMiddleware.run(mySaga)
三、核心函数
1、takeEveny('actionName', 执行的saga函数) - type的action触发,就执行goAge()函数
2、takeLatest()
3、createSagaMiddleware():创建一个Reudx中间件,将Sagas与Redux Store链接起来
4、middleware.run():运行sagas,
四、Effect
import { call, put, takeEveny } from 'redux-saga/effects'
1、take():监听未来的action
function* watchFetchData() {
while(true) {
// 监听一个type为 'FETCH_REQUESTED' 的action的执行,直到等到这个Action被触发,才会接着执行下面的 yield fork(fetchData) 语句
yield take('FETCH_REQUESTED');
yield fork(fetchData);
}
}
2、takeEveny():
function* rootSaga() { // 在store.js中,执行了 sagaMiddleware.run(rootSaga)
yield takeEvery("ADD_SAGA", addSagas) // 如果有对应type的action触发,就执行goAge()函数
}
3、put():发送action的effect,简单把它理解为dispatch
export function* toggleItemFlow() {
let list = []
// 发送一个type为 'UPDATE_DATA' 的Action,用来更新数据,参数为 `data:list`
yield put({
type: actionTypes.UPDATE_DATA,
data: list
})
}
4、call():简单的理解为就是可以调用其他函数的函数
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
export function* removeItem() {
try {
// 这里call 函数就调用了 delay 函数,delay 函数为一个返回promise 的函数
return yield call(delay, 500)
} catch (err) {
yield put({type: actionTypes.ERROR})
}
}
5、fork():用来调用其它函数,是一个非阻塞函数,
export default function* rootSaga() {
// 下面的四个 Generator 函数会一次执行,不会阻塞执行
yield fork(addItemFlow)
yield fork(removeItemFlow)
yield fork(toggleItemFlow)
yield fork(modifyItem)
}
6、select():获取Store中的state值
let tempList = yield select(state => {
debugger;
})
https://www.jianshu.com/p/7cac18e8d870
https://github.com/redux-saga/redux-saga/ 官网
https://redux-saga-in-chinese.js.org/ 中文在线文档
https://segmentfault.com/a/1190000007248878 Redux异步方案选型
https://www.jianshu.com/p/f3c7594c4fb4 redux-saga 初级学习教程
https://github.com/Pines-Cheng/blog/issues/9 从redux-thunk到redux-saga实践
http://yanqiw.github.io/react/2017/03/05/redux-saga.html Redux Saga实践
dva.js
dva是redux和redux-saga的数据流文案,还内置了react-router和fetch的一个应用框架
一、安装
$ sudo npm i dva-cli -g
$ sudo dva new dva-quickstart
二、定义Model
把一个领域的模型管理起来,包含同步更新state的reducers,处理异步逻辑
export default {
namespace: 'products', // 表示在全局 state 上的 key,最好与组件名相同保持统一
state: {}, // 初始值
reducers: { // 等同于 redux 里的 reducer,接收 action,同步更新 state
save(state, action) {
return {
...state,
data: action.payload,
};
},
}
}
三、connect 起来
通过connect将model和component联接起来
class products extends Componet {
...
render {
const { dispatch, products } = this.props
return (
<div></div>
)
}
}
export default connect(({ products }) => ({
products,
}))(Products);
四、models
dispatch
Reducer(state, action):
effects: 异步操作,底层引入redux-sagas,采用了generator
Subscription:
Router:
五、整理
1、调用同步和异步
调用reducers内的方法是同步,直接改变指定state的值
调用effects内的方法是异步的调用,调取成功后在调用reducers中的方法来改变state
安装后目录结构:
|- mock // mock
|- node_modules // 包
|- package.json
|- public
|- src
|- asserts // 静态资源,打包会经过webpack处理
|- components // 存放React组件,公用的无状态组件
|- models // 模型文件
|- routes // 存放需要connect model的路由组件
|- services // 存放服务文件,一般是网络请求
|- utils // 工具库
|- router.js // 路由文件
|- index.js // 项目入口
|- index.css
|- .editorconfig // 编辑器配置文件
|- .eslintrc
|- .gitignore
|- .roadhogrc.mock.js // Mock配置文件
|- .webpackrc // 自定义的webpack配置文件
https://dvajs.com/ // dva
Ant Design Pro
安装:
$ git clone --depth=1 https://github.com/ant-design/ant-design-pro.git my-project
$ cd my-project
$ npm install
启动服务:
$ npm start // mock数据
$ npm start:no-mock // 不走mock数据
$ npm run prettier // 格式化代码
$ npm run lint-staged // 检测
构建:
$ npm run build
一、路由
脚手架默认提供了两种布局模板:基础布局 - BasicLayout 以及 账户相关布局 - UserLayout
router.config.js
1、name、icon - 菜单项和图标
2、hideChildrenInMenu - 菜单子路由是否展示
3、hideInMenu - 菜单中不展示这个路由
4、authority - 指写可以看当前菜单的用户 Array
// app
{
path: '/',
component: '../layouts/BasicLayout',
Routes: ['src/pages/Authorized'],
authority: ['admin', 'user'],
routes: [
{
path: '/dashboard',
name: 'dashboard',
icon: 'dashboard',
hideChildrenInMenu: true, // 隐藏所有子菜单,但这块需要加一个对应dashboard路由的component: './Dashboard/Analysis'
routes: [
{
path: '/dashboard/analysis',
name: 'analysis',
component: './Dashboard/Analysis',
},
...
],
}
]
}
// new 这里是新增布局
{
path: '/new',
component: '../layouts/new_page',
routes:[...]
},
二、config.js 配置代理到后端服务器
{
proxy:{
'/server/api/': {
target: 'https://preview.pro.ant.design/',
changeOrigin: true,
pathRewrite: { '^/server': '' }, // /server/api/currentUser -> /api/currentUser
},
}
}
三、支持SASS
$ npm i node-sass sass-loader --save // 安装依赖
四、页面添加 dva
modle - 分两种: 1、全局models(存在/src/models/) 2、页面models (存在于每个业务下/models 不能被其他页面所引用)
service - 请求数据
五、请求的过程
1、UI 组件交互操作
2、调用 model 的 effect
3、调用统一管理的 service 请求函数
4、使用封装的 request.js 发送请求
5、获取服务端返回
6、然后调用 reducer 改变 state
7、更新 model
六、Mock
export default {
// 支持值为 Object 和 Array
'GET /api/users': { users: [1, 2] },
// GET POST 可省略
'/api/users/1': { id: 1 },
// 支持自定义函数,API 参考 express@4
'POST /api/users/create': (req, res) => { res.end('OK'); },
};
1、可以引用Mock.js第三方库
import mockjs from 'mockjs';
export default {
// 使用 mockjs 等三方库
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
};
七、自定义主题
config/config.js文件
theme: {
'font-size-base': '14px',
'badge-font-size': '12px',
'btn-font-size-lg': '@font-size-base',
'menu-dark-bg': '#00182E',
'menu-dark-submenu-bg': '#000B14',
'layout-sider-background': '#00182E',
'layout-body-background': '#f0f2f5',
};
八、项目总结
1、Mock数据不能在生产环境使用,只能在开发环境
2、使用动态路由时切换时取不到参数,见下面UMI动态路由解决方法
目录
├── config # umi 配置,包含路由,构建等配置
│ ├── config.js # 构建
│ ├── plugin.config.js
│ ├── router.config.js # 路由配置文件
├── mock # 本地模拟数据
├── public
│ └── favicon.png # Favicon
├── layouts # 框架页面
├── ├── BasicLayout # Basic框架页
├── ├── Header # 头
├── ├── Footer # 尾
├── locales
| ├── zh-CN
| | ├── menu.js # 配置显示在菜单对应路由的中文名
├── src # 重要
│ ├── assets # 本地静态资源
│ ├── components # 业务通用组件
│ │ ├── GlobalHeader # 顶部右侧部分内容
│ ├── e2e # 集成测试用例
│ ├── layouts # 通用布局
│ │ ├── BasicLayouts # 基础布局
│ ├── models # 全局 dva model
│ | ├── menu.js # 菜单处理过滤
│ | ├── login.js # 登录请求
│ ├── pages # 业务页面入口和常用模板
│ │ ├── document.ejs # 首页模板
│ │ ├── User # Login/Register 登录和注册组件
│ ├── services # 后台接口服务
│ ├── utils # 工具库
│ ├── locales # 国际化资源
│ ├── global.less # 全局样式
│ ├── defaultSettings.js # 配置菜单样式等
│ └── global.js # 全局 JS
├── utils # 工具
├── ├── request.js # 封装Ajax请求
├── ├── Authorized # 授权
├── models # 全局
├── ├── menu # 菜单的配置和过滤
├── ├──
├── tests # 测试工具
├── README.md
└── package.json
Umi.js
umi,中文可发音为乌米, 内置了react、react-router,以路由为基础,支持next.js的约定式路由
一、安装
$ yarn global add umi // 安装 umi -v 来查看版本
$ mkdir myumi && cd myumi
1、脚手架创建
$ yarn creat umi // 通过creat-umi脚手架创建,其实就安装了 Ant Design Pro 的简版没有components
2、普通创建
$ umi g page index // 简单创建页面
umi g page users
$ umi dev // 启动服务
$ umi build // 构建
二、路由
约定式路由 - 启动服务后在pages下产生一个.umi临时目录里面有router.js,umi 会根据 pages 目录自动生成路由配置
配置式路由 - 如果使用配置可以在,config/router.config.js
1、路由跳转:
import Link from 'umi/link' // 加载link
export default function(){
return (
<div>
<Link to="/user">跳转到用户</Link>
</div>
)
}
命令式:
function goToListPage() {
router.push('/list');
}
2、动态路由
umi约定 带 $ 前缀的目录或文件为动态路由
+ pages/
+ $post/
- index.js
- comments.js
+ users/
$id.js
- index.js
[
{ path: '/', component: './pages/index.js' },
{ path: '/users/:id', component: './pages/users/$id.js' },
{ path: '/:post/', component: './pages/$post/index.js' },
{ path: '/:post/comments', component: './pages/$post/comments.js' },
]
注意: 动态路由切换时不会在调用componentWillMount,需要在componentDidUpdate来侦听
state = {
chartId: ''
};
componentDidUpdate(){
const { computedMatch } = this.props;
const { chartId } = this.state;
if(chartId !== computedMatch.params.id){
this.initChartList();
}
}
initChartList(){
const { dispatch, computedMatch } = this.props;
console.log(computedMatch.params.id)
this.setState({
chartId: computedMatch.params.id
})
dispatch({
type: 'chartData/getChartsData',
payload: {
pageId: computedMatch.params.id
}
});
}
3、路由嵌套
配置routers
export default {
routes: [
{ path: '/users', component: './users/_layout',
routes: [
{ path: '/users/detail', component: './users/detail' },
{ path: '/users/:id', component: './users/id' }
]
},
],
};
4、权限
PrivateRoute.js来渲染/list
https://umijs.org/zh/guide/router.html#%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1
三、Mock 数据
let agentDataList = [...]
function getAgents(req, res) {
const dataSource = agentDataList;
const result = {
list: dataSource,
total: dataSource.length,
};
return res.json(result);
}
export default {
'POST /api/arbitration/agent/query': getAgents,
}
--- 可以引入mock.js ---
import mockjs from 'mockjs';
export default {
// 使用 mockjs 等三方库
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
};
antdPro 流程
一、app.js入口
// 获取权限路由,也可以在这添加路由
fetch('/api/auth_routes')
.then(res => res.json())
.then(ret => {
authRoutes = ret;
oldRender();
});
二、login 登录接口
request: /api/login/account
response: {status: "ok", type: "account", currentAuthority: "admin"}
三、设置和获取权限
router.config.js 配置Routes每个页面都调用Authorized.js来判断权限
{
path: '/',
component: '../layouts/BasicLayout',
Routes: ['src/pages/Authorized'], // 设置
authority: ['admin', 'user', 'ROLE_RDDPL4WSV0'],
routes: [
{ path: '/', redirect: '/dashboard/workplace', authority: ['admin', 'user', 'ROLE_RDDPL4WSV0'] },
]
}
utils/authority getAuthority、setAuthority
存储到localStrage中的antd-pro-authority
四、菜单
modules/menu getMenuData()获取菜单的方法
layouts/BasicLayouts 页面布局,SiderMenu组件menuData菜单数据
menuData格式
[{
"path": "/dictionary",
"icon": "book",
"name": "数据字典",
"locale": "menu.dictionary",
"authority": ["admin", "user"],
"children": [{
"path": "/dictionary/list",
"name": "字典列表",
"exact": true,
"locale": "menu.dictionary.list"
}, {
"path": "/dictionary/create",
"name": "新建字典",
"exact": true,
"locale": "menu.dictionary.create"
}]
}]
react使用typescript
1、state和props定义
interface IProps {
aProps: string;
bProps: string;
}
interface IState {
aState: string;
bState: string;
}
class App extends React.PureComponent<IProps, IState> {
state = {
aState: '',
bState: '',
};
render(){
this.props.aProps;
this.state.aState;
return null;
}
}
const mapStateToProps = () => {
return {
aProps: 'a',
bProps: 'b'
}
}
const mapDispatchToProps = {};
export default connect(mapStateToProps, mapStateToProps)(App)
| https://www.jianshu.com/p/c7b3b9c98d04
| https://umijs.org/zh/plugin/umi-plugin-react.html#%E5%AE%89%E8%A3%85 // umiJS