# 從 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 不會互相影響

## `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)

### 等到 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`**。

*畫面渲染就會觸發 `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 後最後執行的。

*第一次渲染頁面:Component 渲染 -> `useEffect`*

*更改 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` 有更動。

*一開始 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)