Skip to content

Latest commit

 

History

History
681 lines (539 loc) · 24.3 KB

W22_hw2:簡答題.md

File metadata and controls

681 lines (539 loc) · 24.3 KB

hw2:簡答題

1. 請列出 React 內建的所有 hook,並大概講解功能是什麼

答:

Basic hooks

  1. useState:用來設定 component 的 state
  • 使用方法 const [currentValue, setCurrentValue] = useState(initialValue);
  • currentValue:用來存放 state 的值
    • 是一個變數,可自行命名
  • setCurrentValue:用來設定 state 的值
    • 是一個函式,可自行命名
    • 可以是更新後的具體值,也可以是一個返回更新後具體值的函數 註:若 setCurrentValue 接收的是一個函數,則會將舊的 state 作為引數傳遞給此函數
  • initialValue:用來設定 state 的初始值
    • 可以是數字、字串、物件等任意值,也可以是一個 function
    • initialValue 是一個 function 時,這個 function 叫做 Lazy Intializers,且這個 function 只會在 Mount (第一次載入頁面或頁面重整) 時執行 註 1:通常會在初始值需要做複雜計算時使用此方法,這樣就只有 Mount 時會做運算,可以有效減少效能浪費 註 2:用 Lazy Intializers 搭配非同步函式是沒用的,因為它只看同步的結果
  • 第一次 render 時,回傳的 state 值會和 initialValue 回傳的值相同
  • 可透過 setCurrentValue 來更新 state(currentValue),state 一旦改變,就會觸發 React 去重新渲染畫面
useState 範例:
const [count, setCount] = useState(initialCount);

setCount(newValue);

======================================

const [count, setCount] = useState(initialCount);

// setCurrentValue 中也可以傳入函式
// 在這個函式中可以拿到前一次的 state
setCurrentValue((prevCount) => {
  // 最後要記得把新的 state 回傳回去
  return {
    ...preCount,
    newThing
  }
});

  1. useEffect:用來告訴 React,component 在 render 完且『瀏覽器 paint 畫面後』要做的事情
  • 使用方法
useEffect(() => {
  // do something here
  console.log("do something");

  return () => {
    // cleanup function
    // do another thing here
    console.log("do another thing");
  };
}, []);
  • 第一個參數傳入的是 function,會在「render 完且畫面渲染完成後」被呼叫
    • 可以在第一個參數的 funtion 裡 return 另一個 funtion,此 function 會被稱為 cleanup function
    • cleanup function 只會在下一個 useEffect 執行前執行 註:執行 cleanup function 時,裡面用到的 props 跟 state 的值,會是上一個 useEffect 時的資料狀態
  • 第二個參數傳入一個陣列,用來放想要關注的資料,當此資料改變時才會重新呼叫 useEffect
  • 不管第二個參數的陣列有沒有傳入值,第一次 render 一定會執行 useEffect
  • 如果同時有多個 useEffect ,會按程式碼的順序依次執行
useEffect 範例:
useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 此 function 為 cleanup function
    subscription.unsubscribe();
  };
}, [props.source]);

  1. useContext:解決 Component 內部跨多層(父 -> 子)傳遞資料的問題,就像建立了全域變數
  • 使用方法
const PassSomething = createContext();

function App() {
  return (
    <PassSomething.Provider value={something}>
      <Dad>
        <Child />
      </Dad>
    </PassSomething.Provider>
  );
}

function Child() {
  const something = useContext(PassSomething);
}
  • 讓父層的資料能夠被底下的任意子層存取,讓 props 不需要再一層一層傳遞下去,解決 prop drilling 的問題
  • React.createContext 建立一個 context,並由 <MyContext.Provider value={}> 來傳下該物件的值,底下的子層就可以直接透過 useContext 來存取 MyContext
  • 傳下去的 context 可以是任意值 註 1:傳下去的值是什麼,子層接收到的 useContext 的值就會是什麼 註 2:傳下去的 context 可以在中間層被改變,底下的子層則會『往上層找距離最近的 context 的值』 註 3:如果接收 context 的子層跟父層不在同一個檔案時,需要先引入父層宣告的 context 才能接收到
useContext 範例:
// 父層和子層在同一個檔案時

import React, { useState, createContext, useContext } from "react";

// 會像 component 一樣使用,所以要用大寫開頭
const Context = createContext(null);

