V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
VisualStudioCode
V2EX  ›  React

最近在读 React 官方文档,读到「在渲染树中保留状态」这块了。文档提到,有两个示例程序是不同的,但没讲为什么。果然网上有一波人跟我有一模一样的困惑

  •  
  •   VisualStudioCode · 2023-11-04 16:53:39 +08:00 · 2823 次点击
    这是一个创建于 395 天前的主题,其中的信息可能已经有所发展或是发生改变。
    第 1 条附言  ·  2023-11-05 12:32:02 +08:00

    个人总结

    「同一位置」之定义,不仅取决于组件所处位置之JSX层级

    还取决于组件所处层级中,与其他组件的先后顺序。

    在第一个示例程序中:

      return (
        <div>
          {isPlayerA ? (
            <Counter person="Taylor" />
          ) : (
            <Counter person="Sarah" />
          )}
          <button onClick={() => {
            setIsPlayerA(!isPlayerA);
          }}>
            Next player!
          </button>
        </div>
      );
    

    无论isPlayerA是true还是false,<div>下都只有两个元素,一个组件<Counter/>,一个<button>。

    而对于第二个示例程序:

      return (
        <div>
          {isPlayerA &&
            <Counter person="Taylor" />
          }
          {!isPlayerA &&
            <Counter person="Sarah" />
          }
          <button onClick={() => {
            setIsPlayerA(!isPlayerA);
          }}>
            Next player!
          </button>
        </div>
      );
    

    当isPlayerA为true时:

        <div>
          <Counter person="Taylor" />
          {false}
          <button onClick={() => {
            setIsPlayerA(!isPlayerA);
          }}>
            Next player!
          </button>
        </div>
    

    当isPlayerA为false时:

        <div>
          {false}
          <Counter person="Sarah" />
          <button onClick={() => {
            setIsPlayerA(!isPlayerA);
          }}>
            Next player!
          </button>
        </div>
    

    <Counter/>组件所处顺序不同,React就不会保留它的状态。

    第 2 条附言  ·  2023-11-05 14:09:58 +08:00
    最后两个代码块的意思是等价的 JSX ,即:
    「当 isPlayerA 为 true 时:等价的 JSX 是……」
    「当 isPlayerA 为 false 时:等价的 JSX 是……」
    16 条回复    2023-11-05 12:38:58 +08:00
    huangzhiyia
        1
    huangzhiyia  
       2023-11-04 17:00:49 +08:00   ❤️ 1
    区别是这个地方

    {isPlayerA ? (
    <Counter person="Taylor" />
    ) : (
    <Counter person="Sarah" />
    )}

    这个状态会保留

    {isPlayerA &&
    <Counter person="Taylor" />
    }
    {!isPlayerA &&
    <Counter person="Sarah" />
    }
    这个状态不会保留
    VisualStudioCode
        2
    VisualStudioCode  
    OP
       2023-11-04 17:09:17 +08:00
    @zmaplex
    呃,我当然知道啊。但重点在于为什么啊……
    这个回答讲,好像是由于 entry 这个概念。https://stackoverflow.com/a/75885252/10553078
    thevita
        3
    thevita  
       2023-11-04 17:39:42 +08:00
    这样,你把 render tree 分别打出来看就明白了

    ```
    .....

    const r = (
    <div>
    {isPlayerA ? <Counter person="Taylor" /> : <Counter person="Sarah" />}
    <button
    onClick={() => {
    setIsPlayerA(!isPlayerA);
    }}
    >
    Next player!
    </button>
    </div>
    );
    console.log(r);
    return r;

    ......

    ```


    https://codesandbox.io/s/shy-firefly-3xt4ck




    props:
    children: Array(2)
    0: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
    1: {$$typeof: Symbol(react.element), type: 'button', key: null, ref: null, props: {…}, …}

    https://codesandbox.io/s/quizzical-artem-5j8xqk



    props:
    children: Array(2)
    0: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
    1: false
    3: {$$typeof: Symbol(react.element), type: 'button', key: null, ref: null, props: {…}, …}



    render tree 是不一样的, 后面 diff 的时候 前一种 是没办法识别 Counter 这个 element 的变化的


    ps: 不写 react ,描述按我自己理解来的,但大概应该就是这个样子
    codehz
        4
    codehz  
       2023-11-04 17:45:19 +08:00 via iPhone
    你想哈既然 jsx 本质就是 js 语法糖,那如果直接用?:语法 react 怎么知道它是改了整个组件,还是单纯只改了 props 呢(组件相同,仅 props 不同,没有 key 的情况下如何区分?)
    而&&的写法,因为分属于数组的两个不同位置,react 可以直接根据位置来推断是不是同一个组件
    这与其说是 react 特意设计的特性,不如说是 jsx 语法转换的副作用(虽然 jsx 严格意义上也算是 react 的设计),不过好歹 react 给了一个文档说了
    vivipure
        5
    vivipure  
       2023-11-04 17:48:06 +08:00
    这东西太不符合直觉了,按理来说每个组件都应该是自己的状态的,这个切换还能保留状态。
    hua123s
        6
    hua123s  
       2023-11-04 18:02:08 +08:00 via Android
    就是因为 key 不一样,所以状态丢了。
    noe132
        7
    noe132  
       2023-11-04 18:51:44 +08:00
    第一种写法和
    <Counter person={isPlayerA ? "Taylor": "Sarah"} />
    是等价的。

    React 对于同组件同位置的节点会在重新渲染时会被认为是同一个实例。
    区分不同实例的方式是使用不同的 key 。
    lisongeee
        8
    lisongeee  
       2023-11-04 19:40:28 +08:00   ❤️ 3
    把 jsx 转换为 原生 js 就很直白了

    ![image]( https://github.com/gkd-kit/gkd/assets/38517192/d586fcd2-9539-4b94-87b0-3c6a8886297e)
    ![image]( https://github.com/gkd-kit/gkd/assets/38517192/74700d26-b9fc-4bc2-9b13-995d17db5639)

    你的第二个示例程序,返回的数组中当 isPlayerA 真时是 [Fc, null],假时为 [null, Fc]
    而第一个示例程序,返回的数组中当 isPlayerA 真时是 [Fc],假时为 [Fc],因此切换后状态不会丢失
    RabbitDR
        9
    RabbitDR  
       2023-11-04 20:01:29 +08:00
    8 楼的图片很清晰了,前者通过三元算出来的在同一个位置,后者是真的两个不同的位置,这里的位置指 createElement 的入参。
    VisualStudioCode
        10
    VisualStudioCode  
    OP
       2023-11-04 20:56:25 +08:00
    @vivipure 「这东西太不符合直觉了」

    根据文档里讲的「快照机制」,应该也还好理解。
    https://react.dev/learn/state-as-a-snapshot
    VisualStudioCode
        11
    VisualStudioCode  
    OP
       2023-11-04 23:44:08 +08:00
    @lisongeee 谢谢讲解。

    请问这是怎么转换的?
    lisongeee
        12
    lisongeee  
       2023-11-04 23:56:55 +08:00
    @VisualStudioCode

    > 请问这是怎么转换的?

    https://babeljs.io/repl
    SingeeKing
        13
    SingeeKing  
       2023-11-05 02:59:54 +08:00
    本质上就是 children 是一个数组,每个语句会被转换成 children 的一个成员

    使用 ?: 时是一个语句,也就对应一个成员
    使用 && 时是两个语句了,也就对应两个不同的成员了
    HaroldFinchNYC
        14
    HaroldFinchNYC  
       2023-11-05 09:03:14 +08:00
    VisualStudioCode
        15
    VisualStudioCode  
    OP
       2023-11-05 11:40:01 +08:00
    @vivipure
    好吧,我也只是在学习中,不知道这一段文档是否跟快照机制有关。
    VisualStudioCode
        16
    VisualStudioCode  
    OP
       2023-11-05 12:38:58 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1097 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 19:04 · PVG 03:04 · LAX 11:04 · JFK 14:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.