# 從 Hooks 開始,讓你的網頁 React 起來系列的閱讀筆記(7-12)
###### tags: `閱讀筆記` `前端筆記` `React`
## 完成章節
- 7-12(2021/11/23)
## 事件函式的處理
有別於我一開始寫的方式(把加跟減的任務拆成兩個函式),看到文章內也有使用一個函式包著兩個任務(再經判斷執行不同的任務)。
```javascript=
// 分別用兩個不同的函式
const Content = () => {
const [count, setCount] = useState(5);
// 兩個獨立的函式代表兩個不同的任務
function add () {
setCount(count + 1)
}
function minus () {
setCount(count - 1)
}
return (
<div className='container'>
<div
className='chevron chevron-up'
// 點擊事件
onClick={add}
style={{
visibility: count >= 10 && 'hidden'
}}
/>
<div className="number">{count}</div>
<div
className='chevron chevron-down'
// 點擊事件
onClick={minus}
style={{
visibility: count <= 0 && 'hidden'
}}
/>
</div>
)
};
ReactDOM.render(
<Content />,
document.querySelector('#root')
);
```
道理和 Vanilla JS 寫 `addEventListener` 的原理類似。
- 在 Vanilla JS 的 `addEventListener` 不用在 callback 後面寫括號 (),因為函式後面接 () 代表是叫用函式
- 但是是要由事件觸發後執行函式,所以只要在 callback 寫函式名稱就好
```javascript=
// 假設有一個按鈕
const button = document.querySelector('#my-button');
function clickHandler () {
console.log('我被點了');
}
button.addEventListener('click', clickHandler);
// 不是這樣子喔,這樣子代表函式直接執行,並不是透過觸發事件執行
button.addEventListener('click', clickHandler());
```
- 所以依照這個原理「觸發事件後再執行函式」,可以發展出函式包兩個任務
```javascript=
// 文章內的方法,用一個函式包覆著兩個函式(任務)
const Content = () => {
const [count, setCount] = useState(5);
function clickHandler (type) {
return function () {
if (type === 'add') {
setCount(count + 1);
}
else if (type === 'minus') {
setCount(count - 1);
}
}
}
return (
<div className='container'>
<div
className='chevron chevron-up'
// 點擊事件
// clickHandler 已經先執行,但是是回傳另一個函式,所以這時候可以想成 onClick={clickHandler 回傳的 function...}
// 只要事件觸發(點擊 onClick)就會執行 clickHandler 回傳的函式
onClick={clickHandler('add')}
style={{
visibility: count >= 10 && 'hidden'
}}
/>
<div className="number">{count}</div>
<div
className='chevron chevron-down'
// 點擊事件
// clickHandler 已經先執行,但是是回傳另一個函式,所以這時候可以想成 onClick={clickHandler 回傳的 function...}
// 只要事件觸發(點擊 onClick)就會執行 clickHandler 回傳的函式
onClick={clickHandler('minus')}
style={{
visibility: count <= 0 && 'hidden'
}}
/>
</div>
)
};
ReactDOM.render(
<Content />,
document.querySelector('#root')
);
```
### 為什麼回傳的函式可以讀取 `type` Arguments?
因為是靜態範疇(Static Scope)+ 函式範疇(Function Scope)的緣故,**變數及函式在宣告的當下就決定自己的可見範圍了,而且 return 的函式確實是在 `clickHandler` 裡面,所以該函式內找不到的東西就會往外層找。** 所以當事件觸發任務(函式)的時候 return 的函式就會被叫用,然後就會往外層找 `type` Argument了。
## 使用迴圈建立數個相同的 Component
除了自己土法煉鋼地用手貼上數個 Component,也可以使用 JSX 也可以使用迴圈達到同樣子的效果。
```javascript=
function Content = () => {
// component stuff...
}
ReactDOM.render(
// 記得每個 Component 之外一定要有一個 wrapper(包裝紙)
<div
style={{
display: 'flex',
flexWrap: 'wrap'
}}>
// 土法煉鋼狂貼之術
<Content />
<Content />
<Content />
<Content />
</div>
,
document.querySelector('#root')
);
```
使用迴圈來建立數個相同的 Component
- 因為 JSX 是執行 expression(表達式)所以不能用一般的 `for loop`,`foor loop` 是 statement(陳述式),不是 expression(表達式)
- 可以用 `Array.from()` 快速建立一組陣列,之後再搭配 `arr.map()` [Array.from()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from)
```javascript=
const { useState } = React;
const contentQty = Array.from({ length: 4 }, (value, indexNum) => indexNum);
console.log(contentQty) // Array(4)[0, 1, 2, 3]
function Content = () => {
// component stuff...
};
ReactDOM.render(
// 記得每個 Component 之外一定要有一個 wrapper(包裝紙)
<div
style={{
display: 'flex',
flexWrap: 'wrap'
}}>
{contentQty.map(value => {
// 每次迴圈都會產生一個 <Content />
return <Content />
})}
</div>
,
document.querySelector('#root')
);
```
### 為什麼把 `const contentQty = Array.from({ length: 4 }, (value, indexNum) => indexNum);` 放在 Component 函式裡面會出現問題?
我一開始是放在 Component 的函式之內,結果報錯了。
```javascript=
const Content = () => {
const contentQty = Array.from({ length: 4 }, (value, indexNum) => indexNum);
// other stuff...
return (
<div className='container'>
<div
className='chevron chevron-up'
onClick={clickHander('add')}
style={{
visibility: count >= 10 && 'hidden'
}}
/>
<div className="number">{count}</div>
<div
className='chevron chevron-down'
onClick={clickHander('minus')}
style={{
visibility: count <= 0 && 'hidden'
}}
/>
</div>
)
};
ReactDOM.render(
<div
style={{
display: 'flex',
flexWrap: 'wrap'
}}>
{contentQty.map(value => {
// Uncaught ReferenceError: contentQty is not defined
return <Content />
})}
</div>
,
document.querySelector('#root')
);
```

原因是 Content 只是單純地回傳 Component,所以 `contentQty` 出了 `Content` 的函式就掛了。也因為如此在此例才要把 `const contentQty = Array.from({ length: 4 }, (value, indexNum) => indexNum);` 宣告在外層,這樣子 `ReactDOM.render` 才讀取的到 `contentQty`。
## 元件可以由很多的小元件組成
原本是一整個 CardComponent:

看起來是一整個密密麻麻的 JSX
- 維護麻煩,因為都擠在一起了
```javascript=
function CardComponent () {
return (
<div className='container'>
<div className='card-header'>Network Speed Converter</div>
<div className='card-body'>
<div className='unit-control'>
<div className='unit'>Mbps</div>
<span className='exchange-icon fa-fw fa-stack'>
<i className='far fa-circle fa-stack-2x' />
<i className='fas fa-exchange-alt fa-stack-1x' />
</span>
<div className='unit'>MB/s</div>
</div>
<div className='converter'>
<div className='flex-1'>
<div className='converter-title'>Set</div>
<input
type='number'
className='input-number'
min='0'
/>
</div>
<span
className='angle-icon fa-2x'
style={{
marginTop: 30,
}}
>
<i className='fas fa-angle-right' />
</span>
<div className='text-right flex-1'>
<div className='converter-title'>Show</div>
<input
className='input-number text-right'
type='text'
value='125'
disabled
/>
</div>
</div>
</div>
<div className='card-footer'>FAST</div>
</div>
);
}
ReactDOM.render(
<CardComponent />,
document.querySelector('#root')
);
```
把區塊分出來,就可以看到 CardComponent 裡面還有其他小 Component:

- 每個區分開的 Component 開頭必須是大寫
- 插入的時候要記得是 `<ComponentName />` 不是 `{ComponentName()}` 這樣子就會變成叫用函式(如果該函式有回傳東西的話就會回傳,如果是回傳 JSX 的話還是會轉譯成 HTML 但是就不是 Component了)

*寫成 `{CardHeader()}` 後發現 `CardHeader` 不是 Component 了*

*但 `{CardHeader()}` 回傳的 JSX 還是會被轉譯成 HTML*
```javascript=
const { useState } = React;
function CardHeader () {
return (
<div className='card-header'>Network Speed Converter</div>
);
}
function CardUnitControl () {
return (
<div className='unit-control'>
<div className='unit'>Mbps</div>
<span className='exchange-icon fa-fw fa-stack'>
<i className='far fa-circle fa-stack-2x' />
<i className='fas fa-exchange-alt fa-stack-1x' />
</span>
<div className='unit'>MB/s</div>
</div>
);
}
function CardFooter ({ inputValue }) {
// 目前就會遇到 Component 分開後無法讀取變數的問題
// 用 props(properties) 可以把要傳送的資料寫在叫用 Component 時(就像使用一般的 HTML attribute 一樣!!)
// const inputValue = props.inputValue;
// console.log(props)
let status;
if (inputValue < 15) {
status = {
description: 'SLOW',
backgroundColor: 'red'
}
} else if (inputValue < 40) {
status = {
description: 'GOOD',
backgroundColor: 'green'
}
} else if (inputValue >= 40) {
status = {
description: 'FAST',
backgroundColor: '#13d569'
}
}
return (
<div
className='card-footer'
style={{
backgroundColor: status.backgroundColor
}}
>{status.description}</div>
);
}
function CardComponent () {
const [ inputValue, setInputValue ] = useState(0);
function changeHandler (event) {
console.log('onChange!');
// 道理和 VanillaJS 相同
// => 表單的值有改變就會觸發 change
// => 事件本身有 event 物件,所以透過觸發函式藉 Argument 得到該物件
// 傳統的寫法
// const value = event.target.value;
// 用解構的方式寫
const { value } = event.target;
setInputValue(value);
}
return (
<div className='container'>
// Header 部分的 Component
<CardHeader />
<div className='card-body'>
// UnitControl 部分的 Component
<CardUnitControl />
<div className='converter'>
<div className='flex-1'>
<div className='converter-title'>Set</div>
<input
type='number'
className='input-number'
min='0'
onChange={changeHandler}
/>
</div>
<span
className='angle-icon fa-2x'
style={{
marginTop: 30,
}}
>
<i className='fas fa-angle-right' />
</span>
<div className='text-right flex-1'>
<div className='converter-title'>Show</div>
<input
className='input-number text-right'
type='text'
value={inputValue / 8}
disabled
/>
</div>
</div>
</div>
// Footer 部分的 Component
<CardFooter inputValue={inputValue}/>
</div>
);
}
ReactDOM.render(
<CardComponent />,
document.querySelector('#root')
);
```
## 分割後的 Component 可以透過 `props(properties)` 接到其他 Component 的資料
其實寫在一起也是可以取得資料(就是一個很長很長很長的函式)。[從 Hooks 開始,讓你的網頁 React 起來系列 - Day 10(驗證不分開變數是可以讀取的)](https://codepen.io/lun0223/pen/ZEJNJPz?editors=0010)
切分成不同的 Component,就可以靠著函式的特性(因為 Function Component 就是函式!)利用參數(Paramert)及引數(Arguments)的方式達到資料的傳遞。
### 什麼是 `props` 呢?
`props` 就是物件組成單位 `properties` 的簡稱,`property` 又是由一個 `key` 及 一個 `value` 所組成。
```javascript=
// { property }
{ myName: 'Lun' }
// myName => Key
// 'Lun' => Value
```
### 那該如何在 React 中達到運用 `props` 在分割的小 Component 中傳遞資料呢?
**要記得 React 的 Function Component 就是「函式」所以會有函式的特性。**
依照下面的範例可以看出:
1. `CardFooter` 接受一個參數,所以在叫用創造 EC 時就會保存引數。
- 所以在函式中 `inputValue` 的位置就是叫用函式時的引數
- `<ComponentName />` 其實就會叫用 ComponentName 函式
```javascript=
// 在 CardFooter 中寫 console.log() 測試
function CardFooter ({ inputValue }) {
console.log('Hello from CardFooter');
// other stuff...
}
function CardComponent () {
// other stuff...
return (
<div className='container'>
// other stuff...
// Footer 部分的 Component
<CardFooter />
</div>
);
}
ReactDOM.render(
<CardComponent />,
document.querySelector('#root')
);
```

2. 現在就要解決如何在叫用 `inputValue` 傳入引數
- 結果十分簡單,就在 `<CardFooter />` 中寫入 `props`(需要自己定義 `keyName` 及 `value`)
- 就會變成這樣 `<CardFooter inputValue={inputValue}/>`
- 也可以設定多個 `props`
```javascript=
const { useState } = React;
function CardHeader () {
// other stuff...
}
function CardUnitControl () {
// other stuff...
}
function CardFooter ({ inputValue, testing }) {
console.log('Hello From CardFooter');
console.log(inputValue, testing);
// 目前就會遇到 Component 分開後無法讀取變數的問題
// 用 props(properties) 可以把要傳送的資料寫在叫用 Component 時(就像使用一般的 HTML attribute 一樣!!)
// const inputValue = props.inputValue;
// console.log(props)
let status;
if (inputValue < 15) {
status = {
description: 'SLOW',
backgroundColor: 'red'
}
} else if (inputValue < 40) {
status = {
description: 'GOOD',
backgroundColor: 'green'
}
} else if (inputValue >= 40) {
status = {
description: 'FAST',
backgroundColor: '#13d569'
}
}
return (
<div
className='card-footer'
style={{
backgroundColor: status.backgroundColor
}}
>{status.description}</div>
);
}
function CardComponent () {
const [ inputValue, setInputValue ] = useState(0);
function changeHandler (event) {
console.log('onChange!');
// 道理和 VanillaJS 相同
// => 表單的值有改變就會觸發 change
// => 事件本身有 event 物件,所以透過觸發函式藉 Argument 得到該物件
// 傳統的寫法
// const value = event.target.value;
// 用解構的方式寫
const { value } = event.target;
setInputValue(value);
}
return (
<div className='container'>
// other stuff...
// Footer 部分的 Component
// 設定多個 props
// 可以想成 CardFooter({inputValue: inputValue, testing: '測試的'});
<CardFooter inputValue={inputValue} testing={'測試的'}/>
</div>
);
}
ReactDOM.render(
<CardComponent />,
document.querySelector('#root')
);
```

### 主要資料要留在主 Component 中
在分割 Component 的時候就要記得「主要的資料/狀態」要寫在主 Component 而不是分割出去,因為分割出去就沒有辦法傳遞了。
**因為 Function Scope!**
```javascript=
const { useState } = React;
function CardHeader () {
return (
<div className='card-header'>Network Speed Converter</div>
);
}
function CardUnitControl () {
// other stuff...
}
function CardConverter () {
const [ inputValue, setInputValue ] = useState(0);
function changeHandler (event) {
const { value } = event.target;
setInputValue(value);
}
return (
// other stuff...
);
}
function CardFooter () {
// other stuff...
}
function CardComponent () {
return (
<div className='container'>
<CardHeader />
<div className='card-body'>
<CardUnitControl />
<CardConverter />
</div>
<CardFooter inputValue={inputValue}/>
// Uncaught ReferenceError: inputValue is not defined
// 因為在 CardComponent 並沒有辦法找到 CardConverter 中的 inputValue
</div>
);
}
ReactDOM.render(
<CardComponent />,
document.querySelector('#root')
);
```
## 如果忘記的話就速看這
之後如果忘記的話可以看 React 文件提供的[範例](https://codepen.io/lun0223/pen/yLoWQgX?editors=0010)。
## 參考資料
1. [從 Hooks 開始,讓你的網頁 React 起來 7 - 12](https://ithelp.ithome.com.tw/users/20103315/ironman/2668)
2. [Components and Props](https://reactjs.org/docs/components-and-props.html)