Reactでソート可能なテーブルを作成する
公開: 2022-03-10テーブルの並べ替えは、正しく行うのが常に非常に難しい問題でした。 追跡する相互作用がたくさんあり、実行するDOMミューテーションが広範囲に渡り、複雑な並べ替えアルゴリズムもあります。 これは、正しく理解するのが難しい課題の1つにすぎません。 右?
外部ライブラリを取り込む代わりに、自分たちで作成してみましょう。 この記事では、Reactで表形式のデータを並べ替える再利用可能な方法を作成します。 各ステップを詳細に説明し、その過程で役立つテクニックを学びます。
基本的なReactまたはJavaScript構文については説明しませんが、Reactの専門家である必要はありません。
Reactでテーブルを作成する
まず、サンプルテーブルコンポーネントを作成しましょう。 製品の配列を受け入れ、製品ごとの行をリストした非常に基本的なテーブルを出力します。
function ProductTable(props) { const { products } = props; return ( <table> <caption>Our products</caption> <thead> <tr> <th>Name</th> <th>Price</th> <th>In Stock</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product.id}> <td>{product.name}</td> <td>{product.price}</td> <td>{product.stock}</td> </tr> ))} </tbody> </table> ); }
ここでは、一連の製品を受け入れ、それらをテーブルにループアウトします。 静的で、現時点では並べ替えることはできませんが、今のところは問題ありません。
データの並べ替え
すべてのホワイトボードインタビュアーを信じるなら、ソフトウェア開発はほとんどすべての並べ替えアルゴリズムだと思うでしょう。 幸いなことに、ここではクイックソートやバブルソートについては検討しません。
組み込みの配列関数sort()
のおかげで、JavaScriptでのデータの並べ替えは非常に簡単です。 余分な引数なしで数値と文字列の配列を並べ替えます。
const array = ['mozzarella', 'gouda', 'cheddar']; array.sort(); console.log(array); // ['cheddar', 'gouda', 'mozzarella']
もう少し賢いものが必要な場合は、ソート関数を渡すことができます。 この関数は、引数としてリスト内の2つの項目が与えられ、決定した内容に基づいて一方を他方の前に配置します。
取得したデータを名前のアルファベット順に並べ替えることから始めましょう。
function ProductTable(props) { const { products } = props; let sortedProducts = [...products]; sortedProducts.sort((a, b) => { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; }); return ( <Table> {/* as before */} </Table> ); }
では、ここで何が起こっているのでしょうか。 まず、製品の小道具のコピーを作成します。これは、必要に応じて変更および変更できます。 Array.prototype.sort
関数は、新しい並べ替えられたコピーを返すのではなく、元の配列を変更するため、これを行う必要があります。
次に、 sortedProducts.sort
を呼び出し、 sorting
関数を渡します。 最初の引数a
のname
プロパティが2番目の引数b
の前にあるかどうかを確認し、ある場合は負の値を返します。 これは、リストでaがb
の前に来る必要がa
ことを示しています。 最初の引数の名前が2番目の引数の名前の後にある場合は、正の数を返します。これはa
b
をaの前に配置する必要があることを示します。 2つが等しい場合(つまり、両方が同じ名前の場合)、順序を維持するために0
を返します。
テーブルを並べ替え可能にする
これで、テーブルが名前で並べ替えられていることを確認できますが、並べ替え順序を自分で変更するにはどうすればよいでしょうか。
並べ替えるフィールドを変更するには、現在並べ替えられているフィールドを覚えておく必要があります。 useState
フックを使用してこれを行います。
フックは、状態の管理や副作用のトリガーなど、Reactのコア機能の一部に「フック」できる特別な種類の関数です。 この特定のフックを使用すると、コンポーネントの内部状態の一部を維持し、必要に応じて変更できます。 これを追加します。
const [sortedField, setSortedField] = React.useState(null);
まず、何もソートしないことから始めます。 次に、テーブルの見出しを変更して、並べ替えるフィールドを変更する方法を含めましょう。
const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); return ( <table> <thead> <tr> <th> <button type="button" onClick={() => setSortedField('name')}> Name </button> </th> <th> <button type="button" onClick={() => setSortedField('price')}> Price </button> </th> <th> <button type="button" onClick={() => setSortedField('stock')}> In Stock </button> </th> </tr> </thead> {/* As before */} </table> ); };
これで、テーブルの見出しをクリックするたびに、並べ替えるフィールドが更新されます。 きちんと!
ただし、実際の並べ替えはまだ行っていないので、修正しましょう。 以前のソートアルゴリズムを覚えていますか? これは、フィールド名のいずれかで機能するように少し変更されています。
const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortedField] < b[sortedField]) { return -1; } if (a[sortedField] > b[sortedField]) { return 1; } return 0; }); } return ( <table>
まず、並べ替えるフィールドを選択したことを確認し、選択した場合は、そのフィールドで商品を並べ替えます。
昇順と降順
次に見たい機能は、昇順と降順を切り替える方法です。 表の見出しをもう一度クリックして、昇順と降順を切り替えます。
これを実装するには、2番目の状態であるソート順を導入する必要があります。 現在のsortedField
状態変数をリファクタリングして、フィールド名とその方向の両方を保持します。 この状態変数には、文字列を含める代わりに、キー(フィールド名)と方向を持つオブジェクトが含まれます。 少しわかりやすくするために、名前をsortConfig
に変更します。
新しい並べ替え機能は次のとおりです。
sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; });
ここで、方向が「昇順」の場合は、前と同じようにします。 そうでない場合は、逆の順序で降順を指定します。
次に、フィールド名を受け入れる新しい関数requestSort
を作成し、それに応じて状態を更新します。
const requestSort = key => { let direction = 'ascending'; if (sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); }
この新しい関数を使用するには、クリックハンドラーも変更する必要があります。
return ( <table> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')}> Name </button> </th> <th> <button type="button" onClick={() => requestSort('price')}> Price </button> </th> <th> <button type="button" onClick={() => requestSort('stock')}> In Stock </button> </th> </tr> </thead> {/* as before */} </table> );
今、私たちはかなり機能が整っているように見え始めていますが、まだやるべきことが1つ残っています。 必要な場合にのみデータを並べ替えることを確認する必要があります。 現在、すべてのレンダリングですべてのデータを並べ替えています。これにより、将来的にはあらゆる種類のパフォーマンスの問題が発生します。 代わりに、組み込みのuseMemo
フックを使用して、すべての遅い部分をメモ化しましょう。
const ProductsTable = (props) => { const { products } = props; const [sortConfig, setSortConfig] = React.useState(null); React.useMemo(() => { let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortedProducts; }, [products, sortConfig]);
これまでに見たことがない場合、 useMemo
は、コストのかかる計算をキャッシュ(またはメモ化)する方法です。 したがって、同じ入力が与えられた場合、何らかの理由でコンポーネントを再レンダリングする場合、製品を2回ソートする必要はありません。 製品が変更されるたびに、または変更によって並べ替えるフィールドまたは方向が変更されるたびに、新しい並べ替えをトリガーする必要があることに注意してください。
この関数でコードをラップすると、テーブルの並べ替えにパフォーマンスに大きな影響があります。
すべてを再利用可能にする
フックの最も優れている点の1つは、ロジックを再利用可能にすることがいかに簡単かということです。 アプリケーション全体ですべてのタイプのテーブルを並べ替える可能性があり、同じものをもう一度再実装する必要があるのは、ドラッグのように聞こえます。
Reactには、カスタムフックと呼ばれるこの機能があります。 それらは派手に聞こえますが、それらはすべて、それらの内部で他のフックを使用する通常の関数です。 コードをリファクタリングしてカスタムフックに含めて、どこでも使用できるようにしましょう。
const useSortableData = (items, config = null) => { const [sortConfig, setSortConfig] = React.useState(config); const sortedItems = React.useMemo(() => { let sortableItems = [...items]; if (sortConfig !== null) { sortableItems.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortableItems; }, [items, sortConfig]); const requestSort = key => { let direction = 'ascending'; if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); } return { items: sortedItems, requestSort }; }
これは、以前のコードからのコピーアンドペーストであり、少し名前が変更されていますuseSortableData
は、アイテムとオプションの初期ソート状態を受け入れます。 ソートされたアイテムを含むオブジェクトと、アイテムを再ソートする関数を返します。
テーブルコードは次のようになります。
const ProductsTable = (props) => { const { products } = props; const { items, requestSort } = useSortableData(products); return ( <table>{/* ... */}</table> ); };
ラストタッチ
欠落している小さな部分が1つあります。それは、テーブルがどのようにソートされているかを示す方法です。 これを設計で示すには、内部状態( sortConfig
)も返す必要があります。 それも返して、テーブルの見出しに適用できるスタイルを生成するために使用しましょう。
const ProductTable = (props) => { const { items, requestSort, sortConfig } = useSortableData(props.products); const getClassNamesFor = (name) => { if (!sortConfig) { return; } return sortConfig.key === name ? sortConfig.direction : undefined; }; return ( <table> <caption>Products</caption> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')} className={getClassNamesFor('name')} > Name </button> </th> {/* … */} </tr> </thead> {/* … */} </table> ); };
これで完了です。
まとめ
結局のところ、独自のテーブル並べ替えアルゴリズムを作成することは、結局のところ不可能なことではありませんでした。 状態をモデル化する方法を見つけ、一般的な並べ替え関数を作成し、並べ替えの設定を更新する方法を作成しました。 すべてがパフォーマンスに優れていることを確認し、すべてをカスタムフックにリファクタリングしました。 最後に、ユーザーに並べ替え順序を示す方法を提供しました。
このCodeSandboxでテーブルのデモを見ることができます: