聊一聊 React 中更新 ui 视图的几种方式

聊一聊 React 中更新 ui 视图的几种方式

前言

刚为祖国母亲庆完生,眼看假余额就要不足了,小伙伴们玩的是否开心呢,反正我是死宅在家,没出去玩,在家也没好好学习,实属惭愧。这不,今天和大家分享下关于 react 驱动 ui 更新的几种方式,都说 react 是单向数据流,数据驱动 ui,那么你知道在 react 中有几种驱动视图更新的方式呢。

1. setState

setState 是众所周知最常见的更新视图的方式了,只需要给个初始 state,需要更新的时候调用 this.setState, 组件会经历 shoudlComponentUpdate => componentWillUpdate => render => componentDidUpdate 四个过程,如果没有在 shouldComponentUpdate 中手动 return false 的情况下,那么 ui 此时就会更新。

需要注意的是,尽管 this.setState({}) 参数为空对象时,react 一样会更新,调用上面的四个生命周期,只是 ui 视图不有会变化。

当然这些都要基于 class 组件,因为 setState 是 Component 原型上的方法,必须得是继承 Component的组件才能调用 this.setState。(之前有写过一篇关于 setState 的文章,感兴趣的大佬也可以去看看。)

虹晨:你真的理解setState吗?zhuanlan.zhihu.com图标

2. forceUpdate

Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.

官方说的比较清楚,调用 forceUpdate 后当前组件会跳过 shouldComponentUpdate 这个钩子,尽管手动 return false,也一样会更新,单词字面理解也是强制更新,但是需要注意的是,子组件的更新还是会受到 shouldComponentUpdate 控制。

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().

forceUpdate的使用场景一般是视图更新来源于其他非 state、props的数据,比如绑定在 组件实例上的属性,或者是直接修改 this.state.xxx = xxx ,然后调用 this.forceUpdate(),不过官方不建议这么做,正常情况下应该避免使用 forceUpdate,通过 state 或者 props 去更新视图。

3. 原生操作dom

在 react 中难免会有操作原生 dom 的时候,比如你用了第三方比如 jquery 这种需要获取 dom 的库,或者你需要实现一个拖拽,双指缩放的组件,对于这些,你也许可以用操作的 dom 的方式绕过 react 的 setState 再到 dom diff 一系列的计算,直接更新 dom ,提高些许性能。

以上三种更新 ui 的方式,我这边写了个简单的 Demo。

3-ways-react-update - CodeSandboxcodesandbox.io图标

需要注意的是,当通过 setState 更新改变颜色变红,在点击原生 dom 按钮改变颜色变绿,这时在点击 setState 的按钮,发现视图不更新,但是还是走了 render 函数,这是因为点击原生 dom 按钮前, this.state.backgroundColor 值是 red,原生操作是直接改变的 dom style,在点回 setState 按钮,其实 this.state.backgroundColor的值还是 red, 虽然走了更新逻辑,但是由于 react 的 新老 virtual dom 对比的时候,发现颜色并没改变, 导致没有 patch 到 ui 上

4. dispatch action

上面的几种方式都是通过 react 本身自带的 state 去更新 ui, 当项目中使用 redux 时, 我们一般会通过 dispach 一个 action, 改变 store,然后更新 ui,dispatch action 是通过改变 props 去驱动视图的,大家在使用的时候有没有想过为什么 this.props.dispatch({ type: 'xxx' }), 就可以驱动 ui 视图更新呢?

这边简单的讲下,当我们dispatch 一个 action 的时候, 调用的其实是 store.dispatch,这个都没问题,store.dispatch 会去跑一遍所有注册在 createStore 中的 reducer, 找到对应的 type 更新数据,返回一个新的 state。

而我们的组件想拿到 store 的数据必须通过 connect(mapStateToProps, mapDispatchToProps)(App) 像这样,react-redux 中的 Connect 组件会在 componengDidMount 的时候去 调用 一个 trySubscribe 的方法,其内部调用 store.subscribe 去订阅一个 handleChange 的方法。

所以当你 dispatch action 的时候,就会触发 Connect 组件中的方法, Connect 组件中也维护了一个叫做 storeState 的 state,每次拿到新的 sotre 就去调用 setState, 触发 render 函数, render 函数会根据你 connect 中传入的 mapStateToProps, mapDispatchToProps,包括可选参数 mergeProps, 去做一个 props 的合并动作, 最终在 Connect 组件内部 return 出来一个 createElement(WrappedComponent,this.mergedProps) 这样的东西,而 createElement 第二个参数就是你组件的 props, 那么每次 props 变了,就会驱动视图的更新。这就是 Redux 其中的中做原理,当然这边描述的比较简短, 下次单独写一篇关于 react、 redux、 react-redux 的文章。

总结

  1. 通过调用 this.setState 改变 state 驱动视图。
  2. 通过调用 this.forceUpdate 强制更新视图。
  3. 通过操作原生 dom 更新视图。
  4. 通过改变 props 驱动视图(redux 或者 修改父子组件传递 props )。
编辑于 2018-10-07