添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 的渲染主要分两种:首次渲染和重渲染。
首次渲染就是第一次渲染,这是无法避免的就不讨论了,重复渲染是指由于状态改变或 props 改变等原因造成的渲染。

React 默认的渲染特性:当父组件渲染时,会递归渲染下面所有的子组件 (让人诟病的特性)

const App = () => {
  const [count, setCount] = React.useState(0);
  const [name, setName] = React.useState('Tom');
  React.useEffect(() => {
    setInterval(() => {
      setCount(s => s + 1);
    }, 1000);
  }, []);
  return (
      <h3>count: {count}</h3>
      <Child name={name} />
function Child(props: { name: string }) {
  console.log('child render', props.name);
  return <div>name: {props.name}</div>;

React 对这种行为可以说是不负责任,不管你三七二十八,只要父组件渲染,所有它下面的子组件都会渲染,这种方式简单而粗暴。

既然如此,那么这块只能我们这些负责任的开发者去优化了。

浅比较优化

React 把优化问题抛给开发者,它给我们提供了三个用于性能优化的 API。

React.PurComponent

用于 class 写法的组件,它会对传入组件的 props 进行浅比较,如果比较的结果相同,则不会去渲染组件

React.memo

与 React.PurComponent 一样,用于函数组件

在 js 中,数据主要分两种:

  • 基本类型(字符串、数字、undefined...),
  • 引用类型,即对象(JSON、Array、Function、Regex...)
  • 基本类型的数据属于原始值(primitive value),它直接存储在栈内存中,
    引用类型的数据存在于堆内存中,通过存在栈内存中的指针来调用它

  • 原始数据是 immutable 的,引用类型(object)一般是 mutable 的
  • 原始数据比较直接通过值比较,而 object 则通过引用比较
  • var a = 1;
    var b = 1;
    a === b // true
    var a = {};
    var b = {};
    a === b // false 比较的是引用 所以不相等 

    对于对象,不仅有引用比较,也有深比较和浅比较

    const a = {num: 1};
    const b = {num: 1};
    a === b // false 引用不相等
    a.num === b.num // true 对象的一级相等
    const a = { p: {num: 1}, t: 2 }
    const b = { p: {num: 1}, t: 2 }
    a === b // false 引用不相等
    shallowEqual(a, b) // false a.p === b.p 引用不等 浅比较为false
    deepEqual(a, b) // true 深比较相等

    如果对象的一级属性中存在引用比较,则不相等。
    对象的深比较跳过了引用比较,仅仅是比较相同层级下的属性值。

  • 由于对象存在于堆内存中 通过栈内存中的引用来调用,所以如果对象的值不变,对象的引用变了,就会导致 React 组件缓存失败,造成组件无必要的渲染,进而会造成性能问题
  • 如果对象值变,引用不变,React 则不触发渲染,导致界面与数据不一致
  • React.memo 可以让 props 在变化时,该组件才会发生有意义的重新渲染
    我们将子组件用 React.memo 包起来,只要 props 不变,在父组件更新时,子组件也不会重渲染

    const Child = React.memo((props: { name: string }) => {
      console.log('child render', props.name);
      return <div>name: {props.name}</div>;
    });

    看起来问题解决了,React.memo 将前后的 props 进行浅比较,这基本能解决大多数问题。但如果 props 中含有对象数据,在浅比较时比较的是引用,这种方式就行不通了,好在 React.memo 提供了第二个参数,可以自定义比较前后的 props

    浅比较行不通,那么深比较呢

    const Child = React.memo((props) => {
      return <div>{props.name}</div>;
    }, (prev, next) => {
        // 深比较
      return deepEqual(prev,next)
    });

    这样的确可以达到效果,但如果是比较复杂的对象,就会存在较大的性能问题,甚至直接挂掉,因此不建议使用深比较去进行性能优化

    还有一种方式:如果能保证对象的值相等,再保证对象的引用相等,就可以保证子组件在 props 没变的情况下不会渲染。

    React.useRef 的返回值是固定的常量,我们可以使用它来做为对象的引用

    const Child = React.memo(({ obj }) => {
      console.log('child render', obj, obj.name);
      return <div>name: {obj.name}</div>;
    });
    const App = () => {
      const [count, setCount] = React.useState(0);
      React.useEffect(() => {
        setInterval(() => {
          setCount(s => s + 1);
        }, 1000);
      }, []);
      const obj = React.useRef({
        name: 'Jack'
      });
      return (
          <h3>count: {count}</h3>
          <Child obj={obj.current} />
    

    这样还是存在一个严重的问题:如果 name 改变了,但 obj 没有变,导致子组件不会重新渲染,数据与UI界面不一致。 看来 useRef 只能用于常量

    那我们只要保证 name 不变的时候 obj 和上次一样, name 才让子组件更新就可以了。没错,就是 useMemo。

    useMemo 的特性就是保证依项不变时,对应的对象也不会变,只有依赖项变化时,对应的对象才会变

    const App = () => {
      const [count, setCount] = React.useState(0);
      const [name, setName] = React.useState('');
      React.useEffect(() => {
        setInterval(() => {
          setCount(s => s + 1);
        }, 1000);
      }, []);
      // 只有当 name 变化时,obj才会变化
      const obj = React.useMemo(
        () => ({
        }),
        [name]
      return (
          <h3>count: {count}</h3>
          <input
            type="text"
            value={name}
            onChange={e => {
              setName(e.target.value);
          <Child obj={obj} />
    

    immutable

    上面的方式算是一种解决方案,现在我们来看看其他的东西。

    我们在 class 组件中更新状态,只需要一个 setState 就行,不管你传入什么 state,组件都会刷新

    // class 的 setState 传什么都会更新组件
    this.setState({
        name: 'Jack'
    

    但在 hooks 里, 如果前后两次的引用相等,就不会更新组件

    const [state, setState] = useState({})
    // 同一个引用,不会更新
    setState(s => {
        s.name = 'Tom'
        return s
    // 生成新应用,可以更新
    setState(s => {
        const newState = {
            ...s,
            name: 'Tom'
        return newState;
    

    在 hooks 中,如果你想修改状态对象,必须保证前后修改的对象引用不等。这就要求我们不能直接更新老的 state,而是要保持老的 state 不变,去生成一个新的 state,也就是 immutable 方式。
    老的 state 保持不变,也就意味着该 state 应该是 immutable obj

    const state = [
            name: 'Tom',
            age: 20
            name: 'Jack',
            age: 30
    state[0].name = 'Alen'
    // hooks中需要如下写法
    const newState = [
            ...state[0],
            name: 'Alen'
        ...state
    

    immutable 的写法过于繁琐,这不是我们想要的

    其实,综上来说,我们的需求很简单:

  • 需要改变状态
  • 改变状态后和之前的状态引用不相等
  • 第一个冲上脑门的答案就是:先深拷贝,然后做 mutable 修改就可以了

    const state = [
            name: 'Tom',
            age: 20
            name: 'Jack',
            age: 30
    const newState = deepClone(state);
    newState[0].name = 'Alen'

    深拷贝有两个缺点:

  • 拷贝的性能问题
  • 对于循环引用的处理
    虽然市面上有些库支持高性能的深拷贝,但会对参考物对等(reference equality )造成了破坏
  • const state = [
            name: 'Tom',
            age: 20
            name: 'Jack',
            age: 30
    const newState = lodash.cloneDeep(state)
    state === newState // false
    state[0] === newState[0] // false

    可以发现,所有对象的结构都被破坏,在 React 中使用这种方式,即使对象属性没有任何变化,也会导致没有意义的重新渲染,仍会导致比较严重的性能问题

    深拷贝是非常糟糕的

    这么看来,更新状态还要其他的需求。
    我们将 oldState 视为一个属性树,改变其中某节点时,能返回一个新的对象

  • 当前节点及其组件节点的引用在新老 state 中不相等,这样能保证UI组件即时刷新
  • 非当前节点及其祖先节点的引用在新老 state 中保持引用相等,这能保证状态不变时组件不重渲染
  • 遗憾的是,JavaScript 并没有内置这种对 immutable 数据的支持,更不用说对 immutable 数据更新了,但可以使用一些三方库来解决这个问题,比如:immer 和 immutablejs

    import Immutable from 'immutable'
    var state = Immutable.Map({
        a: 1,
        b: 2
    var newState = state.set('a', 3) 

    Immutable 数据使用结构共享的方式,只更新修改了子节点的引用,不会去修改未更改的子节点引用,达到我们想要的需求

  • 默认情况下,React 组件更新会触发其下面所有的子组件递归渲染
  • 通过 React.memo 的浅比较 props,来保证 props 不变的情况下,组件不会刷新
  • 浅比较只对基本类型(primitive)生效,对于对象无效,即使对象中的值不变,也会引发重渲染
  • 通过使用 useRef 和 useMemo 来缓存对象,在对象值没变时不用引发重渲染
  • 不能通过深拷贝的方式修改 state,不仅导致性能问题,还会引起即使 state 内的值没有变化,但引用发生变化,从而造成无意义渲染,引发严重性能问题
  • 这要求我们使用 immutable 的方式更新 state,来保证引用和缓存
  • 可以通过第三方库:immer、immutablejs 来简化 immutable 的 state 更新写法
  •