React(之四)其它框架状态管理及框架

| 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

React(之三)Redux

| createStroe - 创建一个store
| reducer - 用来改变state的值,结合了action的动作来分别处理
| action - 要执行的事件
| combineReducers - 将多个reducer合到一起,赋给createStroe
| replaceReducer - 替换reducer,改变state修改逻辑
| subscribe - 侦听Store的变化
| store.getState - 获取Store中的state值
| store.dispatch - 触发action,改变state的值
| provider - 将Store传递给内部组件
| connect - 将组件与redux的状态、事件关联起来
| applyMiddleware - 加载中间件
| bindActionCreators - 直接调用dispatch(action)()
| react-redux - react与redux结合

Redux

react是纯View层的框架,需要数据流进行支撑, 单一的状态树,主流数据流框架: Flux、Redux

UI -> action -> reducer -> store -> state -> UI

Redux是非异步的流程,如果需要使用异步操作使用 Redux-saga

一、Flux四部分

    1、Dispatcher - 处理动作分发,维持Store之间的依赖关系

    2、Store - 负责存储数据和处理数据相关逻辑

    3、Action - 驱动Dispatcher的JS对象

    4、View - 视图部分,负责显示用户界面

    Dispatcher相当于Controller,Stroe相当于Modle,View相当于View,Action相当于用户的行为


二、Redux三个基本原则

    1、唯一数据源 - 状态数据只存储在唯一的一个Store上

    2、保持状态只读 - 不能直接修改Store的状态,需要派发一个action对象完成

    3、数据改变只能通过纯函数完成

安装

Facebook官方提出了Flux思想管理数据流,同时也给出了自己的实现方案Flux来管理React应用

实现Flux思想的类库 Redux、Flux、reflux

$ npm i redux --save                    // 安装redux

$ npm i react-redux --save            // redux与react结合

$ npm i react-router-redux --save     // redux-router

$ npm i redux-devtools-extension --save        // 安装可视化工具

    引用:import { composeWithDevTools } from 'redux-devtools-extension'

    在chrome中下载redux-devtools插件

    https://github.com/zalmoxisus/redux-devtools-extension#usage

$ npm i redux-thunk --save

$ npm i redux-logger --save

引用redux: import { createStore } from 'redux'

一、Redux流程:

    1、view直接触发dispatch

    2、dispatch将action发送到reducer中后,根节点上会更新props,改变全局view

    3、redux将view和store的绑定从手动编码中提取出来,放在了统一规范放在了自己的体系中    

Action

action指令并不能直接改变state,action必须是json

let todoId = 0;
export const addTodo = (text) => {
    return {
        type: 'ADD_TODO',    // 处理的类型(必要)
        id: todoId++,            // 存储数据当前的id标识
        text                            // 传入的内容
    }
}

通过 store.dispatch(action) 来派发到reducer中, 在reducer中改变state的值

Reducer

接收一个action值,纯方法不能定义ajax定义时间等,需要传入一个旧的状态在返回一个新的状态给action, 根据定义的action名对应处理state,并改变state值

let todo = (state, action) => {

    // 这里通过action的type的值对应不同的事来处理
    switch(action.type){
        case 'ADD_TODO':
            return {
                state + 1;
            }
            break;

        /* 上面只是单个add_todo, 这里要写成多个add_todo
        case 'ADD_TODO':
            return [
                {
                    id: action.id,
                    text: action.text,
                    completed: false
                },
                ...state
            ]
            break;            
        */

        case 'SUB_TODO':
            return {
                state - 1;
            }
            break;

        default: state;
    }
}

1、combineReducers - 将多个的reducer合成一个

    // 目录 reducer/index.js
    import {combineReducers} from "redux";
    import reducerA from "./reducerA";
    import reducerB from "./reducerB";

    export default combineReducers({
        reducerA,
        reducerB,
    });

    // createStore调用reducer
    import reducers from './reducer/'
    let store = createStore(reducers)

Store

Store是来保存数据的容器,只有一个Store,可以根据不同业务拆分成多个reducer, createStroe(reducer, 初始化的)

import { createStore } from 'redux';
import reducer from './reducers'        // 获取定义的reducer

let stroe = createStroe(reducer);    // 使用createStore(reducer, initialState初始化state, 中间件传入)来创建一个数据仓库
store.dispatch();

方法:

    1、store.getState(): 获取State值

    2、store.dispatch(action): 触发action,修改state的值

    3、store.subscribe(listener),用来订阅状态的变化

        添加一个变化的监听器,每当dispatch action的时候就会执行

        // App组件
        class App extends Component {
            constructor(props){
                super(props);
                this.state = {
                    num: 0
                }
            }

            componentDidMount() {
                store.subscribe(this.handleChange.bind(this));
            }

            handleChange(){
                this.setState({num: store.getState()})
            }

            addClick(){
                store.dispatch({type: 'add'});
            }

            subClick(){
                store.dispatch({type: 'sub'});
            }

            showState(){
                debugger;
                console.log(store.getState())
            }

            render() {
                return (
                    <div className = "box" >
                        {this.state.num}
                        <input type="button" onClick={this.addClick} value="+" />
                        <input type="button" onClick={this.subClick} value="-" />
                        <input type="button" onClick={this.showState} value="查看state" />
                    </div>
                )
            }
        }

State

state用于存储数据,state是只读的,改变需要触发action

state树就是一个对象,所有的reducer的都会加到state中,取state值,store.getState().

react-redux

$ npm i react-reudx --save        // 安装

一、provider(store, children) - 将Store传到子组件中,一般用在入口文件

    import React from 'react';
    import ReactDOM from 'react-dom';
    import {Provider} from "react-redux";
    import Store from "src/store";
    import App from 'src/components/app';
    import Chat from 'src/pages/Chat/Index';

    // 1、使用Store让所有组件访问到
    ReactDOM.render(
        <Provider store={Store}>
            <App>
                <Chat />
            </App>
        </Provider>,
        document.getElementById('app')
    );

    // 2、如果使用路由
    ReactDOM.render(
        <Provider store={store}>
            <Router ref="router" history={hashHistory}>
                <Route path='/' component={Index}>
                    <IndexRoute  component={MainPage}></IndexRoute>
                </Route>
            </Router>
        </Provider>
    )


二、connect(mapStateToProps, mapDispatchToProps)(Component连接组件) - 连接react组件与redux store

    1、mapStateToProps(state, ownProps) - 侦听Store的变化

            state参数 - 如果Store中有变化此方法就会被调用,该回调函数返回一个纯对象,这个对象会与组件的props合并

            ownProps参数 - 该参数的值为会飞经到组件的props,

    2、mapDispatchToProps - 逻辑输出,相当于用store.dispatch(actionType) 来发出操作指令,来改变state值

    Example:

        // app.jsx
        <Provider store={store}>
            <Router ref="router" history={hashHistory}>
                <Route path='/' component={Index}>
                    <IndexRoute  component={MainPage}></IndexRoute>
                </Route>
            </Router>
        </Provider>

        // Index.jsx
        import React, { Component } from 'react';
        import ReactDOM from 'react-dom';
        import { bindActionCreators } from "redux";
        import { connect } from "react-redux";
        import actions from "src/actions";

        class wechat extends Component {
            constructor(props){
                super(props);
                this.state = {

                }
            }

            componentDidMount(){
                let { ACTIONS } = this.props;
                ACTIONS.chat_init();
            }

            render(
                // ...
            )
        }

        let mapStateToProps=(state)=>{
            let {sessions,user} = state.chatIndex;
            return {
                _sessions:sessions,
                _user:user
            };
        }; 

        let mapDispatchToProps=(dispatch)=>{
            return {
                ACTIONS: bindActionCreators(actions, dispatch)
            };
        };

        export default connect(mapStateToProps,mapDispatchToProps)(wechat);

