react-redux.js中为何全部组件都要重新setState?


  • 0

    这个问题在33章《动手实现 Redux(四):共享结构的对象提高性能》就开始困扰我了,在这一节中

    store.subscribe(() => renderApp(store.getState())) // 监听数据变化

    这个renderApp非常的粗暴啊,导致任何一个state的修改都会全局刷新。这个 listeners 用了观察者模式,我感觉完全可以做得更强大一点,像jquery的事件队列一样,当state.title.text改变里,只单独renderTitle,可以节省不少性能吧。

    我知道在react中使用了Virtual DOM,用算法来diff 两次状态树的差异来解决这个问题。但我不明白为什么要这样了,明明已经知道了某个state的状态发生了变化,按理来说,只需要通知订阅了该state的组件重新渲染就足够了,为什么又要比较整个状态树??


  • 0
    administrators

    这个问题问得挺好的。

    首先回答队列的问题,React 内部实现其实是有类似的策略的。你每次 setState 的时候其实并不会导致 React 马上重新渲染,React 会把需要更新的 state 放到一个更新队列里面,在同一个事件循环结束以后把所有的 state 合并成一个,然后再用这个 state 去重新渲染内存里面的 Virtual DOM。也就是说:

    this.setState({ name: 'Jerry' })
    this.setState({ name: 'Jerry1' })
    this.setState({ name: 'Jerry2' })
    this.setState({ name: 'Jerry3' })
    this.setState({ name: 'Jerry4' })
    ...// 100 次 setState
    

    上面 100 次 setState 是在同一次事件循环当中的,所以,在这次事件循环结束之后会把上面 100 次都合并成一次,然后去更新 Virtual DOM。所以最后只会执行一次视图更新操作。

    然后再回答重刷整个视图的问题。这里其实确实会有可观的性能损耗,但是 React 基于一个想法:JavaScript 足够快。其实你循环 3000 次和循环 100 次的差异其实是感知不到的,而且 Virtual DOM 是基于内存的操作,算法复杂度也是 O(n),所以重新内存里面刷新视图是可以接受。但是在大型视图当中,这种性能损耗是能感知的,特别是性能较差的移动端。React 也意识到这个问题,所以也提供了 shouldComponentUpdate 这个方法来让你阻止某些视图的重新刷新,挽救大型视图的性能损耗。

    但这种重刷的方式带来的好处也是巨大的,其实这就是 React 的强大之处。如果让视图来监听 state 变化然后更新自己的话,其实就增加了视图对状态的依赖(一个视图需要某个状态,需要依赖共享的 state 然后监听事件),你这个组件的复用性就很差了。但是如果是利用重刷视图的模式,视图就不依赖状态了,就可以做到 view = f(state)。我可以通过构建大量的 pure 组件,这些组件不依赖 state,而是依赖传进来的 props,你需要改变视图你只能通过顶层的 smart component 进行 setState 把新的状态传下去。这样,整个 App 的底层其实都是复用性非常高,对 state 无依赖的组件。这也是为什么一直在 React 编程中鼓吹函数式编程的缘故。


  • 0

    多谢哈哥解惑。写得真他妈的好,完美解决了我的困惑。

    当然我还是对React的这种做法有一些怀疑,为了追求“纯”有点作茧自缚的样子。要单向数据流,要state => view 的可追踪,有点违反直觉,特别是表单,连input的输入都被改掉了。我不知道这算不算是React的一种缺陷,总觉得有点过火了

    提完这个问题之后正好在知乎上看到这个问题 如何看待 snabbdom 的作者开发的前端框架 Turbine 抛弃了虚拟DOM? 徐飞的答案有一部分说到了这个问题。然而自己水平还有限,理解不了那么深


登录后回复
 

与 ScriptOJ 的连接断开,我们正在尝试重连,请耐心等待