---
# System prepended metadata

title: 從 Hooks 開始，讓你的網頁 React 起來系列的閱讀筆記（7-12）
tags: [React, 閱讀筆記, 前端筆記]

---

# 從 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')
);
```

![](https://i.imgur.com/McNoNYT.png)

原因是 Content 只是單純地回傳 Component，所以 `contentQty` 出了 `Content` 的函式就掛了。也因為如此在此例才要把 `const contentQty = Array.from({ length: 4 }, (value, indexNum) => indexNum);` 宣告在外層，這樣子 `ReactDOM.render` 才讀取的到 `contentQty`。

## 元件可以由很多的小元件組成
原本是一整個 CardComponent： 

![](https://i.imgur.com/50JTXOB.png)

看起來是一整個密密麻麻的 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：

![](https://i.imgur.com/SPQ3EeK.png)

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

![](https://i.imgur.com/iA7DcEm.png)
*寫成 `{CardHeader()}` 後發現 `CardHeader` 不是 Component 了*

![](https://i.imgur.com/eC1nByx.png)
*但 `{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')
);
```

![](https://i.imgur.com/xeIz37p.png)

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

![](https://i.imgur.com/GNaFe5l.png)

### 主要資料要留在主 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)
