年末年始に予定が入ってなくて時間が有り余っているので、積読していたLearn React を一通り目を通して重要な部分を抜粋しました。ところどころChatGPTに手伝ってもらってます。Go言語100Tipsに出てきていた単語(race conditionや参照値は違う値として評価される)がちらほらReactにも出てきてました。
Render and Commit
- Triggering a render (delivering the guest’s order to the kitchen)
- Rendering the component (preparing the order in the kitchen)
- Committing to the DOM (placing the order on the table)
Trigger
it’s done by calling createRoot
with the target DOM node, and then calling its render
method with your component.
Render
“Rendering” is React calling your components.
- On initial render, React will call the root component.
- For subsequent renders, React will call the function component whose state update triggered the render.
Commit
After rendering (calling) your components, React will modify the DOM.
- For the initial render, React will use the
appendChild()
DOM API to put all the DOM nodes it has created on screen. - For re-renders, React will apply the minimal necessary operations (calculated while rendering!) to make the DOM match the latest rendering output.
React only changes the DOM nodes if there’s a difference between renders.
Browser paint
https://react.dev/learn/render-and-commit#step-1-trigger-a-render
State as a Snapshot
React re-renders Logic
When React re-renders a component:
- React calls your function again.
- Your function returns a new JSX snapshot.
- React then updates the screen to match the snapshot your function returned.
Setting state only changes it for the next render
so, this increments just one, even though you wanted to increment 3.
|
|
Here is what this button’s click handler tells React to do:
setNumber(number + 1)
:number
is0
sosetNumber(0 + 1)
.- React prepares to change
number
to1
on the next render.
- React prepares to change
setNumber(number + 1)
:number
is0
sosetNumber(0 + 1)
.- React prepares to change
number
to1
on the next render.
- React prepares to change
setNumber(number + 1)
:number
is0
sosetNumber(0 + 1)
.- React prepares to change
number
to1
on the next render.
- React prepares to change
Instead, you need to pass a function that calculates the next state based on the previous one in the queue, like setNumber(n => n + 1)
. It is a way to tell React to “do something with the state value” instead of just replacing it.
|
|
https://react.dev/learn/state-as-a-snapshot
Optimizing Performance
described about tools help findinding out bottlenecks and faster building(transpiling) way such as Rollup, Browselify, Webpack.
https://legacy.reactjs.org/docs/optimizing-performance.html
Controlled and uncontrolled components
It is common to call a component with some local state “uncontrolled”
In contrast, you might say a component is “controlled” when the important information in it is driven by props rather than its own local state. This lets the parent component fully specify its behavior.
When writing a component, consider which information in it should be controlled (via props), and which information should be uncontrolled (via state)
https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components
Preserving and Resetting State
Remember that it’s the position in the UI tree—not in the JSX markup—that matters to React! This component has two
return
clauses with different<Counter />
JSX tags inside and outside theif
:
|
|
You might expect the state to reset when you tick checkbox, but it doesn’t!
This is because both of these <Counter />
tags are rendered at the same position.
React doesn’t know where you place the conditions in your function.
All it “sees” is the tree you return.
In both cases, the App
component returns a <div>
with <Counter />
as a first child. To React, these two counters have the same “address”: the first child of the first child of the root. This is how React matches them up between the previous and next renders, regardless of how you structure your logic.
https://react.dev/learn/preserving-and-resetting-state
Also, when you render a different component in the same position, it resets the state of its entire subtree.
|
|
Here, n => n + 1
is called an updater function. When you pass it to a state setter:
- React queues this function to be processed after all the other code in the event handler has run.
- During the next render, React goes through the queue and gives you the final updated state.
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
https://react.dev/learn/queueing-a-series-of-state-updates
What happens if you update state after replacing it
What about this event handler? What do you think number
will be in the next render?
|
|
Here’s what this event handler tells React to do:
setNumber(number + 5)
:number
is0
, sosetNumber(0 + 5)
. React adds “replace with5
” to its queue.setNumber(n => n + 1)
:n => n + 1
is an updater function. React adds that function to its queue.
What happens if you replace state after updating it
Here’s how React works through these lines of code while executing this event handler:
setNumber(number + 5)
:number
is0
, sosetNumber(0 + 5)
. React adds “replace with5
” to its queue.setNumber(n => n + 1)
:n => n + 1
is an updater function. React adds that function to its queue.setNumber(42)
: React adds “replace with42
” to its queue.
|
|
Strict Mode
In Strict Mode, React will run each updater function twice (but discard the second result) to help you find mistakes.
Treat state as read-only
you should treat any JavaScript object that you put into state as read-only.
NO
|
|
OK
With setPosition
, you’re telling React:
- Replace
position
with this new object - And render this component again
|
|
https://react.dev/learn/updating-objects-in-state#treat-state-as-read-only
Deep Dive
But code like this is absolutely fine because you’re mutating a fresh object you have just created:
const nextPosition = {};
nextPosition.x = e.clientX;
nextPosition.y = e.clientY;
setPosition(nextPosition);
In fact, it is completely equivalent to writing this:
setPosition({ x: e.clientX, y: e.clientY});
Mutation is only a problem when you change existing objects that are already in state. Mutating an object you’ve just created is okay because no other code references it yet. Changing it isn’t going to accidentally impact something that depends on it. This is called a “local mutation”.
|
|
|
|
Write concise update logic with Immer
If your state is deeply nested, you might want to consider flattening it. But, if you don’t want to change your state structure, you might prefer a shortcut to nested spreads. Immer is a popular library that lets you write using the convenient but mutating syntax and takes care of producing the copies for you. With Immer, the code you write looks like you are “breaking the rules” and mutating an object:
|
|
https://react.dev/learn/updating-objects-in-state#write-concise-update-logic-with-immer
Making other changes to an array
you may want to reverse or sort an array. The JavaScript reverse()
and sort()
methods are mutating the original array, so you can’t use them directly.
However, you can copy the array first, and then make changes to it.
|
|
now that the original array has copied, you can do sort or reverse or any array handling to the nextList. It does not affect the original array.
However, even if you copy an array, you can’t mutate existing items inside of it directly. This is because copying is shallow—the new array will contain the same items as the original one. So if you modify an object inside the copied array, you are mutating the existing state.
|
|
Although nextList
and list
are two different arrays, nextList[0]
and list[0]
point to the same object. So by changing nextList[0].seen
, you are also changing list[0].seen
.
When updating nested state, you need to create copies from the point where you want to update, and all the way up to the top level.
You can use map
to substitute an old item with its updated version without mutation.
❌NO
|
|
✅OK
|
|
https://react.dev/learn/updating-arrays-in-state#making-other-changes-to-an-array
Referencing Values with Refs
When you want a component to “remember” some information, but you don’t want that information to trigger new renders, you can use a ref.
const ref = useRef(0);
useRef
returns an object like this:
{
current: 0 // The value you passed to useRef
}
It’s like a secret pocket of your component that React doesn’t track. (This is what makes it an “escape hatch” from React’s one-way data flow—more on that below!)
Unlike state, ref is a plain JavaScript object with the current
property that you can read and modify.
Note that the component doesn’t re-render with every increment. Like state, refs are retained by React between re-renders. However, setting state re-renders a component. Changing a ref does not!
https://react.dev/learn/referencing-values-with-refs
Differences between refs and state
- State
例: ユーザーが入力したテキスト、タイマーの残り時間、チェックボックスのオン/オフなど
setState(あるいは useState で得た setter)を呼び出すと、React はコンポーネントを再描画する
- Ref
DOM 要素への直接アクセスや、一時的に記憶しておきたいが描画には使わない値を保持したい場合などに利用する
例: フォーム入力要素へのフォーカス管理、再描画不要なタイマーの識別子(ID)の保持など
まとめ
UI に反映される情報(表示すべきテキストや数字・画面表示の ON/OFF に影響する値など)は State へ
UI に直接関係ない情報(描画には影響せず、一時的に保持しておきたい値や DOM へのアクセスが目的の場合など)は Ref へ
https://react.dev/learn/referencing-values-with-refs#differences-between-refs-and-state
How does useRef work inside?
you can think of it as a regular state variable without a setter. If you’re familiar with object-oriented programming, refs might remind you of instance fields—but instead of this.something
you write somethingRef.current
|
|
https://react.dev/learn/referencing-values-with-refs#how-does-use-ref-work-inside
Best practices for refs
make your components more predictable enable you to create less bugs.
- Treat refs as an escape hatch. Refs are useful when you work with external systems or browser APIs
- Don’t read or write
ref.current
during rendering. If some information is needed during rendering, use stateinstead. Since React doesn’t know whenref.current
changes, even reading it while rendering makes your component’s behavior difficult to predict. (The only exception to this is code likeif (!ref.current) ref.current = new Thing()
which only sets the ref once during the first render.)
But when you mutate the current value of a ref, it changes immediately:
|
|
Manipulate DOM with Ref
|
|
https://react.dev/learn/manipulating-the-dom-with-refs
How to manage a list of refs using a ref callback
|
|
This is because Hooks must only be called at the top-level of your component. You can’t call useRef
in a loop, in a condition, or inside a map()
call.
solution is to pass a function to the ref
attribute.
|
|
In this example, itemsRef
doesn’t hold a single DOM node. Instead, it holds a Map from item ID to a DOM node. (Refs can hold any values!) The ref
callback on every list item takes care to update the Map:
Synchronizing with Effects
Every time your component renders, React will update the screen and then run the code inside useEffect
. In other words, useEffect
“delays” a piece of code from running until that render is reflected on the screen.
|
|
By wrapping the DOM update in an Effect, you let React update the screen first. Then your Effect runs.
https://react.dev/learn/synchronizing-with-effects
You Might Not Need an Effect
How to remove unnecessary Effects
|
|
https://react.dev/learn/you-might-not-need-an-effect#how-to-remove-unnecessary-effects
Caching expensive calculations
When something can be calculated from the existing props or state, don’t put it in state. Instead, calculate it during rendering
You can cache (or “memoize”) an expensive calculation by wrapping it in a useMemo
Hook:
|
|
This tells React that you don’t want the inner function to re-run unless either todos
or filter
have changed
https://react.dev/learn/you-might-not-need-an-effect#caching-expensive-calculations
Sharing logic between event handlers
When you’re not sure whether some code should be in an Effect or in an event handler, ask yourself why this code needs to run. Use Effects only for code that should run because the component was displayed to the user.
The below code should be avoided because generally notifications appear because the user pressed the button, not because the page was displayed!
|
|
|
|
https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers
Initializing the application
Sometimes you attempted to load auth information at the first amounting phaze. It is intended to be called once but indeed twice in development mode, so it should be avoided.
|
|
|
|
https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application
Fetching data
|
|
there is no guarantee about which order the responses will arrive in. For example, the "hell"
response may arrive after the "hello"
response. Since it will call setResults()
last, you will be displaying the wrong search results
This is called a “race condition”: two different requests “raced” against each other and came in a different order than you expected
To fix the race condition, you need to add a cleanup function to ignore stale responses:
|
|
https://react.dev/learn/you-might-not-need-an-effect#fetching-data
https://react.dev/learn/you-might-not-need-an-effect
Lifecycle of Reactive Effects
Effects has only two life event in the lifecycle, to start synchronizing something, and later to stop synchronizing it .
|
|
How React re-synchronizes your Effect
Recall that your ChatRoom
component has received a new value for its roomId
prop. It used to be "general"
, and now it is "travel"
. React needs to re-synchronize your Effect to re-connect you to a different room.
To stop synchronizing, React will call the cleanup function that your Effect returned after connecting to the "general"
room. Since roomId
was "general"
, the cleanup function disconnects from the "general"
room:
|
|
UI 視点: 「コンポーネントに何を表示するか」「状態がどう変わったら UI がどう変わるか」を考える
Effect 視点: 「外部のリソースやサブスクライブをいつ開始し、いつ終わらせるか」「結果をどうコンポーネントに反映させるか」を考える
https://react.dev/learn/lifecycle-of-reactive-effects#how-react-re-synchronizes-your-effect
|
|
This is why you should think whether the processes are same or separate, not whether the code looks cleaner.
A mutable value like location.pathname
can’t be a dependency. It’s mutable, so it can change at any time completely outside of the React rendering data flow
Declaring an Effect Event
Use a special Hook called useEffectEvent
to extract this non-reactive logic out of your Effect:
|
|
https://react.dev/learn/lifecycle-of-reactive-effects
Is your Effect doing several unrelated things?
The problem with this code is that you’re synchronizing two different unrelated things:
- You want to synchronize the
cities
state to the network based on thecountry
prop. - You want to synchronize the
areas
state to the network based on thecity
state.
|
|
https://react.dev/learn/removing-effect-dependencies#is-your-effect-doing-several-unrelated-things
Does some reactive value change unintentionally?
コンポーネントが再レンダリングされるたびに新しいオブジェクトが毎回生成されることによって、依存配列に含めているオブジェクトが「前回と別物」とみなされてしまう。
- 再レンダリングのたびにオブジェクトを作り直している
たとえば、
|
|
このようにコンポーネントの中で {} を使って毎回オブジェクトを生成していると、レンダリングが起こるたびに「新しいオブジェクト」が作られます。
- 同じ中身でも“別オブジェクト”扱いになる
JavaScript では、オブジェクトや関数の「実体(リファレンス)」が重要です。
見た目(中身)が同じでも、毎回 new や {} を使って作ったオブジェクトは別のものとして扱われます。
- Effect の依存に含めていると、毎回“変更あり”と判定される
もし useEffect の依存配列 ([options] のような箇所) にこのオブジェクトを入れている場合、React は「前回のオブジェクトと今回のオブジェクトが違う!」と判断します。
すると「依存が更新された」とみなされ、毎回 useEffect の処理が走ってしまい、再接続などの“副作用”が都度発生してしまいます。
Reactは前回のrenderと次のrenderをObject.isで比較している。
|
|
In JavaScript, numbers and strings are compared by their content:
|
|
どう対策する?
- useMemo や useCallback を活用
たとえばオブジェクトを useMemo でメモ化すると、依存関係に変化がない限り、同じオブジェクト参照を使いまわします。
|
|
- オブジェクトを外で定義してしまう
状況によっては、コンポーネントの外側で定義して同じ参照を使う。
ただし、外に出すと可変データの場合は同期が難しくなるので注意が必要。
- 依存配列にオブジェクトそのものを入れない
依存関係が複雑になる場合は、実際に変わっているプロパティだけを依存配列に含めるなどの工夫をする。
https://react.dev/learn/removing-effect-dependencies#does-some-reactive-value-change-unintentionally
Read primitive values from objects
When you receive an object as a prop, do not add it to the dependency array with it causes chat room re-connction when a parent re-rendered. This is because JS see object as different one even its content is same.
|
|
To avoid re-connection, read information from the object outside the Effect
|
|
or wrap object with useMemo to ensure that the object was not changed(same as before)
|
|
or receive as variables since serverUrl and roomId are primitive value(string), they do not be affected by a parent re-rendering.
|
|
https://react.dev/learn/removing-effect-dependencies#read-primitive-values-from-objects
Custom Hooks let you share stateful logic, not state itself
Custom Hooks let you share stateful logic but not state itself. Each call to a Hook is completely independent from every other call to the same Hook.
所感
結構長かったですね。いつEffectを使うのか、stateに込めるべきものは何かなど、改めて勉強できてよかった。objectを依存配列に入れたuseEffectは親のre-renderingの影響をもろに受けてしまうから危険危険。rendering中にrefを参照するのも危険危険。