container组件

引用Redux将组件分两类container和component

container - 组件中使用redux获取数据,状态更新,从Redux获取state

bindActionCreators()

是通过dispatch将action包裹起来,这样可以通过bindActionCreators创建的方法,直接调用dispatch(action)(隐式调用)

import * as oldActionCreator from './action.js'

let newAction = bindActionCreators(oldActionCreator,dispatch)

调试工具

两种方法 1、下载redux-devtools包      2、chrome or Firfix 下载 Redux DevTools插件

1、使用Redux DevTools插件 代码中需要加配置

    https://github.com/zalmoxisus/redux-devtools-extension

    /* eslint-disable no-underscore-dangle */        // +
    let store = createStore(
        comput,
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()        // +
    )
    /* eslint-enable */                                // +


2、redux-devtool 包的使用 https://github.com/gaearon/redux-devtools

项目目录

|- src
|-- reduxs     // redux
|------ actions
|------ reducers
|-- router    // 路由
|-- util    // 工具

react-router-redux路由

redux管理的是应用状态(state),router管理的是路由,react-router-redux是保持路由与应用状态(state)同步

$ npm install --save react-router-redux        // 安装

原理:允许使用react router库中的api,使用redux一样去管理应用状态state

https://www.jianshu.com/p/bccca5bb6338
http://blog.csdn.net/isaisai/article/details/78086913
https://segmentfault.com/a/1190000007862103

http://blog.csdn.net/sinat_17775997/article/details/69218382

| http://cn.redux.js.org/index.html // Redux中文文档
| http://www.liuyiqi.cn/2016/01/19/r2-counter/
| https://github.com/lewis617/react-redux-tutorial
| https://github.com/xgrommx/awesome-redux
| https://www.jianshu.com/p/bccca5bb6338 // react-router-redux 保持路由与应用状态(state)同步
| http://www.redux.org.cn/ // react-redux
| https://www.jianshu.com/p/1a2f3db4af61 // react-redux
| https://www.jianshu.com/p/9873d4ccb891 // connect 原理
| https://github.com/GuoYongfeng/redux-complete-sample
| https://github.com/gaearon/redux-devtools
| http://www.aliued.com/?p=3204
| http://blog.csdn.net/liwusen/article/details/54138854 // bindActionCreators
| http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
| https://github.com/allan2coder/React-SPA/blob/master/src/store/configureStore.dev.js // 示例 router3.0

React(之二)组件

| react-create-app 脚手架
| Router、Route
| Link、Links router的导航组件,用来切换路由
| browserHistory、hashHistory
| activeStyle、activeClassName 路由设置样式
| query、params 变量
| Redirect

react-create-app 脚手架

$ sudo npm i create-react-app -g            // 全局安装,以后在安装就直接使用create-react-app命令来创建就可以

$ create-react-app my-app            // 创建+创建项目目录名

$ cd my-app

$ yarn start

$ yarn start、yarn build             // 启动服务、打包

$ yarn add antd        // 安装Antd UI框架

    在index.js中引用ant公用样式
    import 'antd/dist/antd.css';

$ yarn add react-router-dom -D            // react-router是安装4.0之前版本,react-router-dom是4之后版本

$ yarn add redux react-router-redux -D

$ npm run eject            // 注意如果对构建工具和配置选择不满意可以使用eject运行, 这里是单项操作只能一次不能回去

一、create-react-app关闭eslint提醒

    需要先npm run eject 生成本地webpack配置文件,在修改package.json中的配置

    "eslintConfig": {
        "extends": "react-app",
        "rules": {
            "no-undef": "off",
            "no-restricted-globals": "off",
            "no-unused-vars": "off"
        }
    }

二、配置反向代理

    "proxy": {
        "/api/*": {
            "target": "http://goucai.diyicai.com",
            "pathRewrite": {
                "^/api/": "/"
            },
            "changeOrigin": true,
            "secure": false
        }
    }

三、使用.scss文件

    1、$ npm install file-loader sass-loader node-sass --save-dev            // 安装包

    2、node_modules/react-scripts/config/webpack.config.dev.js 和 webpack.config.prod.js文件中module中配置下面两项

        {
            exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/, /\.scss/],            // 这里加添加一个/\.scss/
            loader: require.resolve('file-loader'),
            options: {
                name: 'static/media/[name].[hash:8].[ext]',
            }
        },
        // 解析scss文件
        {
            test: /\.scss$/,
            loaders: ['style-loader', 'css-loader', 'sass-loader'],
        }


四、引入样式

    1、<div style={{width: "1000px"}}></div>

    2、import "./main.css"

        <div className="main"></div>

    3、import Styles from './main.css'

        <div className={Styles.main}></div>


五、常见问题

    1、<src>引用本地图片无效

        import JzkUrl from '../../assets/img/jzk.png'

        <img src={JzkUrl} />

六、build的打包的时候注意的地方

    在package.json中的有一个homePage: '.',这个在打包的时候图片会成为相对路径,如果改成 homePage: '/'


七、修改webpack配置加一个全局配置

    function resolve (dir) {
        return path.join(__dirname, '..', dir)
    }

    resolve: {
        alias: {
            'react-native': 'react-native-web',
            '@': resolve('src')
        }
    }

    在页面里可以直接引用@来找到src目录

        import User from '@/views/User/'

PropTypes验证

React.PropTypes 提供很多验证器来验证传入数据的有效性

当向 props 传入无效数据时,JavaScript 控制台会抛出警告。注意为了性能考虑,只在开发环境验证 propTypes。下面用例子来说明不同验证器的区别: 

Example:

    import PropTypes from 'prop-types';
    class Greeting extends React.Component {
        render() {
            return (
                <h1>Hello, {this.props.name}</h1>
            );
        }
    }
    Greeting.propTypes = {
        name: PropTypes.string
    };

propTypes类型: {

    // 可以声明 prop 为指定的 JS 基本类型。默认
    // 情况下,这些 prop 都是可传可不传的。
    optionalArray: React.PropTypes.array,
    optionalBool: React.PropTypes.bool,
    optionalFunc: React.PropTypes.func,
    optionalNumber: React.PropTypes.number,
    optionalObject: React.PropTypes.object,
    optionalString: React.PropTypes.string,

    // 所有可以被渲染的对象: 数字,
    // 字符串,DOM 元素或包含这些类型的数组。
    optionalNode: React.PropTypes.node,

    // React 元素
    optionalElement: React.PropTypes.element,

    // 用 JS 的 instanceof 操作符声明 prop 为类的实例。
    optionalMessage: React.PropTypes.instanceOf(Message),

    // 用 enum 来限制 prop 只接受指定的值。
    optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),

    // 指定的多个对象类型中的一个
    optionalUnion: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number,
        React.PropTypes.instanceOf(Message)
    ]),

    // 指定类型组成的数组
    optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),

    // 指定类型的属性构成的对象
    optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),

    // 特定形状参数的对象
    optionalObjectWithShape: React.PropTypes.shape({
        color: React.PropTypes.string,
        fontSize: React.PropTypes.number
    }),

    // 以后任意类型加上 `isRequired` 来使 prop 不可空。
    requiredFunc: React.PropTypes.func.isRequired,

    // 不可空的任意类型
    requiredAny: React.PropTypes.any.isRequired,

    // 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接
    // 使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
    customProp: function(props, propName, componentName) {
        if (!/matchme/.test(props[propName])) {
            return new Error('Validation failed!');
        }
    }
}

