React之旅二--Redux

  1. 什么是Redux:Reducer+Flux
    Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
  2. Redux工作流程
    图片
    我们将工作流程比作用户去图书管借书的流程
    图片
  3. 安装redux

    1
    2
    3
    yarn add redux
    &&
    npm i redux
  4. 创建一个Store(图书馆管理员)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        // index.js
    import { createStore } from 'redux'
    const store = createStore();
    export default store
    ```
    5. 创建图书馆系统,并将系统交给管理员
    ```js
    // Store/reducer.js
    const defaultState = {}
    /*
    state:上一次state中存储的数据
    action:用户传递进来修改的方法
    */
    export default (state = defaultState , action) => {
    return state;
    }
    // Store/index.js
    import { createStore } from 'redux'
    import reducer from './reducer.js'
    const store = createStore(reducer);

    export default store
  5. 初步的使用渲染数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // Store/redux.js
    const defaultState = {
    inputValue: '',
    data : [
    'Japanese princess to wed commoner.',
    'Los Angeles battles huge wildfires.',
    ]
    }
    export default (state = defaultState , action) => {
    return state;
    }
    // Store/index.js
    import { createStore } from 'redux'
    import reducer from './reducer.js'
    const store = createStore(reducer);

    export default store

    // todoList.jsx
    import React, { Component } from 'react'
    import store from '../Store/index.js'
    class TodoList extends Component {
    constructor(props) {
    super(props)
    // 也可以 this.state = store.getState()
    this.state = {
    inputValue: '',
    // 在这里获取redux中的数据
    data:store.getState().data
    }
    }
    componentDidMount() { }
    render() { }
    }
  6. 修改store中的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // todoList.jsx 
    // 创建修改方法 调用store.dispatch方法
    handleInputChange = (e) => {
    const action = {
    type: 'change_input_value',
    value:e.target.value
    }
    store.dispatch(action);
    }
    // reducer.js
    // 接受方法 添加判断 并修改属性值
    // reducer中不能修改原有的state中的值 只能先深拷贝一次
    export default (state = defaultState, action) => {
    if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
    }
    return state;
    }
  7. 组件中监听redux修改了 返回最新的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    constructor(props) { 
    // 添加监听的方法
    store.subscribe(this.handleChangeStore)
    }
    // 监听若数据修改 在组件中重新赋值
    handleChangeStore = () => {
    this.setState(() => {
    return {
    inputValue: store.getState().inputValue,
    data:store.getState().data
    }
    })
    }
  8. 统一管理action

    1
    2
    3
    4
    5
    6
    7
    新建actiontypes.js
    将所有的action放在这里定义并导出
    组件和redux使用则单独引入
    解决action拼写错误,缺不报错问题
    export const CHANGE_INPUT_VALUE = 'change_input_value'
    export const ADD_ITEM = 'add_item'
    export const DELETE_ITEM = 'delete_item'
  9. 优化代码 提高可读性方便自动化测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 创建 actionCreateor.js
    // 将原来在组件中创建的action 统一放在此处创建
    import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM} from './actiontypes.js'
    export const getInputValue = (value) => ( {
    type: CHANGE_INPUT_VALUE,
    value
    })
    export const addDataItem = () => ( {
    type: ADD_ITEM,
    })
    export const deleteItem = (index) => ( {
    type: DELETE_ITEM,
    index
    })
  10. 一个完整的列子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    // TodoList.jsx
    import React, { Component, Fragment } from 'react'
    import { Input, Button, List} from 'antd'
    import store from '../Store'
    import { getInputValue, addDataItem, deleteItem } from '../Store/actionCreateor.js'
    class TodoListMy extends Component {
    constructor(props) {
    super(props)
    this.state = {
    inputValue: '',
    data:store.getState().data // 页面加载就获取redux中存储的值
    }
    store.subscribe(this.handleChangeStore)// 添加redux监听 数据发生改变重新赋值
    }
    // input发生改变事件
    handleChangeInput = (e) => {
    let action =getInputValue(e.target.value)
    store.dispatch(action)
    }
    // 监听redux属性值发生改变 冲新赋值
    handleChangeStore = () => {
    this.setState(() => {
    return {
    inputValue: store.getState().inputValue,
    data:store.getState().data
    }
    })
    }
    // 添加一条数据
    handleAddItem = () => {
    if (this.state.inputValue === '') {
    return
    }
    let action = addDataItem()
    store.dispatch(action)
    }
    // 删除一条数据
    handleDeleteItem = (index) => {
    let action = deleteItem(index)
    store.dispatch(action)
    }
    render() {
    return (
    <Fragment>
    <Input style={{ width: '300px' }} placeholder="请输入内容" value={this.state.inputValue} onChange={this.handleChangeInput}></Input>
    <Button onClick={this.handleAddItem} type="primay">提交</Button>
    <List

    style={{width:'300px'}}
    bordered
    dataSource={this.state.data}
    renderItem={(item,index) => (
    <List.Item onClick={this.handleDeleteItem.bind(this,index) } >{item}</List.Item>
    ) }
    />
    </Fragment>
    )
    }
    }
    export default TodoListMy

    // redux.js
    import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM} from './actiontypes.js'
    const defaultState = {
    inputValue: '',
    data : [
    'Racing car sprays burning fuel into crowd.',
    'Japanese princess to wed commoner.',
    ]
    }
    /*
    state:上一次state中存储的数据
    action:用户传递进来修改的方法
    */
    export default (state = defaultState, action) => {
    if (action.type === CHANGE_INPUT_VALUE) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState
    }
    if (action.type === ADD_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.data.push(newState.inputValue);
    newState.inputValue = '';
    return newState
    }
    if (action.type === DELETE_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.data.splice(action.index, 1);
    return newState
    }
    return state;
    }
  11. 拆分reducer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // store/reducer.js
    import { combineReducers } from 'redux'
    import headerReducer from '../common/header/store/reducer.js'

    export default combineReducers({
    header:headerReducer
    })
    // ../common/header/store/reducer.js 在这里的reducer写法与之前一样
    import { CHANGE_INPUT_IS_FOCUS } from '../../../store/actionTypes.js'
    const defaultState = {
    focused:false,
    }
    export default (state = defaultState, action) => {
    if (action.type === CHANGE_INPUT_IS_FOCUS) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.focused = action.value;
    return newState
    }
    return state
    }

    // 注意组件中 也要加模块名
    const mapStateToProps = (state) => {
    return {
    focused:state.header.focused,
    }
    }

