React로 정렬 가능한 테이블 만들기

게시 됨: 2022-03-10
빠른 요약 ↬ React에서 테이블을 정렬 가능하게 만드는 것은 어려운 작업처럼 들릴 수 있지만 너무 어려울 필요는 없습니다. 이 기사에서는 모든 테이블 정렬 요구 사항을 정렬하는 데 필요한 모든 것을 구현할 것입니다.

테이블 정렬은 항상 올바른 작업을 수행하는 데 꽤 어려운 문제였습니다. 추적해야 할 상호 작용이 많고 수행해야 할 광범위한 DOM 변형과 복잡한 정렬 알고리즘도 있습니다. 그것은 바로잡기 어려운 도전 중 하나일 뿐입니다. 오른쪽?

외부 라이브러리를 가져오는 대신 직접 만들어 봅시다. 이 기사에서는 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']

좀 더 똑똑한 것을 원하면 정렬 기능을 전달할 수 있습니다. 이 함수는 목록에 있는 두 개의 항목을 인수로 지정하고 사용자가 결정한 항목에 따라 하나를 다른 항목 앞에 배치합니다.

얻은 데이터를 이름의 알파벳순으로 정렬하는 것부터 시작하겠습니다.

 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 기능을 전달합니다. 첫 번째 인수 aname 속성이 두 번째 인수 b 앞에 있는지 확인하고, 그렇다면 음수 값을 반환합니다. 이것은 목록에서 b a 와야 함을 나타냅니다. 첫 번째 인수의 이름이 두 번째 인수의 이름 뒤에 있으면 양수를 반환하여 b 를 앞에 배치해야 함을 나타 a . 둘이 같으면(즉, 둘 다 같은 이름을 가짐) 순서를 유지하기 위해 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>

먼저 정렬 기준으로 필드를 선택했는지 확인하고 선택한 경우 해당 필드를 기준으로 제품을 정렬합니다.

오름차순 대 내림차순

다음으로 보고 싶은 기능은 오름차순과 내림차순 사이를 전환하는 방법입니다. 표 제목을 한 번 더 클릭하여 오름차순과 내림차순 사이를 전환합니다.

이를 구현하려면 두 번째 상태인 정렬 순서를 도입해야 합니다. 필드 이름과 방향을 모두 유지하기 위해 현재 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> );

이제 우리는 기능이 꽤 완성된 것처럼 보이기 시작했지만 아직 해야 할 큰 일이 하나 남아 있습니다. 필요할 때만 데이터를 정렬해야 합니다. 현재 우리는 모든 렌더에서 모든 데이터를 정렬하고 있으며, 결과적으로 모든 종류의 성능 문제가 발생할 것입니다. 대신 내장된 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 는 값비싼 계산을 캐시하거나 메모하는 방법입니다. 따라서 동일한 입력이 주어지면 어떤 이유로 구성 요소를 다시 렌더링하는 경우 제품을 두 번 정렬할 필요가 없습니다. 제품이 변경될 때마다 또는 변경에 따라 정렬하는 필드 또는 방향이 변경될 때마다 새 정렬을 트리거하려고 합니다.

이 함수에서 코드를 래핑하면 테이블 정렬에 엄청난 성능 영향을 미칠 것입니다!

모든 것을 재사용 가능하게 만들기

후크의 가장 좋은 점 중 하나는 로직을 재사용 가능하게 만드는 것이 얼마나 쉬운지 입니다. 애플리케이션 전체에 걸쳐 모든 유형의 테이블을 정렬하고 동일한 항목을 다시 구현해야 하는 번거로움이 있을 수 있습니다.

React에는 커스텀 훅(custom hook)이라는 기능이 있습니다. 멋진 것처럼 들리지만 내부에 다른 후크를 사용하는 일반 함수입니다. 모든 곳에서 사용할 수 있도록 사용자 정의 후크에 포함되도록 코드를 리팩토링합시다!

 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> ); };

마지막 터치

테이블이 정렬되는 방식을 나타내는 방법인 작은 조각이 하나 빠졌습니다. 디자인에서 이를 나타내기 위해 내부 상태도 반환해야 합니다 — 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에서 테이블의 데모를 볼 수 있습니다.