react-router 4.0

v3到v4版本的变化是静态路由到动态路由的变化,并且页面的嵌套路路由嵌套决定

react-router: 是浏览器和原生应用的通用部分, 核心文件

react-router-dom: 用于浏览器

react-router-native: 是用于原生(react-native)应用的    

react-router-redux: React Router 和 Redux 的集成

react-router-config: 静态路由配置的小助手

一、安装

    $ npm i react-router react-router-dom --save

    import { BrowserRouter as Router, Route, Switch, Link, Redirect, withRouter } from 'react-router-dom';


二、使用路由

    1、react-router和react-router-dom,不需要都引用,只是后者多<Link> <BrowserRouter> 这样的 DOM 类组件

    2、如果搭配 redux ,你还需要使用 react-router-redux

    index.js: 在入口文件加上路由配置

        import React from 'react'
        import { Router, Route, Link } from 'react-router'

        // render中加入路由配置
        React.render((
            <Router>
                <Route path="/" component={App}>
                    <Route path="about" component={About}/>
                    <Route path="users" component={Users}>
                        <Route path="/user/:userId" component={User}/>
                    </Route>
                    <Route path="*" component={NoMatch}/>
                </Route>
            </Router>
        ), document.body)


二、<BrowserRouter> HTML5 history路由组件

    1、basename - 为所有位置添加一个基准的url

        <BrowserRouter basename="/minooo" />
        <Link to="/react" /> // 最终渲染为 <a href="/minooo/react">

    2、getUserConfirmation() - 导航到此页执行的函数

        const getConfirmation = (message, callback) => {
            const allowTransition = window.confirm(message)
            callback(allowTransition)
        }
        <BrowserRouter getUserConfirmation={getConfirmation('Are you sure?', yourCallBack)} />

    3、forceRefresh - bool当浏览器不支持 HTML5 的 history API 时强制刷新页面

        const supportsHistory = 'pushState' in window.history
        <BrowserRouter forceRefresh={!supportsHistory} />    

    4、keyLength - 路由的长度

    5、children - 渲染唯一子元素


三、<HashRouter> 该技术只是用来支持旧版浏览器, Hash history 不支持 location.key 和 location.state


四、<Route> 最重要的组件,页面访问地址与Router的path进行匹配,渲染出对应的UI界面

    <Route path="/" component={Main}> - 渲染的组件

    <Route> 自带三个 render method 和三个 props

    1、render methods 分别是:

        <Route component> - 当访问地址和路由匹配时,一个组件才会被渲染,此时此组件接受match、location、history

            * <Route path="/home" component={Home} />

        <Route render> - 适用于内联渲染,不会产生重复装载问题

            * <Route path="/home" render={() => <h1>Home</h1} />

        <Route children> -

    2、props分别是: match、location、history

            <Route path="/:id" component={ListBasic}></Route>          // 参数传给子组件
            this.props.match.params.id            // 获取参数值

    3、path: 路由路径

         exact: true,表示独一无二的路由

            // 如果不加exact, "/" 时加载main, "/login" 会显示main和login两个组件,如果加了exact在"/"路由时只会显示main组件
            <div style={{ height: "500px", backgroundColor: "#ccc"}}>
                <Route path="/" exact component={Main} />
                <Route path="/login" component={Login} />
                <Route path="/todoList" component={TodoList} />
            </div>

         strict: true,如果path为'/one/'将不能匹配'/one',但可以匹配'/one/two'


五、路由跳转

    1、Link、NavLink

        <Link to="/course" />
        <Link to={{
				pathname: '/course',
				search: '?sort=name',
				state: { price: 18 }
			}} />

        // 给导航使用的
        <NavLink
            to="/about"
            activeStyle={{ color: 'green', fontWeight: 'bold' }}
        >MyBlog</NavLink>

    2、JS跳转

        export default class SelectCard extends Component {
            constructor(props){
                super(props);
            }
            goHomePage(){
                this.props.history.push('/main');            // 跳转
            }
            render(){
                return {
                    <div>
                        <button onClick={this.goHomePage.bind(this)}>跳转</button>
                    </div>
                }
            }
        }

        /** 注意如果使用redux子组件会取不到this.props.history **/
        解决方法: 在子组件中
        import PropTypes from 'prop-types'

        子组件名.contextTypes = {
            router: PropTypes.object.isRequired
        }

        console.log( this.context.router.history.push('/') );


六、<switch> 只渲染出第一个与当前访问地址匹配的 <Route> 或 <Redirect>

    // v3
    <Route path='/' component={App}>
        <IndexRoute component={Home} />
        <Route path='about' component={About} />
        <Route path='contact' component={Contact} />
    </Route>

    v4中使用<switch>来取代<IndexRouter>, 当react渲染时switch下第一个子<route>会被渲染
    // v4
    const App = () => (
        <Switch>
            <Route exact path='/' component={Home} />
            <Route path='/about' component={About} />
            <Route path='/contact' component={Contact} />
        </Switch>
    )


七、添加图像、字体和文件

    组件中加载图片
    import logo from './logo.png';        导入图片
    <img src={logo} alt="Logo" />;

    css中加载图片
    .logo{
        background-image: url('./logo.png');
    }


八、Redirect重定向 和 未匹配路由跳转到404

    // 跳404用法
    render() {
    return (
    <Router>
      <div className="main-container">
        <Route path="/main" component={Main} />
        <Route component={Page404}/>                // 没有定义path就是没有路径匹配时
      </div>
    </Router>
    )
    }

    // Redirect 用法不渲染组件直接重定到其它页面
    render() {
        if(!this.state.logined){
            return (
                <Redirect to="/todoList" />
            )
        }
        return (
            <div>
                这里是Login组件
            </div>
        )
    }


九、动态路由

    1、路由变量

        定义变量: <Route path="/:msg" component={Message} />
        获取变量: props.match.params.定义的名

        <Route path="/hello/:name">         // 匹配 /hello/michael 和 /hello/ryan
        <Route path="/hello(/:name)">       // 匹配 /hello, /hello/michael 和 /hello/ryan
        <Route path="/files/*.*">           // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg

        Example:

            class Message extends Component {
                render(props){
                    return (
                        <div>
                            // 这里this.props.params.msg 接收的是to的名字
                            <h1>{this.props.match.params.jumpUrl || 'hello'}</h1>
                            <Links />
                        </div>
                    )
                }
            }

            const Links = () =>{
                <nav>
                    <Link to="/">Hello</Link>
                    <Link to="/yong">Yong</Link>
                    <Link to="/feng">Feng</Link>
                </nav>
            }

            class ParamsRoute extends Component {
                render() {
                    return (
                        <Router history={hashHistory}>
                            <Route path="/(:msg)" component={Message} />
                        </Router>
                    );
                }
            }
            export default ParamsRoute;


    2、query: 获取URL中的参数

        1) 获取参数

            http://localhost:8080/#/?message=ssssss

            const Page = (props) =>
                <div>
                    <h1>{props.location.query.message || 'Hello'}</h1>            // props.location.query.message输出 ssssss
                </div>

        2) <Link>请求时带参数

            const Page = (props) =>
                <div>
                    <h1>{props.location.query.message || 'Hello'}</h1>
                </div>

            const Links = () =>
                <nav>
                    <Link to={{ pathname: "/", query: {message: "ssssss"} }} />         
                </nav>

        3) js中请求参数 

            goToMessage(){
                that.props.history.push({ 
                    pathname : '/success',
                    query : { messageTitle: '充值成功', messageContent: '请取您的充值单请取您的充值单请取您的充值单'} 
                })
            }


