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()
                ...
        }
    }
    
posted @ 2022-04-26 15:04  sanhuamao  阅读(176)  评论(0编辑  收藏  举报