function App() {
  const [state, setState] = useState(0);
  return (
    // 此處傳入的是物件,子層接收到的 useContext 就會是物件
    <Context.Provider value={{ state, setState }}>
      <div>
        <Baba />
        <Uncle />
      </div>
    </Context.Provider>
  );
}

function Baba() {
  return (
    <div>
      我是爸爸
      <Child />
    </div>
  );
}

function Uncle() {
  const { state, setState } = useContext(Context);
  return <div>我是叔叔 我拿到的 context 資料為 {state}</div>;
}

function Child() {
  const { state, setState } = useContext(Context);
  return (
    <div>
      我是兒子 我拿到的 context 資料為 {state}
      <button onClick={() => setState(state + 5)}>點選改變 context 資料</button>
    </div>
  );
}

======================================

// 父層和子層在不同檔案時

// App.js
import React, { useState, createContext, useContext } from "react";

// 會像 component 一樣使用,所以要用大寫開頭
export const Context = createContext(null);

function App() {
  const [state, setState] = useState(0);
  return (
    // 此處傳入的是物件,子層接收到的 useContext 就會是物件
    <Context.Provider value={{ state, setState }}>
      <div>
        <Baba />
        <Uncle />
      </div>
    </Context.Provider>
  );
}

// Child.js
import React, { useContext } from "react";
import { Context } from "./App"
// 不同檔案的話,要先引入父層宣告的 Context 才能使用


function Child() {
  const { state, setState } = useContext(Context);
  return (
    <div>
      我是兒子 我拿到的 context 資料為 {state}
      <button onClick={() => setState(state + 5)}>點選改變 context 資料</button>
    </div>
  );
}

Additional hooks

  1. useLayoutEffect:用來告訴 React,component 在 render 完且『瀏覽器 paint 畫面前』要做的事情 註 1:功能與用法都跟 useEffect 相似,區別只在於執行的時機點 註 2:useLayoutEffect 會在 useEffect 前執行
  • 使用方法
useLayoutEffect(() => {
  // do something here
  console.log("do something");

  return () => {
    // cleanup function
    // do another thing here
    console.log("do another thing");
  };
}, []);
  • 第一個參數傳入的是 function,會在「render 完且畫面渲染完成後」被呼叫
    • 可以在第一個參數的 funtion 裡 return 另一個 funtion,此 function 會被稱為 cleanup function
    • cleanup function 只會在下一個 useLayoutEffect 執行前執行 註:執行 cleanup function 時,裡面用到的 props 跟 state 的值,會是上一個 useLayoutEffect 時的資料狀態
  • 第二個參數傳入一個陣列,用來放想要關注的資料,當此資料改變時才會重新呼叫 useLayoutEffect
  • 不管第二個參數的陣列有沒有傳入值,第一次 render 一定會執行 useLayoutEffect
  • 如果同時有多個 useLayoutEffect ,會按程式碼的順序依次執行
useLayoutEffect 範例:
useLayoutEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 此 function 為 cleanup function
    subscription.unsubscribe();
  };
}, [props.source]);

  1. useRef:用來抓取 DOM 節點,存放的值不會受到 render 影響
  • 使用方法 const refTarget = useRef(initialValue)
  • 會回傳一個 mutable(可變的) 的 ref object,其 .current 屬性會被初始為傳入的參數 initialValue
  • .current 屬性有變動時不會觸發重新 render,而每次 render 時都會給同一個 ref object 註:因為此特性,所以可以保證每一次 render 後的 ref object 都是同一個 object
  • 可以直接對變數做使用,也可以對 DOM 元素使用 註 1:對 DOM 元素使用時,要在 DOM 裡面傳入 ref={refTarget} 的屬性用來存取此 DOM 註 2:對 DOM 元素使用的 refTarget 回傳的 object 的 .current 就會是此 DOM 元素
useRef 範例:
// 對變數做使用
const refContainer = useRef(initialValue);

console.log(refContainer.current); // 會是 initialValue

======================================

// 對 DOM 元素做使用
const refTarget = useRef(initialValue);

function App() {
  ...

  return (
    <div>
      <input ref={refTarget}/>
    </div>
  )
}