十、路嵌套和展示

    // v2/3版本 就是一个路由配置文件,在放子路由的地方使用
    <div>
        { this.props.childre }                // 加载子路由的位置
    </div>

    // v4版本
    // router.jsx
    export default class Index extends Component {
        render() {
            return (
                <Router>
                    <Switch>
                        <Layout />
                    </Switch>
                </Router>
            )
        }
    }

    // layout.jsx
    export default class Layout extends Component {
        render() {
            let menuHtml = () => {
                const menuItem = [];
                this.state.menuList.forEach(function(menu, idx, arr){
                    menuItem.push(
                        <li>
                            <Link to={menu.path}>{menu.title}</Link>
                        </li>
                    )
                })
                return menuItem
            }

            return (
                <div>
                    <header style={{height: "100px"}}>
                        菜单: { menuHtml() }
                    </header>
                    <main style={{ height: "500px", backgroundColor: "#ccc"}}>
                        // 这下面都是子路由的展示位置
                        <Route path="/" exact component={Main} />
                        <Route path="/login" component={Login} />
                        <Route path="/todoList" component={TodoList} />
                        <Route component={Page404}></Route>                        // 注: 如果没有匹配路由指到到404页面, 不需要使用Redirect
                    </main>
                </div>
            )
        }
    }

    // login.jsx
    export default class Login extends Component {
        constructor(props){
            super(props);
            this.state = {
                logined: true
            }
        }

        render() {
            /* 这里可以不渲染组件直接重定到其它页面 */
            if(!this.state.logined){
                return (
                    <Redirect to="/todoList" />
                )
            }
            return (
                <div>
                    这里是Login组件
                </div>
            )
        }
    }

https://www.jianshu.com/p/7bb4c1a0530d
https://www.jianshu.com/p/548674270455


十一、withRouter 

    withRouter是专门用来处理数据更新问题,否则可能会出现路由地址改变但页面没有相应改变的bug

    import React, { Component } from 'react'
    import { withRouter } from 'react-router'
    import { Route, Redirect } from 'react-router-dom'

    class AuthRouter extends Component {
        render() {  
            const { component: Component, ...rest } = this.props
            const isLogged = sessionStorage.getItem("isLogin") === "1" ? true : false;

            return (
                <Route {...rest} render={props => {
                    return isLogged
                        ? <Component {...props} />
                        : <Redirect to="/login" />
                }} />
            )
        }
    }
    export default withRouter(AuthRouter);

dva+react+Ant 蚂蚁金服脚手架

单独加载ant UI框架,也可以使用dva-cli蚂蚁提供的脚手架,支持IE9及以上版本

一、单独添加ant  

    $ cnpm i antd --save

    import 'antd/dist/antd.css';         // 引入样式
    import { DatePicker } from 'antd';    // 这种方法的引用必须要添加babel-plugin-import的按需加载组件
    ReactDOM.render(<DatePicker />, mountNode);            

二、按需加载插件 babel-plugin-import

    $ cnpm i babel-plugin-import --save

    .babelrc添加参数:
    {
        "plugins": [
            ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }] // `style: true` 会加载 less 文件
        ]
    }

三、dva-cli安装

    $ cnpm i dva-cli    

    $ dva new [项目名] && cd [项目名]        // 创建项目

    $ cnpm install antd babel-plugin-import --save        // 安装 ant 和 babel插件(用来按需加载 antd 的脚本和样式的)

        .webpackrc文件加参数
        {
            "extraBabelPlugins": [
                ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
            ]  
        }

四、运行

    $ npm start         // 生产环境运行

    $ npm run build        // 打包项目,发布线上环境在Dist目录下生成静态文件,js、css压缩

五、redux的使用

    有两个目录,models和components, model来定义state和action的,components来编写组件

    dva提供conect方法,将model和component串联起来

    import { connect } from 'dva';

    const Products = ({ dispatch, products }) => { ... }
    export default connect(({ products }) => ({
        products,
    }))(Products);

六、配置

    1、 .roadhogrc.mock.js  用于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'); },
        };

    2、.webpackrc  

        {
            "extraBabelPlugins": [
                ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
            ],
            "entry": "src/index.js",        // 可以配置webpack的选项,参数见面下roadhog的文档连接
            "proxy": {                        // 设置代理
                "/api": {
                    "target": "http://jsonplaceholder.typicode.com/",
                    "changeOrigin": true,
                    "pathRewrite": { "^/api" : "" }
                }
            }
        }

    3、设置端口

        默认是8000,可以在package.json中设置
        "scripts": {
               "start": "PORT=3000 roadhog server",        // windowns设置 set PORT=3000&&roadhog dev
        }

    4、public目录会在build的copy到dist目录下,存放一些静态文件

https://github.com/sorrycc/blog/issues/18            // dva
https://github.com/sorrycc/roadhog/blob/master/README_zh-cn.md    // roadhog


七、fatch请求

    dva-cli中utils目录中request.js中定义了一个通过fatch来发ajax请求的方法

    业务中调用:
    function IndexPage() {
        var getUser = () => {
            Request('/api/posts', {method: 'GET'})
            .then((data)=>{
                debugger;
            })
        }
        getUser()

        return (
            <div className={styles.normal}>
                ...
            </div>
        );
    }
    exports default IndexPage;

收集组件

1、react-transition-group - 页面切换过渡效果

https://github.com/reactjs?page=2

| https://facebook.github.io/create-react-app/ // create-react-app 官网
| https://react-guide.github.io/react-router-cn/ // React-router 中文网
| https://reacttraining.com/react-router/web/example/basic // 官方示例**
| https://www.jianshu.com/p/25e9ba1ebafb // react源码分析
| https://github.com/YutHelloWorld/Blog/issues/4 // v2、3迁移v4
| https://www.jianshu.com/p/e0a0ed6c3b8a // 路由跳转登录验证
| https://www.jianshu.com/p/e3adc9b5f75c/
| https://segmentfault.com/a/1190000009349377 // router ^4
| https://github.com/reactjs/react-router-redux

React(之一)基础

| 创建组件
| render
| props、propType、state、setState()、replaceState()
| 获取DOM this.refs.xxx 或 ReactDOM.findDOMNode(this.refs.xxx)
| React.Children 获取父组件传的DOM
| mixin

React

React包括几个概念: 1、组件       2、JSX     3、虚拟DOM     4、单向数据绑定        5、受控组件

1、核心是封装组件,只关注UI,状态变更重新渲染整个组件

2、JSX: HTML代码可以直接嵌到JS代码中,这就是React提出的叫JSX的语法,原来前端以表现和逻辑层分离为主,但HTML是组件的一部分所以不能分割,JSX 将动态值放到 { ... }

