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