redux 核心API

  1. createStore // 创建store
    createStore(reducer,window.REDUX_DEVTOOLS_EXTENSION && window.REDUX_DEVTOOLS_EXTENSION());
  2. store.dispatch(action) // 修改Store中属性的唯一方法
  3. store.getState() // 返回Store中所有的属性值
  4. store.subscribe() // 监听Store中有属性值发生改变 ;立即调用用户设置的方法
  5. combineReducers({}), // 合并多个小的reducer 返回一个大的reducer 类似vuex模块的概念

    redux中间件

  6. 什么是redux中间件: 指的是action和store之间。即dispatch的封装和升级。
  7. redux–redux-thunk 用户可以在action中发送异步请求获取数据(thunk将action转换成函数)

    1. 安装

      1
      npm install redux-thunk
    2. 使用

      1
      2
      3
      4
      5
      // 在创建Store的文件中引入
      import { createStore, applyMiddleware } from 'redux';
      import reducer from './reducer.js'
      import thunk from 'redux-thunk';
      const store = createStore(reducer,thunk);
    3. 为了配合redux-devTools 这样使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      import { createStore, applyMiddleware,compose  } from 'redux';
      import reducer from './reducer.js'
      import thunk from 'redux-thunk';

      const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ }) : compose;

      const enhancer = composeEnhancers(
      applyMiddleware(thunk),
      );
      const store = createStore(reducer,enhancer);
    4. 发送异步请求 获取数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      // actionCreateor.js中
      import { SET_DATA_LIST } from './actiontypes.js'
      export const setDataList = (data) => ( {
      type: SET_DATA_LIST,
      data
      })
      export const getItemList = (index) => {
      return (dispatch) => { // 第一个参数dispatch是默认的=== store.disptch
      axios.get('/api/todoList')
      .then(res => {
      let action = setDataList(res.data)
      dispatch(action)
      })
      .catch(error => { console.log(error);});
      }
      }

      // reduerc.js 中 添加修改方法
      if (action.type === SET_DATA_LIST) {
      const newState = JSON.parse(JSON.stringify(state));
      newState.data = action.data;
      return newState
      }
      // 组件中 使用action
      import { getItemList } from '../Store/actionCreateor.js'
      componentDidMount() {
      let action = getItemList()
      store.dispatch(action)
      }

      // actiontypes.js 添加对应的变量
      export const SET_DATA_LIST = 'set_data_list'
    5. redux-saga

      1. 安装
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
         npm install --save redux-saga
        ```
        2. 使用
        ```js
        // Store中添加saga配置
        import { createStore, applyMiddleware,compose } from 'redux';
        import reducer from './reducer.js'
        import createSagaMiddleware from 'redux-saga'
        import todoSaga from './sagas.js'
        const sagaMiddleware = createSagaMiddleware()
        const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ }) : compose;

        const enhancer = composeEnhancers(
        applyMiddleware(sagaMiddleware),
        );
        const store = createStore(reducer, enhancer);
        sagaMiddleware.run(todoSaga)

        export default store

        // 在组件中使用
        import { geiInitList } from '../Store/actionCreateor.js'
        componentDidMount() {
        let action = geiInitList()
        store.dispatch(action)
        }


        // 添加自定义saga 用来存放异步亲求数据的方法
        // saga也可以监听action的type值 与redux一样
        import { put, takeEvery } from 'redux-saga/effects'
        import { GET_INIT_LIST } from './actiontypes.js'
        import { setDataList } from './actionCreateor.js'
        import axios from 'axios'

        // saga监听action的类型 是GET_INIT_LIST(get_init_list)就会调用getInitList方法
        function* todoSaga() {
        yield takeEvery(GET_INIT_LIST, getInitList);
        }
        // 调用本方法后 获取到的数据 使用put方法返回给redux.js
        function* getInitList() {
        const res = yield axios.get('/api/todoList');
        let action = setDataList(res.data)
        yield put(action)
        }
        export default todoSaga;

        // reducer.js
        // 判断action的类型 进行数据修改
        if (action.type === GET_INIT_LIST) {
        const newState = JSON.parse(JSON.stringify(state));
        newState.data = action.data;
        return newState
        }

        // actionCreateor.js
        // saga 使用的action
        export const geiInitList = () => ( {
        type: GET_INIT_LIST,
        })
        // actiontypes.js
        export const GET_INIT_LIST = 'get_init_list'

