状态管理工具 redux

(类似vuex) - 推荐使用(共享) -或者( flux mobx)

中文官网

redux-阮一峰-年代较早-值得学习

一、为什么使用状态管理工具,而不是sessionStorage或localStorage?

vuex和redux是以对象形式存在内存中,单向数据流。 sessionStorage存的是字符串(序列化和反序列化),性能差。

二、手动引入store&手写redux

2.1. 安装

cnpm i redux --saveyarn add redux

2.2. 三大原则

  1. 单一数据源(唯一仓库)
  2. 状态State是只读的; 想要修改状态只能修改副本,目的是数据可追溯
  3. 使用纯函数(reducer)来执行修改 — 只要是同样的输入,必定得到同样的输出。对外界没有副作用的函数。

    1. 不得改写参数
    2. 不能调用系统 I/O 的API
    3. 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

共享数据,全局状态,组件交互

2.3. 使用步骤及其结构

1.步骤
  1. 引入createStore import {createStore} from 'redux';

  2. 设置初始数据
    const initState ={ key:value}

  3. 初始值传给reducer -作用:给store传递state和副本修改state,副本替代原state实现修改
    reducer(state=initState,action) 纯函数

  4. const store = createStore(reducer);

  5. 导出 export default store

  6. 何处使用就在组件内部导入 或者 在根目录index引入一次,通过context传递store

2.代码

1584274313473

2.1 文件store的index.js
  1. /** 文件store的index.js */
  2. //引入创建仓库的工具
  3. import { createStore } from 'redux'
  4. //引入总的reducer纯函数
  5. import reducer from './reducer'
  6. //仓库是唯一的(参数是函数-回调执行)
  7. const store = createStore(reducer);
  8. export default store;

2.2文件store的reducer.js — 即分模块
重名情况 vue react
模块重名 会报错,或者使用命名空间 别重名,action的type一定不能重名
mutation重名 都执行
action重名 都执行
报错

注意:有时候页面不更新,就是浅拷贝的锅,只监听到表层的变化,深层次的变化无法更新视图。此时需要深拷贝:

let newState= { ...state } //浅拷贝,做副本

let newState= JSON.parse(JSON.stringify(state)) //深拷贝(先转成字符串,再转回来),做副本

1584336438481

  1. /** store中总reducer.js */
  2. import {combineReducers} from 'redux' //合并每个组件各自的reducer
  3. import newcounterReducer from '../components/NewConuter/reducer'
  4. import hocReducer from '../components/hoc/reducer'
  5. export default combineReducers({
  6. newcounter: newcounterReducer,
  7. hoc: hocReducer
  8. })

2.3组件中的文件(reducer.js和actionCreator.js)

let newIncrement = { ...state } //浅拷贝,做副本

let newIncrement = JSON.parse(JSON.stringify(state)) //浅拷贝,做副本

使用immutable做深复制

  1. /** 组件目录的reducer纯函数 -- 快捷键: rxreducer */
  2. import {Increment, Decrement } from 'store中的actionType'
  3. //状态的初始值
  4. const initialState = {
  5. n: 5,
  6. countList: [2, 3, 7]
  7. }
  8. //修改数据的reducer纯函数。包含state-数据 和 action-修改数据的动作。switch写了return不写break。
  9. //默认导出,导入时自己随便起名字
  10. export default (state = initialState, action = {}) => {
  11. switch (action.type) {
  12. case Increment:
  13. let newIncrement = { ...state } //浅拷贝,做副本
  14. newIncrement.countList[action.payload] = newIncrement.countList[action.payload] * 1 + 1;//修改副本
  15. return newIncrement;//返回副本
  16. case Decrement:
  17. //简洁写法-有条件限制(缺陷),不是所有都可以简写
  18. //比如复杂的判断情景就会不适用
  19. return { ...state, n: state.n*1-1 };
  20. default:
  21. return state;
  22. }
  23. }
  24. /** 组件目录的actionCreator.js - 创建dispatch的action对象 */
  25. import { Increment, Decrement } from 'store中的actionType'
  26. export default {
  27. inIncrement (payload) {
  28. return {
  29. type: Increment,
  30. payload
  31. }
  32. },
  33. inDecrement (payload) {
  34. return {
  35. type: Decrement,
  36. payload
  37. }
  38. }
  39. }

2.4.具体操作(存,取,改)

某个组件的状态,需要共享; 某个状态需要在任何地方都可以拿到; 一个组件需要改变全局状态; 一个组件需要改变另一个组件的状态

1.获取数据

store.getState().模块名.变量 — 存于state

2.修改数据

action的type值千万不要重复,因为每个type都对应着不同的操作。就算分模块后也不能同名

  1. 引入actionCreator是为了得到action对象
  2. 组件内部先派发动作: store.dispatch({type:'一般大写XXX',payload})
    action 是一定含有type的一个对象,其他参数根据需求有无
  3. 动作发给了 reducer 第二个参数 action
    action一定有type ,还可能接收一些其他参数
  4. 根据type修改数据
    a. 做 state 的复本 let newState = {...state} 浅拷贝,再无瓜葛
    b. 修改state的复本对象
    c. 返回新的state

