aono1234
@aono1234

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

オプショナルチェーンが必要な理由

オプショナルチェーンが必要な理由がわからない

複数の要素への参照を親コンポーネントに持たせるために以下のページを参考にさせて頂きました
https://qiita.com/mackie0122/items/4ad6ca24102139dfc56a

こんな感じで実装が出来ました。

a.tsx(親コンポーネント)
import React, {useRef} from "react"
import Test from "./test";

const A = () => {
  const testRefs = useRef([])
  console.log(testRefs.current[0]?.current)

  return(
    <div>
        {["test1","test2", "test3"].map((_, i) => {
          testRefs.current[i]  = React.createRef()
          return(
            <div>
              <Test
                ref={testRefs.current[i]}
              />
            </div>
          )
        })}
    </div>
  )
}

export default A
Test.tsx(子コンポーネント)
import React, { useEffect, forwardRef, useState, useRef } from "react"

const Test = forwardRef((props, testRef) => {
  return (
    <>
      <div>
        <div ref={testRef}>test</div>
      </div>
    </>
  )
})

export default Test

このコードはちゃんと動作して、以下のようにコンソール画面に表示されます。
image.png

しかし、納得できないところがあったので質問させてください。
親コンポーネントのconsole.log(testRefs.current[1]?.current)?を外すと以下のエラーがコンソール画面にでます。
image.png
image.png

これはなぜでしょうか?

testRefs.current[0]nullだからエラーになるという事ですよね。

しかし、実際はtestRefs.current[0]?.currentはちゃんと動作します。
これはどういう事でしょうか?

動作させたらちゃんとtestRefs.current[0]?.currentで参照できる
だから?を外してもちゃんと動作するだろうと思ったらtestRefs.current[0].currentnullだからcurrentはreadできないというエラーが発生する。
わけが分かりません。

ここからは私の勝手な推測ですが
私が気づかないだけでReactは2回動作をしているのでしょうか?
?をなくしてエラーになるのは
最初の1回目の動作でエラーとなっている。

?を付けてちゃんと動作するのは
最初の1回目の動作ではエラーを回避しており、
2回目の動作の時にtestRefs.current[0]に値が入って、testRefs.current[0]?.currentで正しくreadできる。

すみませんが、教えて頂けますと幸いです。
以上、よろしくお願い致します。

0

1Answer

答えは単純で,その時点ではrefが割り当てられていないからです.
refを通して実DOMを参照できる(すなわち,ref属性によってrefが割り当てられる)のはレンダリングが完了した後,つまりそのコンポーネントのreturnが終わった後になります.公式マニュアルにもレンダリングが終わる前にrefを参照するなと書いてあります.
具体的にはrefを正しく参照するのは早くともuseEffect以降でないといけません.つまり,レンダリング時に必要となる情報にはrefを使用してはいけません.

あと以前も指摘しましたが,配列上にrefを保持する際はコールバックを使用してフラットに保管してください.でないと型管理が面倒になるので…

1Like

Comments

  1. @aono1234

    Questioner

    回答頂きありがとうございます。

    レンダリングが完了した後,つまりそのコンポーネントのreturnが終わった後になります.

    の所でまだ理解が出来てないのですが、
    returnが終わった後にrefの中に要素の参照が入るという事だと理解しました。
    ``
    その場合だとconsole.log(testRefs.current[0]?.current)の`testRefs.current[0]`がnullであるためconsole.lof(undefined)となり`undefined`がコンソール画面に表示されて終わりですよね?
    しかし、実際には`

    test
    `が表示されるのはなぜなのでしょうか?
  2. @aono1234

    Questioner

    あと

    あと以前も指摘しましたが,配列上にrefを保持する際はコールバックを使用してフラットに保管してください.でないと型管理が面倒になるので…

    ここに関しては型管理が面倒となるのはなんとなくわかるのですが、この書き方以外でうまく実装できなかったので、testRefs.current[i] = React.createRef()というコードのままにしています。
    具体的にどうやって書けばよいでしょうか…

    この記事に書いているコードだけみると親コンポーネントと子コンポーネントの2つしか存在しないように見えますが、実際は親コンポ―ネント1つ、子コンポ―ネントが0~10個以上ほどあります。

    一方の子コンポーネントを別の子コンポーネントからstyleを変化させたいのでこんな感じに書いてます。

    親コンポーネントに子コンポーネントのrefsを保管させて、それを子供たちに受け渡す。という感じです。

  3. create-react-app等ではStrictModeを自動で適用するものがあります.これはバグ検出等の目的でコンポーネントを2回描画する機能です.

    適当に対照となるログ出力を挟んでおけば,2回の描画に対してrefの出力が1回しかないのが分かります(このrefをそのままレンダリング時に使用してはいけない点についてはもう説明不要だと思います).

    ここに関しては型管理が面倒となるのはなんとなくわかるのですが、この書き方以外でうまく実装できなかったので、testRefs.current[i] = React.createRef()というコードのままにしています。
    具体的にどうやって書けばよいでしょうか…

    前回もリンクを提示しましたが公式に具体例が載っています.

    一方の子コンポーネントを別の子コンポーネントからstyleを変化させたいのでこんな感じに書いてます。

    根本的なところとして,Reactのデザインパターン上,通常このような操作が起こらないようなコンポーネント設計を考えるべきです.(そもそもがバニラAPIに過度に頼るようなコードを書いてると,仮想DOMフレームワークを使ってる意味が分からなくなりますので…)
    統一して管理しなければならない情報がある場合は,親のstateのみにそれらを集約してください.propsリレーを防ぐためにContextなども用意されています.

Your answer might help someone💌