react-redux的使用

  1. 安装 react-redux

    1
    npm i react-redux
  2. 创建redux数据仓库(store)

    1
    2
    3
    4
    import { createStore } from 'redux'
    import reducer from './reducer.js'
    const store = createStore(reducer)
    export default store
  3. 在渲染的父组件中(App.js)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import React from 'react'
    import store from '../Store/index.js'
    import { Provider } from 'react-redux'
    import TodoList from './TodoList/TodoList.jsx'
    const App = (
    <Provider store={store}>
    <TodoList></TodoList>
    </Provider>
    )
    export default App
  4. 在TodoList中使用(TodoList.jsx)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    import React,{ Component } from 'react'
    import store from '../Store/index.js'
    import { connect } from 'react-redux'

    class TodoList extends Component {
    render() {
    return (
    <Fragment>
    <div>
    <input onChange={this.props.handleChangeInputValue} placeholder="请输入内容" value={this.props.inputValue} />
    <button>提交</button>
    </div>
    <ul>
    <li>dell</li>
    </ul>
    </Fragment>
    )
    }
    }
    // 获取store中的数据 在这里定义数据 jsx中就可以使用this.props.inputValue方式显示
    const mapStateToProps = (state)=>{
    return {
    inputValue:state.inputValute // 从redux中获取inputValue
    }
    }
    // store.dispatch 将修改redux的方法放在这里 jsx即可使用this.props.handle 使用
    const mapDispatchProps = (dispatch)=>{
    return {
    handleChangeInputValue(e){
    let action = {
    type: 'change_input_value',
    value:e.target.value
    }
    dispatch(action)
    }
    }
    }
    // connect 会自动监听 不需要我们在使用subscribe 方法了
    export default connect(mapStateToProps, mapDispatchProps)(TodoList)
  5. 创建reducer.js 并添加对应的方法 修改store中数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const defaultState = {
    inputValue:''
    }
    export default (state=defaultState, action)=>{
    if(action.type === 'change_input_value'){
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value
    return newState
    }
    return state
    }

