携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
简介
说到Redux,使用React的小伙伴肯定很清楚,是React用的最多的一个状态管理工具。但对于Mobx,可能有些小伙伴不太熟悉。
今天我们以对比的方式来说说Redux和Mobx在React中的应用以及它们之间的区别。
Redux
Redux我们很熟悉,并且笔者前面有两篇文章专门介绍过Redux的使用。感兴趣的小伙伴可以先去看看。
Vue和React对比学习之状态管理 (Vuex和Redux)
对比React-Redux看看Redux Toolkit有哪些优点
这里我们再来简单复习下Redux。
核心
Redux中主要分为state、actions、reducers三大模块。每个模块各司其职。
state 定义状态,一般定义在reducer中。
actions 传递type和payload参数,用于触发reducer里面的方法。
reducers 改变 state 的唯一方法,必须为纯函数,判断 action 的type相应更新state。每次 reducer 都是返回一个全新的 state。
原则
- 单一数据源。整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- State 是只读的。唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改。为了描述 action 如何改变 state tree ,你需要编写 reducers。
更新过程
Redux的更新过程大体如下:
- 应用程序中发生了某些事情,例如用户单击按钮
- dispatch 一个 action 到 Redux store,例如
dispatch({type: 'increment', payload: 1}) - store 触发相应的 reducer 函数返回全新的 state,并将返回值保存为新的
state。 - store 通知所有订阅过的 UI,通知它们 store 发生更新
- 每个订阅过 store 数据的 UI 组件都会检查它们需要的 state 部分是否被更新。
- 发现数据被更新的每个组件都强制使用新数据重新渲染,紧接着更新网页
下面我们写个简单例子使用一下。
安装
redux是状态管理库,我们肯定是需要安装的。
npm i redux
react-redux是连接React和Redux的桥梁,所以也是需要安装的。
npm install react-redux
创建
下面我们分别创建下各模块。
action
// store/actions/UserInfoAction.js
export const setUserInfoAction(payload) {
return {
type: 'setuserinfo',
payload
}
}
state和reducer
在Redux中,初始state一般会定义在reducer中。
// store/reducers/UserInfoReducer.js
const initialState = { userinfo: {}}
export default function(state=initialState, action) {
switch(action.type) {
case 'setuserinfo':
return {...state, userinfo: action.payload}
default:
return state
}
}
上面我们也可以把
action的type(比如上面的 setuserinfo)单独抽取出来,放到一个文件里,这里简单演示我们就没抽取出来。
React通过Redux的createStore方法传递reducer创建store。当reducer有很多的时候,我们可以借助combineReducers方法,将多个reducer合并成一个reducer。
这里我们只有一个reducer,所以我们传递到createStore方法就可以了
// store/index.js
import { createStore } from "redux";
import UserInfoReducer from "./reducers/UserInfoReducer.js";
const store = createStore(UserInfoReducer);
使用
接下来我们需要在根组件将React和Redux连接起来。这里需要用到react-redux。
通过react-redux的Provider组件传递store进去,将React和Redux连接在一起。
import { Provider } from "react-redux";
<Provider store={store}>
<App />
</Provider>
在React class组件中,需要借助React-Redux的connect方法
// 类组件
// 通过react-redux的connect方法参数mapStateToProps将state定义到组件的props属性上。
import { connect } from "react-redux";
class ReduxTest {
// ...
}
mapStateToProps(state) {
return {
userinfo: state.userinfo
}
}
// 通过react-redux的connect方法参数mapDispatchToProps将dispatch方法传递进来。
mapDispatchToProps(dispatch) {
return {
handleSetUserInfo() {
dispatch(setUserInfoAction({name: 'demi'}))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReduxTest);
在函数组件中需要借助useSelector和useDispatch 两个 hooks。
// 函数组件
// 函数组件通过react-redux的useSelector和useDispatch两个hook
const userinfo = useSelector((state) => state.userinfo);
const dispatch = useDispatch();
dispatch(setUserInfoAction({name: 'demi'}))
上面的例子我们只简单的使用了一个reducer,并且也只使用了同步action。对于多个reducer我们还需要借助combineReducers方法。对于异步操作,我们还需要借助reduc-thunk、redux-promise、redux-saga等第三方库。这里笔者就不再赘述了,感兴趣的小伙伴可以看看笔者前面写的Redux 异步数据流方案对比(redux-thunk、redux-promise、redux-saga)
下面我们再来看看Mobx
Mobx
Mobx也可以作为React的状态管理库,但是相对Redux来说使用得相对较少。
核心
MobX主要有以下三个概念:
- State(状态)
- Actions(动作)
- Derivations(派生)
State(状态) 是驱动你的应用程序的数据。和Redux中的state是一个意思,就是状态数据。
Action(动作) 是任意可以改变 State(状态) 的代码,比如用户事件处理、后端推送数据处理、调度器事件处理等等。
Derivations 任何来源是State(状态) 并且不需要进一步交互的东西都是 Derivation(派生)。
Mobx 区分了两种 Derivation :
- Computed values,总是可以通过纯函数从当前的可观测
State中派生。 - Reactions,当
State改变时需要自动运行的副作用 (命令式编程和响应式编程之间的桥梁)
对于Derivations可能有些小伙伴不太理解,别急,我们接着往下看,后面笔者会详细介绍。
原则
Mobx 使用单向数据流,利用 action 改变 state ,进而更新所有受影响的 view
- 所有的 derivations 将在 state 改变时自动且原子化地更新。因此不可能观察中间值。
- 所有的 derivations 默认将会同步更新,这意味着 action 可以在 state 改变 之后安全的直接获得 computed 值。
- computed value 的更新是惰性的,任何 computed value 在需要他们的副作用发生之前都是不激活的。
- 所有的 computed value 都应是纯函数,他们不应该修改 state。
更新过程
MobX的更新相对来说会简单很多,更新过程大体如下:
- 通过事件驱动(UI 事件、网络请求…)触发 Actions
- 在 Actions 中修改了 State 中的值,这里的 State 既应用中的 store 树(存储数据)
- 然后根据新的 State 中的数据计算出所需要的计算属性(computed values)值
- 响应(react)到 UI 视图层
下面我们写个简单例子使用一下。
安装
mobx是状态管理库,我们肯定是需要安装的。
npm i mobx -S
与react-redux不同,mobx的连接库有两个,一个是mobx-react另外一个是mobx-react-lite。
mobx-react-lite 是 mobx-react 的轻量级版本,只支持函数组件使用。并且只支持react16.8及以上。
这里我们使用mobx-react
npm i mobx-react -S
我们使用的是最新版的Mobx 6,在 Mobx 6中,为了与标准 JavaScript 的最大兼容性,已经在默认情况下放弃了装饰器。所以这里我们就不使用装饰器语法了。
创建
我们创建一个testStore模块
// store/mobx/testStore.js
import {
makeObservable,
observable,
computed,
action,
flow,
} from "mobx";
class testStore {
// observable
count = 10;
constructor() {
// 将参数对象中的属性设置为 observable state
// 将计算属性设置为computed
// 将参数对象中的方法设置为 action
makeObservable(this, {
count: observable,
double: computed,
increment: action,
decrement: action,
fetch: flow,
});
}
// action
increment() {
this.count += 1;
}
// action
decrement() {
this.count -= 1;
}
//computed
get double() {
return this.count * 2;
}
//flow
*fetch() {
const response = yield fetch(
"https://jsonplaceholder.typicode.com/todos/100"
);
const jsonResult = yield response.json();
this.count = jsonResult.id;
}
}
export default new testStore();
这里我们用到了makeObservable方法,它的作用就是创建可观察状态
observable定义一个存储 state 的可追踪字段。action将一个方法标记为可以修改 state 的 action。computed标记一个可以由 state 派生出新的值并且缓存其输出的 getter。flow定义异步方法。
这里我们还可以使用makeAutoObservable,自动识别。makeAutoObservable 就像是加强版的 makeObservable,在默认情况下它将推断所有的属性。
上面的例子我们使用makeAutoObservable改写也是可以的。
// store/mobx/testStore.js
import { makeAutoObservable } from "mobx";
class testStore {
count = 10;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
get double() {
return this.count * 2;
}
*fetch() {
const response = yield fetch(
"https://jsonplaceholder.typicode.com/todos/100"
);
const jsonResult = yield response.json();
this.count = jsonResult.id;
}
}
export default new testStore();
定义好之后,接下来我们到组件中去使用
使用
我们先定义一个组件然后引入我们的testStore
import React from "react";
import { observer } from "mobx-react";
import testStore from "../store/mobx/testStore";
class Mobx extends React.PureComponent {
render() {
return (
<div>
<div>{testStore.count}</div>
<button onClick={() => testStore.increment()}>increment</button>
<button onClick={() => testStore.decrement()}>decrement</button>
</div>
);
}
}
export default observer(Mobx);
这里类似react-redux的connect,我们需要使用observer来包裹我们的组件。然后我们需要用到哪个store的数据就引入哪个store就可以了。
在页面中可以看到如下效果
可以看到,count和double都正确显示出来了,我们再来点击按钮操作下state。
当我们点击increment的时候,count和double就会跟着变化。
下面我们再来看看异步操作。
异步的使用
异步action,我们只需要在store中将函数写成generator形式就可以了,比如上面的fetch方法。
接下来在我们的组件加上异步按钮,调用异步方法。
<div>
<button onClick={() => testStore.fetch()}>异步操作</button>
</div>
点击异步操作按钮,调用接口,我们来看下接口返回结果。
通过返回的数据我们动态设置state。可以看到,count被修改成100了,相应的double变成了200。
到这我们mobx的初步使用就完成了。可以看到它的使用相较Redux来说非常是简单的。哪里需要使用再在哪里进行引入,而不是像Redux一样在根组件进行注入。如果使用过最新版Vue的状态管理Pinia就会发现他们有很多相似点。
前面我们说了,Derivations分为computed和Reactions两类,computed上面已经演示了,当依赖的state更新才会更新,类似vue里面的计算属性。下面我们再来说说Reactions。
Reactions
reactions 的目的是对自动发生的副作用进行建模。它们的意义在于为你的可观察状态创建消费者,以及每当关联的值发生变化时,自动运行副作用。
类似Redux里面的subscribe方法,不过相对来说功能更强大一点。
对于reactions我们主要研究autorun、reaction、when这三个函数就可以了。
autorun
autorun 函数接受一个函数作为参数,每当该函数所观察的值发生变化时,它都应该运行。 当你自己创建 autorun 时,它也会运行一次。它仅仅对可观察状态的变化做出响应。
import { autorun } from "mobx";
class Mobx extends React.PureComponent {
componentDidMount() {
autorun(() => {
console.log(testStore.count);
});
}
}
当点击按钮改变count的时候,可以看到我们的autorun回调函数就运行了,count就被输出来了。
reaction
reaction 类似于 autorun,但可以让你更加精细地控制要跟踪的可观察对象。 它接受两个函数作为参数:第一个,data 函数,其是被跟踪的函数并且其返回值将会作为第二个函数,effect 函数,的输入。 重要的是要注意,副作用只会对 data 函数中被访问过的数据做出反应,这些数据可能少于 effect 函数中实际使用的数据。
import { reaction } from "mobx";
class Mobx extends React.PureComponent {
componentDidMount() {
reaction(
() => {
return testStore.count + 10;
},
(value, previousValue, reaction) => {
console.log(value, previousValue, reaction);
}
);
}
}
当点击increment按钮改变count的时候,可以看到我们的reaction第一个回调函数就运行了,并且第二个回调函数也执行了,在第二个回调函数我们还可以拿到前一个回调函数返回的新、老值。
when
when 会观察并运行给定的 predicate 函数,直到其返回 true。 一旦 predicate 返回了 true,给定的 effect 函数就会执行并且自动执行器函数将会被清理掉。
如果你没有传入 effect 函数,when 函数返回一个 Promise 类型的 disposer,并允许你手动取消。
import { when } from "mobx";
class Mobx extends React.PureComponent {
componentDidMount() {
when(
() => {
return testStore.count > 20;
},
() => {
console.log("满足条件执行");
}
);
}
}
当点击increment按钮改变count的时候,只有当count大于20的时候,第二个回调函数才会执行。并且只会执行一次。
总结
相同点
都是状态管理库
都是状态管理库,都能解决项目状态混乱问题。
都支持多个前端框架
都支持在多个框架使用,不仅仅是React。
都是单向数据流
都是单向数据流
不同点
单一Store和多Store
Redux 鼓励一个应用只用一个 Store。
Mobx 鼓励使用多个 Store,需要使用哪个Store就引入哪个Store。有点类似Vue里面Pinia的感觉,有多个Store,每个Store又分为state、getter、action三个模块。
数据的不可变(Immutable)和可变(Mutable)
Redux对状态的处理使用不可变值,更改state必须在reducer里面,reducer函数必须是纯函数,并且每次都是返回全新的state而不是修改state。也就是说数据是不可变的。
Mobx是直接调用action,并在里面直接修改state。也就是数据是可变的。
存储的数据结构
Redux默认是存储的一个原生的JavaScript对象,而MobX则是存储了一个可观察的对象。
异步action
在Redux中,异步操作需要借助第三方库reduc-thunk、redux-promise、redux-saga等。
在Mobx中,可以直接定义异步action,只需要写成generator形式的函数即可。
计算属性
Mobx支持计算属性,但是Redux是不支持的。
个人感受
Mobx简单易上手,通过 OOP 风格和良好的开发实践,你可以快速的构建各种应用。并且相对灵活,可以直接修改state,比如上面的例子testStore.count = 100直接修改state也是可以的。这样就很容易编写糟糕的不可维护的代码。
Redux相对来说,模块拆分更细,更严格。使用起来确实要麻烦一点。
所以,如果是小项目,追求快,或者是Vue开发者,习惯了Vuex、Pinia那一套,我推荐使用Mobx。如果是大项目,大团队协作开发推荐使用Redux。
参考文档
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!