前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >觉得mobx不错,但又放不下redux?

觉得mobx不错,但又放不下redux?

作者头像
腾讯IVWEB团队
发布2020-06-27 23:39:01
1.4K0
发布2020-06-27 23:39:01
举报

react的状态管理

说到react的状态管理工具,大家都会想到redux或者mobx。

代码语言:javascript
复制
redux || mobx     // => true

redux

redux出现较早,包括我们项目组在内,redux几乎已经成了react工程的标配。

redux带来的事件分发机制,将复杂的操作分发到各个reducer,有一种大事化小的睿智,确实将复杂的数据更改逻辑解耦得足够简单。包括我leader在内的很多同学都觉得redux的事件分发机制对于现代前端工程是再适合不过的了。

重绘

但redux的缺点也是足够明显的。每一次dispatch事件之后都会导致整个虚拟dom至顶向下的重绘。重绘剪枝需要在shouldComponentUpdate中完成,如果事件足够复杂, store足够大,shouldComponentUpdate方法的剪枝粒度就不那么容易控制了(实际情况下,shouldComponentUpdate基本和TODO一样不可保证)。

reducer

redux的另一个缺点是:reducer要求每次返回一个新的对象引用。当需要修改的数据层级较深,reducer写起来很难保证优雅。例如有如下store结构:

代码语言:javascript
复制
var state = {
    list: [{
        baseInfo: {
            anchorUid: 12312321,
            anchorLogo: '…'
        },
        roomInfo: {
            rateList: [{
                id: 324234,
                score: 100
            }]
        }
    }]
}

如果需要更改state.list[0].roomInfo.rateList[0].score = 90。这个reducer应该怎么写呢?如果用原生的js应该是这样:

代码语言:javascript
复制
let newRate = Object.assign({}, state.list[0].roomInfo.rateList[0])
newRate.score = 90;
let newRateList = state.list[0].roomInfo.rateList.slice();
newRateList[0] = newRate;
let newRoomInfo = Object.assign({}, state.list[0].roomInfo)
newRoomInfo.rateList = newRateList;
//...

这样的代码看起来像是吃坏了肚子一般。

所以一般redux项目都会刻意的保持store的平坦化,没有深层级的数据,用Object.assign几步搞定。

如果store不可避免的太大了,怎么办呢?很多工程开始使用Immutable.js,以上的代码可以改写为:

代码语言:javascript
复制
let newState = state.updateIn(['list',0,'roomInfo','rateList',0, 'score'], 90);

store 大了,你不用immutable还能怎么办呢?

瞬间感觉高大上!于是我经不住诱惑也npm i immutable -S了。结果被它api恶心到了,最后卸载决定还是用Object.assign

Mobx

总结一下,上一节列出的redux的两个缺点:

  • 每次dispatch触发至顶向下的重绘
  • 新的state对象引用难于构造

新出现的mobx带来激动人心的特性,刚好解决这两个问题。请看下面的例子:

代码语言:javascript
复制
import {Provider} from 'mobx-react'

let appInfo = mobx.observable(state)  // 这个state就是上一节例子中提到的state

ReactDom.render(
  	<Provider appInfo={appInfo}>
		<App />
  	</Provider>,
  document.getElementbyId('container')
);

P. S. 这里隐藏了<App />的实现细节。

第一点,mobx中数据的每一次更新,都会定点的重绘特定组件,整个过程不需要shouldComponentUpdate的参与。<App />中的所有组件都不在需要再管理重绘剪枝。

第二点,如果需要更新内层数据,只需像下方的代码一样,直接赋值。重绘操作会自动进行:

代码语言:javascript
复制
appInfo.list[0].roomInfo.rateList[0].score = 90;

这样的开发体验简直跟做梦一样。

P.S. 更加详细的例子可以去mobx的官网上下载,这篇文章的重点并不是介绍mobx的使用方法。

问题来了

既然mobx这么方便和magic。它又有什么缺点呢?

在实践中,一个问题一直困扰着我:

mobx并没有提供一套数据层的更新模型,可以在用户事件句柄中直接更改数据,也可以代理给其他方法。那怎样做才是最佳实践?怎样才能更好的解耦?

是不是应该创建一个controller,用controller统一处理用户事件、统一管理应用状态。回到我们在MVC架构的时代?于是我默默动手写了下面的代码:

代码语言:javascript
复制
class Controller{
  constructor(store){
   	this.store = store 
  } 
  @action.bound
  setRateScore(index, val){
    this.store.appInfo.rateList[index].score = val;
  }
}

这样可以将数据层与业务逻辑解耦,不需要将繁重的业务逻辑交给mobx来完成。

然而,我的leader拒绝了这种想法。原因是,controller是强逻辑的,也就是说,所有用户事件和数据管理都交给了controller,造成了controller臃肿,同时controller和mobx强耦合,mobx数据层对象变更了,controller就会报错。