3.监听/订阅store数据变化-(vue是放在计算属性里面实现监听)

不能在生命周期中监听store的数据变化。而且不使用subscribe,store数据变了(reducer中可以发现变化了),但是不更新state中的值,因为constructor只执行一回。所以引入subscribe(原理: 把回调函数放进数组里面,当数据变化,会依次执行数组里的函数,达到更新的目的)

  1. constructor(props) {
  2. super(props);
  3. console.log(store.getState());
  4. this.state = {
  5. countList: store.getState().newcounter.countList
  6. };
  7. // 直接写函数就行,不用在回调里面调用
  8. store.subscribe(this.storeListener.bind(this))
  9. }
  10. storeListener () {
  11. //console.log('监听');
  12. this.setState({
  13. countList: store.getState().newcounter.countList
  14. })
  15. }
  16. // 或者 - 不推荐回调去写
  17. constructor(props) {
  18. super(props);
  19. console.log(store.getState());
  20. this.state = {
  21. countList: store.getState().newcounter.countList
  22. };
  23. // 在回调里面调用
  24. store.subscribe(()=>{
  25. this.setState({
  26. countList: store.getState().newcounter.countList
  27. })
  28. })
  29. }

2.5.subscribe-订阅

1-constructor监听(见上面的订阅)

2-componentDidMount监听

1584673516724

2.6.单向数据流图示

1584275411051

2.7.职责单一原则

把组件拆分为容器组件和ui组件

UI组件 只管渲染
容器组件 Container 和store打交道

三、手写组件实现传递store — 仅传递store,派发,监听,获取都需要自己去写

Context上下文对象(生产者和消费者) - 组件之间传值

乌鸦坐飞机

聊一聊我对 React Context 的理解以及应用-18年此博客是老版本语法,所讲的生产者、消费者是新款语法api。博客值得借鉴

只能用属性value传值,不然会出问题。多个用对象

插槽-此处渲染的是可能外层有Router的App组件

MyProvider是最外层,再是Router,再是App

1.MyProvider组件
  1. // components/myprovider/index.js
  2. import React, {Component, createContext} from 'react'
  3. const context = createContext()
  4. const {Provider, Consumer} = context
  5. class MyProvider extends Component{
  6. render(){
  7. return(
  8. <>
  9. {/* 只能用属性value传值,不然会出问题。多个用对象 */}
  10. <Provider value={{...this.props}}>
  11. {/* 插槽-此处渲染的是App组件 */}
  12. {this.props.children}
  13. </Provider>
  14. </>
  15. )
  16. }
  17. }
  18. export {MyProvider, Consumer, context}

2.组件导出的3个内容的具体作用

使用Consumer只能在render中拿到value,在进行渲染

使用contetx设置好静态属性,在其他生命周期可通过this.context拿到value

  1. /1/MyProvider包裹着根目录index.jsApp组件,可以传递value
  2. //通过属性props传递给value
  3. <MyProvider store={store}>
  4. <App />
  5. </MyProvider>
  6. {/** 组件中还是要引入Consumer、context以及actionCreator */}
  7. /2/Consumer在任意组件去使用,先引入在使用
  8. //通过Consumer只能在render中拿到value,在进行渲染
  9. <Consumer>
  10. {(value)=>{return <div>value.传递的具体的值</div>}}
  11. </Consumer>
  12. /3/通过contetx在其他生命周期拿到value
  13. static contextType = context//先在组件设置contextType的静态属性
  14. //constructor的第二个参数就是context
  15. constructor(props,context) {
  16. super(props)
  17. this.state = {
  18. 变量: context.getState().模块名.变量名
  19. }
  20. }
  21. //其他(不包括render和constructor)的其他钩子和函数-通过this.context拿到store
  22. componentDidMount(){
  23. console.log(类组件的名字.contextType)
  24. console.log(this.context)
  25. //设置好静态属性之后就可通过this.context拿到value,即store
  26. //修改
  27. this.context.dispatch(actionCreator.函数名(payload参数))
  28. }

四、插件 react-redux 实现context传值

即react-redux通过connect自动生成容器组件,只负责UI组件即可

不用自己手写MyProvider组件,作用:把store的所有模块和需要actionCreator全部映射到props上,不需要获取getState()和监听subscribe和派发时dispatch

  1. 安装 cnpm i react-redux -S 注意:redux不能就不安了,store还是基于redux的,react-redux只是对用法做了封装

  2. import {Provider} from 'react-redux' 替代手写的MyProvider包裹App(最外层)

  3. 组件内部:
    import {connect} from 'react-redux'
    import actionCreator from './actionCreator'
    组件内引入connect高阶组件。高阶函数的作用:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    作用:第一个参数传入((state)=>state,actionCreator)后。1:把Provider的属性(即仓库里的store)store的模块数据映射到当前组件的props上,并且自动执行store.subscribe监听数据变化。2:把actionCreator中的每个动作映射到props,可直接使用