3、单向数据流: 当数据更新会渲染整个app

    React的渲染方式: 用户输入 ~> 从API获取数据 ~> 将数据传给顶层组件 ~> React将每个组件渲染出来, 不会象MVC一样的双向数据绑定、和数据模型的脏值检测、不会有确切的DOM操作

4、虚拟DOM树: React重建一个DOM树,找到与上一个版本的DOM的差异,计算出新的DOM更新操作,从操作队列中指执行DOM更新操作

    http://www.alloyteam.com/2015/04/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF%E8%A7%A3%E5%AF%86-virtualdom/      // 前沿技术解密——VirtualDOM

5、IE浏览器要在8.0版本以上

rrc 创建带redux组件
rcc 创建react组件
rca 创建action
reducer 创建reduer

react引用方式

// Component作为所有组件的基类,提供很多组件共有的功能
import React, { Component } from 'react';
import { reactDom } from 'react-dom';

// 定义组件
class SimpleComponent extends Component {
    render(){
        return <div> React,我们来了... </div>;
    }
}
export default SimpleComponent;

// 组件渲染
reactDom.render(<HelloMessage />, document.getElementById('app'));

生命周期

组件的生命周期三个过程

    装载过程(Mount) - 把组件第一次在DOM树中渲染的过程

    更新过程(Update) - 当组件被重新渲染的过程

    卸载过程(Unmount) - 组件从DOM中删除的过程


一、装载过程

    1、constructor - ES6每个类的构造函数,创建一个组件就会先调用对应的构造函数

    2、getInitialState() - 初始化this.state,(ES6不起作用)

    3、getDefaultProps - 设置默认的props (ES6不起作用)

    4、componentWillMount() - 组件挂载前(调用render方法之前触发, 没有任何渲染,就算调用了this.setState()也不会引发绘制)

    5、render() - 创建虚拟dom,进行diff算法,更新dom树,如果不需要渲染可以将render函数返回一个null或false

    6、componentDidMount() - 组件渲染后已经装载到DOM树中,这时候可以refs操作获取或操作DOM节点


二、更新过程(当props或state被修改时就会引发组件的更新过程)

    1、componentWillReceiveProps(nextProps) - 当父组件改变了传递给子组件props的值,这时候子组件此方法会被调用

    2、shouldComponentUpdate(nextProps, nextState) - react性能环节,父组件传给子组件的props值改变,子组件中的shouldComponentUpdata()方法会先调用,前后两个props相同返回false阻止更新,不需要在创建新的dom树在进行diff算法,默认为true

        shouldComponentUpdate(nextProps, nextState){
            return nextProps.name === nextState.name        // 如果返回false就算父组件传递的props值改变,也不会执行render
        }

    3、componentWillUpdate() - 与装载过程相同 

    4、render() - 与装载过程相同

    5、componentDidUpdate() - 与装载过程相同


三、卸载组件

    1、componentWillUnmount() - 卸载前调用

JSX语法

一、注释

    {/* 这里用于单多行注释 */}


二、根元素只能有一个

    class ComponentDemo extends Component {
        render(){
            // 以下写法直接报错
            return (
                <div>
                    hello
                </div>
            );
        }
    }


三、属性名不能和 js 关键字冲突

    1、class => className

    2、read => readOnly

    3、for => htmlFrom 不能直接使用for

        <div htmlFor='male'>Male</div>


四、JSX spread

    { ...spreadObj } 可以直接将spreadObj的对象传递过去

    import SpreadDemo from './components/spread';
    const spreadObj = {
        name: 'siguang',
        carType: '卡宴'
    }
    render(<SpreadDemo {...spreadObj} />, document.querySelector('#app'));


五、渲染HTML

    const elements = '<p>aaa</p><p>bbb</p><p>ccc</p>';
    <p dangerouslySetInnerHTML={{ __html: elements }}></p>

http://lib.csdn.net/article/react/22655

class和style

一、class

    方法1:
        import './admin.css'
        render() {
            return (
                <div className="container">            // 注意这里的值为字符串而不是{}
                    <div className="sild">xxxx</div>
                </div>
            );
        }

    方法2:
        import styles from './admin.css'
        render() {
            return (
                <div>
                    <div className={style.sild}>xxxx</div>
                </div>
            );
        }


二、style 行内样式

    方法1:
        const container = {
            display: flex;
            height: 100%;
        }

        render() {
            return (
                <div style={container}> xxx </div>
            );
        }

    方法2: <div style={{ width: '100px', float: 'left'}}>       // 注意这里属性值要使用引号

组件

一、组件创建

    1、类式组件

        import React, { Component } from 'react'
        import ReactDom, { render } from 'react-dom'

        // 所有创建的组件都继承于Component对象
        class ShowMessage extends Component{
            constructor(props){
                super(props);
            }
            render(){
                return <div>Hello name {this.props.name}</div>
            }
        }
        export default ShowMessage;


    2、函数式组件

        const Welcome = (props) => {
            return <h1>Hello, {props.name}</h1>;
        }
        <Welcome username="this.state.username" />


    函数组件与类式组件的区别:

        类组件有state, 函数组件没有state

        类组件有生命周期函数


二、组件注意的地方

    1、组件名首字母必须大写

    2、根元素只能有一个标签元素

        class ComponentDemo extends Component {
            render(){
                return (
                    <div class="boxA">            {/* 根元素只能有一个 */}
                        aaaaaaaa
                        <div class="boxB">
                            bbbbbb
                        </div>
                    </div>
                )
            }
        }
        export default ComponentDemo;

    3、可以使用es6的 spread 变量传递 {...obj}

        let person = <Person name={window.isLoggedIn ? window.name : ''} />

    4、refs属性获取真实的DOM节点

        组件并不是真实的DOM节点,而是在内存中的一种数据结构叫虚拟DOM,如果要从组件内获取真实的DOM节点,需要用到refs属性

组件嵌套

childList.jsx:

    class ChildrenA extends Component{
        constructor(props){
            super(props);
        }
        render(){
            return (<div>
                {/* 这里是子组件A   this.props.children获取组件的内容,相当前于vue中的solt */}
                {
                    React.Children.map(this.props.children, function(c){        // React.Children是获取组件中的元素
                        return <p>{c}</p>
                    })
                }
            </div>)
        }
    }

    class ParentComponent extends Component{
        render(){
            return <div>
                <ChildrenA>
                    <p>福特</p>
                    <p>丰田</p>
                    <p>本田</p>
                </ChildrenA>
            </div>
        }
    }
    export default ParentComponent;

index.js:

    import ParentComponent from './components/ParentComponent'
    render(<ParentComponent />, docuemnt.querySelector('#box'));

组件间通信

一、组件之间通信的几种情况

    1、父组件向子组件通信

    2、子组件向父组件通信

    3、跨级组件之间通信

    4、非嵌套组件间通信


二、父向子通信 通过props

    parent.jsx
        <child message={this.state.sayMessage} />

    child.jsx
        {this.props.message}        // 接收


三、子向父通信

    parent.jsx
        <child getMessage={this.getMessage.bind(this)} />
        getMessage(msg){
            this.setDate({message: msg})
        }

    child.jsx
        this.props.getMessage('这里告诉父组件消息')


四、跨级组件

    父组件向很深的几层子组件通信有两种方法: 1、props一层一层传   2、使用context对象


