#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節將要製作的專案內容。

可以從上圖知道,這次專案中會有parent component(App.js)以及child component(AnimalShow.js)
**專案功能:**
一開始,畫面上只會看見一個按鈕「Add Animal」,當點擊一次,便會出現一種動物with愛心,而愛心經點擊後會變大。
1. 一開始,沒有任何動物出現

2. 點擊2次後,出現不同的動物
但希望使用相同的component(AnimalShow),經由不同的props來達到跑出不同的動物的目的。

**專案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。

在這裡先提到React有兩種系統:
1. **Event System**
> Detect a user clicking the button
2. **Status System**
> Update content on screen
****
## 4-43. Events in Detail

這章節主要提到event system。
**1. 決定想要怎麼樣的事件**
React中支援的event可以參考[官方文件](https://zh-hant.legacy.reactjs.org/docs/events.html)(課程提供的是舊版的,新版的在[這裡](https://react.dev/reference/react-dom/components/common#common-props))
其中舉兩個較常見的例子:

**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,如下圖所示:

了解我們希望呈現的方式之後,開始思考如何達成目的,如下圖所示:

****
## 4-47. More on State
> 完整資料:
> [官方文件State: A Component's Memory](https://react.dev/learn/state-a-components-memory)
接續上一章節,這裡繼續談論State。

基本上可以分為4個步驟:

**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);
```

:::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
這章節老師大略提到該怎麼實作,簡單來說就是要寫一個陣列,然後當使用者點擊按鈕之後,就會更新動物名稱進到陣列中。(如下圖所示)
一開始尚未點擊按鈕的狀態:

點擊兩次按鈕的狀態:

****
## 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**

其中,可以注意到他使用到了「展開運算子(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之中。實作想法如下圖:

而上圖中提到的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} />;
});
```
圖片說明:

具體化說明:

所有的程式碼則如下所示:
**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
};
```
事實上,如果展開這個物件來看,他會長這樣:

<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檔案。

建立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>
```