1. 条件付きレンダリングには 0 を使用します
これは多くの初心者が遭遇する問題かもしれません。私も過去にこの間違いを犯しましたが、間違いと言えば落とし穴とも言えます。0 は偽の値ですが、条件付きレンダリングには使用できません。react
次の例を考えてみましょう。
items
おそらく、配列が空のときにコンポーネントが表示される ことを当然のことと考えているかもしれません ShoppingList
。しかし、実際には 0 が表示されます。これは、 の 0 JavaScript
が false 値であり、&&
演算子が短絡し、式全体が 0 として解析されるためです。これは以下と同等です。
function App() {
return (
<div>
{0}
</div>
);
}
他の false 値 ( ''
、など) とは異なり、数値 0 はnull
の有効な値です。結局のところ、多くの場合、数字の 0 を出力したいのです。false
JSX
正しいアプローチは次のとおりです。
function App() {
const [items, setItems] = React.useState([]);
return (
<div>
{items.length > 0 && (
<ShoppingList items={items} />
)}
</div>
);
}
// 或者用三元运算符
function App() {
const [items, setItems] = React.useState([]);
return (
<div>
{items.length
? <ShoppingList items={items} />
: null}
</div>
);
}
2. 突然変異の状態
まずはこの共通ページをご覧ください。
コード:
この関数は、新しい項目が追加されるたびにhandleAddItem
呼び出されます。しかし、それはうまくいきません!商品を入力してフォームを送信しても、その商品はショッピング リストに追加されません。
問題は、私たちがおそらく React
世界の最も核となる原則、つまり不変の状態に違反していることです。React は、状態変数のアドレスに基づいて状態が変化したかどうかを判断します。項目を配列にプッシュするとき、配列のアドレスは変更されないため、 React
値が変更されたことを知る方法はありません。
正しいアプローチは次のとおりです。
function handleAddItem(value) {
const nextItems = [...items, value];
setItems(nextItems);
}
既存のアレイを変更するのではなく、新しいアレイを最初から作成することをお勧めします。ここでの違いは、既存の配列の編集と新しい配列の作成の違いです。
同様に、オブジェクト型のデータの場合:
// ❌ 不建议
function handleChangeEmail(nextEmail) {
user.email = nextEmail;
setUser(user);
}
// ✅ 推荐
function handleChangeEmail(email) {
const nextUser = { ...user, email: nextEmail };
setUser(nextUser);
}
react
突然変異状態が推奨されない理由
-
デバッグ: を使用し
console.log
、状態を変更しない場合、最近の状態の変更によって過去のログが破損することはなく、レンダリング間の状態の変更を明確に確認できます。 -
最適化:一般的な最適化戦略は、前の状態
props
と次の状態が同じ場合にレンダリングをスキップすることです。状態をまったく変更しない場合、変更のチェックは非常にブロック的になる可能性があります。そうであれば、反応はそれが内部的に起こっていないことを確認できます。バラエティstate
react
prevProps === props
-
新機能: React は状態をスナップショットとして扱うことに依存する新機能を構築しています。これにより、過去の状態バージョンを更新すると新機能が壊れてしまいます。
-
要件の変更: 元に戻す/やり直しと履歴の表示を必要とする一部の値は、変更せずに実行する方が簡単です。これは、過去の値をコピーに保持し、必要に応じてやり直すことができるためです。
-
よりシンプルな実装:
react
ミューテーションに依存しないため、オブジェクトに対して何もする必要がなく、オブジェクトをハイジャックする必要もありません。常にそれらをプロキシにラップするか、多くの「リアクティブ」ソリューションと同様に、初期化で他の作業を実行します。これにより、react
追加のパフォーマンスや正確性の落とし穴を発生させることなく、オブジェクト (大小を問わず) を状態に置くことができます。
3. 固有のキー
確かに、コンソールに次のような警告が表示されることがよくあります。
Warning: Each child in a list should have a unique "key" prop.
例えば:
コンソールは次の警告を報告します。
要素の配列をレンダリングするときは常に、各項目 (通常は一意の識別子) を識別できるように、追加のコンテキストを React に提供する必要があります。
あなたはこれを行うことができます:
function ShoppingList({ items }) {
return (
<ul>
{items.map((item, index) => {
return (
<li key={item}>{item}</li>
);
})}
</ul>
);
}
しかし、これは推奨される方法ではありません。このアプローチはうまくいく場合もありますが、場合によってはかなり大きな問題が発生することがあります。React がどのように動作するかをより深く理解すると、ケースバイケースで問題がないことが判断できるようになります。
この問題を完全に安全に解決する方法があります。
function handleAddItem(value) {
const nextItem = {
id: crypto.randomUUID(),
label: value,
};
const nextItems = [...items, nextItem];
setItems(nextItems);
}
crypto.randomUUID
ブラウザに組み込まれたメソッドです (サードパーティのパッケージではありません)。すべての主要なブラウザで利用できます。このメソッドは、次のような一意の文字列を生成しますd9bb3c4c-0459-48b9-a94c-7ca3963f7bd0
。ユーザーがフォームを送信するときに ID を動的に生成することで、ショッピング リスト内の各アイテムに一意の ID が割り当てられるようになります。
したがって、より良いアプローチは次のとおりです。
function ShoppingList({ items }) {
return (
<ul>
{items.map((item, index) => {
return (
<li key={item.id}>
{item.label}
</li>
);
})}
</ul>
);
}
賢くないほうがいいです。便宜上、次のようにしてください。
// ❌ 不建议这么做
<li key={crypto.randomUUID()}>
{item.label}
</li>
このように スポーンすると、レンダリングのたびに変更されJSX
ます key
。key
これらの要素は変更があるたびに React
破棄され、再作成されるため、パフォーマンスに大きな悪影響を及ぼす可能性があります。
このスキーマは、データが最初に作成されたときに生成され key
、さまざまな状況に適用できます。たとえば、サーバーからデータを取得するときに一意の ID を作成する方法は次のとおりです。
async function retrieveData() {
const res = await fetch('/api/data');
const json = await res.json();
const dataWithId = json.data.map(item => {
return {
...item,
id: crypto.randomUUID(),
};
});
setData(dataWithId);
}
4. スペースが欠けている
あなたも次のような状況に遭遇したことがあるはずです。
import React from 'react';
function App() {
return (
<p>
Welcome to the new world!
<a href="/login">Log in to continue</a>
</p>
);
}
export default App;
前にスペースがないこと に注意してください。“login in”
これは、(私たちが作成した JSX をブラウザーに適した JavaScript に変換するツールは) 構文上の空白と、インデントやコードの読みやすさのために追加した空白を実際には区別できないためです。 JSX编译器
正しいアプローチは次のとおりです。
<p>
Welcome to the new world!
{' '}
<a href="/login">Log in to continue</a>
</p>
ヒント: Prettier を使用すると、これらのスペース文字が自動的に追加されます。
5. 状態変更後のアクセス状態
これはおそらく、React の初心者が犯す最も一般的な間違いです。
import React from 'react';
function App() {
const [count, setCount] = React.useState(0);
function handleClick() {
setCount(count + 1);
console.log({ count });
}
return (
<button onClick={handleClick}>
{count}
</button>
);
}
export default App;
どうしても取らなければならない場合はどうすればいいですか?やり方がある:
function handleClick() {
const nextCount = count + 1;
setCount(nextCount);
console.log({ nextCount });
}
6. 複数の要素を返す
場合によっては、コンポーネントが複数のトップレベル要素を返す必要があることがあります。
function LabeledInput({ id, label, ...delegated }) {
return (
<label htmlFor={id}>
{label}
</label>
<input
id={id}
{...delegated}
/>
);
}
export default LabeledInput;
HTML では、htmlFor
属性は<label>
要素に関連付けられたフォーム コントロールの ID です。これは、<label>
フォーム コントロールをマークしたりアクセスしたりするために、要素とフォーム コントロールの間の関係を指定するために使用されます。
htmlFor
属性の役割は、<label>
要素をフォーム コントロールに関連付けることであり、ユーザーが<label>
要素をクリックすると、対応するフォーム コントロールが自動的にトリガーされます。これにより、フォーム コントロール自体をクリックするのではなく、要素をクリックすることで、関連付けられたフォーム コントロールを選択またはフォーカスする便利な方法が提供されます<label>
。
このプロパティを使用するときはhtmlFor
、その値を、関連付けるフォーム コントロールの ID に設定する必要があります。例は次のとおりです。
<label htmlFor="username">用户名:</label>
<input type="text" id="username" name="username" />
上記のコードでは、htmlFor
属性は<label>
要素を ID「username」のテキスト入力に関連付けます。ユーザーが<label>
要素をクリックすると、ブラウザーは自動的にテキスト入力ボックスにフォーカスするため、ユーザー エクスペリエンスが向上します。
使用シナリオには以下が含まれますが、これらに限定されません。
- テキスト ラベルをクリックして、関連する入力ボックスまたはチェックボックスにフォーカスします。
- ラベルをクリックしてラジオ ボタンを選択します。
- アクセシビリティが向上し、スクリーン リーダーが関連するフォーム コントロールを適切にマークアップできるようになります。
要約すると、htmlFor
属性は要素をフォーム コントロールに関連付けるために使用され、ユーザーが要素<label>
をクリックして関連するフォーム コントロールを操作できるようになり、対話性とアクセシビリティが向上します。<label>
コンポーネントが 2 つの要素 ( oneと one )LabeledInput
を返せるようにしたいと考えています 。これは、JSX が次のようにプレーンな JavaScript にコンパイルされるために発生します。 <label>
<input>
function LabeledInput({ id, label, ...delegated }) {
return (
React.createElement('label', { htmlFor: id }, label)
React.createElement('input', { id: id, ...delegated })
);
}
JavaScript では、このように複数のものを返すことはできません。これは、次のようなこの方法が実行できない理由でもあります。
function addTwoNumbers(a, b) {
return (
"the answer is"
a + b
);
}
正しいアプローチは次のとおりです。
function LabeledInput({ id, label, ...delegated }) {
return (
<>
<label htmlFor={id}>
{label}
</label>
<input
id={id}
{...delegated}
/>
</>
);
}
7. 非制御から制御への切り替え
入力を React 状態にバインドする、典型的なフォーム シナリオを見てみましょう。
import React from 'react';
function App() {
const [email, setEmail] = React.useState();
return (
<form>
<label htmlFor="email-input">
Email address
</label>
<input
id="email-input"
type="email"
value={email}
onChange={event => setEmail(event.target.value)}
/>
</form>
);
}
export default App;
入力を開始すると、コンソールに次の警告が表示されます。
この問題をどうやって解決すればいいでしょうか?状態を空の文字列に初期化する必要があります。
const [email, setEmail] = React.useState('');
value
プロパティを 設定するとき 、equals はReact
これを制御コンポーネントにすることを指示します。ただし、これは定義された値を渡した場合にのみ機能します。email
値を 空の文字列に初期化して、 値が決して設定されないようにしますundefined
。
8. インラインスタイルには括弧がありません
JSX
構文は直感的に HTML
は と似ていますが、この 2 つの間にはいくつかの違いがあります。たとえば、 class
の代わりに を 使用した場合ですclassName
。HTML
には、style
文字列として記述されるスタイルもあります 。
<button style="color: red; font-size: 1.25rem">
Hello World
</button>
ただし、 では、それをオブジェクトとして指定し、 (キャメルケース) プロパティ名を使用する必要があります。たとえば、通常は次のようにします。 JSX
camelCased
import React from 'react';
function App() {
return (
<button
style={ color: 'red', fontSize: '1.25rem' }
>
Hello World
</button>
);
}
export default App;
次に、コンソールがエラーを報告していることがわかります。
正しいアプローチは次のとおりです。
<button
// 用 "{
{", 而不是 "{":
style={
{ color: 'red', fontSize: '1.25rem' }}
>
Hello World
</button>
なぜこれをしたいのですか?では JSX
、このタグ内に有効な JS 式を入れることができます。例えば:
<button className={isPrimary ? 'btn primary' : 'btn'}>
何を {}
入力しても考慮され JavaScript
、結果がこのプロパティに設定されます。className
それは であるか 、あるいは であるかの どちらかです。'btn primary'
'btn'
もう少し細分化すると、オブジェクトを変数に取り出すことがより明確になります。
// 1. 创建一个样式属性对象
const btnStyles = { color: 'red', fontSize: '1.25rem' };
// 2. 把样式对象放到标签属性中
<button style={btnStyles}>
Hello World
</button>
// 或者,一步到位
<button style={
{ color: 'red', fontSize: '1.25rem' }}>
9. useEffect の非同期メソッド
で useEffect
リクエスト しAPI
、そこからサーバー側データを取得するとします。通常は、次のようにリクエスト メソッドを非同期として記述する必要があります。
import React, { useEffect } from 'react';
import { API } from './constants';
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null);
useEffect(() => {
const url = `${API}/get-profile?id=${userId}`;
const res = await fetch(url);
const json = await res.json();
setUser(json.user);
}, [userId]);
if (!user) {
return 'Loading…';
}
return (
<section>
<dl>
<dt>Name</dt>
<dd>{user.name}</dd>
<dt>Email</dt>
<dd>{user.email}</dd>
</dl>
</section>
);
}
export default UserProfile;
すると、次のようなコンソールが表示されます。
キーワードが欠けているのではないかと考えているはずです async
。
useEffect(async () => {
const url = `${API}/get-profile?id=${userId}`;
const res = await fetch(url);
const json = await res.json();
setUser(json);
}, [userId]);
残念ながら、これはまだ機能せず、新しいエラー メッセージが表示されます。
destroy is not a function
useEffect
フック関数の特徴の 1 つはクリーンアップ関数、つまり return
関数であることは誰もが知っています。useEffect
フック関数から何かを返す場合 、それはコンポーネントのアンマウント時に実行されるクリーンアップ関数である必要があります。クラス コンポーネントの componentWillUnmount
ライフサイクル メソッドに相当します。ただし、JavaScript では、 async...await
プログラムは非同期タスクの完了を待ってから続行します。また、非同期関数は常に Promise を返します。関数がまだ返していない場合、戻り値は自動的に Promise にラップされます Promise
。
上記の書き方によれば、アロー関数は戻り値を直接指していることになり、プロミス関数を返すことと等価となり、クリーンアップ関数ではなくなります。
正しいアプローチは次のとおりです。
useEffect(() => {
async function runEffect() {
const url = `${API}/get-profile?id=${userId}`;
const res = await fetch(url);
const json = await res.json();
setUser(json);
}
runEffect();
}, [userId]);
10. イベントバインディングを時間内にキャンセルできなかった場合
useEffect()
副作用を管理するために使用する場合は 、必ず自分で手動でクリーンアップすることを忘れないでください。そうしないと、メモリ リークやその他の問題が発生する可能性があります。
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
正しいアプローチは次のとおりです。
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
}
}, []);