yaofly2012 / note

Personal blog
https://github.com/yaofly2012/note/issues
43 stars 5 forks source link

Reactjs-协调(Reconsiliation) #71

Open yaofly2012 opened 4 years ago

yaofly2012 commented 4 years ago

组件的props或者state发生变化时react会自动同步UI页面。

  1. react是如何同步VM的?
  2. 如何进行优化的?

优化方式1: 批处理【待研究】

React may batch multiple setState() calls into a single update for performance.

优化方式2: 协调differing 算法?

具体是如何降低时间复杂度的呢?待研究

  1. 看样子是只比较元素本身的类型,类型不同就替换,相同则更新。
  2. 两个假设都是为了降低时间复杂度了吗?看样子只有假设1是为了降低时间复杂度

https://zhuanlan.zhihu.com/p/51483167 http://echizen.github.io/tech/2019/04-06-react-fiber https://zhuanlan.zhihu.com/p/26027085

yaofly2012 commented 4 years ago

Lists and Keys

引入背景

React组件render方法返回的是个React元素构成的树。当组件的props或者state发生变更时,render方法会返回一个新的React元素构成的树。React采用一个启发式(简化的)diffing算法计算出两颗树的差异。详细的得看看React协调

React用key标记React元素。

关于key的描述

  1. 必要;
  2. 唯一;
  3. 固定。

必要

A “key” is a special string attribute you need to include when creating lists of elements. 列表元素必须要指定key属性。

没有指定key属性会提示:

Warning: Each child in a list should have a unique "key" prop.

唯一

Keys Must Only Be Unique Among Siblings 兄弟(siblings)之间唯一(不必全局唯一)。

不唯一会提示:

Warning: Encountered two children with the same key, xxx. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.

不唯一造成的影响: 重复key的组件可能会重复渲染或者被忽略(测试v@16.8.6版本是被渲染的,只是控制台有个warning)。但是行为不保证,未来可能会变更。 总之行为不可预测(unpredictable),不要存在重复key

固定

key值充当React元素标识符,要能唯一识别数据,并且每次re-render时子元素的key要保持不变,否则影响diffing性能。

使用

语法

key属性的取值可以是任意类型吗? 直接看ts声明:

type Key = string | number;
/**
 * @internal You shouldn't need to use this type since you never see these attributes
 * inside your component or have to validate them.
 */
interface Attributes {
  key?: Key | null | undefined;
}

引用类型会被转成字符串。 优先valueOf,没有再toString

function NumberList(props) {
  const { numbers } = props;
  const listItems = numbers.map(number => 
    <li key={CustomerKey(number)}>{number}</li> // 相当于 CustomerKey(number) + ''
  );

  return (
    <ul>{listItems}</ul>
  )
}

function CustomerKey(number) {
  return {
    toString: () => {
      console.log(`toString`)
      return number;
    },
    valueOf: () => {
      console.log(`valueOf`)
      return number * 2;
    }
  }
}

何如提升性能的?

复用子组件实例,避免重复销毁-创建组件实例。即在re-render过程中如果key不变,则复用之前的组件实例(组件实例状态不变)。 Demo:

// 旧v-dom
<ul>
  <li key="first">first</li>
  <li key="second">second</li>
</ul>

// 新v-dom
<ul>
  <li key="zero">zero</li>
  <li key="first">first</li>
  <li key="second">second</li>
</ul>

React只创建元素<li key="zero">zero</li>插入到列表首部,剩下两个元素复用。 当使用index作为key时,但实际新创建的是index=2的元素,前面两个元素复用(不过prop发生变化)。

不合适的key引发的问题?

经常会看到用数组索引作为key

  1. 该复用的没有复用
    • diff性能变差。
  2. 不该复用的复用了
    • 如果组件是个非受控组件,会导致组件实例状态不变,可能会产生意向不到的效果。
    • 如果是受控组件,并没有影响。但这是埋坑,很难保证以后子组件会不会包含非受控组件

进阶

动态的key

React元素的key变更等同类型变更,React会视为不同的React元素树,会重新创建组件实例。

import { useEffect, useState } from "react";

export default function App() {
  const [key, setKey] = useState();
  return (
    <div>
      <button onClick={() => setKey(Date.now())}>Update Key</button>
      <Foo key={key} />
    </div>
  );
}

function Foo() {
  useEffect(() => {
    console.log("Foo.mounted");

    return () => {
      console.log("Foo.willUnmount");
    };
  }, []);

  return <h2>Foo</h2>;
}

点击【Update Key】按钮都会造成组件Foo重新创建。

索引index作为key行不行?

官方态度

  1. When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort.

  2. We don’t recommend using indexes for keys if the order of items may change.

  3. If you choose not to assign an explicit key to list items then React will default to using indexes as keys. 但是此时React还会给出Warning。

总之:虽然React内部默认采用index作为列表元素的key,但是官方不建议使用索引index作为key

为啥索引index不能作为key

索引index表示的是子元素位置,索引index作为key本质是表示子元素的位置能够识别子元素。特定的条件下索引可能能代表元素,但是如果列表数据发生排序、增加或者删除操作会造成,可能会造成错误的渲染效果,以及对性能造成影响。 总之索引index不是合适的key

  1. 该复用的没有复用;
  2. 不该复用的复用了 非受控组件Demo

非列表元素增加key属性

本质上任何组件都可以声明key属性,只不过非列表的元素key属性不是必须的。不管是否是列表元素key属性的用法是一致的。 如果子元素发生位置变化(类似列表发生排序),也可以利用key属性复用子组件实例。 Demo

import { useEffect, useState } from "react"

export default () => {
  const [swapped, setSwapped] = useState();

  return (
    <div>
      <button onClick={() => setSwapped(swapped => !swapped)}>Swap</button>
      {
        swapped
        ? (
          <>
            <Right key="right"/>
            <Left key="left"/>
          </>
        )
        : (
          <>
            <Left key="left" />
            <Right key="right" />
          </>
        )
      }
    </div>
  )
}

Issues/Concern

  1. Use unique and constant keys when rendering dynamic children, or expect strange things to happen. 动态子元素应该增加key?

  2. 怎样理解key is not really about performance, it's more about identity

参考

  1. Reactjs Doc: Lists and Keys
  2. Reactjs Doc: Reconciliation
  3. Reactjs Doc: Keyed Fragments
  4. Medium: Index as a key is an anti-pattern
  5. 简书:React中key的必要性与使用