三項演算子と論理演算子ではReactのstateの扱いが異なる？？？

## はじめに
[Reactのドキュメント](https://ja.react.dev/)は入門書としても、一通り触った人がReactに対する理解を深めるためにも秀でている良いサイトです。そのため、社内の活動として有志で読み合わせ会を毎週開催しています。

この記事は「stateの保持とリセット」のページを読んだ会で発生した疑問が興味深かったのでそれについて追求した記事になります。

https://ja.react.dev/learn/preserving-and-resetting-state

## 疑問
発生した疑問は「出力するDOMが同じであるにも関わらず、`Counter`を三項演算子によって分岐させたときは`state`を保持し、論理演算子で表現したときは`state`を保持しないのはなぜですか？」というものでした。
### 三項演算子の例
```tsx
// isFancyを切り替えてもCounterの値が保持される
<div>
  {isFancy ? (
    <Counter isFancy={true} /> 
  ) : (
    <Counter isFancy={false} />
  )}
</div>
```
https://codesandbox.io/embed/lun-li-yan-suan-zi-tlv699?fontsize=14&hidenavigation=1&theme=dark&view=preview
### 論理演算子の例
```tsx
// isFancyを切り替えるとCounterの値がリセットされる
<div>
  {isFancy && (
    <Counter isFancy={true} /> 
  )}
  {!isFancy && (
    <Counter isFancy={false} />
  )}
</div>
```
https://codesandbox.io/embed/lun-li-yan-suan-zi-59xcf7?fontsize=14&hidenavigation=1&theme=dark&view=preview
### ドキュメントを振り返る
ここでドキュメントの内容をおさらいしてみます。
まずは、ReactのコンポーネントをブラウザのDOMに伝える方法についてです。
> React もまた、ユーザが作成した UI を管理しモデリングするためにツリー構造を使用します。React は JSX から UI ツリーを作成します。次に React DOM が、その UI ツリーに合わせてブラウザの DOM 要素を更新します。（React Native の場合は UI ツリーをモバイルプラットフォーム固有の要素に変換します。）

次に`state`が更新される方法についてです。
> React は、UI ツリーの中でコンポーネントが当該位置にレンダーされ続けている間は、そのコンポーネントの state を維持します。

ここで得た知識だけではUIツリーがどのようなものでどのように構築されるか分からないので、JSXとそれを元に構築されるDOMツリーを元に`state`の維持について考察するのが自然だと思います。
それを前提に考えるとこの疑問は真っ当で、その場では「DOMツリーではなくUIツリーを元に`state`の保持が決まります。UIツリーは`false`や`null`のようなDOMツリーに現れない部分も情報として持つので前者は`state`が保持されて、後者は保持されません。」のように説明しました。しかし、UIツリーがどのようなものについては説明していないので煮え切らない回答になってしまいました。

そこでこの記事ではUIツリーとは何か、そしてどのように構築されるかを確認してこの曖昧さを払拭します。

## UIツリーについて
UIツリーはReactがReact DOMで扱いやすいような形にJSXを変形して渡す時の構造です(React DOMの部分はReact Nativeにも置き換えられます)。
UIツリーが何を指すかを確認するためにReact DOMがReactコンポーネントを扱うメソッドの1つとして持つ`createRoot`に注目します。よく利用される書き方は以下のようになっています。
```tsx
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
);
```
`render`の箇所がJSXを渡す部分です。直接JSXを渡しているのでReactがJSXをUIツリーに変換する処理は見当たりません。
そこでJSXについて考えてみます。JSXはJavaScriptの拡張構文であって、実行時は[SWC](https://swc.rs/)や[ESBuild](https://esbuild.github.io/)などを用いてブラウザで扱えるようなJavaScriptの形式にトランスパイルするのでした。
[ESBuild](https://esbuild.github.io/content-types/#jsx)のドキュメントを元にコードを変換すると
```jsx
import React from 'react';
let button = <button>Click me</button>
render(button)
```
上記のコードは`React.createElement`を利用した下記のようなコードになります。
```js
import React from 'react';
let button = React.createElement('button', null, "Click me");
render(button);
```
つまり、JSXはトランスパイルによって隠されているが、Reactの`createElement`メソッドを利用してReact DOMで扱いやすいようなものへと変換しているということです。
`ESBuild`の説明がわかりやすかったので参考のために利用しましたが、React17以降では`createElement`を利用しない方法が主流になっています。
[当時のドキュメント](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)によればこのケースは以下のようにトランスパイルされます。
```js
import {jsx as _jsx} from 'react/jsx-runtime';
import React from 'react';
let button = _jsx('button', { children: 'Click me' });
render(button);
```
`react`の`createElement`メソッドを利用せずに、`react/jsx-runtime`の`_jsx`を利用するようになりました。これによるメリットは先ほどのドキュメントに書かれています。
せっかくなので新しい変換形式で追っていきます。
`_jsx`はどのように変換しているのでしょうか。`react/jsx-runtime`は[このコード](https://github.com/facebook/react/blob/9e3b772b8cabbd8cadc7522ebe3dde3279e79d9e/packages/react/jsx-runtime.js)を起点に始まっており、[ReactJSXElement.js](https://github.com/facebook/react/blob/9e3b772b8cabbd8cadc7522ebe3dde3279e79d9e/packages/react/src/jsx/ReactJSXElement.js)にほとんどの処理が書かれています。
大雑把にみてみるとJSXは以下のようなオブジェクトに変換されるようです。
```js
{
  $$typeof: REACT_ELEMENT_TYPE,
  type: type,
  key: key,
  ref: ref,
  props: props,
  _owner: owner,
};
```
`_jsx`の第一引数が`type`に、第二引数が`ref`と`props`に、第三引数が`key`になります。
`props`の`children`がJSXの場合はさらに`_jsx`が続くので以下のようにネストされたツリーが形成されます。

```js
{
  $$typeof: REACT_ELEMENT_TYPE,
  type: type1,
  key: key1,
  ref: ref1,
  props: {
    children: [
      {
        $$typeof: REACT_ELEMENT_TYPE,
        type: type2,
        key: key2,
        ref: ref2,
        props: props2,
        _owner: owner,
      },
    ],
  },
  _owner: owner,
};
```
これがドキュメントにあるUIツリーと考えられます(ドキュメントで明言されていませんのでおそらくです)。
先ほどの例は以下のように変換されます。
```js
{
  $$typeof: Symbol(react.element),
  type: 'button',
  key: null,
  ref: null,
  props: {
    children: 'Click me'
  },
  _owner: null,
};
```

:::note
実はReactのコードを追わなくてもブラウザに表示する時点ではトランスパイルが完了しているのでお使いの環境で`console.log(<Button>Click me</Button>)`のようにして`console`に出力するとUIツリーを確認できます。
:::

### 該当のJSXをUIツリーに変換する
UIツリーが何かわかったところで議題にあったJSXを変換して確認します。
まずは三項演算子によって分岐させるJSXです。
```tsx
<div>
  {isFancy ? (
    <Counter isFancy={true} /> 
  ) : (
    <Counter isFancy={false} />
  )}
</div>
```
トランスパイルすると以下のようになります。
```js
const Counter = ({ isFancy }) => (_jsx("button", { className: isFancy ? 'fancy' : undefined, children: "0" }));
_jsx("div", { children: isFancy ? (_jsx(Counter, { isFancy: true })) : (_jsx(Counter, { isFancy: false })) });
```
`isFancy`が`true`の時、UIツリーは以下のようになります(いくつかの情報は削っています)。

```js
{
  $$typeof: Symbol.for("react.element"),
  type: 'div',
  props: {
    children: [
      {
        $$typeof: Symbol.for("react.element"),
        type: ({ isFancy })=> {…},
        props: { isFancy: true }
      }
    ]
  }
}
```
`({ isFancy })=> {…}`はカウンターコンポーネントの部分です。
それに対して論理演算子を活用したJSXについてみていきます。
```tsx
<div>
  {isFancy && (
    <Counter isFancy={true} /> 
  )}
  {!isFancy && (
    <Counter isFancy={false} />
  )}
</div>
```
トランスパイルすると以下のようになります。
```js
const Counter = ({ isFancy }) => (_jsx("button", { className: isFancy ? 'fancy' : undefined, children: "0" }));
_jsxs("div", { children: [isFancy && (_jsx(Counter, { isFancy: true })), !isFancy && (_jsx(Counter, { isFancy: false }))] })
```
`_jsxs`というものが出てきていますが`jsx`同じようなものと考えてください([参考](https://github.com/facebook/react/blob/9e3b772b8cabbd8cadc7522ebe3dde3279e79d9e/packages/react/src/jsx/ReactJSX.js#L16-L19)、実際に本番では同じように動きます)。
`isFancy`が`true`の場合は以下のように
```js
{
  $$typeof: Symbol.for("react.element"),
  type: 'div',
  props: {
    children: [
      {
        $$typeof: Symbol.for("react.element"),
        type: ({ isFancy })=> {…},
        props: { isFancy: true }
      },
      false
    ]
  }
}
```
`false`の場合は下記のようになります。
```js
{
  $$typeof: Symbol.for("react.element"),
  type: 'div',
  props: {
    children: [
      false,
      {
        $$typeof: Symbol.for("react.element"),
        type: ({ isFancy })=> {…},
        props: { isFancy: false }
      }
    ]
  }
}
```
こうして見ると確かに後者の書き方では`Counter`に値するUIツリーの位置が`isFancy`の切り替えによって`false`と入れ替わっています。
ここまで追うとコンポーネントがUIツリー上で異なる箇所に構築されたと解釈され`state`が維持されないことにも納得です。

## おわりに
Reactのドキュメントを読んだ中で出てきた疑問を元にUIツリーについて実装を元に解決しました。
Reactでは背景や思想を元に考えると自然な挙動でもそれを知らないと不自然な挙動に見えることがあるのでより背景と思想の理解を深めてReactの眼鏡をかけて開発を進められるようになっていければと思います。