思路拓展:如何打造高性能的 React 应用?

朴素思路:善用 shouldComponentUpdate

React 组件会根据 shouldComponentUpdate 的返回值,来决定是否执行该方法之后的生命周期,进而决定是否对组件进行 re-render(重渲染)默认值是true,也就是无条件更新,我们可以通过对比传入的数据是否发生改变,让组件实现有条件的更新

进阶玩法:PureComponent + Immutable.js

PureComponent:提前帮你安排好更新判定逻辑

每一次实现shouldComponentUpdate比较麻烦,React提供了PureComponent帮我们实现对props和state的对比,但是这种对比是浅对比

  • 针对值类型数据对比其值是否相等
  • 针对数组、对象等引用类型的数据则对比其引用是否相等
    • 如果值发生了改变,但是引用没有变化,会导致组件不渲染
    • 如果值没有改变,但是应用地址发生了变化,会导致组件的渲染

      Immutable:“不可变值”让“变化”无处遁形

      Immutable.js:实现持久性数据结构的库
      所谓“持久性数据”,指的是这个数据只要被创建出来了,就不能被更改。我们对当前数据的任何修改动作,都会导致一个新的对象的返回

针对PureComponent对于引用类型数据存在的问题,可以通过使用immutable解决,Immutable每一次改变对象,都会重新生成一个新的对象(生成新的引用地址)
**

函数组件的性能优化:React.memo 和 useMemo

React.memo:“函数版”shouldComponentUpdate/PureComponent

React.memo 会帮我们“记住”函数组件的渲染结果,在组件前后两次 props 对比结果一致的情况下,它会直接复用最近一次渲染的结果,这里的 areEqual 函数是一个可选参数,当我们不传入 areEqual 时,React.memo 也可以工作,此时它的作用就类似于 PureComponent——React.memo 会自动为你的组件执行 props 的浅比较逻辑。

  1. import React from "react";
  2. // 定义一个函数组件
  3. function FunctionDemo(props) {
  4. return xxx
  5. }
  6. // areEqual 函数是 memo 的第二个入参,我们之前放在 shouldComponentUpdate 里面的逻辑就可以转移至此处
  7. function areEqual(prevProps, nextProps) {
  8. /*
  9. return true if passing nextProps to render would return
  10. the same result as passing prevProps to render,
  11. otherwise return false
  12. */
  13. }
  14. // 使用 React.memo 来包装函数组件
  15. export default React.memo(FunctionDemo, areEqual);

useMemo:更加“精细”的 memo

React.memo 控制是否需要重渲染一个组件,而 useMemo 控制的则是是否需要重复执行某一段逻辑。

  1. const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

可以把目标逻辑作为第一个参数传入,把逻辑的依赖项数组作为第二个参数传入。这样只有当依赖项数组中的某个依赖发生变化时,useMemo 才会重新执行第一个入参中的目标逻辑。

跟 React 学设计模式:掌握编程“套路”,打造高质量应用

高阶组件(HOC):最经典的组件逻辑复用方式

什么是高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。——React 官方

  1. const withProps = (WrappedComponent) => {
  2. const targetComponent = (props) => (
  3. <div className="wrapper-container">
  4. <WrappedComponent {...props} />
  5. </div>
  6. );
  7. return targetComponent;
  8. };

接受一个组件作为参数,返回另一个组件的函数

高阶组件是如何实现逻辑复用的?

将公共的业务逻辑抽象到高阶组件中,将业务组件作为高阶组件的函数入参传入,可以避免代码的冗余和重复代码的多次编写 ```javascript // 假设 checkUserAccess 已经在 utils 文件中被封装为了一段独立的逻辑 import checkUserAccess from ‘./utils // 用高阶组件包裹目标组件 const withCheckAccess = (WrappedComponent) => { // 这部分是通用的逻辑:判断用户身份是否合法 const isAccessible = checkUserAccess()
// 将 isAccessible(是否合法) 这个信息传递给目标组件 const targetComponent = (props) => (

); return targetComponent; };

const EnhancedAComponent = withCheckAccess(Acomponent);

  1. <a name="zluan"></a>
  2. ### Render Props:逻辑复用的另一种思路
  3. > 术语> [“render prop”](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce)> 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术。——React 官方> <br />
  4. <a name="ukdPe"></a>
  5. #### 什么是 render props?
  6. 高阶组件的使用姿势是用“函数”包裹“组件”,而 render props 恰恰相反,它强调的是用“组件”包裹“函数”
  7. ```javascript
  8. import React from 'react'
  9. const RenderChildren = (props) => {
  10. return(
  11. <React.Fragment>
  12. {props.children(props)}
  13. </React.Fragment>
  14. );
  15. };
  16. // 使用
  17. <RenderChildren>
  18. {() => <p>我是 RenderChildren 的子组件</p>}
  19. </RenderChildren>

render props 是如何实现逻辑复用的?

  1. // 假设 checkUserAccess 已经在 utils 文件中被封装为了一段独立的逻辑
  2. import checkUserAccess from './utils
  3. // 定义 render props 组件
  4. const CheckAccess = (props) => {
  5. // 这部分是通用的逻辑:判断用户身份是否合法
  6. const isAccessible = checkUserAccess()
  7. // 将 isAccessible(是否合法) 这个信息传递给目标组件
  8. return <React.Fragment>
  9. {props.children({ ...props, isAccessible })}
  10. </React.Fragment>
  11. };
  12. ---------------------------------------------------------------
  13. <CheckAccess>
  14. {
  15. (props) => {
  16. const { isAccessible } = props;
  17. return <ChildComponent {...props} isAccessible={isAccessible} />
  18. }
  19. }
  20. </CheckAccess>

理解 render props 的灵活之处

  • 高阶组件:目标组件对于数据的获取没有主动权,数据的分发逻辑全部收敛在高阶组件的内部
  • render props:除了父组件可以对数据进行分发处理之外,子组件也可以选择性地对数据进行接收

    有状态组件与无状态组件:“单一职责”原则在组件设计模式中的实践