一、react状态管理核心概念
- redux和mobx都是状态管理的库,与react本身并无关系,通过react-redux、mobx-react作为桥梁来结合react。
- 状态管理的主线:
- 创建state
- 注入state
- state的变化触发ui的更新
1、redux + react-redux
redux
// redux
// 创建
store = createStore(reducer);
//方法
store.dispatch(action)
store.getState()
store.subscribe(listener)
combineReducers
bindActionCreators(actionCreator,dispatch)
异步中间件 redux-thunk
react-redux
// react-redux
connect(mapStateToProps, mapDispatchToProps)(App)
Provider + context
// hook版本
useSelector
useDispatch
2、mobx + mobx-react
mobx-react依赖于mobx-react-lite。后者是应用于函数组件的mobx库,也可以单独使用,如果只用hook版本的接口,可以考虑只引入mobx-react-lite。mobx-react既支持class组件,又支持函数组件。 mobx
observable
reaction
autorun
computed
action
flow
mobx-react
observer
inject
Provider + context
// hook版本
useLocalObservable //useLocalStore 已宣告废弃中,直接学习useLocalObservable
Observer
3、安装流程
yarn create react-app mobx-redux
cd mobx-redux
yarn add redux react-redux mobx mobx-react -S
yarn start
二、redux的原理和应用
1、原理
- redux推崇单向数据流,状态只能由store派发,ui展示,不能反过来,状态的更新只能通过action=>reducer触发,并替换原先的状态,而非修改原先的状态。
- 状态管理的主线:创建state、注入state、ui与state同步。
2、使用redux实现这一过程
1、创建state
store.js
// store.js
import {createStore} from 'redux'
// store是状态的容器,包含了状态、修改状态的方法,监听状态改变的方法
const initialState = {count:0};
const reducer = function(state=initialState,action){
switch (action.type){
case 'ADD':{
return {count:state.count + 1}
}
default:
return state;
}
}
const store = createStore(reducer)
export default store;
2、注入state
import React, {Component} from 'react';
import store from './store';
export default calss App extends Component {
onAdd = () => {
store.dispatch({
type: 'ADD'
})
};
render(){
return (
<div className='App'>
App-{store.getState().count}
<button onClick={this.onAdd}>增加</button>
</div>
)
}
}
此时界面上的数据还是不能更新的,接下来:
3、ui与state同步
import React, {Component} from 'react';
import store from './store';
export default calss App extends Component {
constructor(){
super();
store.subscribe(()=>{
this.forceUpdate() //组件的强制刷新方法
})
}
}
现在页面数据更新了,下面我们来看看redux的部分源码来了解下如何实现的更新。
4、实现redux源码中的createStore
function createStore(reducer){
let state;
const listeners = []; //订阅事件的数组
const getState = () =>{
return state;
};
const dispatch = action =>{
state = reducer(state,action);
listeners.forEach(listener => listener());
};
const subscribe = listener =>{
if(!listeners.includes(listener)){
listeners.push(listener)
}
return function unsubscribe(){
listeners = listeners.filter(l=>l!==listener)
}
};
// 执行一次业务中不存在的type,目的是初始化state
dispatch({type:'@@redux-init@@'})
return {
getState, dispatch, subscribe
}
}
5、实际业务中的应用-connect
- 上述仅是示例,在实际应用中我们不会将store引入到业务组件中,而是借助react-redux的connect方法,将组件需要的状态注入到props中: App.js中
import {connect} from 'react-redux'
// 这两个映射用来生成注入组件的props
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch =>({
dispatch,
add:()=>dispatch({
type:'ADD'
})
})
export default connect(mapStateToProps, mapDispatchToProps)(App);
- react的context context的使用方式很灵活,可以使用Provider/Consumer,也可以通过contextTypes/getChildContext,useContext等方式;
import {createContext, useContext, useState} from 'react';
const Context = createContext();
// 父组件通过Context.Provider传递数据到下级、后代组件
export default function Parent(){
const [count, setCount] = useState(0);
const value = {
count,
increment(){
setCount(c => c+1)
},
decrement(){
setCount(c => c-1)
}
}
return (
<Context.Provider value={value}>
<Sub />
</Context.Provider>
)
}
// 子组件通过context拿到上层数据
function Sub(){
const ctx = useContext(Context);
return (
<div>
{ctx.count}
<button onClick={ctx.increment}>增加</button>
<button onClick={ctx.decrement}>减少</button>
</div>
)
}
- 模拟实现一个connect
import React,{Component} from 'react';
import PropTypes from 'prop-types';//类型判断工具,只在开发环境有用
export function connect(mapStateToProps, mapDispatchToProps){
return function (WrappedComponent){
static contextTypes = {
store:PropTypes.object
}
componentDidMount(){
// 从context获取store并订阅更新
this.context.subscribe(this.forceUpdate.bind(this));
}
render(){
return (
<WrappedComponent
//传入该组件的props,需要由connect这个高阶组件原样传回原组件
{...this.props}
//根据mapStateToProps把state挂到this.props上
{...mapStateToProps(this.context.store.getState())}
//根据mapDispatchToProps把state挂到this.props上
{...mapDispatchToProps(this.context.store.getState())}
/>
)
}
}
return Connect;
}
connect导出的组件都是provider的后代组件,因此能够取到从provider传递的store;
- 整条链路如下: 初始化:
- createStore生成全局的store
- Provider转入store到项目根节点
- connect通过context api拿到store并映射为被包装组件的props,同时把connect组件的更新方法注册到store listeners; 更新:
- 被包装的组件调用props中的action,即dispatch=>reducer
- reducer生成新的state,同时遍历执行上一步采集的listeners;
- connect基于变化的状态强制更新,包括子组件都会受到影响
6、异步中间件redux-thunk
- what? 它是一个applyMiddleware(中间件)
- 作用
- 一个action中进行多次dispatch
- 一个action中做其他任何函数能做的事情
- 使用
// store.js
import { createStore, applyMiddleware, combineReducers} from 'redux';
import thunk from 'redux-thunk';
// 如果只有一个reducer
const store = createStore(reducer, applyMiddleware(thunk));
// 如果有多个reducer,我们会维护在不同的模块,然后集中到这里合并为一个reducer:
// 借助combineReducers,同时下⽂ mapStateToProps 及 useSelector的第⼀个参数,也将相应地变成⼀个 { home, about } 对象
import home from '@/home/reducer';
import about from '@/about/reducer';
const reducer = combineReducers({
home,
aboutState:about
})
const store = createStore(reducer,applyMiddleware(thunk));
7、redux使用useSelector和useDispatch支持hook方式的状态管理
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
export default function App(){
const number = useSelector(state=>state.number);
// 如果上文使用了combineReducers,这里应该写成
// const number = useSelector(state=>state.home.number)
const dispatch = useDispatch();
const onDecrement = ()=>{
dispatch({
type:'DECREMENT',
payload:2
})
}
return <button onClick={onDecrement}>{number}</button>
}
三、mobx
1、概念
- mobx推崇数据响应式,状态的mutable(可变的)。即状态创建后,后续都在修改这个状态,基于代理拦截数据的stter,从而触发副作用(在react中,包括render也可认为是mobx的副作用回调)。
- 注意事项:
- 在 react 中使用 mobx 的时候,你应该忘记 react 自带的组件更新方式,时刻牢记这句话,否则使用 mobx 将 失去价值,甚至引入 bug!
- 不要随意解构或使用基本类型的变量代替代理对象的属性,下文将演示这样导致的问题。
- 出于优化的目的,列表的每一项尽量封装为组件,这样 mobx 将会尽情出体现它的速度!
2、原则
mobx支持单向数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图。 action——>state——>views
3、常用api示例
1. observable: 创建状态,将值转为可响应的
2. reaction: 更新状态时的操作,第二个参数首次不执行
import {observable, reaction} from 'mobx'
// 1、创建state
const data = observable({value:0});
const dispatch = reaction(
() => data.value,
(cur,prev)=>{
console.log(cur,prev);
if(cur>3){
dispose();
alert('不再追踪data')
}
}
);
export default function Mobx(){
const onChange=()=>{
data.value++;
}
return (
// 2、注入state
<button>改变data{data.value}</button>
)
}
回顾状态管理的主线,第三步中的ui与state同步还没实现? 这里需要引入一个新的observer函数
import { observer } from 'mobx-react';
export default observer(function Mobx(){
const value = data.value;
return (
<button onclick={onChange}>改变data {value}</button>
)
});
import React, { Component } from 'react';
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react';
// 1、创建state
class State {
constructor(){
// 这是mobx6.x的方式,可以代替下面所有的声明
makeAutoObservable(this);
// this.value = observable.box(0);
// this.data = observable({});
}
value = 0;
onChange = ()=>{
this.value++;
}
}
class Mbox extends Component {
// 2、注入state
state = new State()
render(){
return (
<button onclick={onChange}>改变data {value}</button>
)
}
}
// 3、state与ui同步
export default observer(Mobx)
3. action
动作,只对修改状态的函数使用动作,以允许mobx跟踪它们的调用。
action.bound,可以用来自动地将动作绑定到目标对象。绑定的this永远是正确的。(不能和箭头函数一起使用,箭头函数已经是绑定过的并且不能重新绑定)
示例:class + mobx
import React, { Component } from 'react';
import { makeObservable, action, observable } from 'mobx';
import { observer } from 'mobx-react'
// 1.创建state
class State {
constructor(){
// 这里不是自动处理,而是人为控制那些数据具有响应能力,哪些方法是修改状态的
makeObservable(this,{
value:observable,
//onChangeValue不是属性声明的箭头函数,所以应当绑定state实例
onChangeValue: action.bound,
})
}
count = 0
value = 0
onChangeValue(){
this.value++;
}
onChangeCount(){
this.count++;
}
}
@observer //2、state与ui同步
class Mobx extends Component {
state = new State()
render(){
return (
<>
<button onClick={this.state.onChangeValue}>
改变value {this.state.value}
</button>
<button onClick={this.state.onChangeCount}>
改变count {this.state.count}
</button>
</>
)
}
}
export default Mobx;
4. makeObservable
定义了响应能力的属性和方法。 上述例子中count不是响应式的,所以改变count,页面上不会有变化。 对比makeAutoObservable会更清晰操作哪些需要响应,哪些不需要响应,从而达到节约性能的目的。
5. makeAutoObservable
自动转化 target 对象的属性和方法
6. flow
处理异步action的方法。 示例:实现请求的控制
import React,{Component} from 'react';
import {makeOberservable, action, observable,flow,computed} from 'mobx';
import {observer} from 'mobx-react';
// 模拟一个请求接口
const api = (name='')=>new Promise(resolve=>{
setTimeout(()=>{
const length = Math.cell(Math.random()*10);
resolve({
code:200,
data:Array(length).fill(0).map((_,index)=>({name:name +Math.random(),id:''}))
})
},1000)
})
class State {
constructor(){
// 人为控制数据是否是响应式
makeObservable(this,{
list: observable,
loading:observable,
assign:action.bound,
overflow: computed //衍⽣的属性只能访问,不要直接修改
})
}
list = []
loading=false
get overflow (){
return this.list.length >30
}
// flow来处理异步action的方法
onFetch = flow(function *(string){
this.loading = true
const {data} = yield api(string)
this.list = data;
this.loading = false
})
}
export default @observer class Mobx extends Component {
state = new State()
render(){
if(this.state.loading){
return <p>loading...</p>
}
return (
<>
<div>
{
this.state.list.map(item=>(
<p key={item.id}>{item.name}</p>
))
}
</div>
<button onClick={()=>this.state.onFetch('响应')}>获取数据</button>
</>
)
}
}
7. autorun
自动执行回调
dispose = autorun(()=>{
if(this.state.list.length > 5){
....
}
})
8. functional+mobx
函数式组件的更新相当于重新调用组件自身(只有render部分) 那么怎么在实例化后的状态不会因为更新而被重置呢? 使用useLocalObservable创建响应式数据
import React from 'react';
import { useLocalObservable, observer } from 'mobx-react';
export default observer(function FunctionalMobx() {
const state = useLocalObservable(() => ({
list: [],
get overflow() {
return this.list.length > 30;
},
loading: false,
async onFetch(string) {
// 内部的 this 已经默认绑定到了 state
this.loading = true;
const { data } = await api(string);
this.list = data;
this.loading = false;
}
}));
if (state.loading) {
return <p>loading...</p>
}
return (
<>
<div>
{
this.state.list.map(item=>(
<p key={item.id}>{item.name}</p>
))
}
</div>
<button onClick={()=>this.state.onFetch('响应')}>获取数据</button>
</>
)
})
9. Observer
用于追踪页面更新与state使用情况的部分,只会更新包裹的部分组件
import React from 'react';
import {useLocalObservable, Observer} from 'mobx-react';
// 函数组件不再被observer包裹
export default function FunctionalMobx(){
const state = useLocalObservable(()=>({
...
}))
return (
// 使用Observer包裹[使用了响应式数据]的jsx
<Observer>
{()=>{
if (state.loading) {
return <p>loading...</p>
}
return (
<>
<div>
{
this.state.list.map(item=>(
<p key={item.id}>{item.name}</p>
))
}
</div>
<button onClick={()=>this.state.onFetch('响应')}>获取数据</button>
</>
)
}}
</Observer>
)
}
四、如何在全局状态共享中使用mobx
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'mobx-react';
import App from './App';
import store from './store';
render(
<Provider {...store}>
<App />
</Provider>,
document.getElementById('root')
)
store.js
class Home {
constructor(){
makeObservable(this,{
data:observable,
onChange:action.bound
})
}
data = 'home'
onChange(data){
this.data = data;
}
}
class About {
constructor(){
makeObservable(this,{
value:observable,
onChange:action.bound
})
}
value = 'About'
onChange(value){
this.value = value;
}
}
export default {
home: new Home(),
about:new About()
}
下级组件使用
import React from 'react';
import { inject, Observer } from 'mobx-react';
function MobxReact ({home,about}){
return (
<h3>
<h2>mobx-react</h2>
<button onClick={()=> home.onChange(Math.random())}>
home:
<Observer>{()=> home.data}</Observer>
</button>
<br />
<button onClick={()=> home.onChange(Math.random())}>
about:
<Observer>{()=> about.value}</Observer>
</button>
</h3>
)
}
export default inject('home','about')(MobxReact);