---
# System prepended metadata

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

---

# 從 Hooks 開始，讓你的網頁 React 起來系列的閱讀筆記（13-17）
###### tags: `閱讀筆記` `前端筆記` `React`
## 完成章節
- 13（2021/11/24）
- 14-17（2021/11/25）

## 使用 Emotion 寫 styled Component
使用這個套件可以直接創立一個已經有 CSS 的 Component，就不必再自己命名 class，然後再 return Component 的時候寫對應的 className。

### 沒用套件的一般寫法

1. return Component 的時候寫 className

```javascript=
function App () {
  return (
    <div className="container">
      <h1>Hello</h1>
    </div>
    )
}
```

2. 回去 CSS 寫對應的 class 之樣式

```css=
.container {
  /* other stuff... */
}
```

### 藉由 Emotion 得到的神力

1. 寫 Component 的時候直接寫 CSS（styled Element）
   - 比方來說 `styled div` 有 CSS 的 `div` 元素
   - `styled p` 有 CSS 的 `p` 元素

```javascript=
function App () {
  // styled element` CSS 屬性...`
  // 想像成這個已經是一個有 CSS 的 div 元素了
  const Container = styled.div`
    background-color: #ededed;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  `;
    
  return(
    <Container>
      <p>Hello</p>
    </Container>
  );
}
```

2. Emotion 套件也可以讀取 `props` 的物件，並執行函式達到不同效果
   - [測試 Emotion](https://codesandbox.io/s/ce-shi-emotion-ps9dl)
   - 因為 Emotion 使用 `styled.Element` + 反引號（backtick）所以要用 `${函式}` 的方式執行任務

3. 使用 Emotion 建立的 styled HTML 有獨一無二的 className，所以每個 Component 不會互相影響

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

## `useState` 的 `State` 可以是任何東西（Number, String, Object）

課程範例是需要地名、天氣概況、溫度、風速、濕度等資料，並切透過 API 得到新的資料。
學習到 PJ 大是先以「需要什麼資料的角度」整理物件（因為需要很多資料，用物件包在一起比較方便）。
（設定 `useState` 的初始值）

```javascript=
// WeatherApp.js

// other stuff...

export function WeatherApp () {
  // other stuff...
  const [currentWeather, setCurrentWeather] = useState({
  locationName: "臺北市",
  description: "多雲時晴",
  obsTime: "2021-11-25 15:50:00",
  tempareture: "27.5",
  windSpeed: "0.3",
  humid: "0.88"
  });
  // other stuff...
  return (
      // other stuff...
  );
}
```

專案需要的資料（也可以學習怎麼好的切割 Component）
[*ref.:[Day 15 - 即時天氣] 就是這個畫面 - 使用 Emotion 為組件增添 CSS 樣式*](https://ithelp.ithome.com.tw/articles/10223594)

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

### 等到 API 回傳資料後透過 `useState` 的 `setXXX` 來告訴 React 資料收到 + 渲染

`Fetch` 到 API 之後整理完資料再藉著 `setXXX` re-render。

```javascript=
// WeatherApp.js