五、events非嵌套组件通信, 使用事件订阅

    $ npm i events --save

    1、创建一个ev.js

        import { EventEmitter } from 'events'
        export default new EventEmitter();

    2、brotherA.jsx

        // 在组件装载完成以后
        componentDidMount(){
            // 声明一个自定义事件        
            this.eventEmitter = emitter.addListener("callMe", (msg)=>{
                this.setState({
                    msg
                })
            });
        }

        // 组件销毁前移除事件监听
        componentWillUnmount(){
            emitter.removeListener(this.eventEmitter);
        }

        render(){
            return (
                <div>
                    {this.state.message}
                </div>
            )
        }

    3、brotherB.jsx

        sendBrotherMessage(){
            return emitter.emit("callMe", "Hello");     // callMe为自定义函数,hello为发的消息参数
        }
        <input type="button" value="向A子组件发消息" onClick={this.sendBrotherMessage.bind(this)} />


六、redux或其它状态管理库

Context对象

context对象来跨级传递

context与react版本分两种写法,以下为react v16.3版本的示例

一、React.crateContext() - 创建一个Context

二、初始化createContext下的方法

    1、provider - 父组件中传递的值

    2、consumeer - 子组件接收的值

三、示例:

    创建一个contextStor.js:
        import React, { Component } from 'react'
        export const { Provider, Consumer } = React.createContext("Light");        // 默认主题是Light

    parentPage.jsx:
        import Ca from './cA'
        import { Provider } from './ccontextStor';

        export default class ParnentPage extends Component {
            render () {
                return (
                    <Provider value={{contextVal: '这里是context的传递的内容'}}>
                        <h1>这里是Parent组件</h1>
                        <Ca />
                    </Provider>
                )
            }
        }

    cA.jsx:
        import Cb from './cB'
        export default class Ca extends Component {
            render () {
                return (
                    <div>
                        <h2>这里是CA</h2>
                        <Cb />
                    </div>
                )
            }
        } 

    cB.jsx:
        import { Consumer } from './ccontextStor';
        export default class Cb extends Component {
            render () {
                return (
                    <div>
                        <h3>这里子CB</h3>
                        <Consumer>
                            {
                                context => {
                                    return (
                                        <div>{context.contextVal}</div>
                                    )
                                }
                            }
                        </Consumer>
                    </div>     
                )
            }
        }

数据流

React是单向数据流,数据从父组件传到子组件,子组件通过props获取数据,顶层组件改变了props,React会遍历整个组件树,重新渲染整个组件

数据流包括: Props 和 State

props

{ this.props.name } 读取props的值

一、props API:

    this.props.children - 所有子组件的内容

        <ChildrenComponent>
            <p>aa</p>
            <p>bb</p>
            <p>cc</p>
        </ChildrenComponent>

    this.props.map

    this.props.filter 


二、设置一个默认的props

    /* 通过 defaultProps 来定义 */
    class PropsDemo extends Component {
        constructor(props) {
    super(props);        // 调用父类,如果不写会调不到
       }
        render(){
            return (
                <div class="box">
                    name: {this.props.name}, age: {this.props.age}
                </div>
            )
        }
    }

    // 初始化props
    PropsDemo.defaultProps = {
        name: '123123'        // 默认一个name值,如果父组件没有传值就会取默认值
    }


三、propsType 校验props类型

    $ npm install --save prop-types

    import React, { Component, PropTypes } from 'react';

    class PropTypesDemo extends Component {
        render(){
            return <b>{this.props.title}</b>
        }
    }

    // 静态属性定义propTypes, title只能为string
    PropTypesDemo.propTypes = {
        title: React.PropTypes.string.isRequired
    }

    export default PropTypesDemo;


四、Example

    PropsComponent组件:

        class PropsComponent extends Component {
            render(){
                return (
                    <div class="box">
                        name: {this.props.name}, age: {this.props.age}         {/* 这里接收props值 */}
                    </div>
                )
            }
        }

        // 输出组件接口
        export default PropsComponent;

    index.js:

        import PropsComponent from '../components/props';

        // 要传的值
        let obj = {
            name: 'haha',
            age: '300'
        }

        // 方法1、直接传给属性
        render(<PropsComponent name={obj.name} age={obj.age} />, document.querySelector('body'));

        // ...obj 来对多值进行解析赋值
        render(<PropsComponent {...obj} />, document.querySelector('body'));


五、this.props.children 访问自定义子节点

    // 将组件下的所有子节点通过map获取到,并重新包装
    import React, { Component } from 'react';

    class UseChildren extends Component {
        render(){
            return  <ul>
                {
                    // 会将UseChildren组件下所有的元素取出
                    React.Children.map(this.props.children, function(c){
                        return <li> {c} </li>
                    })
                }
            </ul>
        }
    }

    class ChildrenDemo extends Component {
        render(){
            return (
                <UseChildren>
                    <a href="#">Facebook</a>
                    <a href="#">Google</a>
                    <a href="#">Space</a>
                </UseChildren>
            )
        }
    }
    export default ChildrenDemo;


六、this.props.content 可以传任意结构的JSX结构

    // 父组件通过content传值
    <ContentChildrenComponent content={
        <div>
            <h2>React.js 小书</h2>
            <div>开源、免费、专业、简单</div>
            订阅:<input />
        </div>
    } />

    // 子组件调用
    render() {
        return (
            <div>
                { this.props.content }
            </div>
        );
    }


七、组件传值

    <ChildComponent propsData={this.state.username} />

    <LikeButton wordings={{likedText: '已赞', unlikedText: '赞'}} onClick={() => console.log('Click on like button!')}/>

state

每一个React组件都有一个自己的state对象,与props区别是state只能在当前组件内部使用,props可以将数据传递给子组件

一、初始化state

    class StateDemo extends Component {
        constructor(){
            super();            // 继承Component

            // 这里初始化state
            this.state = { 
                userName: 'siguang' 
            }
        }

        setUserName: function(){
            this.setState({userName: 'lulu'});        // 修改state值
        },

        render(){
            return (
                <div>
                    这里是props获取的值 {this.state.userName};        // 获取state的值
                    <input type="button" onClick={this.setUserName.bind(this)} value="点击修改state" />
                </div>
            )
        }
    }


二、setState中this的指向

    1、<Button type="primary" onClick={ this.addCar.bind(this) }>开始</Button>
         <Button type="primary" onClick={ (e) => this.addCar(e) }>开始</Button>        // 渲染中箭头函数

    2、<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>

    3、addCar = () => {
            this.setState(function(state, porps){
                return {
                    car: state.car + 1
                }
            })
        }
        <Button type="primary" onClick={ this.addCar }>开始</Button>


三、setState()异步的问题

    setState()是异步导致如果直接在下面在取state值还没变过来,所以第二个回调是当state值设置成功后在执行的函数

    this.setState(
        { username: uname }, 
        () => {  console.log(this.state.username); }        // 这里还是为之前的值而不是「sg」
    );

refs操作真实DOM

获取ref: this.refs.xxx

Example:

    class RefsDemo extends Component {
        componentDidMount(){
            // refs直接获取
            const contentB = this.refs.content;
            console.log( contentB.innerHTML );
        }

        render(){
            return(
                <div>
                    <h3>React操作DOM</h3>
                    <p ref="content">这里是DOM元素的内容</p>        // 这里定义ref访问的名
                </div>
            )
        }
    }

mixin

