react.devを読んでいく
読んでいて個人的に気になったところをメモしていきます
Components can render other components, but you must never nest their definitions:
コンポーネントは他のコンポーネントをレンダリングすることができるが、決してその定義をネストしてはならない:
export default function Gallery() {
// 🔴 Never define a component inside another component!
function Profile() {
// ...
}
// ...
}
The snippet above is very slow and causes bugs. Instead, define every component at the top level:
上記のスニペットは非常に遅く、バグの原因になる。代わりに、すべてのコンポーネントをトップレベルで定義する:
export default function Gallery() {
// ...
}
// ✅ Declare components at the top level
function Profile() {
// ...
}
When a child component needs some data from a parent, pass it by props instead of nesting definitions.
When a child component needs some data from a parent, pass it by props instead of nesting definitions.
子コンポーネントが親コンポーネントから何らかのデータを必要とする場合、定義をネストする代わりにpropsで渡す。
たまに見たことあって読みづらいなーと思っていたけど、ちゃんと公式で否定されていた
default exportとnamed exportについて
どっちがいいというわけではなく、チームに合った方向で最適なものを選択してください、とのことが書かれている
To reduce the potential confusion between default and named exports, some teams choose to only stick to one style (default or named), or avoid mixing them in a single file. Do what works best for you!
デフォルトと名前付きエクスポートの間の潜在的な混乱を減らすために、一部のチームは 1 つのスタイル (デフォルトまたは名前付き) のみに固執するか、単一のファイル内でそれらを混合しないことを選択します。自分にとって最も効果的なことをしてください!
Why do multiple JSX tags need to be wrapped?
複数の JSX タグをラップする必要があるのはなぜですか?
JSX looks like HTML, but under the hood it is transformed into plain JavaScript objects. You can’t return two objects from a function without wrapping them into an array. This explains why you also can’t return two JSX tags without wrapping them into another tag or a Fragment.
JSX は HTML のように見えますが、内部ではプレーンな JavaScript オブジェクトに変換されます。関数から 2 つのオブジェクトを返すには、それらを配列にラップする必要があります。これは、2 つの JSX タグを別のタグまたはフラグメントにラップしない限り返すことができない理由を説明しています。
なるほど。オブジェクトを配列に入れて返してるのか。
class → classNameの理由
Since class is a reserved word, in React you write className instead, named after the corresponding DOM property:
class は予約語であるため、React では代わりに、対応する DOM プロパティにちなんで名付けられた className を記述します。
「Reactではそういうもん」という感じで理解していたけど、DOMプロパティにちなんでそうなっていたのね
In practice, returning null from a component isn’t common because it might surprise a developer trying to render it. More often, you would conditionally include or exclude the component in the parent component’s JSX
実際には、コンポーネントから null を返すことは一般的ではありません。コンポーネントをレンダリングしようとしている開発者を驚かせる可能性があるためです。多くの場合、親コンポーネントの JSX にコンポーネントを条件付きで含めたり除外したりします。
Are these two examples fully equivalent?
これら 2 つの例は完全に同等ですか?
If you’re coming from an object-oriented programming background, you might assume that the two examples above are subtly different because one of them may create two different “instances” of <li>. But JSX elements aren’t “instances” because they don’t hold any internal state and aren’t real DOM nodes. They’re lightweight descriptions, like blueprints. So these two examples, in fact, are completely equivalent. Preserving and Resetting State goes into detail about how this works.
オブジェクト指向プログラミングのバックグラウンドがある場合は、上の 2 つの例は、どちらかが <li> の 2 つの異なる「インスタンス」を作成する可能性があるため、微妙に異なると考えるかもしれません。ただし、JSX 要素は内部状態を保持せず、実際の DOM ノードではないため、「インスタンス」ではありません。これらは設計図のような軽量の説明です。したがって、これら 2 つの例は実際には完全に同等です。「状態の保持とリセット」では、これがどのように機能するかについて詳しく説明します。
JSX要素は内部状態を持たない(immutableである)
状態が変更されたら、また新たにJSX要素が作られるため
↑ という理解
0の罠がちゃんと書かれてた
Don’t put numbers on the left side of &&.
&&の左側には数字を入れないでください。
To test the condition, JavaScript converts the left side to a boolean automatically. However, if the left side is 0, then the whole expression gets that value (0), and React will happily render 0 rather than nothing.
For example, a common mistake is to write code like messageCount && <p>New messages</p>. It’s easy to assume that it renders nothing when messageCount is 0, but it really renders the 0 itself!
To fix it, make the left side a boolean: messageCount > 0 && <p>New messages</p>.
条件をテストするために、JavaScript は左側をブール値に自動的に変換します。ただし、左側が 0 の場合、式全体がその値 (0) を取得し、React は何も表示しないのではなく、喜んで 0 を表示します。
たとえば、よくある間違いは、messageCount && <p>新しいメッセージ</p> のようなコードを記述することです。 messageCount が 0 の場合は何もレンダリングされないと思われがちですが、実際には 0 自体がレンダリングされます。
これを修正するには、左側をブール値にします: messageCount > 0 && <p>Newmessages</p>。
配列のインデックスやランダム生成したkeyではなく、なるべくデータに基づいたidなどをkeyにするのがよいとのこと
You might be tempted to use an item’s index in the array as its key. In fact, that’s what React will use if you don’t specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs.
配列内の項目のインデックスをキーとして使用したくなるかもしれません。実際、キーをまったく指定しない場合、React はこれを使用します。ただし、項目が挿入、削除された場合、または配列の順序が変更された場合、項目をレンダリングする順序は時間の経過とともに変化します。インデックスをキーとして使用すると、微妙で混乱を招くバグが発生することがよくあります。
Reactは純粋関数(同じ入力、同じ出力。同じ入力が与えられた場合、同じ結果を返す関数)の概念に基づいて設計されている。
作成するコンポーネントはすべて純粋な関数であるということが前提。
In the above example, double is a pure function. If you pass it 3, it will return 6. Always.
React is designed around this concept. React assumes that every component you write is a pure function. This means that React components you write must always return the same JSX given the same inputs:
StrictModeがなんで2回レンダリングするのか全然わかってなかったけど、純粋ではない関数を検出するためだったのか……
React offers a “Strict Mode” in which it calls each component’s function twice during development. By calling the component functions twice, Strict Mode helps find components that break these rules.
React は、開発中に各コンポーネントの関数を 2 回呼び出す「Strict モード」を提供します。 Strict モードでは、コンポーネント関数を 2 回呼び出すことで、これらのルールに違反するコンポーネントを見つけることができます。
useEffectを使うのは最後の手段
とにかくReactでのコンポーネントは純粋関数で書こう! とのこと
https://react.dev/learn/keeping-components-pure#where-you-can-cause-side-effects
If you’ve exhausted all other options and can’t find the right event handler for your side effect,
you can still attach it to your returned JSX with a useEffect call in your component.
This tells React to execute it later, after rendering, when side effects are allowed. However, this approach should be your last resort.
他のオプションをすべて使い果たし、副作用に適したイベント ハンドラーが見つからない場合でも、コンポーネント内の useEffect 呼び出しを使用して、返された JSX にイベント ハンドラーをアタッチできます。
これは、副作用が許可されるレンダリング後の後で実行するように React に指示します。ただし、この方法は最後の手段としてください。
ReactというよりJavaScriptだけど、この辺曖昧だったのでメモ
Don’t confuse e.stopPropagation() and e.preventDefault(). They are both useful, but are unrelated:
・e.stopPropagation() stops the event handlers attached to the tags above from firing.
・e.preventDefault() prevents the default browser behavior for the few events that have it.
e.stopPropagation() と e.preventDefault() を混同しないでください。どちらも便利ですが、無関係です。
・e.stopPropagation() は、上記のタグにアタッチされたイベント ハンドラーの起動を停止します。
・e.preventDefault() は、それが存在するいくつかのイベントに対するブラウザーのデフォルトの動作を防止します。
レンダーは常に純粋な計算であるべきです。
同じ入力には同じ出力。同じ入力が与えられた場合、コンポーネントは常に同じ JSX を返す必要がある。(トマトサラダを注文した人がオニオンサラダを受け取ってはいけない!)
自分の仕事に専念する。レンダー前に存在したオブジェクトや変数を変更しない。(ある注文が他の誰かの注文を変更してはいけない。)
reactは純粋な関数(計算)であるべきとのこと。
各セクションで繰り返すのでかなり重要な思想だと理解。
コンポーネントのメモリとしての state は、関数が終了したら消えてしまう通常の変数とは異なります。state は実際には React 自体の中で「生存」しています。まるで棚に保管しているかのように、関数の外部で存在し続けます。React がコンポーネントを呼び出すとき、React はその特定のレンダーに対する state のスナップショットを提供します。あなたのコンポーネントは、props やイベントハンドラの新たな一式を揃えた JSX という形で UI のスナップショットを返し、それらはすべてその特定のレンダー時の state の値を使って計算されます!
state = 変数
ではない。
レンダーごとにstateを用いてDOMが構築される。レンダーされる時は「その時のstate」がスナップショットとして用いられる。
という理解。
イベントハンドラのコードが非同期であっても、レンダー内の state 変数の値は決して変わりません。
React は、クリックのような意図的に引き起こされるイベントが複数ある場合、それらのバッチ処理を行いません。
言い換えると、state として格納するすべての JavaScript オブジェクトは読み取り専用として扱う必要があります。
以下、理解メモ。
オブジェクトのstateは技術的には変更可能。でもレンダリングは走らないので直接変更されても何も変わらない。
set関数に入れて再レンダーをトリガーしよう。
下みたいな書き方はちゃんと動く
スプレッド構文は「浅い (shallow)」ことに注意してください。これは 1 レベルの深さでのみコピーを行います。これは高速ですが、ネストされたプロパティを更新したい場合は、スプレッド構文を複数回使用する必要があるということでもあります。
罠になりそうメモ
オブジェクトの振る舞いを考える場合、「ネスト」という考え方は正確ではありません。コードが実行されてしまえば「ネストされた」オブジェクトというものは存在しません
へえ〜〜〜!!
裏側では、Immer は常に、draft に対して行った書き換え操作に基づいて、次の state をゼロから構築します。これにより、state を書き換えてしまう心配をせず、イベントハンドラを非常に簡潔に保つことができます。
stateは直接更新しない。もし必要な場合はImmerを使用する。
props を state に「コピー」することが意味を持つのは、特定の props のすべての更新を意図的に無視したい場合だけです。慣習として、新しい値が来ても無視されるということを明確にしたい場合は、props の名前を initial または default で始めるようにします。
非制御(uncontrolled)コンポーネント
- ローカルステートを持つ
- 親から影響を与えることができない
制御(controlled)コンポーネント
- 重要な情報がstateではなくpropsによって駆動する
- 親コンポーネントからそのコンポーネントの振る舞いを完全に指示できる
Reactはjsxマークアップの位置は関係なく、UIツリー内の位置でstateを保持する
Reducerの由来
リデューサによりコンポーネント内のコード量を「削減 (reduce)」することもできますが、実際にはリデューサは配列で行うことができる reduce() という操作にちなんで名付けられています。
コンテクストを使うことで、どんな場所で、すなわちどんな文脈(コンテクスト)でレンダーされているかに応じて異なる内容を表示できる、「周囲に適応」するコンポーネントを書けるようになります。
コンテクストの仕組みはCSSの継承に似ている。
refのベストプラクティス
・ref を避難ハッチ (escape hatch) として扱う。ref が有用なのは、外部システムやブラウザ API と連携する場合です。アプリケーションのロジックやデータフローの多くが ref に依存しているような場合は、アプローチを見直すことを検討してください。
・レンダー中に ref.current を読み書きしない。レンダー中に情報が必要な場合は、代わりに state を使用してください。React は ref.current が書き換わったタイミングを把握しないため、レンダー中にただそれを読みこむだけでも、コンポーネントの挙動が予測しづらくなってしまいます。(唯一の例外は if (!ref.current) ref.current = new Thing() のような、最初のレンダー中に一度だけ ref をセットするコードです。)
stateはレンダー時のスナップショットとして値を保持するが、refは現在値を書き換えるとすぐに変更される。ref.currentの変更をReactは検知せず、再レンダリングが発生するわけではないため画面上に表示されているテキストなどは変更されない。
React に DOM を同期的に更新、あるいは「フラッシュ (flush)」するよう強制することができます。これを行うには、react-dom から flushSync をインポートし、flushSync の呼び出しで state 更新をラップします。
通常、state更新関数によって再レンダリングが走ってからDOMが更新されるが、flushSyncを用いることで、ラップされたコードが実行された直後に、DOMを同期的に更新するようになる。
refはフォーカスやスクロールのような「非破壊的」なアクションであれば問題は発生しない。
ただ、DOMを手動で書き換えるなどを行うと、React側の動作と競合する恐れがある。
React が管理する DOM ノードの変更は避けてください。React が管理する要素を変更しようとしたり、子要素を追加あるいは削除しようとすると、見た目の一貫性が失われたり、上記のようなクラッシュが発生することがあります。
useEffectで第2引数に"[]"を指定すると、コンポーネントのマウント時(初めて表示される時)のみに処理が実行される。
コンソールで確認すると、useEffet内にあるconsole.logが2回実行されている → なぜ?
開発環境にて、コンポーネントがアンマウントされた時の処理を確認するため
このようなバグは、手動での徹底的なテストがないと見逃しやすいものです。これらをすばやく見つけるために、開発環境では React は、初回マウント直後にすべてのコンポーネントを一度だけ再マウントします。
"✅ Connecting..." のログが 2 回表示されることで、実際の問題に気付くことができます。つまり、コンポーネントがアンマウントされたときに接続を閉じるコードがないということです。
useEffect内でのfetch
開発環境では、ネットワークタブに 2 つのフェッチが表示されます。これには何の問題もありません。上記のアプローチでは、最初のエフェクトがすぐにクリーンアップされるため、ignore 変数のコピーが true に設定されます。そのため、余分なリクエストがあっても、if (!ignore) チェックのおかげで state に影響を与えません。
既存のpropsやstateから計算できるものは、stateに入れるべきではない。
レンダー中に計算を行う。
各userIdによってcommentフォームがリセットされるような実装を行う場合、useEffectによってリセットを行うのではなく、コンポーネントを分割する。
分割したコンポーネントは異なったuserIdがpropsとして渡るたびに再作成されるので、自動的にcommentフォームのstateもクリアされる。
メモ: useSyncExternalStoreについては後でもうちょっと調べる