// other stuff...

  function fetchCurrentWeather() {
    console.log("in fetchCurrentWeather");
    const url =
      "https://opendata.cwb.gov.tw/api/v1/rest/datastore/O-A0003-001?Authorization=CWB-C20B184B-3718-4949-B864-BC009F8325A0&limit=1";
    fetch(url)
      .then((response) => response.json())
      .then((data) => {
        // 一開始土法煉鋼 XD
        // locationData 是 array
        // const locationData = data.records.location[0];
        // wdsd 風速、temp 溫度、humd 濕度
        // const locationName = locationData.locationName;
        // const obsTime = locationData.time.obsTime;
        // const wdsd = locationData.weatherElement[2].elementValue;
        // const temp = locationData.weatherElement[3].elementValue;
        // const humd = locationData.weatherElement[4].elementValue;
        // setCurrentWeather({
        //   locationName,
        //   description: "多雲時晴",
        //   obsTime,
        //   tempareture: temp,
        //   windSpeed: wdsd,
        //   humid: humd
        // });
        
        // 整理回來的資料
        const locationData = data.records.location[0];
        const weatherEles = locationData.weatherElement.reduce(
          (obj, currentIndex) => {
            if (["WDSD", "TEMP", "HUMD"].includes(currentIndex.elementName)) {
              obj[currentIndex.elementName] = [currentIndex.elementValue];
            }
            return obj;
          },
          {}
        );
        
        // 整理完叫用 setCurrentWeather 並寫入更改後的東西
        // => React 就會知道有更改並渲染
        setCurrentWeather({
          locationName: locationData.locationName,
          description: "多雲時晴",
          obsTime: locationData.time.obsTime,
          tempareture: weatherEles.TEMP,
          windSpeed: weatherEles.WDSD,
          humid: weatherEles.HUMD
        });
      });

// other stuff...
```

### 不能只新增一個或者改一個資料，因為每次 `setXXX` 的 Arguments 都會改變起始值

`useState` 背後原理類似這樣子：
[*ref.:Why React Hook useState uses const and not let*](https://stackoverflow.com/questions/58860021/why-react-hook-usestate-uses-const-and-not-let)

```javascript=
let _state;
let _initialized = false;
function useState(initialValue) {
  if (!_initialized) {
    _state = initialValue;
    _initialized = true;
  }
  return [_state, v => _state = v];
}

function Component() {
  const [count, setCount] = useState(0);

  console.log(count);
  setCount(count + 1);
}

Component();
Component(); // in reality `setCount` somehow triggers a rerender, calling Component again
Component(); // another rerender
```

所以每次 `setXXX` 的 Arguments 都會更新起始值。

### 那麼如果真的只是想要改一個屬性怎麼辦？
這個時候就要使用「解構」：
[ref.[Day 16 - 即時天氣] 定義並請求組件會使用到的資料 - useState 的更多使用](https://ithelp.ithome.com.tw/articles/10224031)

```javascript=
setCurrentWeather({
  ...currentWeather,
  temperature: 31,
});

console.log(currentWeather);
// {
//   observationTime: '2019-10-02 22:10:00',
//   locationName: '臺北市',
//   description: '多雲時晴',
//   temperature: 31,
//   windSpeed: 0.3,
//   humid: 0.88,
// }
```

## `useEffect` Component render 後處理事情的好幫手

`useEffect` 是另一個 Hook，這個函式不會回傳任何東西，接受兩個 Arguments：
1. function => 當 useEffect 被叫用時要做的任務（自己寫的函式）
2. [] => 被叫為 dependency

觸發的條件是==畫面有 render 就會觸發 `useEffect`==！
所以**一開始載入頁面的時候也會觸發 `useEffect`**。

![](https://i.imgur.com/sJ81UZw.png)
*畫面渲染就會觸發 `useEffect`*

所以可以利用這個特性達到「第一次渲染頁面後馬上叫用 API 渲染（`setXXX`）頁面」：

```javascript=
// WeatherApp.js 

// other stuff...

export function WeatherApp() {
  console.log("--- involke function component ---");
  const [currentWeather, setCurrentWeather] = useState({
    locationName: "臺北市",
    description: "多雲時晴",
    obsTime: "2021-11-25 15:50:00",
    tempareture: "27.5",
    windSpeed: "0.3",
    humid: "0.88"
  });
  function fetchCurrentWeather() {
    console.log("in fetchCurrentWeather");
    const url =
      "https://opendata.cwb.gov.tw/api/v1/rest/datastore/O-A0003-001?Authorization=CWB-C20B184B-3718-4949-B864-BC009F8325A0&limit=1";
    fetch(url)
      .then((response) => response.json())
      .then((data) => {
        const locationData = data.records.location[0];
        const weatherEles = locationData.weatherElement.reduce(
          (obj, currentIndex) => {
            if (["WDSD", "TEMP", "HUMD"].includes(currentIndex.elementName)) {
              obj[currentIndex.elementName] = [currentIndex.elementValue];
            }
            return obj;
          },
          {}
        );

        setCurrentWeather({
          locationName: locationData.locationName,
          description: "多雲時晴",
          obsTime: locationData.time.obsTime,
          tempareture: weatherEles.TEMP,
          windSpeed: weatherEles.WDSD,
          humid: weatherEles.HUMD
        });
      });
  }
  // 第一次載入畫面後觸發 useEffect 接 API 渲染(setCurrentWeather)
  useEffect(fetchCurrentWeather);
  return (
    // Component...
  );
}
```

結果 GG 了，瘋狂地執行 `useEffect`...
[*ref.:[Day 17 - 即時天氣] 頁面載入時就去請求資料 - useEffect 的基本使用*](https://ithelp.ithome.com.tw/articles/10224270)

因為只要有 render 就會執行 `useEffect`，而且 `useEffect` 是在 render 後最後執行的。

![](https://i.imgur.com/SOTxAlo.png)
*第一次渲染頁面：Component 渲染 -> `useEffect`*

![](https://i.imgur.com/asQvqrO.png)
*更改 state 渲染頁面：`setXXX` -> Component 渲染 -> `useEffect`*

### 那麼為什麼會瘋狂觸發 `useEffect`？
因為上例確實符合 `useEffect` 的規則：==render 就會執行 `useEffect`==。

一開始 Component 載入 => 執行 `useEffect` => 結果觸發 `setCurrentWeather` 又**渲染（render）**-> chufa `useEffect` => 結果觸發 `setCurrentWeather` => loop...

### 那麼要怎麼打破 `useEffect` render 後就觸發？

使用 `useEffect` 第二個參數，`dependency` 也就是空陣列（[]）來控制 `useEffect` 是否該觸發。換句話說，如果有下 `dependency`，那麼 `useEffect` 在第一次 Component 渲染的後觸發完就不會再觸發了，除非 `dependency` 有更動。

![](https://i.imgur.com/C7Iq9s5.png)
*一開始 Component 渲染 -> 渲染完畢觸發 `useEffect` -> 執行 `fetchCurrentWeather` -> 執行 `setCurrentWeather` -> 再次渲染（不過因為 `useEffect` 的 `dependency` 還是維持 `[]` ==沒改變 `dependency` 就不會觸發 `useEffect`==）*

```javascript=
// WeatherApp.js 

// other stuff...

export function WeatherApp() {
  console.log("--- involke function component ---");
  const [currentWeather, setCurrentWeather] = useState({
    locationName: "臺北市",
    description: "多雲時晴",
    obsTime: "2021-11-25 15:50:00",
    tempareture: "27.5",
    windSpeed: "0.3",
    humid: "0.88"
  });
  function fetchCurrentWeather() {
    console.log("in fetchCurrentWeather");
    const url =
      "https://opendata.cwb.gov.tw/api/v1/rest/datastore/O-A0003-001?Authorization=CWB-C20B184B-3718-4949-B864-BC009F8325A0&limit=1";
    fetch(url)
      .then((response) => response.json())
      .then((data) => {
        const locationData = data.records.location[0];
        const weatherEles = locationData.weatherElement.reduce(
          (obj, currentIndex) => {
            if (["WDSD", "TEMP", "HUMD"].includes(currentIndex.elementName)) {
              obj[currentIndex.elementName] = [currentIndex.elementValue];
            }
            return obj;
          },
          {}
        );

        setCurrentWeather({
          locationName: locationData.locationName,
          description: "多雲時晴",
          obsTime: locationData.time.obsTime,
          tempareture: weatherEles.TEMP,
          windSpeed: weatherEles.WDSD,
          humid: weatherEles.HUMD
        });
      });
  }
  // 第一次載入畫面後觸發 useEffect 接 API 渲染(setCurrentWeather)
  useEffect(() => {
    console.log('hi from useEffect');
    fetchCurrentWeather();
  });
  return (
    // Component...
  );
}
```

## 參考資料
1. [從 Hooks 開始，讓你的網頁 React 起來 13-17](https://ithelp.ithome.com.tw/users/20103315/ironman/2668)
2. [Full React Tutorial #14 - useEffect Hook (the basics)](https://www.youtube.com/watch?v=gv9ugDJ1ynU&list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d&index=14)
3. [Full React Tutorial #15 - useEffect Dependencies](https://www.youtube.com/watch?v=jQc_bTFZ5_I&list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d&index=15)
4. [Using the Effect Hook](https://reactjs.org/docs/hooks-effect.html)