console.log(refContainer.current); // 會是 input 這個 DOM 元素

  1. useMemo:當 component 重新渲染時,可以避免 component 內複雜的運算被重複執行
  • 使用方法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 第一個參數是要執行的 function
  • 第二個參數是其依賴陣列
  • useMemo 會回傳一個 memoized 的值(也就是把傳入的第一個參數 function return 的值給記起來並回傳)
  • useCallback 的用法其實差不多,但大部分的情況都只會用到 useMemo
  • 主要目的是避免在 component 內部的複雜運算,會因為每次的 render 需要不斷重新計算,導致效能上不必要的消耗,使用 useMemo 可以讓複雜的運算結果被記起來,並且只會在依賴改變時才需要重新計算。
useMemo 範例:
const redStyle = {
  color: "red",
};

const blueStyle = {
  color: "blue",
};

function App() {
  const [value, setValue] = useState(5);

  const handleClick = () => {
    setValue(value - 1);
  };

  const s = useMemo(() => {
    console.log("complex calculate s");
    return value ? redStyle : blueStyle;
  }, [value]);

  // UI
  return (
    <div className="App">
      Count: {value}
      <button onClick={handleClick}>click</button>
      <Test style={s} />
    </div>
  );
}

  1. useCallback:用來解決當 component 重新渲染後,component 內的 function 也會重新產生的問題 註:useCallback 的底層就是用 useRef 做的
  • 使用方法
// 原本的 function
const Example = (a, b) => {
  console.log(a + b);
};

// 使用 useCallback 後的 function
const Example = useCallback((a + b) => {
  console.log(a + b);
}, []);
  • 第一個參數是要回傳的 function
  • 第二個參數是其依賴陣列
  • useCallback 會回傳一個 memoized 的 callback(也就是會把傳入的第一個參數 function 的版本給記起來並回傳)
    • 註 1:useCallbackuseMemo 的一種變體,用來記住一個 function instance
    • 註 2:其實就相當於一個回傳 function useMemo
    • 註 3:useCallback(fn, deps) 相當於 useMemo( () => fn, deps)
  • 主要目的是避免在 component 內部宣告的 function,會因為每次的 render 不斷重新被宣告跟建立,導致每次拿到的都是不同的 instance。這樣的 function 如果被當成 prop 往下傳給其他 component,就可能導致下面的 component 無意義地被重新 render,使用 useCallback 可以讓 function 只會在依賴改變時才更新,防止不必要的渲染,減少效能上的消耗。
useCallback 範例:
// 原本的新增 todo
const handleButtonClick = () => {
  setTodos([
    {
      id: id.current,
      content: value,
      isFinished: false,
    },
    ...todos,
  ]);
  setValue("");
  id.current++;
}


// 用 useCallback 後的新增 todo
const handleButtonClick = useCallback(() => {
  setTodos([
    {
      id: id.current,
      content: value,
      isFinished: false,
    },
    ...todos,
  ]);
  setValue("");
  id.current++;
}, []);

function App() {

  return (
    <div>
      <button onClick={handleButtonClick}>
    </div>
  )
};

/*
component 第一次 render 的時候會執行到 useCallback(...) 小括號裡面的程式碼,
然後 useCallback 就會幫我把 handleButtonClick 這個 function 記起來。
component 觸發 re-render 後,因為 dependency array 傳空陣列,所以永遠不會有東西改變,
因此 handleButtonClick 這個 function 也永遠都不會變。
下一次執行時就會直接用 useCallback 記起來的那個版本,
也就是說,不會再有新的 handleButtonClick 產生了(handleButtonClick 永遠都會是同一個 function)
*/

  1. useImperativeHandle:可以在父層調用子層中 ref,選取指定的 DOM 節點
  • 使用方法 useImperativeHandle(ref, createHandle, [deps])
  • useImperativeHandle 可以讓使用 ref 時能向父 component 暴露自定義的 instance 值
  • 第一個參數是 component 接收到的 ref
  • 第二個參數是傳給父層的方法
  • useImperativeHandle 應與 forwardRef 一同使用
useImperativeHandle 範例:
function App() {
  const myRef = useRef(null);
  useEffect(() => {
    console.log(myRef.current.real);
    console.log(myRef.current.getParent());
  }, []);
  return (
    <div>
      我是父元件
      <Child ref={myRef} />
    </div>
  );
}