通过mixin可以将组件间共享代码,将两个组件共同的属性、方法存储到mixin对象中

mixin不支持ES6的声明组件方式

ES5写法

    import React from 'react';

    // 抽取出的公用方法
    var SetIntervalMixin = {
        componentWillMount: function() {
            this.intervals = [];
        },
        setInterval: function() {
            this.intervals.push(setInterval.apply(null, arguments));
        },
        componentWillUnmount: function() {
            this.intervals.forEach(clearInterval);
        }
    };

    var MixinDemo = React.createClass({

        // 这里加载mixin
        mixins: [SetIntervalMixin],

        getInitialState: function() {
            return {seconds: 0};
        },

        componentDidMount: function() {
            // Call a method on the mixin
            this.setInterval(this.tick, 1000);
        },

        tick: function() {
            this.setState({seconds: this.state.seconds + 1});
        },

        render: function() {
            return (
                <p>
                    计时器已经运行了:  {this.state.seconds} 秒.
                </p>
            );
        }
    });

    export default MixinDemo;

表单

一、defaultValue 与 value 的区别

    <input type="text" value={this.state.inputValue} />

    如果使用 value来绑定state的值,输入值时不会被改变,使用 defaultValue 值会被改变

    class App extends Component {
        constructor(props) {
            super(props);
            this.state = {
                username: ''
            }
        }
        handleUsername(e){
            const val = e.target.value;
            this.setState({username: val});
        }
        getName(){
            alert(this.state.username)
        }
        render() {
            return (
                <div className="App">
                    <input placeholder="Basic usage" defaultValue={this.state.username} onChange={this.handleUsername.bind(this)} />
                    <button type="primary" onClick={this.getName.bind(this)}>Primary</button>
                </div>
            );
        }
    }

二、react表单组件与html的不同

    1、value、checked: 属性设置值后,用户输入无效

    2、textarea: 的值要设置在value属性

        <textarea name="description" value="This is a description." />

    3、select: value属性可以是数据,不建使用option的selected属性

        <select multiple={true} value={['B', 'C']}>
            <option value="A">Apple</option>
            <option value="B">Banana</option>
            <option value="C">Cranberry</option>
        </select>

    4、radio/checkbox/option 点击后触发 onChange

受控组件

受控组件就是为某个form表单组件添加value属性;非受控组件就是没有添加value属性的组件

一、受控组件

    <input>,<textarea>以及<select>通常保持自己的状态和基于用户输入更新它

    handleChange(event) {
        this.setState({value: event.target.value});
    }
    <input type="text" value={this.state.value} onChange={this.handleChange} />


二、不受控制组件

    this.input = React.createRef();                    // 创建不受控组件
    <input type="text" ref={this.input} />    // 绑定
    this.input.current.value                                // 调用不受控组件

    class NameForm extends React.Component {
        constructor(props) {
            super(props);
            this.handleSubmit = this.handleSubmit.bind(this);

            // 创建不受控组件
            this.input = React.createRef();
        }

        handleSubmit(event) {
            alert('A name was submitted: ' + this.input.current.value);
            event.preventDefault();
        }

        render() {
            return (
                <form onSubmit={this.handleSubmit}>
                    <label>
                        Name:
                        <input type="text" ref={this.input} />
                    </label>
                    <input type="submit" value="Submit" />
                </form>
            );
        }
    }

碎片

render的时候需要有一个父的元素,如果组件嵌套会多出来无用的元素,可以使用<React.Fragment> ... </React.Fragment>

class Columns extends React.Component {
    render() {
        return (
            <div>
                <td>Hello</td>
                <td>World</td>
            </div>
        );
    }
}
class Table extends React.Component {
    render() {
        return (
            <table>
                <tr>
                    <Columns />                    // 输出<div> ... </div>
                </tr>
            </table>
        );
    }
}

可以写成

class Columns extends React.Component {
    render() {
        return (
            <React.Fragment>
                <td>Hello</td>
                <td>World</td>
            </React.Fragment>
        );
    }
}

React脚手架搭建

一、可以使用官网的 create-react-app、ant或自己搭  https://github.com/facebook/create-react-app

二、使用的JSX和ES6所以需要转换,使用版本:

    react: "^15.4.2", react-dom: "^15.4.2", react-router: "^2.0.0"", webpack: "^3.10.0", webpack-dev-server: "^2.11.1"

    react 16以上版本和 react-router 4以上版本会有问题

