欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

Redux vs Mobx系列(-):immutable vs mutable

发布时间:2025/3/20 编程问答 43 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Redux vs Mobx系列(-):immutable vs mutable 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

**注意:**我会写多篇文章来比较说明redux和mobx的不同,redux和mobx各有优缺点, 如果对React/Mobx/Redux都理解够深刻,我个人推荐Mobx(逃跑。。。)

React社区的大方向是immutable, 不管是用immutable.js 还是函数式编程使用不可变数据结构。为什么React需要不可变数据结构呢? 考虑下面的一个应用

class Root extends Component {state = {something: 'sh'}render() {return (<div><div onClick={e => { // onClick setState 空对象this.setState({})}}>click me!!</div><L1/><Dog sh={this.state.something}/></div>)} }... class L1 extends Component {render() {console.log('invoke L1')return (<div><L11/><L12/></div>)} } ... class L122 extends Component {render() {console.log('invoke L122')return (<div>L122</div>)} } 复制代码

当我点击 Root上的 click me 的时候, 执行了this.setState({}),于是触发Root更新, 这个时候L1, Dog会怎么样呢? 结论是当点击的时候 控制台会打印:

invoke L1 invoke L11 invoke L111 invoke L112 invoke L12 invoke L121 invoke L122 invoke Dog 复制代码

当一个组件需要跟新的时候,react并不知道哪里会更新,在内部react会用object(存js对象)来代表dom结构, 当有更新的时候 react暴力比较前后object的差异,增量的处理更新的dom部分。 对于刚才的这个例子, react暴力计算的结果就是没有增量。。。虽然react暴力比较算法已经非常高效了,这些无意义的计算也应该避免, 起码可以节省计算机的电 --> 少用煤 --> 减少二氧化碳排放 --> 保护地球。 毕竟 蝴蝶效应!

ui = f(d) 相同的d得到相同的ui(设计组件的时候最好这样)。例如我们上例的Dog,我们可以直接比较sh

class Dog extends Component {shouldComponentUpdate(nextProps) {return this.props.sh !== nextProps.sh}... } 复制代码

更加一般的情况, 我们怎么确定组件的props和state没有变化呢? 不可变对象 ! 如果对象是不可变的, 那么当对象a !== a' 就代表这是2个对象,不相等。而在传统可变的对象中 需要deepEqual(a, a')。 如果我们的React应用里面 props和state都是不可变对象, 那么:

class X extends Component {shouldComponentUpdate(nextProps, nextState) {return !( shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState))} } 复制代码

react也考虑到了一点 提供了PureComponent帮助我们默认做了这个shouldComponentUpdate

把 L1, Dog, L11 ... L122改为PureComponent, 再次点击,打印:

// 没有输出。。。 复制代码

拯救了地球!

Redux

redux 每次action发生的时候,都会返回一个全新的state,�天生是immutable。 Redux + PureComponent 轻松开发出高效web应用

Mobx

Mobx刚好相反,它依赖副作用(so 所有组件不在继承PureComponent), 那它是怎么工作的呢?

mobx-react的 @observer通过收集组件 render函数依赖的状态, 当状态有修改的时候精确的控制组件的更新。

比如现在 Root组件依赖状态 title, L122 依赖状态x(Root传递x给L1,L1传递给L12, L12传递给L122)。 那么应该:

const store = observable({x: 'x'title: 'title', })window.store = store @observer export default class MobxRoot extends Component {render() {console.log('invoke MobxRoot')const { title, x } = storereturn (<div><div>{title}</div><L1 x={x}/><Dog/></div>)} } class L1 extends Component {render() {console.log('invoke L1')return (<div><L11/><L12 x={this.props.x}/></div>)} } class L12 extends Component {render() {console.log('invoke L12')return (<div><L121/><L122 x={this.props.x}/></div>)} } @observer class L122 extends Component {render() {console.log('invoke L122')return (<div>{ this.props.x || 'L122'}</div>)} } 复制代码

这样当title变化的时候, Mobx发现只有MobxRoot组件关心title,于是更新MobxRoot, 当x变化的时候 Mobx发现有MobxRoot, L122 依赖与x,于是更新MobxRoot,L122 。 工作很正常。

细想当title变化的时候,更新MobxRoot,由于更新了MobxRoot进而导致L1,Dog的递归暴力diff计算,显而易见的是无意义的计算。 当x变化的时候呢, 由于MobxRoot,L122依赖了x, 会先更新MobxRoot,然后更新L122,然而在更新MobxRoot的时候又会递归的更新到L122, 这里更加麻烦了(实际上React不会更新两次L122)。

Mobx也在文档里指出了这个问题(晚一点使用间接引用值), 对应的解决方法是 L1 先传递store。。。最后在L122里面从store里面获取x。

这里暴露了两个问题:

  • 父组件的更新,会影响到子组件,由于不是使用不可变数据,还不能简单的通过PureComponent优化
  • props传递的过程中 不可避免的会提前使用引用值,导致某些组件无意义的更新, 状态越多越复杂
  • 记住在mobx应用里, 应该把组件是否更新的绝对权完全交给Mobx,完全交给Mobx,完全交给Mobx。 即使是父组件也不应该引起子组件的跟新。 所以所有的组件(没有被@observer修饰)都应该继承与PureComponent(这里的PureComponent的作用已经不是原来的了, 这里的作用是阻止更新行为的传递)。 另外一点, 由于组件是否更新取决与Mobx, 组件更新的数据又取值与Mobx,所以还有必要props传递吗? 基于这两点代码:

    const store = observable({x: 'x'title: 'title', })window.store = store @observer export default class MobxRoot extends Component {render() {console.log('invoke MobxRoot')const { title} = storereturn (<div><div>{title}</div><L1/><Dog/></div>)} } class L1 extends PureComponent {render() {console.log('invoke L1')return (<div><L11/><L12/></div>)} } class L12 extends PureComponent {render() {console.log('invoke L12')return (<div><L121/><L122/></div>)} } @observer class L122 extends Component {render() {console.log('invoke L122')const x = window.store // 直接从Mobx获取return (<div>{ x || 'L122'}</div>)} } 复制代码

    这样当title改变的时候, 只有MobxRoot会跟新, 当x改变的时候只有L122 会更新。 现在我们可以把应用里面的所有组件分为两类: 关注状态的@observer组件, 其他PureComponent组件。这样每当有状态改变的时候, Mobx精确控制需要更新的@observer组件(最小的更新集合),其他PureComponent阻止无意义的更新。 问题的关键是开发者一定要搞清楚 哪些组件需要 @observer。 这个问题先放一下, 我们在看一个mobx的问题

    假设L122复用了一个第三方库提供的组件(表明我们不能修改这个组件)

    @observer class L122 extends Component {render() {console.log('invoke L122')const x = window.store // 直接从Mobx获取return (<div><BigComponent x={x}/></div>)} } 复制代码

    组件 BigComponent 正如其名 是一个很‘大’的组件,他接收一个props对象 x,x结构如下:

    x = {name: 'n'addr: '', } 复制代码

    此时当我们执行: window.store.x.name = 'fcdcd' 的时候, 我们期待的是BigComponent按照我们的意愿,根据改变后的x重新渲染, 其实不会。 因为在这里没有任何组件 依赖name, 为了让L122 正常工作, 我们必须:

    @observer class L122 extends Component {render() {console.log('invoke L122')const x = window.store.x const nx = {name: x.name,addr: x.addr}return (<div><BigComponent x={nx}/></div>)} } 复制代码

    如果不明白mobx的原理, 可能会很疑惑,疑惑这里为什么要这么写, 疑惑哪里为啥不更新, 疑惑哪里为啥莫名其妙更新了。。。

    什么组件需要@observer? 当一个render方法里,出现我们不能控制的组件(包括原生标签, 第三方库组件)依赖于状态的时候, 我们应该使用@observer, 其他组件应该继承PureComponent。 这样我们的应用在状态发送改变的时候,更新的集合最小,性能最高。

    除此之外,Mobx还有一个性能隐患,希望mobx的拥护者能够清楚的认知到,假设现在 L122 不仅也依赖title, 还依赖状态a, b, c, d, e, f, g, h:

    class L122 extends Component {render() {console.log('invoke L122')const { title, a, b, c, d, e, f, g, h } = window.storereturn (<div><span>{title}</span><span>{a}</span><span>{b}</span>...<span>{h}</span></div>)} }function changeValue() {window.store.title = 't'window.store.a = 'a1'window.store.b = 'b1'window.store.c = 'c1' } 复制代码

    当执行 changeValue()的时候 会发生什么呢?控制台会打印:

    invoke MobxRoot invoke L122 invoke L122 invoke L122 invoke L122 复制代码

    一身冷汗!!得好好想想这里的数据层设计, 是否把这几个属性组成一个对象,状态越来越复杂的时候可能不是那么简单。

    第三方库结合

    redux与第三方库结合没有好说的,工作的很好。 很多库现在已经假定了 传人的状态是 不可变的。

    mobx正如前文所说 不管是发布为第三方库, 还是使用第三方库

  • mobx写的组件,发布给其他应用使用比较困难,因为要不我们直接从全局取数据渲染(context获取 道理相同), 要不推迟引用值的获取, 不管是哪一种,组件都没有任何可读性。
  • mobx 使用第三方 例如BigComponent, 没有那么自然。
  • 开发效率

    这里我们只说 immutable的开发效率,mutable的开发效率应该是最低的。 0. 结合对象展开浮, js裸写。 也不难

  • immutable.js 学习成本略高, 包大小也毕竟大
  • 函数式编程,项目组自己一个人 可以考虑
  • immer 如果不考虑IE,强烈推荐, 强烈推荐 (作者是mobx的作者)。 immer和mutable的修改数据的方法是一摸一样的, 最后会根据你的修改返回一个不可变的对象。 github地址
  • 结论

    如果你能无痛的处理immutable, 那么Redux + PureComponent 很方便写出高性能的应用。

    如果你对Mobx掌握的足够好, 那么Mobx绝对会迅速的提高开发效率。

    本文代码github地址


    总结

    以上是生活随笔为你收集整理的Redux vs Mobx系列(-):immutable vs mutable的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。