const Child = forwardRef((props, ref) => {
  const childRef = useRef(null);
  useImperativeHandle(ref, () => {
    return {
      real: childRef.current,
      getParent() {
        return childRef.current.parentNode;
      },
    };
  });
  return (
    <div>
      我是子元件,我有一個子DOM
      <button ref={childRef}>按鈕</button>
    </div>
  );
});

/*
在這個範例中,render 子 component <Child ref={myRef} /> 的父 component  <App />
能呼叫 myRef.current.real 或是 myRef.current.getParent()
*/

  1. useDebugValue:可用來在 React DevTools 中顯示自訂義 hook 的標籤 註: 詳細用法建議參考 useDebugValue 基础用法 - GitHub
  • 使用方法 useDebugValue(value, function);
  • useDebugValue 可以用來讓自訂義的 hook 在 React DevTools 中顯示額外的信息,方便我們一眼就能找到對應的自定義 hook
    • 註: React 官方不建議在每個自定義的 Hook 都加上 debug 值
  • 第一個參數是我們想要在 React DevTools 中顯示的標籤內容
  • 第二個參數是一個格式化 function (可傳可不傳)
    • 註: 該 function 只有在 Hook 被檢查時才會被呼叫。它接受 debug 值作為參數,然後回傳一個被格式化的顯示值
useDebugValue 範例:
// 直接傳 debug 值
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? "Online" : "Offline");

  return isOnline;
}

======================================

// 延遲格式化 debug 值
const date = new Date()
useDebugValue(date, date => date.toDateString())

  1. useReducer:是 useState 的升級版,當 state 邏輯變得複雜,需要操作多種 state 時可使用
  • 使用方法 const [state, dispatch] = useReducer(reducer, initialState);
  • state:目前的 state 值
    • 對應到 Redux 中『Store 裡面的狀態』
  • dispatch:透過不同參數來和 reducer 溝通,藉此控制處理方式
    • 對應到 Redux 中『由 dispatch 指定 Store 執行哪些事』
  • reducer: 是一個 function
    • 會接受兩個參數 state(目前的狀態), action(要執行的操作),並根據對應的 dispatch 方法,回傳新的 state
  • initialState:用來設定 state 的初始值
useReducer 範例:
// React 官方範例
const [state, dispatch] = useReducer(reducer, initialState);

// 初始狀態為 count: 0
const initialState = { count: 0 };

// 由 reducer 回傳 state
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      // 根據不同的 action,return 新的 state 並取代原本的 state
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      // 非預期指令時則丟出 Error
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      {/* 由 dispatch 發送指令,{type: 'decrement'} 這個物件就代表一個動作 */}
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

參考來源: Hook 概觀 - React 官方文件 [學習筆記] React 內建的所有 Hooks 功能整理 - 前端新米 30 分鐘帶你全面瞭解 React Hooks 什麼時候該使用 useMemo 跟 useCallback [Day 07]React Hooks,useRef 與 useMemo 與 useCallback React Hook 系列教程,学习和探索 Hooks 世界。 - GitHub

2. 請列出 class component 的所有 lifecycle 的 method,並大概解釋觸發的時機點

答:

圖片來源:React LifeCycle Methods Diagram

Class Component 生命週期

  • Mount:初始化階段(也就是把元件放到畫面上,只會執行一次)
  • Update:元件更新階段(也就是 re-render)
  • Unmount:元件卸載階段(也就是把元件從畫面拿掉,只執行一次)

註:以下 method 皆按觸發順序排列

Mount

  • constructor:會在 component 被 mount 之前被呼叫
    • 你沒有初始化 state 也不綁定方法的話,你的 React component 就不需要 constructor
    • 進行資料宣告、初始化、預備、函式綁定的地方,但通常只會用來做兩件事:
      • 透過指定一個 this.state 物件來初始化內部 state
      • 為 event handler 方法綁定 instance 註 1: 在 ES6 的時候,如果沒有寫 super() ,就調用 this 來初始化 state 的話會報錯 註 2: 但是 ES7 出現之後可以不用寫 constructor 甚至不用調用 this 就可以寫 state
  • getDerivedStateFromPropsconstructor 初始化後或者 props/state 改變時執行
  • render:將 component 輸出到 DOM
  • componentDidMountrender 執行完畢且放到 DOM 上後呼叫
    • 第一次 render 後唯一觸發的生命週期函數,只會執行一次
    • 通常用於處理數據,比方說 call API 資料

