---
# System prepended metadata

title: 'React(MRWR)第 4 節： State: How to Change Your App'
tags: [React, Udemy]

---

#React(MRWR)第 4 節： State: How to Change Your App

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

`20230725Tue.~20230802Wed.`
:::danger
重要章節：

4-47 useState()
4-51 spread operator
4-52 map()
:::


## 4-41. Initial App Setup
先來看看第4節將要製作的專案內容。
![](https://hackmd.io/_uploads/S1q9KmT92.png)

可以從上圖知道，這次專案中會有parent component（App.js）以及child component（AnimalShow.js）

**專案功能：**
一開始，畫面上只會看見一個按鈕「Add Animal」，當點擊一次，便會出現一種動物with愛心，而愛心經點擊後會變大。

1. 一開始，沒有任何動物出現
![](https://hackmd.io/_uploads/rkuiqQach.png)

2. 點擊2次後，出現不同的動物
但希望使用相同的component（AnimalShow），經由不同的props來達到跑出不同的動物的目的。
![](https://hackmd.io/_uploads/BySzsX693.png)

**專案Set Up：**
1. 同之前的專案，先把`src`資料夾中的所有資料給刪除。
2. 建立三個JS檔案在`src`中：`index.js`、`App.js`、`AnimalShow.js`
3. **index.js**
```javascript=
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const el = document.getElementById("root");
const root = ReactDOM.createRoot(el);

root.render(<App />);
```

****

## 4-42. Introducing the Event System

接下來要完成的目標，是每點擊按鈕「Add Animal」，數字就會增加1。
![](https://hackmd.io/_uploads/rkumA7Tq2.png)

在這裡先提到React有兩種系統：
1. **Event System**
    > Detect a user clicking the button
2. **Status System**
    > Update content on screen

****

## 4-43. Events in Detail
![](https://hackmd.io/_uploads/Bk1EUplj2.png)

這章節主要提到event system。

**1. 決定想要怎麼樣的事件**
React中支援的event可以參考[官方文件](https://zh-hant.legacy.reactjs.org/docs/events.html)（課程提供的是舊版的，新版的在[這裡](https://react.dev/reference/react-dom/components/common#common-props)）

其中舉兩個較常見的例子：
![](https://hackmd.io/_uploads/BkwykCesn.png)

**2. 建立function**
建立的function，一般我們會稱其為"event handler"或"callback function"。

e.g.
```javascript=
const handleClick = () =>{
    console.log("Button was Clicked!!!")
}
```

**3. function的命名**
這沒有強制規定，但大多數會遵循一個模板，也就是寫成「handle+event的名稱」。

如第二步驟中的例子"handleClick"。

**4. 傳遞function**
將function透過props system傳入明確的element（如`<div>`、`<button>`...）

e.g.底下的button傳入prop名為`onClick={handleClick}`
```javascript=
function App(){
    const handleClick = () =>{
        console.log("Button was Clicked!!!")
    }

    return(
        <div>
            <button onclick={handleClick}>Add Animas!</button>
        </div>
    )
}

export default App;
```

**5. 確保使用有效的event name**
其中的onClick，即[這裡](https://react.dev/reference/react-dom/components/common#common-props)React中支援的event name。

**6. 確保傳入的是function的參考**
event name後方傳入的是function 的名稱，而非呼叫函式（如`handleClick()`）
```javascript=
onClick={handleClick}
```


****

## 4-46. Introducing the State System
透過event system，我們可以捕捉到user做了什麼事情，進而去觸發function。而想要更改螢幕上的內容，就得看看state system的部份了。

在此專案中，我們的想法是，當我們點擊按鈕，數字就會增加1，如下圖所示：
![](https://hackmd.io/_uploads/BJbiR0gs2.png)

了解我們希望呈現的方式之後，開始思考如何達成目的，如下圖所示：
![](https://hackmd.io/_uploads/ryOzk1-i3.png)

****

## 4-47. More on State
> 完整資料：
> [官方文件State: A Component's Memory](https://react.dev/learn/state-a-components-memory)

接續上一章節，這裡繼續談論State。
![](https://hackmd.io/_uploads/HJOF7kWs2.png)

基本上可以分為4個步驟：
![](https://hackmd.io/_uploads/SkvAlebjn.png)

**1. 先import useState到檔案中**
```javascript=
import { useState } from 'react';
```

**2. 利用useState function 定義state與setter function**
    
其中，index是state的變數、setIndex是setter function。而`[ ]`中括號的語意代表著陣列解構（array destructuring）
```javascript=
const [index, setIndex] = useState(0);
```
![](https://hackmd.io/_uploads/Sy0Brgbs3.png)


:::info
The useState Hook provides those two things:

1. A state variable to retain the data between renders.
2. A state setter function to update the variable and trigger React to render the component again.
:::

:::warning
**陣列解構（array destructuring）**
可參考：
1. [Danny文章](https://ithelp.ithome.com.tw/articles/10292493)
2. [Destructuring assignment](https://javascript.info/destructuring-assignment)<---官網提供的
:::

**3. 將state（此例的state為index）運用在component中**

**4. 當使用者觸發某事，則去更新state（此例的state為index）**

****

## 4-49. Why Array Destructuring?
先說結論，利用Array Destructuring是為了簡化code。

若不使用Array Destructuring，useState可能會長底下的樣子（useState的物件為假設）
```javascript
function useState(defaultValue){
    return{
        yourState: defaultValue,
        yourSetter: () => {}
    }
}

function App(){
    const stateConfig = useState(0);
    const count = stateConfig.yourState;
    const setCount = stateConfig.yourSetter;
    
    const handleClick = () => {
        setCount(count + 1);
    }
    
    return(
        <div>
            <button onClick={handleClick}>Add Animas!</button>
            <div>Number of Animals: {count}</div>
        </div>
    )
}
```

可以看見中間useState若沒使用array destructuring，則需要三行來完成。（如下所示）
```javascript
const stateConfig = useState(0);
const count = stateConfig.yourState;
const setCount = stateConfig.yourSetter;
```

不過若我們使用array destructuring，則只需要短短一行即可解決：
```javascript
const [count, setCount] = useState(0);
```

這就是為什麼使用陣列解構的原因，為了簡潔、便利。

****

## 4-50. Back to the App
這章節老師大略提到該怎麼實作，簡單來說就是要寫一個陣列，然後當使用者點擊按鈕之後，就會更新動物名稱進到陣列中。（如下圖所示）

一開始尚未點擊按鈕的狀態：
![](https://hackmd.io/_uploads/HJBqut7sn.png)

點擊兩次按鈕的狀態：
![](https://hackmd.io/_uploads/BJyh_Y7i2.png)

****

## 4-51. Picking a Random Element
這章節要來寫一個可以隨機取得一種動物的function。

首先，要設立一個變數animals作為陣列，裡面當作一個pool，放置所有想讓使用者取得的動物名稱。
```javascript=
function getRandomAnimal(){
    const animals = ['bird', 'cat', 'cow', 'dog', 'gator', 'horse']
}
```

然後我會希望這個function可以return陣列中的任意動物，因此我們會想讓return值為`animal[隨機數字]`。其中，
1. Math.floor() 函式會回傳小於等於所給數字的最大整數。
2. Math.random() 會回傳一個偽隨機小數 (pseudo-random) 介於 0 到 1 之間(包含 0，不包含 1)

**App.js**
```javascript!
function getRandomAnimal(){
    const animals = ['bird', 'cat', 'cow', 'dog', 'gator', 'horse']
    
    return animals[Math.floor(Math.random() * animals.length)];
}
```

接著就要運用到React中的useState了，要記得若想更新state，一定使用setter去更新。

前面已實做出一個`getRandomAnimal`的function。我們希望的作法是，每點擊一次按鈕，就會觸發`handleClick` function，而`handleClick` function便會隨機新增一種動物進我們的aniamls陣列（aniamls陣列為一個state）當中，這個動作將透過useState的setter功能來實作，這裡我們命名做`setAnimals`。

**App.js**
```javascript!
function getRandomAnimal(){
    const animals = ['bird', 'cat', 'cow', 'dog', 'gator', 'horse']
    
    return animals[Math.floor(Math.random() * animals.length)];
}

function App(){
    const [animals, setAnimals] = useState([]);

    const handleClick = () =>{
        setAnimals([...animals, getRandomAnimal()]);
    }

    return(
        <div>
            <button onClick={handleClick}>Add Animas!</button>
            <div>{animals}</div>
        </div>
    )
}
```

**圖文解說**
**App.js**
![](https://hackmd.io/_uploads/ryDbp9Xsh.jpg)

其中，可以注意到他使用到了「展開運算子（Spread Operator）」，可參考[這篇文章](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/rest_spread.html)。裡面有提到「展開運算符可以作陣列的淺拷貝」，至於淺拷貝可以參考[這篇文章](https://medium.com/andy-blog/%E9%97%9C%E6%96%BCjs%E4%B8%AD%E7%9A%84%E6%B7%BA%E6%8B%B7%E8%B2%9D-shallow-copy-%E4%BB%A5%E5%8F%8A%E6%B7%B1%E6%8B%B7%E8%B2%9D-deep-copy-5f5bbe96c122)。

:::info
再次複習useState（存狀態）。

當使用useState，代表你告訴React，你想要這個component去記住一些事情。

```javascript!
const [animals, setAnimals] = useState([]);
```

以上方為例，這代表你希望React記住`animals`，並且useState()的唯一參數，即`animals`的初始值，此處範例便可知`animals`的初始值為一個空陣列。

`setAnimals`便是setter，用於更新state `animals`的。
:::

****

## 4-52. List Building in React
再來我們要創建list，list在很多應用程式中都非常常見。例如Gmail顯示mails、Youtube顯示影片清單連結、FaceBook顯示廣告或post等等的清單、snapchat顯示你收到的訊息的清單。list這個想法存在幾乎每一種應用程式裡面。

**實作想法**
在前面章節中，我們已經利用陣列實作出一個動物的清單。接著，我們希望可以將該陣列中的動物們，轉換進component之中。實作想法如下圖：
![](https://hackmd.io/_uploads/Sy7T2jQjn.jpg)

而上圖中提到的magic transform step，事實上可以運用JS的語法達成，也就是"map function"。

> 關於map的運用，可以直接參考阿傑的文章[Day 11 咩色用得好 - Array.prototype.map](https://ithelp.ithome.com.tw/articles/10299099)。
> 
> 發現除了有map()之外，JS中還有個Map constructor，Map constructor與object相似，皆擁有key value pair。

圖中的magic transform step，若寫成程式則如下所示：
```javascript!
const renderedAnimals = animals.map((animal, index)=>{
        return <AnimalShow type={animal} key={index} />;
    });
```
圖片說明：
![](https://hackmd.io/_uploads/BkIyiIIsn.jpg)

具體化說明：
![](https://hackmd.io/_uploads/BkUko8Iih.jpg)

所有的程式碼則如下所示：
**App.js**
```javascript!
import {useState} from "react";
import AnimalShow from "./AnimalShow";

function getRandomAnimal(){
    const animals = ['bird', 'cat', 'cow', 'dog', 'gator', 'horse']
    
    return animals[Math.floor(Math.random() * animals.length)];
}
console.log(getRandomAnimal());

function App(){
    const [animals, setAnimals] = useState([]);

    const handleClick = () =>{
        setAnimals([...animals, getRandomAnimal()]);
    };

    const renderedAnimals = animals.map((animal, index)=>{
        return <AnimalShow type={animal} key={index} />;
    });

    return(
        <div>
            <button onClick={handleClick}>Add Animas!</button>
            <div>{renderedAnimals}</div>
        </div>
    );
}

export default App;
```

****

## 4-54. Loading and Showing SVGs
接著，這個章節要加入圖片。

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">step1. 將圖片import進檔案</h3>
</div>

首先，將圖片import進檔案，同時將圖片給各自命名：
**AnimalShow.js**
```javascript!
import bird from './svg/bird.svg';
import cat from './svg/cat.svg';
import cow from './svg/cow.svg';
import dog from './svg/dog.svg';
import gator from './svg/gator.svg';
import horse from './svg/horse.svg';
```

至於bird, cat, cow...這些變數代表著什麼，可以印出來看看，也可以參考[3-34筆記](https://hackmd.io/yIqdj6sHS7OGPSN38Z_Miw?view#3-34-Including-Images)。不過可以知道的是這些變數都被賦予了「字串（string）」

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">step2. 建立物件放圖片</h3>
</div>

將這些圖片放進一個物件中：
**AnimalShow.js**
```javascript!
const svgMap = {
    bird,
    cat,
    cow,
    dog,
    gator,
    horse
};
```
事實上，如果展開這個物件來看，他會長這樣：
![](https://hackmd.io/_uploads/Hkp4Arwo2.png)

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">step3. 建立component放圖片</h3>
</div>

再來，要建立一個component，將動物圖片顯示到螢幕上。
此處的`type`是props物件中的屬性之一，資料會透過parent component（App.js）傳給child component（AnimalShow.js）。
```javascript!
function AnimalShow({type}){
    return(
    <div>
        <img alt = "animal" src = {svgMap[type]} />
    </div>
    )
}
```

這裡可以注意到，一般物件取值有兩種方式，透過`.`(dot)以及`[ ]`(brackets)，但是我們這裡物件取值必須只能使用`[ ]`(brackets)，因為我們是利用變數'type'取物件的值。

可以參考[[JavaScript] 何謂物件取值？在什麼時機上會用到？點(.)和方括號([])取值的不同之處](https://hackmd.io/@peter77730/BkOHgKBUK?utm_source=preview-mode&utm_medium=rec)這篇文章。裡面提到，大致上遇到以下三種狀況，僅能使用`[ ]`(brackets)取值：
1. 以「變數」取值
2. 屬性開頭是「數字」
3. 新增特殊字元

這裡遇到的狀況便是上述第一種，也因為這裡是變數取值，所以type不需要再用單引號（`''`）或雙引號（`""`）包起來。

不過如果例如用法是`svgMap['bird']`，就要記得用單引號（`''`）或雙引號（`""`）。

****

## 4-55. Increasing Image Size
這章節要加上愛心，當我們點擊動物圖片時，這個愛心就會變大。而在React中，若要記得每次更新後的狀態，便需要使用到useState方法。

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">step1. import useState</h3>
</div>

使用useState之前，記得要先import進檔案中：
**AnimalShow.js**
```javascript!
import {useState} from 'react';
```
<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">step2. 新增onClick事件＆觸發後要執行的函式</h3>
</div>

我們會把愛心和動物放在同一個`<div>`中，所以我們要把onClick 事件加到`<div>`上。每當onClick事件被觸發，就要去執行handleClick function。
**AnimalShow.js**
```javascript!
function AnimalShow({type}){
    const handleClick = () =>{
        
    }

    return(
    <div onClick={handleClick}>
        <img alt = "animal" src = {svgMap[type]} />
    </div>
    )
}
```

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">step3. 設置state</h3>
</div>

希望點擊一次，點擊次數就加1。利用useState()，並在handleClick function中用setter改變state。
**AnimalShow.js**
```javascript!
function AnimalShow({type}){
    const [clicks, setClicks] = useState(0);
    const handleClick = () =>{
        setClicks(clicks + 1);
    }

    return(
    <div onClick={handleClick}>
        <img alt = "animal" src = {svgMap[type]} />
    </div>
    )
}
```

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">ste4. 加入heart圖片並給予style</h3>
</div>

props物件中的屬性style值得注意，style會是一個物件，在JSX中屬性的value若有變數（此處變數為clicks），其value要再用`{ }`（curly braces）包起來，因此看起來會是兩層curly braces`{{ }}`。

也可以從底下程式碼得知，尚未點擊時，clicks的初始值為0（因為useState(0)），每點擊一次就會增加10。

最後記得加上單位'px'。
**AnimalShow.js**
```javascript!
function AnimalShow({type}){
    const [clicks, setClicks] = useState(0);
    const handleClick = () =>{
        setClicks(clicks + 1);
    }

    return(
    <div onClick={handleClick}>
        <img alt = "animal" src = {svgMap[type]} />
        <img 
            alt = "heart" 
            src = {heart} 
            style = {{ width: 10 + 10 * clicks +'px' }}
        />
    </div>
    )
}
```

****

## 4-56. Adding Custom CSS
先建立兩個css檔案。
![](https://hackmd.io/_uploads/r1sFmwPj2.png)

建立css檔案之外還不夠，因為此時的css檔案不起任何作用，所以還必須將css檔import進要使用該css檔的js檔中。
**App.js**
```javascript
import './App.css'
```
**AnimalShow.js**
```javascript!
import './AnimalShow.js'
```

可以看到import時，並沒有命名任何的變數，這是因為我們想使用整個css檔案的內容。

複習可參考[2-19](https://hackmd.io/fxY8uO0fTpa9XBizBKTYWg#2-19-Applying-Styling-in-JSX)。在HTML檔案中，若要使用css的class，用法如下：

**A.css**
```css!
.item{
    display: inline;
    color: white;
    background-color: black;
}
```
**A.html**
```htmlembedded!
<li class="item">first</li>
```
但如果換作是JSX來寫，則class要改成className。
```htmlembedded!
<li className="item">first</li>
```