immutable.js的使:使store 变成不可更改的对象

  1. 安装

    1
    yarn add immutable
  2. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // header.reducer.js 
    // 引入immutable 并将初始数据转换成immutable对象
    import { CHANGE_INPUT_IS_FOCUS } from './actionTypes.js'
    import { fromJS } from 'immutable'

    const defaultState = fromJS(
    {
    focused:false,
    }
    )
    export default (state = defaultState, action) => {
    if (action.type === CHANGE_INPUT_IS_FOCUS) {
    // const newState = JSON.parse(JSON.stringify(state));
    // newState.focused = action.value;
    // return newState
    // state.set()方法是将原来的数据与新的数据对比 返回一个全新的对象
    return state.set('focused',action.value)
    }
    return state
    }

    // 在组件中使用
    // 不需要在引用immutable
    const mapStateToProps = (state) => {
    return {
    focused:state.header.get('focused'),
    }
    }
  3. Api锦集

    1
    2
    3
    4
    5
    6
    xxx.formJS()                         // 将js转换成immutable对象
    xxx.get('属性').get('') // 获取属性值
    xxx.getIn(['模块','属性']) // 获取属性值
    xxx.toJS('') // 将immutable对象转成js对象
    xxx.set('属性',"值").set('属性',"值") // reducer.js 中修改一个state中的值
    xxx.merge({}) [([])], // reducer.js 中修改多个state中的值 可以用对象的形式 多个set用merge性能能高

缺点:immutable只是将reducer中的数据转换成immutable对象 但是在组件中使用 获取数据的时候
focused:state.header.get(‘focused’), 是先调用js对象 再去调用immutable对象中的属性值
解决办法:统一成immutable对象 使用redux-immutable库

使用redux-immutable管理数据

  1. 安装

    1
    yarn add redux-immutable
  2. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 在总的reducer.js中 将原来import { combineReducers } from 'redux' 更换为以下方式
    import { combineReducers } from 'redux-immutable'
    import headerReducer from '../common/header/store/reducer.js'

    export default combineReducers({
    header:headerReducer
    })

    // 组件中获取值
    const mapStateToProps = (state) => {
    return {
    // 二种方式都可以
    focused:state.get('header').get('focused'),
    // focused:state.getIn(['header','focused'])
    }
    }

react-router-dom react路由的使用

  1. 安装

    1
    yarn add  react-router-dom
  2. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // App.js
    import { BrowserRouter, Route} from 'react-router-dom'
    function App() {
    return (
    <Provider store={store}>
    <>
    <Header></Header>
    // 路由包裹器他下面只能有一个子元素
    <BrowserRouter>
    <>
    // path="/" 路径
    // exact 匹配路径与url一直才会显示
    // render() 需要渲染的组件
    <Route path="/" exact render={() => <div>home</div>}></Route>
    <Route path="/details" exact render={() => <div>details</div>}></Route>
    </>
    </BrowserRouter>
    </>
    </Provider>
    );
    }
  3. 常用Api

    1. BrowserRouter: 路由包裹器 包裹Route
    2. Route: 路由
      1. path: 路由地址
      2. exact: 匹配路径与url一直才会显示
      3. render:渲染的页面
      4. component: 渲染的组件 传组件
    3. Link 用户路由跳转
      1. to:路由地址
    4. withRouter:多层级页面 获取路由传递的参数 组件套组件 路由参数只能在路由组件中获取 使用withRouter可以使子组件也能获取到路由参数
      1. export default connect(mapState,mapDispatch)(withRouter(Detail)) 使用方法
    5. Redirect: 路由重定向
      1. to:路由地址

        异步组件 减少打包体积 提高首页渲染速度

  4. 安装

    1
    yarn add react-loadable
  5. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import Loadable from 'react-loadable';
    import React from 'react'

    const LoadableComponent = Loadable({
    // 加载组件
    loader: () => import('./index.js'),
    // 组件还没有加载好显示的提示信息
    loading() {
    return <div>正在加载</div>
    }
    })
    export default ()=> <LoadableComponent></LoadableComponent>

    //在引入路由组件的地方更改下 引入异步组件 如果路由传参数了 请记得使用withRouter:多层级页面
    // import Detail from './pages/detail/index'
    import Detail from './pages/detail/loadable.js'

yarn 常用的命令

  1. yarn init 初始化
  2. yarn add * 安装一个库
  3. yarn remove 删除一个库
  4. yarn install 安装一个框架所有的包
  5. yarn upgrade * 升级一个包

    注意

  6. reducer必须是纯函数
  7. 什么是纯函数:给出固定的输入,必须给出固定的输出,而且不会有副作用
  8. 只有store才能修改store中的值 redux中修改即store中修改
  9. redux的属性是单一数据流
  10. redux中不能又异步操错,setTimout,setInterval 因为他是纯函数