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

**注意:**我会写多篇文章来比较说明redux和mobx的不一样,redux和mobx各有优缺点, 若是对React/Mobx/Redux都理解够深入,我我的推荐Mobx(逃跑。。。)javascript

React社区的大方向是immutable, 不论是用immutable.js 仍是函数式编程使用不可变数据结构。为何React须要不可变数据结构呢? 考虑下面的一个应用html

应用结构

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会怎么样呢? 结论是当点击的时候 控制台会打印:java

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暴力比较算法已经很是高效了,这些无心义的计算也应该避免, 起码能够节省计算机的电 --> 少用煤 --> 减小二氧化碳排放 --> 保护地球。 毕竟 蝴蝶效应!react

ui = f(d) 相同的d获得相同的ui(设计组件的时候最好这样)。例如咱们上例的Dog,咱们能够直接比较shgit

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

更加通常的状况, 咱们怎么肯定组件的props和state没有变化呢? 不可变对象 ! 若是对象是不可变的, 那么当对象a !== a' 就表明这是2个对象,不相等。而在传统可变的对象中 须要deepEqual(a, a')。 若是咱们的React应用里面 props和state都是不可变对象, 那么:github

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

react也考虑到了一点 提供了PureComponent帮助咱们默认作了这个shouldComponentUpdateweb

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

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

拯救了地球!编程

Redux

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

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 } = store
        return (
            <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。

这里暴露了两个问题:

  1. 父组件的更新,会影响到子组件,因为不是使用不可变数据,还不能简单的经过PureComponent优化
  2. 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} = store
        return (
            <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.store
        
        return (
            <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正如前文所说 不论是发布为第三方库, 仍是使用第三方库

  1. mobx写的组件,发布给其余应用使用比较困难,由于要不咱们直接从全局取数据渲染(context获取 道理相同), 要不推迟引用值的获取, 不论是哪种,组件都没有任何可读性。
  2. mobx 使用第三方 例如BigComponent, 没有那么天然。

开发效率

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

  1. immutable.js 学习成本略高, 包大小也毕竟大
  2. 函数式编程,项目组本身一我的 能够考虑
  3. immer 若是不考虑IE,强烈推荐, 强烈推荐 (做者是mobx的做者)。 immer和mutable的修改数据的方法是一摸同样的, 最后会根据你的修改返回一个不可变的对象。 github地址

结论

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

若是你对Mobx掌握的足够好, 那么Mobx绝对会迅速的提升开发效率。

本文代码github地址

相关文章
相关标签/搜索