グリッドに表示するオブジェクトの配列があります。目標は、データに複数のフィルターを適用することです。1 つは特定の列のテキストによるもの、もう 1 つは異なる日付範囲によるものです。次に、各列を昇順/降順で並べ替え、ページネーションも行います (このためのヘルパー関数は既に用意されています)。所定の位置に)。私の最初のアプローチは、データをフィルタリング/ソートして状態を更新する複数の useEffects を持つことでした。問題は、状態を設定する関数が状態をすぐに更新しないように見えるため、次の useEffect ごとに、前のデータでフィルタリングされたものではなく、元のデータで作業していることです。基本的に、元の配列で 3 つのフィルター メソッド、並べ替え、ページネーションをこの順序でチェーンする必要がありますが、ユーザーが設定を変更しない限り、フィルタリング/並べ替えの一部を実行したくありません。これにアプローチするための最良の方法についてのアイデアは大歓迎です。これは、動作しない簡単なプロトタイプです。https://codesandbox.io/s/peaceful-nigh-jysdyユーザーはいつでもフィルターを変更でき、3 つすべてが有効になります。また、初期フィルター値を保持するように初期構成を設定する必要があります。
1 に答える
コードには 2 つの主な問題があります。
- レデューサーは、ディスパッチごとに新しいフィルター処理された配列を生成しています。
- useEffect と useState を悪用しており、非常に複雑なデータ フローを構築しています。
両方の問題を説明しましょう。
レデューサーフィルタリング
レデューサーの初期状態にはn個のアイテムがあります。
[
{ id: "124", name: "Michael", dateCreated: "2019-07-07T00:00:00.000Z" },
{ id: "12", name: "Jessica", dateCreated: "2019-08-07T00:00:00.000Z" },
{ id: "53", name: "Olivia", dateCreated: "2019-01-07T00:00:00.000Z" }
]
dispatch
アクション ( など) を実行FILTER_BY_TEXT
すると、新しいフィルター処理された配列が生成されます。
dispatch({ type: "FILTER_BY_TEXT", payload: { text: 'iv' } });
与えます:
[ { id: "53", name: "Olivia", dateCreated: "2019-01-07T00:00:00.000Z" } ]
しかし、新しい値をディスパッチすると (この場合はMichael と一致ae
するように):
dispatch({ type: "FILTER_BY_TEXT", payload: { text: 'ae' } });
空の配列が得られます!
[]
これは、既にフィルター処理されae
たリストにフィルターを適用しているためです!
複雑なデータ フロー
アプリケーションの状態は次のもので構成されます。
- 表示したいデータをフィルタリングして並べ替え、
- ユーザーがフィルターと並べ替えに選択した現在の値。
すべてのフィルター/ソート「XXX」に対して、現在のアプローチでは次のパターンを使用します。
function reducer(state, action) {
switch (action.type) {
case 'FILTER_BY_XXX': return filterByXXX(state, action);
default: return state;
}
}
function filterByXXX(state, action) { … }
function App() {
const [state, dispatch] React.useReducer(reducer, []);
// (1) Double state “slots”
const [xxx, setXXX] = React.useState(INITIAL_XXX);
const [inputXXX, setInputXXX] = React.useState(INITIAL_INPUT_XXX);
// (2) Synchronization effects (to apply filters)
React.useEffect(() => {
dispatch({ type: 'FILTER_BY_XXX', payload: xxx });
}, [xxx]);
return (
<input
value={inputXXX}
onChange={event => {
// (4) Store the raw input value
setInputXXX(event.currentTarget.value);
// (5) Store a computed “parsed” input value
setXXX((new Date(event.currentTarget.value));
}}
/>
);
}
誤解を示しましょう。
(1) の double 状態は必要ありません。コードが複雑になりすぎているだけです。
filterByText
これは、やなどの文字列値については明らかですinputVal
が、「解析された」値については少し説明が必要です。UI 駆動状態を「解析済み」状態から分離することは理にかなっていますが、状態に格納する必要はありません。実際、実際の入力状態からいつでも再計算できます。
React ドキュメントのこの部分をご覧ください:主な概念 › React で考える › UI 状態の最小限の (しかし完全な) 表現を識別する
「正しい」アプローチは、(1) でダブル スロットを削除し、(5) でセッターを削除することです。次に、レンダリング時に値を再計算できます。
const [inputXXX, setInputXXX] = React.useState(INITIAL_INPUT_XXX);
// Here we just recompute a new Date value from the inputXXX state
const xxx = new Date(inputXXX);
return (
<input
value={inputXXX}
onChange={event => {
setInputXXX(event.currentTarget.value);
}}
/>
);
レデューサーは、前に説明したように、完全なデータ セットから開始し、ディスパッチごとに強制的にフィルター処理されます。すべてのディスパッチは、以前に適用されたフィルターの上に別のフィルターを追加するようにアプリケーションに指示します。
これは、状態では前の操作の結果しかないために発生します。これは、長い算術演算を一連の単純なものとして扱う方法に似ています。
11 + 23 + 560 + 999 = 1593
として書き換えることができます
( ( ( 11 + 23 ) + 560 ) + 999 ) = 1593 ( ( ( 34 ) + 560 ) + 999 ) = 1593 ( ( 594 ) + 999 ) = 1593
すべてのステップで、どのようにしてその値に到達したかについての情報が失われていますが、それでも結果が表示されています!
アプリケーションで「テキストによるフィルタリング」が必要な場合、フィルタを「追加」するのではなく、以前のテキスト フィルタを新しいテキスト フィルタで置き換えます。
- 「状態からリデューサーの状態へ」をエフェクトと同期させることで、状態の変化に反応しています。
これは確かに useEffect の非常に正しい使用法ですが、同期する値と同期する値の両方がこの場合のように同じ場所 (または非常に近い場所) で定義されている場合、明らかな理由もなくコードを過度に複雑にしている可能性があります。 .
たとえば、イベント ハンドラーでディスパッチすることができます。
return <input onChange={event => { dispatch( … ); } />;
しかし、本当の問題は次の問題です。
計算結果を「状態」として保存していますが、実は「計算値」です。あなたの実際の状態はデータセット全体であり、フィルタリングされたリストは「データセット全体にフィルターを適用する」成果物です。
レンダリング時に値を計算する恐怖から解放される必要があります。
const [inputVal, setInputVal] = React.useState("");
const [selectTimeVal, setSelectTimeVal] = React.useState("");
const [selectSortVal, setSelectSortVal] = React.useState("");
const data = [ {…}, {…}, {…} ];
let displayData = data;
displayData = filterByText(displayData, inputVal);
displayData = filterByTimeFrame(displayData, selectTimeVal);
displayData = sortByField(displayData, selectSortVal);
return (
<>
<input value={inputVal} onChange={………} />
<select value={selectTimeVal} onChange={………} />
<select value={selectSortVal} onChange={………} />
{displayData.map(data => <p key={data.id}>{data.name}</p>)}
</>
);
参考文献
上記のトピックを理解したら、無駄な再計算を避けたいと思うかもしれませんが、実際にパフォーマンスの問題が発生した場合のみです! ( React docs のパフォーマンスの最適化に関する非常に詳細な手順を参照してください)
その点に到達すると、次の React API とフックが非常に役立つことがわかります。