---
# System prepended metadata

title: React(MRWR)第 16 節：Getting Clever with Data Sorting
tags: [React, Udemy]

---

# React(MRWR)第 16 節：Getting Clever with Data Sorting

> Udemy課程：[Modern React with Redux [2023 Update]](https://www.udemy.com/course/react-redux/)

`20240203Sat.~20240209Fri.`
:::danger
16-260 Sorting in JavaScript
（數字 > return a-b 或 b-a）
16-261 Sorting Strings
（字串 > 用到localeCompare()幫忙排序）
16-263 Object Sort Implementation
（物件 > 先轉換、用typeof判斷，再排序）

16-268 React Fragments
（可加上key prop，且直接回傳children，不會衍生其他DOM）
:::

## 16-259. Adding Sorting to the Table
這章節主要處理資料排序問題。
![2024-02-03 10-01-45 的螢幕擷圖](https://hackmd.io/_uploads/H1ui0fi5p.png)

在做sorting時，要注意我們是會整個row一起移動！
分成上升排序或下降排序：

* Ascending（上升）：
![2024-02-03 10-03-56 的螢幕擷圖](https://hackmd.io/_uploads/r16d1Xjca.png)
* Decending（下降）：
![2024-02-03 10-06-36 的螢幕擷圖](https://hackmd.io/_uploads/ry2p1Qsqp.png)

****

## 16-260. Reminder on Sorting in JavaScript
> 參考資料：
> 1. [JS 將陣列 Array 重新排列的 sort()](https://ithelp.ithome.com.tw/articles/10225733)
> 2. [ECMAS-Array.prototype.sort ( comparefn )](https://tc39.es/ecma262/#sec-array.prototype.sort)
> 3. [Day 28 咩色用得好 - Array.prototype.sort (part - 1)](https://ithelp.ithome.com.tw/articles/10308185)
> 4. [Day 29 咩色用得好 - Array.prototype.sort (part - 2)](https://ithelp.ithome.com.tw/articles/10308587)

在JS中做sorting，第一個想到的就是array method中的`sort()`。基於上學期剛修完演算法，好奇查了下`sort()`背後的演算法是什麼，找到了一篇在[stackoverflow](https://stackoverflow.com/questions/57763205/what-is-array-prototype-sort-time-complexity)的解釋。

![2024-02-03 11-13-42 的螢幕擷圖](https://hackmd.io/_uploads/r1Ztk4jcp.png)

他提到在不同瀏覽器所實現的演算法不同，例如：
* Firefox中是使用"merge sort"
* Chrome中則是使用一種混合merge sort+insertion sort的演算法，叫做"[Timsort](https://v8.dev/blog/array-sort#timsort)"（此算法時間複雜度遠小於排序演算法最佳複雜度O(nlogn)）

回到`sort()`本身，其實`sort()`並不是單純去做sorting而已，他會先把我們放進去的array每一個元素轉成string形式，利用各個string的Unicode 編碼做排序的，所以就會碰到例如10排在3, 4, 5之前的怪異現象。

![2024-02-03 11-58-16 的螢幕擷圖](https://hackmd.io/_uploads/Sk9y5Nj96.png)

所以說當我們想在JS中使用`sort()`的話，就必須去新增一個"comparator（比較器）"函式（一般都用箭頭函式，這邊的comparator只是給它的一個代稱）。

這裡用簡單的實際案例看看。

首先，假如我們直接對data陣列做`sort()`，會得到[10, 3, 4, 5]的排序，因為在JS的`sort()`，他會先將這四個數字轉成字串，再用Unicode 編碼做排序，但是這不符合我們的期望。
```javascript!
const data = [5, 10, 4, 3]
data.sort(); //[10, 3, 4, 5]
```

於是，我們加上一個匿名函數，也就是先前提到的comparator函式，
```javascript!
const data = [5, 10, 4, 3]
data.sort((a, b) => {
    return a - b;
}); //[3, 4, 5, 10]
```

可以用chrome的dev tool跑跑看：
* 第一種方法：
![2024-02-03 12-18-29 的螢幕擷圖](https://hackmd.io/_uploads/SJnaR4sqT.png)
* 第二種方法：
![2024-02-03 12-18-38 的螢幕擷圖](https://hackmd.io/_uploads/Sy3aAVo9p.png)

但...這是為什麼呢？為什麼加上所謂的comparator函式，就可以成功排序了呢？我們先把a, b給印出來，看看這兩個參數到底代表著什麼：

![2024-02-03 12-25-44 的螢幕擷圖](https://hackmd.io/_uploads/B1qPxHiqp.png)

先不急著研究，每次看到array method，就會想到阿傑的文章！發現在sort的部份，阿傑用了兩篇來解釋sort：
1. [Day 28 咩色用得好 - Array.prototype.sort (part - 1)](https://ithelp.ithome.com.tw/articles/10308185)
2. [Day 29 咩色用得好 - Array.prototype.sort (part - 2)](https://ithelp.ithome.com.tw/articles/10308587)

原來`sort()`可以帶入一個compareFn參數（這個參數代入與否為optional）。因此我們在直接使用`sort();`而沒有給予任何參數時，`sort()`會用預設的排序方法做排序，也就是根據Unicode 編碼做排序。

而compareFn參數，顧名思義就是一個函式（也就是先前提到的comparator function），這函式可以接收兩個參數a, b，分別代表要用來比較的元素，這個compareFn參數會根據回傳值來決定a, b的排列順序：

（此表格式用來定義排序方式，例如說要遞增？遞減？或是若不傳入compareFn參數，則利用unicode編碼由小到大排序。至於排序的實作方式如同前面提到的，在不同瀏覽器下，會有不同的實作演算法，不太理解這裡的意思的話，可以看看阿傑[Day29](https://ithelp.ithome.com.tw/articles/10308587)最後的程式碼說明，會清楚很多！）

| compareFn(a, b)回傳值 | a, b的排序            |
| --------------------- | --------------------- |
| > 0                   | a排在b後面 e.g. [b,a] |
| < 0                   | a排在b前面 e.g. [a,b] |
| === 0                 | 照a, b原先的順序排序  |

這個時候再回來看下圖，就能更輕易理解為什麼要return a-b了，因為return a-b若回傳值大於0，那麼a就要排在後面，因此這裡就可以形成一個遞增的排序。

![2024-02-03 12-25-44 的螢幕擷圖](https://hackmd.io/_uploads/B1qPxHiqp.png)

另外，可以從阿傑的[Day28](https://ithelp.ithome.com.tw/articles/10308185)文章最後看到，關於comparator function的注意事項，他必須符合以下幾點

（其中的reflexive反身性，symmetric對稱性, trensitive遞移性，剛好這陣子在念離散一直被這些概念瘋狂轟炸，念到懷疑人生，不過...看到原來這些知識也可以運用在這裡好感動）

![2024-02-03 12-53-34 的螢幕擷圖](https://hackmd.io/_uploads/ryqbwSo5T.png)

****

## 16-261. Sorting Strings

> 參考資料：[String.prototype.localeCompare()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare)

前面說過，若不傳入參數到sort() method，則JavaScript會對陣列中的元素，先轉成String的形式，再依照Unicode編碼排序，故若我們現在要對字串做排序，使用sort()就會相當的方便好用。

但是，如果同時出現大小寫，可能就不會照我們預期的排序了，這裡示範簡單的例子：
```javascript!
const data = ["t", "A", "a", "B", "b"]
data.sort(); 
```
結果如下所示，他會先把大寫排出，再排小寫，但我們希望的是所有的"a"都排在最前面，接著是"b"，無論大寫還是小寫：
![2024-02-05 11-03-39 的螢幕擷圖](https://hackmd.io/_uploads/rkxEgRpca.png)

這裡會使用到一個JS內建的函式`localeCompare()`，這個函式其實就是所謂的comparator function，他會回傳正數、負數或是0，回傳值如下：
![2024-02-05 11-19-19 的螢幕擷圖](https://hackmd.io/_uploads/rJyAQR65T.png)


可參考下方範例：
```javascript!
// "a" 在 "c" 之前，所以會回傳負數
"a".localeCompare("c"); // -2、-1 或是其他負數值
// 按字母順序，「check」的順序在「against」之後，所以回傳正數
"check".localeCompare("against"); // 2、1 或其他正數值
// "a" 和 "a" 相同，所以回傳 0
"a".localeCompare("a"); // 0
```

因此當他得到回傳值之後，我們就能依照先前的表格對照，的確回傳負數代表著a排在b前面，所以基本上我們不需要再做其他的動作即可做好排序。
| compareFn(a, b)回傳值 | a, b的排序            |
| --------------------- | --------------------- |
| > 0                   | a排在b後面 e.g. [b,a] |
| < 0                   | a排在b前面 e.g. [a,b] |
| === 0                 | 照a, b原先的順序排序  |

此時，我們把剛才的範例，加上`localeCompare()`，看看結果如何：
```javascript!
const data = ["t", "A", "a", "B", "b"]
data.sort((a, b) => {
    return a.localeCompare(b);
}); 
```
結果如下所示：
![2024-02-05 11-26-39 的螢幕擷圖](https://hackmd.io/_uploads/HJvKrAaq6.png)

****

## 16-262. Sorting Objects
![2024-02-05 11-31-04 的螢幕擷圖](https://hackmd.io/_uploads/ry5YLCpqp.png)

在物件中做排序，有非常多方法，以上圖為例，我們可以依照weight排序、可以依照cost排序，也可以照name的第一個字母排序，當然我們甚至可以拿 cost/weight 算出每一單位重量要花多少錢來排序，又或者依照name的單字長度做排序（雖然沒啥意義）。

因此，這裡要做的就是從物件抽取我們要的資料即可，轉變成單純數字或單純字串，如此一來我們就可以拿著這些數字或字串，依照前面幾節學到的方法來排序。

****

## 16-263. Object Sort Implementation
![2024-02-05 11-37-36 的螢幕擷圖](https://hackmd.io/_uploads/BkUr_Aa5a.png)

實作程式碼：
```javascript!
const data = [
    {weight: 15, cost: 5, name: "Onion"},
    {weight: 10, cost: 2, name: "Tomato"},
    {weight: 4, cost: 8, name: "Carrot"}
];

function getSortValue(vegitable){
    return vegitable.cost;
};

data.sort((a, b) => {
    const valueA = getSortValue(a);
    const valueB = getSortValue(b);

    return valueA - valueB;
})
```
實作結果：
![2024-02-05 11-43-46 的螢幕擷圖](https://hackmd.io/_uploads/B1rKKAa9p.png)

所以若要針對物件中的某個值去做排序，最主要的就是getSortValue() 函式，因為他可以幫我們把物件中的屬性值轉換出來，讓我們可以去做比較。

現在的問題是，我們要排序的東西是"string"還是"number"？

因為從16-260與16-261大概可以看出我們針對"string"或是"number"的作法，是不同作法的。

因此，這裡將使用到`typeof`，來幫助我們判斷是"string"還是"number"，之後再進一步做處理。

首先，先來看看[typeof](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/typeof)如何使用以及他的回傳值，基本上這裡只會用到"string"或是"number"，其他更詳細的內容，可以到MDN上查詢。

![2024-02-05 11-54-21 的螢幕擷圖](https://hackmd.io/_uploads/r1GWnAaca.png)

實作程式碼：
```javascript!
const data = [
    {weight: 15, cost: 5, name: "Onion"},
    {weight: 10, cost: 2, name: "Tomato"},
    {weight: 4, cost: 8, name: "Carrot"}
];

function getSortValue(vegitable){
    return vegitable.cost;
};

data.sort((a, b) => {
    const valueA = getSortValue(a);
    const valueB = getSortValue(b);

    if(typeof valueA === "string"){
        return valueA.localeCompare(valueB);
    }else{
        return valueA - valueB;
    }
})
```
這裡改用name做測試，測試結果如下：
![2024-02-05 11-56-01 的螢幕擷圖](https://hackmd.io/_uploads/HJI_nAp56.png)

所以說，如果想要改變sorting的目標，我們只需要更改getSortValue()即可。

例如說，我想要依照cost/weight算出每一單位重量要花多少錢來排序，那麼只要更改getSortValue()成如下所示，即可做排序：
```javascript!
function getSortValue(vegitable){
    return vegitable.cost / vegitable.weight;
};
```

****

## 16-264. Reversing Sort Order
在[16-260](https://hackmd.io/vGXi5O3nQhaFk6S6XHUJvw?both#16-260-Reminder-on-Sorting-in-JavaScript)提過，遞增或是遞減排序，可以依照傳入的compareFn 參數來決定。

前幾節都是遞增排序，所以若我們想要遞減排序，其實只要在return值上乘上一個負號就可以了。

但問題是，若每次要統一成遞增或是遞減，都要對兩邊return值乘上1，或是乘上-1，動作非常瑣碎，且還找到return值的位置。

因此這裡我們一樣在外面建立一個變數`sortOrder`，讓它專門幫助我們處理要乘1（遞增）或是乘-1（遞減）。

實作程式碼：
```javascript!
const data = [
    {weight: 15, cost: 5, name: "Onion"},
    {weight: 10, cost: 2, name: "Tomato"},
    {weight: 4, cost: 8, name: "Carrot"}
];

function getSortValue(vegitable){
    return vegitable.cost;
};

const sortOrder = "ascending"
data.sort((a, b) => {
    const valueA = getSortValue(a);
    const valueB = getSortValue(b);
    const reverseOrder = sortOrder === "ascending" ? 1 : -1;

    if(typeof valueA === "string"){
        return valueA.localeCompare(valueB) * reverseOrder;
    }else{
        return (valueA - valueB) * reverseOrder;
    }
})
```
遞增結果：
![image](https://hackmd.io/_uploads/Hyw2U1Rc6.png)

遞減結果：
![image](https://hackmd.io/_uploads/BkrTI1C9T.png)

****

## 16-267. Customizing Header Cells
接下來的這幾節，基本上就是講如何將前面所學的排序應用到React中，重點在於要讓整個table reusable！！！

這裡會用到config變數幫助我們處理，主要在於"header"，若config中出現"header"，則會用"header"的函式去覆蓋原先的樣式或DOM元素等等。

![2024-02-05 12-57-18 的螢幕擷圖](https://hackmd.io/_uploads/BJGgxlAca.png)
![2024-02-05 13-00-30 的螢幕擷圖](https://hackmd.io/_uploads/Skflxe0qT.png)

****

## 16-268. React Fragments
* 問題：
![image](https://hackmd.io/_uploads/SyigreA56.png)

* 問題解決：
    利用React內建的方法解決，利用「Fragment元件」，幫我們達成想要加上key prop，卻又不希望產生多餘的DOM元素。

    「Fragment元件」的原理老師上課有稍微提到，我們建立一個Echo元件，而此元件只會return children，至於children是什麼？children prop即是Echo元件裡面所包覆的東西，以此處為例便是`{column.header()}`
    ![image](https://hackmd.io/_uploads/SJiwdxA9a.png)

    不過，React其實有內建一個叫做"Fragment"的元件，可以幫助我們做到像是上方Echo元件在做的事情，使用方法如下圖所示，有了"Fragment元件"，即便需要添加key prop，也不必再額外渲染不必要的DOM元素了。
    ![image](https://hackmd.io/_uploads/Byqh_gCcT.png)

****

## 16-269. The Big Reveal
這裡稍做複習：
![image](https://hackmd.io/_uploads/SJZWolRq6.png)
![image](https://hackmd.io/_uploads/BJni2xA96.png)

現在，我們要將先前提到的`getSortValue()`在需要時，可以加進config陣列中的任一物件裡使用。

不過，我們稍微改個名字，改成叫做`sortValue()`。

![2024-02-05 14-18-26 的螢幕擷圖](https://hackmd.io/_uploads/rJ2f0eCqa.png)

接著，我們還要再做一個元件叫做"SortableTable"，在"SortableTable"將會寫入大量的邏輯，專注在處理排序我們的資料，以及顯示我們的header（就是每一欄位的名稱，例如name、color、score）是否要顯示可以排序的按鈕，而當顯示可排序按鈕時，使用者可以點擊，並且在點擊之後進行資料的排序。

![image](https://hackmd.io/_uploads/ByS4M-Acp.png)

****

## 16-270. Adding SortableTable
（這裡只條列出一些碰到可能會出錯的問題）

* 在React中，基本上不會去修改傳進來的props，所以像我們要針對config中擁有sortValue屬性的物件，對傳進來的config prop新增header屬性時，不是直接去新增，而是用mapping function，這樣才不會動到原先的config prop。
* Event Handlers with Arrow Functions
    ![image](https://hackmd.io/_uploads/H1JpQYliT.png)

****

## 16-272. Quick State Design
* **Bad Idea：**
    ![2024-02-07 12-18-03 的螢幕擷圖](https://hackmd.io/_uploads/Bkh9EtliT.png)

* **Good Idea：**
    ![image](https://hackmd.io/_uploads/ByDTuYxip.png)

    流程圖：
    ![image](https://hackmd.io/_uploads/HyJRKYxsp.png)

* **程式碼實作：**
    // SortableTable
    ```javascript!
    import { useState } from "react";
    import Table from "./Table";

    function SortableTable(props) {
        const [sortOrder, setSortOrder] = useState(null);
        const [sortBy, setSortBy] = useState(null);

        const { config } = props;

        const handleClick = (label) => {
            if (sortOrder === null) {
                setSortOrder("asc");
                setSortBy(label);
            } else if (sortOrder === "asc") {
                setSortOrder("desc");
                setSortBy(label);
            } else if (sortOrder) {
                setSortOrder(null);
                setSortBy(null);
            }
        }

        const updatedConfig = config.map((column) => {
            if (!column.sortValue) return column;
            return {
                ...column,
                header: () =>
                    <th onClick={() => handleClick(column.label)}>
                        {column.label} IS SORTED
                    </th>
            }
        })

        return (
            <div>
                {sortOrder} - {sortBy}
                <Table {...props} config={updatedConfig} />
            </div>
        )
    }

    export default SortableTable;
    ```
    
****

## 16-275. Determining Icon Set
![2024-02-07 14-38-50 的螢幕擷圖](https://hackmd.io/_uploads/rydsSolsa.png)

