反応機能コンポーネントでのsetStateの機能構文の使用は何ですか?[複製]
私たちは持っている機能的なコンポーネントについて話している useState
まあ言ってみれば
const [age, setAge] = useState(0)
今、私は更新age
中に前のものを使用する必要があると言いましょうage
Reactのドキュメントには、関数を渡すことができるFUNCTIONAL UPDATESと呼ばれるものが記載されており、その引数は状態の前の値になります。
setState((previousAge) => previousAge + 1)
できるのになぜこれをする必要があるのですか
setState(previousAge + 1)
機能を使用する利点は何ですかsetState
、
クラスベースのコンポーネントには、機能的な方法で状態更新のバッチ処理と呼ばれるものがあったことは知っていますが、機能コンポーネントのドキュメントにはそのようなものはありません。
回答
それらは同じではありません。更新が状態で見つかった以前の値に依存している場合は、関数形式を使用する必要があります。この場合、関数形式を使用しないと、コードが壊れることがあります。
なぜ壊れたのか
React機能コンポーネントは単なるクロージャであり、クロージャにある状態値は古くなっている可能性があります-これは、クロージャ内の値がそのコンポーネントのReact状態にある値と一致しないことを意味します。これは、次の場合:
1-非同期操作(この例では、 [低速追加]をクリックし、[追加]ボタンを複数回クリックすると、後で、[低速追加]ボタンがクリックされたときに状態がクロージャー内の状態にリセットされたことがわかります)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick={() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
2-同じクロージャーで更新関数を複数回呼び出す場合
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
セッターが呼び出される速度/頻度によっては、問題が発生する可能性があります。
クロージャーから値を取得するという単純な方法を使用している場合、2つのレンダリング間の後続の呼び出しは望ましい効果をもたらさない可能性があります。
簡単な例:
function App() {
const [counter, setCounter] = useState(0);
const incWithClosure = () => {
setCounter(counter + 1);
};
const incWithUpdate = () => {
setCounter(oldCounter => oldCounter + 1);
};
return (<>
<button onClick={_ => { incWithClosure(); incWithClosure(); }}>
Increment twice using incWithClosure
</button>
<button onClick={_ => { incWithUpdate(); incWithUpdate(); }}>
Increment twice using incWithUpdate
</button>
<p>{counter}</p>
</>);
}
両方のボタンは、増分メソッドの1つを2回呼び出します。しかし、私たちは観察します:
- 最初のボタンは、カウンターを1だけインクリメントします
- 2番目のボタンは、カウンターを2インクリメントします。これは、おそらく望ましい結果です。
これはいつ起こりますか?
- 明らかに、if
incWithClosure
が互いに直後に複数回呼び出された場合 - 非同期タスクが関係している場合、これは簡単に発生する可能性があります(以下を参照)
- おそらく、Reactにやるべきことがたくさんある場合、そのスケジューリングアルゴリズムは、同じイベントハンドラーを使用して複数の非常に高速なクリックを処理することを決定する可能性があります
非同期作業の例(リソースのロードをシミュレート):
function App() {
const [counter, setCounter] = useState(0);
const incWithClosureDelayed = () => {
setTimeout(() => {
setCounter(counter + 1);
}, 1000);
};
const incWithUpdateDelayed = () => {
setTimeout(() => {
setCounter((oldCounter) => oldCounter + 1);
}, 1000);
};
return (
<>
<button onClick={(_) => incWithClosureDelayed()}>
Increment slowly using incWithClosure
</button>
<button onClick={(_) => incWithUpdateDelayed()}>
Increment slowly using incWithUpdate
</button>
<p>{counter}</p>
</>
);
}
最初のボタンを2回(1秒以内)クリックし、カウンターが1だけインクリメントされることを確認します。2番目のボタンは正しい動作をします。
そうしないと、ある時点での古い値を取得することに気付くからですage
。問題は、あなたが提案したことがうまくいくことです。しかし、そうでない場合もあります。現在のコードでは壊れないかもしれませんが、数週間前に書いた別のコードや、数か月後の現在のコードでは壊れることがあります。
症状は本当に、本当に奇妙です。あなたは使ってJSXコンポーネント内の変数の値を印刷することができます{x}
構文をして、後に使用して同じ変数を印刷しconsole.log
た後(前ではなく)JSXコンポーネントをレンダリングしていることを発見console.log
値が古くなっている-console.log
の後に起こることは、何らかの形で古い値を持つことができますレンダリングレンダリングよりも。
したがって、状態変数の実際の値は、通常のコードでは常に正しく機能するとは限りません。これらは、レンダリングで最新の値を返すように設計されているだけです。このため、状態セッターのコールバックメカニズムが実装され、レンダリングの外部の通常のコードで状態変数の最新の値を取得できるようになりました。