• React的通信方式与状态管理:Redux与Mobx


    kerwin-《千锋react全家桶》笔记,部分参考了:

    一整个整理下来可谓是十分酸爽 ,欢迎指正

    image-20220426150429155

    一 基本通信方式

    1.1 props

    image-20220420170359842

    1. 父组件中,给子组件添加属性,属性值为函数

    2. 子组件内部通过props执行该函数,以修改父组件的状态

      【例子01-子传父-表单通信】


    1.2 Ref

    1. 子组件内部维护自身的state

    2. 父组件中,通过React.createRef()创建ref对象。并给子组件添加ref属性,属性值为该对象

    3. 父组件通过this.ref对象.current.state获取子组件内部的state状态

      【例子03-表单通信】

    1.3 订阅发布

    image-20220420170359842

    const center={
        //保存回调函数
        subscribers:[],
        //订阅:将回调函数保存起来
        subscribe(callback){
            this.subscribers.push(callback)
        },
        //发布,遍历所有回调函数并执行
        publish(value){
            this.subscribers.forEach(callback=>callback(value))
        }
    }
    
    //组件一般在初始化的时候进行订阅,
    center.subscribe((value)=>{
        ...
    })
    //发布数据,所有订阅的组件都会监听到更新
    center.publish('人民日报')
    

    【例子04-电影条目与对应详情】

    1.4 context(生产者消费者)

    image-20220420191442261

    生产者管理状态并暴露方法,消费者在特定环境内可使用生产者提供的状态和方法。

    1. 通过Context=React.createContext()创建一个上下文对象。其中有两个角色:

      • 生产者:<Context.Provider></Context.Provider>
      • 消费者:<Context.Consumer></Context.Consumer>
    2. 生产者组件通过前者包裹消费者组件,并设置value属性,属性值是一个对象,提供一些状态和方法

    3. 消费者通过后者包裹一个回调函数(value)=>{return ...dom...},消费者可以通过value.获取生产者的状态或者调用生产者提供的方法

      const GlobalContext=React.createContext()//创建对象
      
      //生产者
      <GlobalContext.Provider value={{
      	info:this.state.info,
          changeInfo:(v)=>{
              this.setState({
          		info:v
              })
          }
      }}>
            ...一些享受服务的组件...
      </GlobalContext.Provider>
      
      //消费者
      <GlobalContext.Consumer>
      {
          (value)=>{
          	return <div onClick={()=>{value.changeInfo('参数')}}>
          				{value.info}
          			</div>
          }
      }    
      </GlobalContext.Consumer>
      

      【例子05-context】

    二 redux

    redux属于flux模式的一种实现形式

    2.1 执行过程

    1. 创建reducer:接收两个参数:原状态与action,对状态进行修改后返回新状态

    2. 创建store:引入createStore并传入reducer

    3. 订阅:通过subscribe订阅,并通过getState()获取状态

    4. 发布:通过dispatch发布action

    5. 监听:reducer拦截到action后修改状态并返回新状态

      //reducer.js
      const reducer=(prevState={...},action={...})=>{
          //对原状进行深拷贝,这种写法仅对第一层管用。留坑,后面有解决方案
          let newState={...prevState} 
          switch(action.type){
              case 'open-show':
                  newState.isShow=true
                  return newState;
              case 'getlist':
                  newState.list=action.payload
                  return newState;
              default:return prevState
          }
      }
      export default reducer
      
      //store.js
      import {createStore} from 'redux'
      import reducer from './reducer'
      const store=createStore(reducer)
      export default store
      
      store.subscribe(()=>{
          store.getState()
      })
      
      store.dispatch({
          type:'...'
          payload:'...'
      })
      

      使用纯函数reducer执行state更新。纯函数表示对外界没有副作用,同样的输入得到同样的输出

    2.2 redux原理

    function createStore(reducer){
        let state=reducer() //reducer没传参数是初始状态
    
        //订阅
        let callbackList=[]
        function subscribe(callback){
            callbackList.push(callback)
        }
    
        //分发
        function dispatch(action){
            state=reducer(state,action) //处理事件并覆盖状态
            //通知订阅者
            for(let i in callbackList){
                callbackList[i]&&callbackList[i]()
            }
        }
    
        //返回状态
        function getState(){
            return state
        }
    
        return{
            subscribe,
            dispatch,
            getState
        }
    }
    

    2.3 常规目录结构

    - redux
    	- actionCreator :定义每个组件涉及的action
        	- AComponentAction.js
    		- BComponentAction.js
    	- reducers:定义每个组件的reducer
        	- AComponentreducer.js
    		- BComponentreducer.js
    

    AComponentAction.js

    function show(){
        return{type:'test-show'}
    }
    export {show}
    
    import {show} from './actionCreator/AComponentAction'
    store.dispatch(show())
    

    重组:1.combineReducers用于合并reducer;2.此时getState()包含多个模块

    import {combineReducers,createStore} from'redux'
    import {AComponentreducer,BComponentreducer} from './reducers'
    const reducer=combineReducers({
        CityReducer,TabbarReducer
    })
    const store=createStore(reducer)
    

    2.4 开发者工具

    1. 扩展程序下载

    2. 在store.js中配置(上线前要删除)

      import {applyMiddleware,combineReducers,createStore,compose} from'redux'
      const composeEnhancers=window._REDUX_DEVTOOLS_EXTENSION_COMPOSE_||compose;
      const store=createStore(reducer,/* preloadedState,*/composeEnhancers(applyMiddleware(reduxThunk,reduxPromise)))
      

    三 redux中间件

    当处理异步事件时,需要使用到中间件,包括redux-thunkredux-promiseredux-saga等。此时需要从redux中引入applyMiddleware来使用中间件。

    3.1 redux-thunk

    import {applyMiddleware,createStore} from'redux'
    import reducer from './reducer'
    import reduxThunk from 'redux-thunk'
    const store = createStore(reducer,applyMiddleware(reduxThunk))
    

    此时action既可以是对象也可以是函数,中间件会判断返回的是对象还是函数,如果是对象则按之前的流程进行,忽略中间件;如果是函数,就按照异步的方式进行。最后对结果进行dispatch

    store.dispatch(getListAction())
    
    function getListAction(){
        return (dispatch,store)=>{
            axios.get(...).then(res=>{
                dispatch({
                    type:'getlist'
                    value:res.data
                })          
            })
        }
    }
    

    3.2 redux-promise

    与前者效果差不多,只是写的方式不一样。

    import {applyMiddleware,createStore} from'redux'
    import reducer from './reducer'
    import reduxPromise from 'redux-promise'
    const store = createStore(reducer,applyMiddleware(reduxPromise))
    
    store.dispatch(getListAction())
    

    第一种写法

    function getListAction(){
        return axios.get(...).then(res=>{
                return{
                	type:'getlist'
                    value:res.data         
                }      
            })
        }
    }
    
    async function getListAction(){
        let list=await axios.get(...).then(res=>{
                return{
                	type:'getlist'
                    value:res.data         
                }      
            })
        }
        return list
    }
    

    3.3 redux-saga

    对于前两者方式,action的格式不统一,异步操作分散,如果需要为每一个异步操作都如此定义一个action,不容易维护。saga是基于生成器的中间件,统一了action 的格式,更易于维护

    import { createStore, applyMiddleware } from 'redux'
    import createSagaMiddleware from 'redux-saga'
    import reducer from './reducers'
    import watchSaga from './watchSaga'			  //自身定义的saga文件,实现监听
    
    const sagaMiddleware = createSagaMiddleware() //创建saga中间件
    const store = createStore(
      reducer,
      applyMiddleware(sagaMiddleware)
    )
    sagaMiddleware.run(watchSaga)				  //用saga中间件开启saga文件
    
    • take:监听dispatch的action
    • fork:非阻塞立即执行生成器函数
    • takeEvery:上面两个的合体
    • put:抛出最后的action
    • all
    //watchSaga.js
    import { call, put, takeEvery,take, fork} from 'redux-saga/effects'
    
    function *watchSaga(){
        while(true){
            yield take("getlist")
            yield fork(getList)	
        }
        //简写:takeEvery('getlist',getList)
    }
    
    function *getList(){
        let res=yield call(getListAction)   //阻塞式调用异步事件
        yield put({                        
            type:'change-list',
            payload:res
        })
    }
    
    function getListAction(){
        // 返回promise对象的异步事件
    }
    
    export default watchSaga
    

    如果有多个saga:

    1. 通过all进行合并

      //watchSaga.js
      import { all } from 'redux-saga/effects'
      import watchSaga1 from ./sagas/watchSaga1
      import watchSaga2 from ./sagas/watchSaga2
      function *watchSaga(){
          yield all([watchSaga1(),watchSaga2()])
      }
      export default watchSaga
      
    2. 省略每个子saga的watchSaga,将其汇总在这里

      import { takeEvery } from 'redux-saga/effects'
      import getList1 from './..'  					//引入生成器函数
      import getList2 from './..'  					//引入生成器函数
      function *watchSaga(){
          takeEvery('getlist1',getList1)
          takeEvery('getlist2',getList2)
      }
      export default watchSaga
      

    三 redux-persist

    对数据进行持久化保存

    import {persistStore,persistReducer} from 'redux-persist'
    import storage from 'redux-persistlib/storage'
    const persistConfig={
        key:'root',
        storage,
        widthList:['cityName']
    }
    
    //包装原来的reducer
    const persistedReducer=persistReducer(persistConfig,reducer)
    const store=createStore(persistedReducer)
    //持久化store
    let persistor=persistStore(store)
    
    export {store,persistor}
    
    import {PersistGate} from 'redux-persist/intergration/react'
    
    const App=()=>{
        return(
            <Provider store={store}>
            	<PersistGate loading={null} persistor={persistor}>
            		根组件
            	</PersistGate>
            </Provider>
        	
        )
    }
    

    四 react-redux

    4.1 性能问题

    redux需要自己取消订阅

    useEffect(()=>{
       ...
       let subscribe=store.subscribe(()=>{
           ...
       })
       return ()=>{
           subscribe()
       }
    },[])
    

    react-redux可以帮助取消订阅

    4.2 基本使用

    • Provider

      import {Provider} from 'react-redux'
      import store from './..'
      
      <Provider store={store}>
          ...
      </Provider>
      
    • connect(给子组件的属性,给子组件的回调)

      当有人改了状态,作为包装组件,订阅了之后会再传

      import {connect} from 'react-redux'
      export default connect((state)=>{
        return{
            a:1,
            isShow:state.TabbarReducer.show
        }
      })(App)
      
      this.props.a
      

      可装入同步的和异步的action,实现自动分发(dispatch)

      import {connect} from 'react-redux'
      export default connect(null,{
        actionA(value){return{
            type:'...',
            payload:value
        }},
        show
        hide
      })(App)
      
      this.props.actionA(value)
      

    4.3 基本原理

    (1)connect是高阶组件(HOC,high order component)
    (2)Provider组件,可以让容器组件拿到state,使用了context

    跟着老师自己学着封装了一下,应该有错误的理解,也不知道怎么处理异步。(留坑)

    import store from './redux/store'
    export default function testConnect(callback,funcobj){
        let value=callback(store.getState())    //获取值
        let dispatchObj={}
        for (let key in funcobj){
            dispatchObj[key]=()=>{store.dispatch(funcobj[key]())}
        }
        return (Mycomponent)=>{
            return (props)=>{                   //返回函数组件,若组件外层有包裹,接收
                return <div>
                    <Mycomponent {...value} {...dispatchObj} {...props}/>
                </div>
            }
        }
    }
    export default testConnect(()=>{
        return{
            a:1,
            b:2
        }
    })(MyComponent)
    

    ?为什么props是路由参数:(自己的理解,留坑)

    因为MyComponent是Route组件component属性的的属性值。Route组件的作用是接受组件,并将路由信息解构给组件、返回带了路由信息的组件。

    <Route path="" component={MyComponent}></Route>
    
    class Route extends React.Component{
        render(){
            var MyComponent=this.props.component
            return <div>
            	<MyComponent history={} match={} .../>
            </div>
        }
    }
    

    此时给MyComponent外面又包裹了一层函数组件,那么Route传递下来的路由信息将被外层的函数组件接收到,进而再解构给原组件

    return (props)=>{
        return <div>
            <MyComponent {...value} {...props} {...obj}/>
        </div>
    }
    

    五 Mobx

    与redux相比的优点

    • 直接修改
    • 多个store
    • 可观察对象

    5.1 基本使用

    • observable:将状态变为可观察

    • autorun:监听可观察对象

      import { observable, autorun } from "mobx"
      
      //创建可观察对象
      let observableObj=observable({
          name:'啦啦啦',
          age:100
      })
      
      //注册监听
      autorun(()=>{
          console.log("对象的name属性改变了",observableObj.get("name"))
      })
      
      setTimeOut(()=>{
          observableObj.set("name","哈哈哈")
      })
      

      该写法是的状态可以随处修改

    5.2 action

    action限制了状态修改自由,让状态集中修改,便于维护

    import { observable,action,configure } from "mobx"
    
    configure({
        enforceAction:'always'	//开启严格模式
    })
    //创建可观察对象
    const store=observable({
        name:'啦啦啦',
        age:100,
        changeName(value){
            this.name=value
        }
    },{
        changeName:action	//标记方法action
    })
    

    使用runInAction处理异步

    import { observable,action,runInAction } from "mobx"
    import axios from 'axios'
    const store=observable({
        list=[],
        getList(){...}
    },{
        getList:action
    })
    
    //写法1
    getList(){
        axios({...}).then(res=>{
               runInAction(()=>{
               		this.list=res.data
            	})
        })
    }
    
    //写法2
    async getList(){
        let res=await axios({...}).then(res=>{
          return res.data
        })
        runInAction(()=>{
            this.list=res
        })
        
    }
    

    5.3 装饰器写法

    所谓装饰器,就是将对象按一定规则包装后吐出来

    import {observable,configure, action} from 'mobx'
    class Store{
        @observable name:'啦啦啦',
        @action changeName(){...}
    }
    export default new Store()
    

    在用装饰器语法前需要进行配置

    1. vscode配置:首选项——设置——搜索experimentalDecoration

    2. 安装支持

    3. 创建.babelrcconfig-overrides.js

    4. 安装依赖与修改脚本

      npm i @babel/core @babel/plugin-proposal-decorators @babel/preset-env
      
      //.babelrc
      {
          "presets":[
          	"@babel/preset-env"
          ],
          "plugins":[
              [
                  "@babel/plugin-proposal-decorators",
                  {
                       "legacy":true
                  }
              ]
          ]
      }
      
      //config-overrides.js
      const path=require(' path')
      const { override, addDecoratorsLegacy }=require('customize-cra')
      function resolve(dir){
      	return path.join(_dirname, dir)
      }
      const customize=()=>(config, env)=>{
          config.resolve.alias['@']=resolve('src')
          if(env==='production'){
              config.externals={
                  'react':'React',
                  'react-dom':' ReactDoM'
              }I 
          }
          return config
      }; 
      module.exports=override(addDecoratorsLegacy(), customize())
      
      //安装依赖
      npm i customize-cra create-app-rewrired
      
      //package.json
      "scripts":{
          "start":"react-app-rewired start",
          "build":"react-app-rewired build",
          "test":"react-app-rewired test",
          "eject":"react-app-rewired eject"
      },
      

    5.4 mobx-react

    可以自动监听

    import React from 'react'
    import ReactDOM from 'react-dom'
    import {Provider} from 'mobx-react'
    import store from './..'
    ReactDOM.render(
        <Provider store={store}>
        	 <App/>
        </Provider>,
    document.getElementById('root'))
    

    以属性的方式获取和修改store

    import {inject,observer} from 'mobx-react'
    
    @inject("store")	//与在Privider中的属性key一致
    @observer
    class App extends React.Component{...}
    
    this.props.store
    

    对于函数式组件,需要用<Observer>包起来

    import {Observer} from 'mobx-react'
    
    <Observer>
        {
            ()=>{
                return DOM
            }
        }
    </Observer>
    

    六 Immutable

    在上面的第二节中留了个深拷贝的坑,Immutable是用于解决深拷贝问题的方案

    6.1 旧方案

    下面的方案只对第一层管用

    • 解构
    • slice
    • concat
    • Object.assign

    需要保证没有undifine,因为该方法会忽略undifine的属性

    • JSON.parse(JSON.stringify())

    6.2 基本使用

    • Map(obj):将普通对象转为map类型。此时在map结构上修改不会应该旧状态

      import {Map} from 'immutable'
      let oldImmuObj=Map(obj)						     //转换
      let newImmuObj=oldImmuObj.set("name","xiaoming") //更改
      newImmuObj.get("name")							 //获取
      
    • List(Arr):将数组转为特殊对象。被List转换后的数据结构可以通过原数组对象的方法对其进行操作

      import {List} from 'immutable'
      let oldArr=List([1,2,3])			//转换
      let newArr=oldArr.push(4)			//更改
      

    6.3 实际应用

    6.3.1 单层

    • 自始而终:在源头转换

      state={
          info:Map({
              name:"kerwin",
              age:100,
          }),
          food:List(['水果茶','番茄','蛋糕']),
      }
      

      对于只含一层的info对象和food数组,其改变状态的方式如下:

      this.setState({
          info:this.state.info.set("name","xiaoming").set("age",18)
          info:this.state.info.set("food",this.state.info.get("food").splice(index,1))
      )}
      

      实际上,数组元素是一个对象{{0:'水果茶'},{1:'番茄'},{2:'蛋糕'}},上面的写法很明显是多层结构,显而易见,这种写法很复杂。

    • 按需转换:在过程中转换——toJS()转换为js对象
      上面的写法无法预知后端数据来执行对应转换。下面的写法是在用到的时候进行map或list转换。最后通过返回原始结构

      state={
          info:{
              name:"kerwin",
              age:100,
          },
          food:['水果茶','番茄','蛋糕'],
      }
      
      let old=Map(this.state.info)			//转为Map
      let newObj=old.set("name","xiaoming")	//修改
      this.setState({
          info:newObj.toJS()					//恢复为js对象
      )}
      

    6.3.2 SCU

    对于复杂类型,可以使用JSON.stringify转为字符串后再对比。但如果属性值有undefined会出错。

    通过get获取属性值进行对比

    <Child filter={this.state.info.get("name")}/>
    
    shouldComponentUpdate(nextProps,nextState){
        if(this.props.name===nextProps.name){
    		return false
        }
        return true
    }
    

    6.3.3 复杂结构

    • fromJS():自动将普通js对象转为对应的map和list,帮助我们解决了无法根据后端发来的数据进行对应转换的问题
    • setIn(['第一层key',...,'目标key'],内容):操作深层对象
    • updateIn(['第一层数组key',...,'目标数组key'],(list)=>对list操作并返回):操作深层数组

    import {fromJS} from 'immutable'
    
    state={
        info:fromJS({
            name:"kerwin",
            age:100,
        }),
        food:fromJS(['水果茶','番茄','蛋糕']),
        charecter:fromJS({
            language:['Chinese','English','Japanese'],
            location:{
                province:'广东',
                city:'江门'
            }
        })
    }
    

    • 引入:如果想对深层的结构进行修改,单纯使用set会很复杂:

      this.setState({
          charecter:this.state.charecter
          .set("location",this.state.charecter.get("location").set("city","广州")),	//多层
      )}
      

    为了更简单地对深层的结果进行修改,可以使用setIn(['第一层key',...,'目标key'],内容)

    this.setState({
        //多层
    	charecter:this.state.charecter.setIn(["location",'city'],"广州")  //先改对象
        	.setIn(['language',index],111),								 //再改数组元素
        
        //单层也可以使用setIn
        info:this.state.info.setIn(["name"],'haha')						 
    )}
    

    setIn()中,数组可以传入索引值修改数组元素。但数组更适合用updateIn(['数组key'],(list)=>对list操作并返回)

    this.setState({
    	charecter:this.state.charecter.updateIn(['language'],(list)=>list.spice(index,1)),
    )}
    

    6.3.4 reducer中应用

    1. 在reducer中将初始值用fromJS()转换

    2. 对状态通过setsetInupdateIn直接修改即可

      const reducer=(prevState=fromJS({
          show:true,
          city:'北京'
      }),action={})=>{
          switch(action.type){
              case:'change-show':
              	return prevState.set("show",false)
                  ...
          }
      }
      

    1. 在函数内进行fromJS()转换

    2. 为了与初始状态一致,返回状态时再用toJS()

      const reducer=(prevState={
          city:'北京'
      },action={})=>{
          let newState=fromJS(prevState)
          switch(action.type){
              case:'change-city':
              	return newState.set("city",action.playload).toJS()
                  ...
          }
      }
      
  • 相关阅读:
    js笔记18
    (6)《Head First HTML与CSS》学习笔记---结尾、《HTML5权威指南》读书笔记
    (5)《Head First HTML与CSS》学习笔记---布局与定位
    一些效果实现
    高效程序员的45个习惯·敏捷开发修炼之道(Practices of an Agile Developer)读书笔记
    (4)《Head First HTML与CSS》学习笔记---文本的CSS规则和盒模型;div与span;<a>元素的链接色;伪类
    酷壳上的几篇文章
    《Head First HTML与CSS》的CSS属性
    (3)《Head First HTML与CSS》学习笔记---CSS入门
    (2)《Head First HTML与CSS》学习笔记---img与基于标准的HTML5
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/16194865.html
Copyright © 2020-2023  润新知