三、搭建

    $ npm init

    $ npm i react react-dom react-router --save

    $ npm i webpack webpack-dev-server --save

    2、安装插件

    $ npm i babel babel-loader babel-core babel-preset-es2015 babel-preset-react --save

    $ npm i node-sass file-loader url-loader css-loader sass-loader style-loader --save

    $ npm i react-hot-loader prop-types events react-slot --save             // react组件,热更新、检测props类型、事件用来通信包、slot

    $ npm i html-webpack-plugin open-browser-webpack-plugin --save            // 自动引入静态资源到相应的html、自动打开浏览器

    $ touch .babelrc webpack.config.js

    $ mkdir src && cd src && touch index.html app.js

    $ .babelrc 加入

        {
            "presets": ["es2015", "react"]
        }

    $ webpack.config.js

        /**
        * User: siguang
        * Date: 2016/12/28
        * Time: 15:04
        */
        const webpack = require('webpack');
        const path = require('path');
        const HtmlWebpackPlugin = require('html-webpack-plugin');
        const openBrowserWebpackPlugin = require('open-browser-webpack-plugin');

        const basePath = __dirname;
        const appPath = path.resolve(basePath, 'src');
        const buildPath = path.resolve(basePath, 'build');

        module.exports = {
            entry: {
                app: path.resolve(appPath, 'app.js')
            },

            output: {
                path: buildPath,
                filename: '[name].min.js?[hash]',
                // chunkFilename: "[name].min.js?[hash]"
            },

            module: {
                loaders: [

                    // 处理require()引入的css文件,并将代码显示到页面的<style>中
                    { test: /\.css$/, loader: "style-loader!css-loader" },

                    // 将scss文件转成css文件
                    { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},

                    // ?limit=8192  limit设置小于8k的图片转成64位编码,大小8于不会被转码
                    { test: /\.(png|jpg|woff|eot|ttf|svg|gif)$/, loader: 'url-loader?limit=8192'},

                    // ES6 转 ES5
                    {
                        test: /\.jsx?$/,
                        exclude: /node_modules/,
                        loader: 'babel-loader',
                        query: {
                            presets: ['es2015','react']
                        }
                    }
                ]
            },


            plugins: [

                // 压缩打包的文件
                new webpack.optimize.UglifyJsPlugin({
                    compress: {
                        //supresses warnings, usually from module minification
                        warnings: false
                    }
                }),

                // html
                new HtmlWebpackPlugin({
                    // 改变页面的<title>标签的内容
                    title: 'Hello World app',
                    // 模版地址
                    template: path.resolve(appPath, 'index.html'),
                    // 构建后的文件名和目录
                    filename: 'index.html',
                    //chunks这个参数告诉插件要引用entry里面的哪几个入口
                    // chunks:['app'],
                    //要把script插入标签里
                    inject:'body'
                }),

                // 热启动
                new webpack.HotModuleReplacementPlugin(),
                // 自动打开浏览器
                new openBrowserWebpackPlugin({ url: 'http://localhost:3000' })
            ],

            // 查找依赖
            resolve:{

                // require或alias时不需要写后缀
                extensions: [".js", ".jsx", ".css", ".json"],
            },

            // webpack-dev-server 配置
            devServer: {
                port: 3000,                 // 端口
                contentBase: 'build',       // 内容目录
                hot: true,                    // 热刷新
                inline: true
            }
        }`

$ package.json

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "webpack-dev-server --progress --profile --colors --hot --inline --history-api-fallback",
        "build": "webpack --progress --profile --colors --config webpack.production.config.js"
    },

$ 运行

    $ npm run dev     生产环境,并打开webpack服务器

    $ webpack        执行打包到build目录线上环境使用

https://segmentfault.com/a/1190000005969488

Diff算法

当组件更新时候,react会创建一个新的虚拟dom树并会和之前的dom树进行比较,这个过程就用到了diff算法

常用插件

https://github.com/enaqx/awesome-react#boilerplates

1、路由 - react-router

2、布局 - react-blocks     http://whoisandie.github.io/react-blocks/

3、拖拽 - react-dnd        https://github.com/react-dnd/react-dnd

4、代码编辑器 - react-codemirror        https://github.com/JedWatson/react-codemirror

5、富文本编辑器 - react-quill react-draft-wysiwyg        https://github.com/jpuri/react-draft-wysiwyg

6、拾色器 - rc-color-picker、react-color        https://github.com/react-component/color-picker        http://casesandberg.github.io/react-color/

7、响应式 - react-responsive、react-media    https://github.com/contra/react-responsive        https://github.com/ReactTraining/react-media

8、复制到剪贴板 - react-copy-to-clipboard       https://github.com/nkbt/react-copy-to-clipboard

9、管理 document head - react-helmet          https://github.com/nfl/react-helmet

10、Font Awesome 图标 - react-fa        https://github.com/andreypopp/react-fa

11、二维码 -  qrcode.react         https://ant.design/docs/react/recommendation-cn

12、不在使用className - styled-components

    http://www.alloyteam.com/2017/05/guide-styled-components/
    https://github.com/styled-components/styled-components

13、react写动画效果 - css3transform-react

    $ npm install css3transform-react
    http://www.alloyteam.com/2016/12/react-animations-difficult-to-write-try-react-transformjs/

react-slot - react中写solt    http://npm.taobao.org/package/react-slot

积累问题

1、jsx中for循环出标签并插入到render中, 或使用map

    class SideBar extends Component {
        constructor(props) {
            super(props);
            this.state ={
                menu: [
                    {
                        path: '/home',
                        name: '主页'
                    },
                    {
                        path: '/parentToChild',
                        name: '将父向子组件传数据'
                    },
                ]
            }
        }

        render() {
            // 定义一个返回菜单html内容的方法
            let renderMenuHtml = () => {
                let homeHtml = [];
                this.state.menu.forEach(function(item, idx, arr){
                    homeHtml.push(<li>
                        <Link to={item.path}>{item.name}</Link>
                    </li>)
                })
                return homeHtml
            }

            // 另一种写法map推荐
            {/*
                var renderMenuHtml = this.state.menu.map(function(c){
                    return(
                        <li>
                            <Link to={item.path}>{item.name}</Link>
                        </li>
                    );
                });
            */}

            return (
                <div className="side-box">
                    <ul>
                        {renderMenuHtml()}        {/* 插入内容 */}
                    </ul>
                </div>
            );
        }
    }


2、使用redux子组件会取不到 this.props.history

    因为redux会将值传给props,所以就需要通过context来获取

    解决方法: 在子组件中
    import PropTypes from 'prop-types'

    子组件名.contextTypes = {
        router: PropTypes.object.isRequired
    }

    console.log( this.context.router.history.push('/') );


3、子组件调用父组件中的方法

    class ComponentClider extends Component {
        constructor(props){
            super(props);
        }

        render(){
            return(
                <div>{ this.props.add(111,555) }</div>
            )
        }
    }

    export default ComponentParent extends Component {
        add(a, b){
            return a + b;
        }

        sub(a, b) {
            return a - b;
        }

        render(){
            return (
                <div>
                    {/* 这里可以父组件中自己的方法直接调用 */}
                    { this.add(100, 200) }
                    { this.sub(200, 10) }

                    <ComponentClider add={(a, b) => this.add(a, b)} sub={(a, b) => this.sub(a, b)}></ComponentClider>
                </div>
            )
        }
    } 

    <!-- 父组件也可以写成 -->
    export default ComponentParent extends Component {
        multiplicative(a, b) {
            return a * b
        }

        add = (a, b) => {
            return a + b;
        }

        sub = (a, b)  =>  {
            return a - b;
        }

        render(){
            const propsFun = {
                add: this.add,
                sub: this.sub
            }

            return (
                <div>
                    {/* 这里可以父组件中自己的方法直接调用 */}
                    { this.multiplicative(100, 200) }

                    <ComponentClider {...propsFun}></ComponentClider>
                </div>
            )
        }
    } 


4、react原生动态添加多个className会报错

    // 下面的引用会报错
    import style from './style.css'
    <div className={style.class1 style.class2}</div>

    // 解决 使用classnames
    $ npm install classnames --save

    import classnames from 'classnames'
    import styles from './index.less';

    const clsString = classNames(styles.submit, className);
    <div className={clsString}></div>

常用插件

1、react-document-title    根据不同的路由改变文档的title
    https://www.jianshu.com/p/07ed93350483

2、react-container-query   媒体查询 响应式组件

3、classnames      react引用多个className会报错问题

Antd UI组件问题收集

1、Table的每页条数  通过pagination属性来控制分页的内容

    <Table pagination={{ pageSize: this.state.queryInfo.pageSize }} rowSelection={rowSelection} columns={columns} dataSource={data}  />

    https://www.cnblogs.com/jenifurs-blog/p/6020737.html

| https://react.docschina.org/docs/hello-world.html
| http://www.css88.com/react/docs/try-react.html
| https://juejin.im/post/5a9410c25188257a61325eda // react 新老context
| https://www.jianshu.com/p/fb915d9c99c4 // 组件通信
| http://huziketang.com/books/react/
| http://blog.csdn.net/liwusen/article/category/6522963
| http://react-china.org/ 中文社区
| https://github.com/BruceCham/react-cli // 全家桶
| http://www.alloyteam.com/2015/04/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF%E8%A7%A3%E5%AF%86-virtualdom/ // virturalDOM 虚拟DOM
| https://github.com/gaearon/redux-devtools // redux-devtools
| https://guoyongfeng.github.io/book/
| http://uprogrammer.cn/react-tutorial-cn/
| http://huziketang.com/books/react/lesson1
| https://github.com/hyy1115/react-redux-webpack2
| https://github.com/zhbhun/react-learning/tree/master/boilerplate
| http://blog.csdn.net/u013063153/article/details/52497271 // 事件
| https://github.com/gaearon/babel-plugin-react-transform#transforms // babel-plugin-react-transform 相关插件
| http://www.alloyteam.com/2016/03/using-react-to-write-a-simple-activity-pages-design-of-operating-system-article/
| https://juejin.im/post/5a84682ef265da4e83266cc4 // 源码解析
| https://www.jianshu.com/p/ec7c2bab16cc?utm_campaign=maleskine&utm_content=note&utm_medium=pc_all_hots&utm_source=recommendation // vscode 调试react 通过debugger for chrome