反观redux中的事件管理机制,所有事件都被分发到细粒度的reducer上,至于这个reducer怎么处理,事件发送者并不清楚。这一点在大型工程中十分重要。

mobx适合小工程,大工程还是得上redux

难怪网上很多相关的论调,觉得mobx不适合大型工程,多数同学仍然持有redux不放。这种见解过于片面,不过也暴露了mobx在使用上鸡肋的地方。

那么,对于已经用惯了redux的前端猿们,我们是否可以即使用mobx,又同时保持redux的事件分发机制不变呢?

解法1:同时使用redux和mobx

mobx的开发者也开始注意到,mobx主要是作为一个响应式的数据结构而存在,虽然它总是和redux相提并论,其实两者并不冲突,mobx实质上并没有抢redux的生意!

怎么理解呢?回到传统的MVC上来看,redux的工作类似于一个controller,而mobx的工作类似于model。redux负责分发事件,reducer并没有限定store对象就是一个简单的js对象,可以用immutable,那也肯定可以用mobx。

mobx官方的MST已经提供了这样的支持,官方的opinion 以及 demo。我们可以将store替换成一个MST对象,MST对象本质上是immutable的数据类型,这样在reducer中可以避免繁琐的Object.assign代码,这个用法与你使用Immutable.js别无二致。在redux中引入MST很简单,几乎无痛。简要用法如下:

代码语言:javascript
复制
import { asReduxStore } from "mst-middlewares"
import { Provider } from "react-redux"  //使用redux的privider

const todos = todosFactory.create({})
const store = asReduxStore(todos)  // 但使用mobx的store

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById("root")
)

action维持不变,reducer被改写为更加方便的形式:

代码语言:javascript
复制
// reducer的写法
todo.actions(self=>({
    [DELETE_TODO]({ id }) {
        const todo = self.findTodoById(id)
        self.todos.remove(todo)
    },
    [EDIT_TODO]({ id, text }) {
        self.findTodoById(id).text = text
    },
    [COMPLETE_TODO]({ id }) {
        const todo = self.findTodoById(id)
        todo.completed = !todo.completed
    }
}))

这个解法,相当于mobx抢了Immutable.js的生意,如果开发者想继续用redux,但是(和我一样)对Immutable.js的api深恶痛绝的话,不妨试试这种方法,开发体验顺滑了不少, ೭(˵¯̴͒ꇴ¯̴͒˵)౨”。

缺点是:数据更新仍然由redux控制,自顶向下的重绘开销不小,剪枝操作复杂而没有保证。

##解法2:实现数据分发层

如果完全去掉redux,改用mobx-react进行页面重绘,就可以达到精确的重绘定位。剩下的工作就是我们自己实现一套redux的数据分发逻辑。

这里提供一个简单的版本供参考:

先定义一个Dispatcher类,对外暴露dispatch方法和setMiddleware方法。

代码语言:javascript
复制
class Dispatcher{
    constructor(store){
        this._store = store;
        this.dispatch = this.dispatch.bind(this);
    }
    init(store){
        this._store = store;
    }
    setMiddleware(...args){
        if(!args.length) return;
        let _args = args.reverse();
        let dispatch = this.dispatch;
        _args.forEach(mid=>{
            dispatch = mid(this)(dispatch);
        })
        this.dispatch = dispatch;
    }
    dispatch(opts){
        let keys = Object.keys(this._store)
        keys.forEach(key=>{
            let item = this._store[key];
            if(item.reducer){
                item.reducer(opts);
            }
        });
    }
}

然后在mobx的数据模型中定义一个reducer方法,将原有的reducer逻辑照搬过来,例如:

代码语言:javascript
复制
let Count = types.model({
	count: types.number
}).actions(self=>({
  reducer(action){
    switch(action.type){
      case 'ADD_COUNT':
		self.count += 1;
        break;
      case 'DE_COUNT':
		self.count -= 1;
        break;
    }
  }
}))

大功告成。可以继续沿用redux中的action和middleware代码,照搬无误,例如:

代码语言:javascript
复制
let dispatcher = new Dispatcher(store);
dispatcher.setMiddleware(cgiFetch, login, report);

// index.jsx
addClick = ()=>{
  dispatcher.dispatch({type: 'ADD_COUNT'})
}
deClick = ()=>{
  dispatcher.dispatch({type: 'DE_COUNT'})
}
render(){
  return <div>
    <div>{this.props.Count.count}</div>
    <span onClick={this.addClick}>+</span>
    <span onClick={this.deClick}>-</span>
  </div>
}

P.S. 以上代码中的dispatcher的实现,中间件部分逻辑没有封装getStore方法,实际情况需要自己加上。

最后。本文提到只是自己在工程实践中得出的一些总结,绝非唯一的架构方法。欢迎找我谈论,欢迎大大们评论指导。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • react的状态管理
    • redux
      • 重绘
      • reducer
    • Mobx
    • 问题来了
      • 解法1:同时使用redux和mobx
      相关产品与服务
      消息队列 TDMQ
      消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档