reactまとめ
再レンダリングに関与する値はすべてリアクティブな値といえる
props, component内で宣言された変数, state
ロジックにもリアクティブなものとそうでないものがある
例えば、send(message)はリアクティブ出ないといえる。なぜなら、messageはユーザーの入力によって変わるリアクティブな値(state)であるが、状態の変化をトリガーに送信を実行したいわけではないからである。そして、このようにリアクティブでないロジックに関してはイベントハンドラで行うべきである。リアクティブなロジックに関しては、useEffectで同期させる。
リアクティブなロジックというと、chatroomに接続するようなロジックなどである。なぜならroomIdなどが変われば、それに応じて接続するroomが変わりこれはリアクティブに実行されるべきロジックだからである。
つまり、ロジックが受け取る引数がリアクティブであっても、そのロジックが実行されるタイミングがリアクティブ出ない場合はそのロジックはイベントハンドラで行うべきであって、リアクティブである場合はエフェクトで処理を実装する。
不要なエフェクトのパターン
具体的な例に入る前に。エフェクトでstateを更新すると、再計算が余分に起きるので非効率かつ可読性も下がるので基本的には避けられるのなら避ける。
あと、僕も含めあるあるなのが、stateやpropsから何かしらの値を計算する場合useeffectが依存配列の変化でトリガーされるので、effectを使いがちだけど、そもそもstateが変わればそのコンポーネントとその子の再レンダリング走るんだから普通に計算しろ
- propsまたはstateに基づいてstateを更新する例
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 🔴 Avoid: redundant state and unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Good: calculated during rendering
const fullName = firstName + ' ' + lastName;
// ...
}
stateが変われば再計算が走るので、コンポーネントのトップレベルで普通に計算すればよい。
- 重たい計算のキャッシュ
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// 🔴 Avoid: redundant state and unnecessary Effect
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
// ...
}
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// ✅ This is fine if getFilteredTodos() is not slow.
const visibleTodos = getFilteredTodos(todos, filter);
// ...
}
これも普通に状態にしなくて変数で計算すればいい。何度も言わせるなよ?stateが変わったら再計算走るのだろうが。
まあ、以下のようにnewTodoの変更のたびに実行されるのが、getFilterdTodosの処理に1sぐらいかかるとかで嫌なんだったら、useMemo
import { useMemo, useState } from 'react';
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// ✅ Does not re-run getFilteredTodos() unless todos or filter change
const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// ...
}
state
- stateの評価値を定数として定義することで、複数のstate管理を避けることができる
messageを送るコンポーネントについて考える
export default function Form() {
const [text, setText] = useState('');
const [isSending, setIsSending] = useState('');
const [isSent, setIsSent] = useState('');
async function handleSumbmit(e) {
// 省略
}
if (isSent) {
return <h1> thanks for feedback!</h1>
}
return (
<form onSubmit={handleSubmit}>
<p>How was your stay at The Prancing Pony?</p>
<textarea
disabled={isSending}
value={text}
onChange={e => setText(e.target.value)}
/>
<br />
<button
disabled={isSending}
type="submit"
>
Send
</button>
{isSending && <p>Sending...</p>}
</form>
);
}
以上のようにすると
isSending, isSentがともにtrueであるというありえない場合の余地を残してします
statusというstateに置き換えて評価値を定数に格納して管理する
exprot default function FeedbackForm() {
const [text, setText] = useState('');
const [status, setStatus] = useState('typing');
const isSending = status === 'sending';
const isSent = status === 'sent';
if (isSent) {
return <h1>Thanks for feedback!</h1>
}
return (
<form onSubmit={handleSubmit}>
<p>How was your stay at The Prancing Pony?</p>
<textarea
disabled={isSending}
value={text}
onChange={e => setText(e.target.value)}
/>
<br />
<button
disabled={isSending}
type="submit"
>
Send
</button>
{isSending && <p>Sending...</p>}
</form>
);
}
}
- 冗長なstateを避ける
before
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
after
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;