Update

  • getDerivedStateFromPropsconstructor 初始化後或 props/state 改變時執行
  • shouldComponentUpdate:在 render 前執行
    • 用來決定 component 會不會跟著被更新的 state 或者 props 而有所變動
    • 回傳 false 時,可以阻止 component 重新 render,避免沒必要的更新,以達到優化效能的效果
    • 註:因為 React 的預設行為是每次只要有更新就會觸發重新 render,所以在使用這個方法時要特別注意。雖然這個方法的存在是為了效能最佳化,使我們可以透過比對前後的 state, prop 來決定是否做此次的更新,但透過回傳 false 不會阻止子元件在自身 state 改變時的重新 render
  • render:將 component 輸出到 DOM
  • getSnapshotBeforeUpdate:在 render 執行完但是 DOM 真正更新前 執行
    • 讓元件在 DOM 發生變化前捕獲一些信息(如滾動位置),用意是把更新前最後一刻的 DOM 狀況紀錄下來
    • 註:當使用了這個方法時,會回傳一個值作為參數傳遞給 componentDidUpdate()
  • componentDidUpdaterender 執行完畢且 DOM 也更新完畢後會馬上執行
    • 在這邊也非常適合做網路請求,比方說 call API 資料
    • 註:需要特別注意的是,在 componentDidUpdate 中記得比較前後的值是否有差異,如果有差異的話才做新的一次網路請求,否則可能會影響 component 的效能。

Unmount

  • componentWillUnmount:元件被移除時呼叫,只會執行一次
    • 通常用來進行一些收尾工作,比方說移除新增的元素、監聽事件與定時器

參考來源: Class-based Component 的生命週期(實際操作) Class-based Component 生命週期方法(用法講解)) [Day06]生命週期 Lifecycle(Class Component) React lifeCycle 生命週期

3. 請問 class component 與 function component 的差別是什麼?

答:

class component

  • 透過 ES6 的 class 語法來實作物件導向的 class component
  • 由於 this 指向的關係,state 和 props 會拿到最新的結果,但是會較不易於進行 callback 操作
  • 提供許多 lifecycle method 使用,方便管理較複雜的 component 狀態
  • 關注的是「每個生命週期」要做的事情
  • 只要調用了 setState function 就會觸發 component 的重新渲染。

function component

  • 透過 function 來實作的 function component
  • 透過閉包的形式來管理狀態(props 會一直是傳進來的那個)
  • 生命週期的方法,是以 useEffect 來決定 render 要做的事情
  • 關注的是「每次的 render」要做的事情,因為每一次的 render,都是「重新」呼叫一次 function,並且會記住「當次 render 」傳入的值
  • 把許多 method 都寫在 function 中,自己本身就像是 render function,較容易抽出共同邏輯,或是進行模組化測試
  • 如果我們使用 setState 時,是用與目前的 state 相同的值來更新 state 的話,React 將會跳過子 component 的 render 及該 component effect 的執行,但要注意依然會 render 該 component。

Class component 與 Function component 兩者用法差異的實際範例推薦: How Are Function Components Different from Classes? 從實際案例看 class 與 function component 的差異

參考來源: [week 22] 再探 React:Function component vs Class component [Day 8]Class component && Functional component [Day 07] Functional Component v.s Class Component React Class-based vs Functional Component - 從特性淺談兩種寫法之異同

4. uncontrolled 跟 controlled component 差在哪邊?要用的時候通常都是如何使用?

答:

兩者之間最大的差別,其實就在於 component 的資料是否由 React 控制

uncontrolled component:資料不受 React 的控制

  • 例如 input、textarea 等表單元素,如果作為 component 通常不會用 useState 來綁定 state
  • 若想取得 uncontrolled component 的值,可透過直接操作 DOM 或使用 useRef 來選取特定元素

controlled component:資料受到 React 的控制

  • 也就是 component 有透過 useState 來保存資料,setState 來更新資料
  • 如果將資料的控制權交給 React 來處理,畫面就會根據 state 是否改變來重新渲染

參考來源: [week 21] React Hooks API:useState & 再戰 Todo List [Day 15] React controlled components v.s uncontrolled components React Controlled Component 與 Uncontrolled Component 差別