React状态管理--Redux


2019-7-27 React Redux

状态管理的思想

  1. web 应用是一个状态,视图和状态时对应的
  2. 所有的状态都是可以统一保存的
  3. 使用状态区影响视图,更利于视图的统一性和操作性。
  4. Redux 知识 react 的视图层而不是一定和 react 一起使用的

Redux 原理图解

原理图1

什么时候使用 Redux

  1. 有复杂的状态需要共享
  2. 有状态需要传递很多次并且有多次应用
  3. 有触发一个组件状态改变全局的状态
  4. 用户多层权限等等多用户用心等等,这种情况使用 Redux 比较好
  5. 与服务器之间有大量交互
  6. 其他。。

Redux 使用的原则

  1. 唯一的数据源 Single Source of Truth
  2. 状态时只读的 State is read-only
  3. 数据的修改需要通过纯函数来执行 Changes are made with pure funtion

基本使用方法

安装

npm i redux
# or
yarn add redux
1
2
3

store 和 reducer

使用 Redux 首先需要创建 store 和 reducers 这两个是配套的

# 在store.js 中
import { createStore } from 'redux'
import reducers from './reducer'
export default createStore(reducers)
# 在reducers.js 中
const initState = {
  list: [],
  placeHolder: 'hello world'
}
export default (state = initState, action) => {
  return state
}

1
2
3
4
5
6
7
8
9
10
11
12
13

在 组件使用的时候

import React, { Component } from 'react'
import store from './store'

export default class App extends Component {
  constructor() {
    super()
    this.state = {
      ...store.getState()
    }
  }
  render() {
    return <div>{this.state.placeHolder}</div>
  }
}
// 这里不推荐使用把 store 完全覆盖this.state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这个时候界面就会显示 hello world

  1. reducer 一定要默认导出 state
  2. reducer 需要有初始值给 reducer 第一次运行使用
  3. reducer 内部不可以更该 state
  4. reducer 只能返回新的 state
  5. reducer 必须是一个纯函数不可以有异步等等操作。

修改 state

需要修改 state 就需要创建 action 和对应的 actionTypes 以及对应 actionTypes 的 reducer

  1. 创建 actionTypes

    export const ADD_TODO = 'ADD_TODO'
    export const CHANGE_TODO = 'CHANGE_TODO'
    //或者
    export const TODO = { ADD: 'ADD', CHANGE: 'CHANGE' }
    export const CART = { ADD: 'ADD', CHANGE: 'CHANGE', DELETE: 'DELETE' }
    
    1
    2
    3
    4
    5
  1. 创建 action.js
