| 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