4.1不对connect参数做改变

  1. import React,{Component} from 'react'
  2. import {connect} from 'react-redux'
  3. import actionCreator from './actionCreator'
  4. class 组件名 extends Component{
  5. render(){
  6. let n = this.props.模块名.n;//n是这个模块里的变量
  7. return(
  8. <div>
  9. {n}
  10. <button onClick={this.props.incAction}>+</button>
  11. </div>
  12. )
  13. }
  14. }
  15. //柯里化函数-没别的需求,最简单写法
  16. export default connect((state)=>state,actionCreator)(组件名)
  17. //或者(即第一个参数有两个参数,一个函数,一个actionCreator对象)
  18. const mapState = (state) =>state
  19. export default connect(mapState,actionCreator)(组件名)
  20. /**
  21. 柯里化解读
  22. function sum(x,y){return x+y}
  23. function sum(x){return (y)=>{return x+y}}
  24. let sum=(x)=>(y)=>x+y
  25. console.log(sum(2)(3))
  26. */

4.2.connect高阶组件第一个参数中两个参数解读不同写法!!!!!!important

都是返回对象,对象的每个键值对都是一个映射

4.1.1 对于函数(state)=>state
  1. let mapStateToProps = (state, ownProps)=>({//省略大括号和return,返回一个对象
  2. //1.store中的变量
  3. 变量名(可重命名): state.模块名.变量名(需和仓库一一致),
  4. //2.类似于计算属性
  5. 变量名(想要的计算属性的变量):state.one.n>18?'先生或女士':'小朋友'
  6. })
  7. // 1.ownProps作为参数,容器组件的参数变化也会引发UI组件重新渲染
  8. // 2.connect省略mapStateToProps,则Ui组件不会订阅store-store的更新不会引起UI组件的更新
  9. // 导出时mapState替代
  10. export default connect(mapStateToProps,actionCreator)(组件名)

4.1.2 对于actionCreator(是一个包含创造action对象的函数的大对象)
  1. //1.仅仅为了改名-原理都是通过解构改名(在传入参数之前-下面即是、在最开始引入actionCreator是导出多个也可以改名、在在使用时从props引入时解构改名)
  2. let {changeAction: 修改后的名字} = actionCreator
  3. let mapDispatch={修改后的名字}
  4. //2.老的写法:通过redux中的bindActionCreators映射所有方法。等价于直接写actionCreator
  5. import {bindActionCreators} from 'redux'
  6. const mapDispatch=(dispatch)=>bindActionCreators(actionCreator,dispatch)
  7. //3.推荐方法:可以实现改名字、可以拿到组件的props以及显示了dispatch,就可以写逻辑控制派发的时机。当然若不需要写时机,则直接用actionCreator传入即可
  8. let mapDispatchToProps=(dispatch,ownProps)=>{
  9. console.log(ownProps)//1.拿到父组件给这个组件传递的props
  10. return{//对象里面包含很多方法
  11. //2.这里可以改名
  12. change(payload){
  13. //3.写逻辑控制派发时机
  14. dispatch(actionCreator.changeAction(payload))
  15. },
  16. inc: function(payload){//function可以省略
  17. dispatch(actionCreator.incAction(payload))
  18. }
  19. }
  20. }
  21. // 导出时mapDispatch替代
  22. export default connect(mapStateToProps,mapDispatchToProps)(组件名)

4.3用法

这时候redux的属性和action动作都挂载到了props上,替代容器组件

五、异步中间件

5.1 redux-thunk

1.异步操作:

a) yarn add redux-thunk
b) applyMiddleware
c)createStore(reducer,applyMiddleware(thunk));
4) actionCreator里面
原来是返回一个对象; 现在方法要返回回调函数,参数就是 dispatch

  1. export default {
  2. IncAction(){
  3. return (dispatch)=>{
  4. 异步的动作 成功后再异步动作回调里 使用dispatch发出动作给reducer
  5. }
  6. }
  7. }

2.thunk: 缺点(副作用:sideEffect)-使得actionCreator的职责不纯粹(发动作,还要发请求)

1584455716025

3.thunk在actionCreator中使用

1584455677821

4.错误想法

1584456710437

5.2 redux-promise

1584696471591

1584696488508

1584696496521

1584696507280

5.3 redux-saga

运行-生成器函数(Generator函数)—特征带星号

function *fun(){

yield ‘hello’;

yield ‘world’

return 666 // return最后一次

}

let g = fun();//调用只能拿到一个Generator对象,对象里的next方法取值

g.next();// 第一次返回:{value: ‘hello’}

g.next();// 第二次返回:{value: ‘world’}

g.next();// 第三次返回:{value: 666}

g.next();// 再次返回不会报错,而是返回undefined:{value: undefined}

多次调用,会等待上一次执行完毕

g.next().next();//不能链式调用,因为第一个g.next()没有返回Generator对象

saga在store中的配置

1584455250596

生成器函数

1584455430762