// 在action.js 中
import { ADD_TODO, CHANGE_TODO } from './actionTypes'
// 这种方法返回action 的方式成文 actioncreater
export const addTodo = todo => {
  return {
    type: ADD_TODO,
    payload: { todo }
  }
}
export const changeTodo = value => {
  return {
    type: CHANGE_TODO,
    payload: { value }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 在使用的时候,需要 store dispatch 对应的 action 给具体的 reducer
store.dispatch(addTodo(value))
1
  1. 书写对应的 reducer 处理

    switch (action.type) {
      case ADD_TODO:
        newState = JSON.parse(JSON.stringify(state))
        newState.list = [...state.list, action.payload]
        newState.inputVal = ''
        return newState
    
      case CHANGE_TODO:
        newState = JSON.parse(JSON.stringify(state))
        newState.inputVal = action.payload
        return newState
    
      default:
        //一定要有默认导出
        return state
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  1. 在页面订阅 state 的变化
store.subscribe(this.storeChange) //订阅
storeChange = () => {
  //获取state的公共参数  也就是每次获取新的都会被订阅到store
  this.setState(store.getState())
}
1
2
3
4
5

combineReducers 的使用

因为在大型应用中不可能只有一个 reducer 而 createStore 又只接受一个 reducer,并且要求是方法

所以在 redux 中给出了一个方法就是 combineReducers,这个方法可以合并导出 reducers

使用:

import {combineReducers} from 'redux'
import 具体的reducer from './对应的reducer文件'
export default combineReducers({
    // 把多个reducer 当作combineReducers的对象参数传入即可
    ...reducers
}//把reducers  给store 使用
1
2
3
4
5
6
7

注意不要手动引入 reducer

combineReducers 接受的是一个对象

异步的 action

因为 reducer 是一个纯函数不可以又异步的操作,所以所有异步的操作都需要在 action dispatch 前完成,在异步完成后手动来 dispatch。

常用的 redux-thunkredux-saga

使用方法

#1 安装
npm i redux-thunk
1
2

使用

// 在 store.js 中引入中间件
// 引入applyMiddleware 应用中间件
import { createStore, applyMiddleware } from 'redux'
// 引入中间件
import thunk from 'redux-thunk'
import rootReducer from './reducers'
//穿件STore
export default createStore(rootReducer, applyMiddleware(thunk))
1
2
3
4
5
6
7
8

在 action 中

// 传入TOdo参数  之后 会得到dispatch 然后通过手动dispatch  即可异步执行
export const asyncAddTodo = todos => dispatch => {
  setTimeout(() => {
    dispatch(addTodo(todos))
  }, 2000)
}
1
2
3
4
5
6

中间件增强函数

Redux 中间件在 applyMiddleware 中正常在执行 createStore 的时候智能接受 2 个参数 1 reducer 2 applyMidleware

那么当使用多个中间件的时候需要使用增强函数 compose ,这是有 redux 提供的:

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const composeEnhancers =
  typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
        // 其他的扩展选项  name, actionsBlacklist, actionsCreators, serialize...
      })
    : compose

const enhancer = composeEnhancers(
  applyMiddleware(...middleware)
  // 其他的 增强 这样可以使用多个中间件
)
const store = createStore(reducer, enhancer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

配合 react-redux 使用

目录结构

使用步骤

  1. 创建 reducers
  2. 合并 reducers
  3. 创建 store createStore
  4. Provider store={store}
  5. connect(mapState,{...actionCreators})(component)
  6. 根据需求做 actionTypes 和 actionsCreater
  7. 根据 action 修改 reducers

使用方法

  1. 引入react-redux

    import React from 'react'
    import {render} from 'react-dom'
    import {provider} from 'react-redux'
    import App from './app'
    import store from './store' //store是需要Redux穿件的STore
    //把提供者组件包裹在最外层 这个组件必须接受一个store  属性  这个属性的值就是我们穿件的store
    render(
    <Provider store={store}>
        <App/>
        </Provider>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  2. 创建 store

// createStore 是用来创建store 的方法
import { createStore } from 'redux'
//引入 合并后的reducer
import rootReducer from './reducers'
//createStore 第一个参数一个是一个 reducer ,如果是多个 在reducer 目录下使用 combineReducers 合并导出
export default createStore(rootReducer)
1
2
3
4
5
6
  1. 创建 reducers
# 在 reducers 项目下 index.js文件中
//在实际的项目中 由于只有单一的store 但是状态会有很多 所以需要对reducer进行划分
// 而 createStore的参数又指接受一个reducer 所以 redux 使用 combineReducers 合并
//多个reducer 继续引入 使用 combineReducers合并导出
import {combineReducers} from 'redux'
import 具体的reducer from './对应的reducer文件'
export default combineReducers({
    // 把多个reducer 当作combineReducers的对象参数传入即可
    ...reducers
}
1
2
3
4
5
6
7
8
9
10

具体的 reducer 中

# 在 reducers 项目下 cart.js文件中 ,在此文件中定义初始化的state状态 和 具体action修改动作
//actionType 是action状态 把状态统一管理
import actionType from '../actions/actionType'
//这是一个初始化的状态
const initState =[{
    id:1,
    context:'todo somethings',
    componented:'false'
}]
//创建reducer 这是一个纯函数 reducer 固定写法是
//两个参数  第一个就是state 必须又一个初始值 第二个是action

export default (state=initState,action)=>{
    // 根据不同的actionType 做不同的处理
    //每次返回一个新的state
    switch(action,type){
        case actionType.TODO_ADD:
            return //TOdo sh
            // 默认 不做任何处理  必须有这一行
        defaultreturn state
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 创建 actions

    # actions 中有 两种文件
    # 1. actionType.js 所有的状态进行统一管理并且 必须是 大写的函数名对应大写函数值 标识常量
    # 2. 具体的action 例如 todo.js 内部返回actioncreater 需要有type  和payLoad 载荷
    import actionType from './actionType.js'
    //action  有两种写法
    
    //1.  这是标准的action  但是不方便传递参数  因此写成函数式
    export const addTodo{
                type:actionType.TODO_ADD//  payload:{
             //   todo
           // }
    }
    //2. 函数式action 方便传递参数 并且在和redux-react 集成 connenct 的时候 是可以直接使用的
    // 这种actioncreater 是常用的
    export const addTodo=(todo)=>{
            return {
                type:actionType.TODO_ADD,
                payload:{
                todo
            }
        }
    }
    export const changeTodo=(todo)=>{
            return {
                type:actionType.TODO_CHANGE,
                payload:{
                todo
            }
        }
    }
    
    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
  2. 获取属性 链接到 store 使用 connnet

在 provider 包裹的组件中都可以使用 redux.connect 做链接

import React,{Component} from 'react'
import {connect} from 'redux-react'
//导入actionCreater
import {addTodo,changeTodo} from '../../todo.js'

class TodoList extends Component {
    render(){
        return (
        )
    }
}
//mapStateToprops 这里的state 实际上就是store.getState()的值
const mapState=(state)=>{
    return {//这里return了什么在 this.props 中就可以获取什么
        todos:state.todo
    }
}
//connect 方法执行之后才是一个高阶组件
//connect  方法有四个参数  常用前两个
// 第一个参数 mapStateToprops 作用是把store内的state注入到 当前组件的props 上
//第二个参数是可以是mapDispatchToProps,这个的主要作用是把action生成的方法注入到当前组件props上面
//直接第二个参数传递一个对象这里的对象就是actionCreaters 只要是传入了actionCreaters,在组件内就可以通过
//this.props.actionCreater来调用,在调用之后就会自动把他内部的actiondispach出去
export default connect(参数1,参数2,参数3,参数4)(TodoList)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

配合 Immutable.js 使用

因为再reducer 中state状态时不可以被修改的,因此如何有效率的拷贝state和修改返回state就是一个技术,再正常情况下修改state,难免会修改到原来的state忘记返回新的,因此使用Immutable.js 可以解决问题

安装

yarn add immutable
# or
npm i immutable
1
2
3

示例

Immutable 可以通过共享部分对象来创建新的对象,对象被创建就不能被修改数据,对任何 Immutable 的任何操作包括添加==更改和删除==都会产生一个==新的 Immutable 对象==. 他的原理是实现了 ==persistent (持久化数据解构)==也就是使用旧数据创建新的数据的时候保证==旧的数据不可变==。

为了避免深拷贝把所有节点都复制造成的性能损耗,Immutable 使用了 structural sharing(节点共享),即为如果只有修改这个节点和受他影响的父节点,其他节点进行共享,也就是节点共享

import {map,fromJs} from 'Immutable'

let m1 =Map({a:1,b:2,c:3,d:{aa:1}})
clog(m1[a])//1 //原数据解构
let m2 = m1.set('a','11')
clog(m2.a)//2 新数据解构
clog(m1.a)//1 原数据解构
clog(m2===m1)//false 新内存地址
clog(m1.d===m2.d)//true 说明d的内存地址相同 这个节点共享了
//如果更改D 下面的aa则
let m3 = m1.set(['d','aa'],'22222')//欲修改的子节点在数组后面  是按从前向后排列的
//fromJS 把原生的JS对象转换成一个Immutable 数据解构对象
let m4 = fromJS({a:1,b:2,c:3,d:{aa:1}})
let m5 = m4.setIn([d,aa],'333')//添加 多级
clog(m4.getIn(['d','aa']))//获取多级的方法
let m6 = m4.updateIn(['d','aa'],value=>value+1)//更新
let m6 = m4.updateIn(['d'],value=>value(hhhh)=22233)//更新
let m7 =m4.mergeDeep({d:{bb:222}})//深度合并
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

上面的库很大而且需要重新特定的方法,所有后面出现了 seamless-Immutable 这个库可以无缝的使用 js 大小只有 2K 这个库只支持 array 和 object

 let immutable = require('seamless-immutable')
let m1 = immutable({a:1,b:2,{info:{name:'zhangsan'}}})
let m2 = m1.merge({a:2,b:3,c:6)
clog(m1.info.name)//使用js输出对象的方式使用即可
clog(m2.b)
1
2
3
4
5

使用再rendux中

首先因为需要把state变为Immutable对象所以需要使用redux中间件 redux-immutable

import {
  combineReducers
} from 'redux-immutable';
 
import {
  createStore
} from 'redux';
 
const initialState = Immutable.Map();
const rootReducer = combineReducers({});
const store = createStore(rootReducer, initialState);

//这样就把 state再reducer 导出的时候变为一个immutable对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14

使用:

import Immutable from 'immutable';
 
const initialState = Immutable.fromJS({
  name: null
});
 
export default (state = initialState, action) => {
  if (action.type === 'SET_NAME') {
    return state.set('name', action.payload);
  }
  return state;
};
1
2
3
4
5
6
7
8
9
10
11
12

或者使用 seamless-immutable 与 redux-seamless-immutable 配合使用

//再配置的时候
import { combineReducers, routerReducer, stateTransformer } from 'redux-seamless-immutable'
import { createStore, applyMiddleware } from 'redux'
import createLogger from 'redux-logger'
 
import reducer from './reducers'
 
const rootReducer = combineReducers({ // 配置reducer
  reducer,
  routing: routerReducer //配置解决router和redux冲突
})
 
const loggerMiddleware = createLogger({//配置state解析
  stateTransformer: stateTransformer
})
 
const store = createStore(//加载中间件
  rootReducer,
  applyMiddleware(
    loggerMiddleware
  )
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22