---
# System prepended metadata

title: '[Udemy - React] 課程流程筆記（sec. 15）'
tags: [React, Udemy 課程筆記, 前端筆記]

---

# [Udemy - React] 課程流程筆記（sec. 15）

###### tags: `Udemy 課程筆記` `React` `前端筆記`

## 189. custom hook 就是從 component 把共用函式取出

- ==custom hook 必須遵從 hooks 的命名規則：useXXX==
- custom hook -> function（不需要有 view，是用來讓各個 component 用的共同函式）
> 1. 共用的 stateful function
> 2. custom hook 的 state 更新就會觸發引用 custom hook 之 parent component 的 re-render(re-execute)


- component -> 需要回傳 view（也就是 JSX）

![](https://hackmd.io/_uploads/ryroMp5Es.png)

- 將共用邏輯抽出（custom hook）之後 component 內就可以刪除
- custom hook 可以回傳各種東西（也是 pure function 的概念，給同樣的 input 就會得到同樣的 output）

## 190. custom hook 就是 function

- function 是透過 parameters 增加 function 的靈活度
- 練習：
    1. 用開關（`true` / `false`）
    2. 傳整個 callback
    > 傳 callback 就要記得使用的東西是什麼，因為 `useCallback` 就只是回傳第一個參數（callback）
 
 
## 193. custom hook 可以達到類似 factory function 的效果

- 一個函式（工廠）建立不同的零件（object -> properties and methods）
- factory function 建立 methods 也可以用 `useCallback` 包，確保 function 不會因為 re-render 而重建
> `useCallback` 有 external state 才要放 dependency array，因為那些東西是這個函式不相干的，需要用 dependency array 偵查是否有變動
> methods 可以開 paramter 省去要回頭確保叫用 custom hook 的 argument 是否有變動（`useMemo`, `useCallback`）
- 同樣區域的東西可以用 object 組合，但是不相干的東西就是放在其他 parameter（比如說 `testFn({requestConfig: url: '', method: ''}, callback)`（因為 `requestConfig` 只處理打 API 的設定，額外的 function 就不要放在裡面，而是另開的一個 parameter）
- `function.bind()` 用來 preconfigure 函式：
1. 會回傳叫用 `function.bind()` 的函式（但不會叫用 function 本身）
2. `function.bind(thisArg[, arg1[, arg2[, ...]]])` 第一個參數是綁定 `this`（如果沒有要綁定 `this` 就給 `null`），第二個參數之後是讓其參數自動加到原來的函式

```javascript=
const printFn = (name) => {
  console.log(name);
}

/* 透過 function.bind() 把 'lun' 綁到 printFn 的 argument 上 */
const preConfigedPrintFn = printFn.bind(null, 'lun');
preConfigedPrintFn(); // 'lun'

/* 以上等價於 */
const wrapperFn = (name) => {
  printFn(name);
}

wrapperFn('lun'); // 'lun'
```

```javascript=
const printFn = (name, secondArg) => {
  console.log(this.name);
  console.log(secondArg);
}
```

### 範例一：上層 `useCallback` 包函式，custom hook `useCallback` 回傳函式確保不會重複 re-render

![](https://hackmd.io/_uploads/S1CSiitwi.png)


```javascript=
// src/App.js

function App() {
  const [tasks, setTasks] = useState([]);

  const fetchConfig = {
    method: 'GET',
    requestUrl: 'https://udemy-react-15-190-default-rtdb.firebaseio.com/tasks.json',
  };

  // useCallback 包覆函式傳給 custom hook，確保 custom hook 不會因為新 scope 而建立新 callback 給 app component
  const getDataHandler = useCallback(data => {
    const loadedTasks = Object.entries(data).map(([key, value]) => ({ id: key, ...value }));

    setTasks(loadedTasks);
  }, []);

  // sendRequest 為 custom hook 回傳的 callback，處理打 API 的共用邏輯
  const { isLoading, error, sendRequest: fetchTasks } = useHttp({ handler: getDataHandler });

  useEffect(() => {
    fetchTasks({ fetchConfig });
    // 非 useEffect 內的東西就是 external state，所以需要放入 dependency 中記憶
  }, [fetchTasks]);

  const taskAddHandler = task => {
    setTasks(prevTasks => prevTasks.concat(task));
  };

  return (
    <React.Fragment>
      <NewTask onAddTask={taskAddHandler} />
      <Tasks items={tasks} loading={isLoading} error={error} onFetch={fetchTasks} />
    </React.Fragment>
  );
}
```

```javascript=
// src/hooks/use-http.js

// 叫用時傳入 callback
const useHttp = ({ handler }) => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  // 傳上去的 callback 決定其上層是否需要再次打 API
  const sendRequest = useCallback(
    async ({ fetchConfig }) => {
      const { method, requestUrl, headers, body } = fetchConfig;
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(requestUrl, {
          method: method ?? 'GET',
          headers: headers ?? {},
          ...(body && { body: JSON.stringify(body) }),
        });

        if (!response.ok) {
          throw new Error('Request failed!');
        }

        const data = await response.json();

        // 往外找拿 handler
        handler(data);
      } catch (err) {
        setError(err.message || 'Something went wrong!');
      } finally {
        setIsLoading(false);
      }
    },
    // 非 useEffect 內的東西就是 external state，需要放入 dependency 記憶
    [handler]
  );

  return {
    isLoading,
    error,
    sendRequest,
  };
};
```

### 範例二：可以更進一步省略非必要的 `useCallback`

- 回到函式本質，函式因為 parameter 而產生更多的靈活性，且函式透過 input / output 使得每次執行完畢的結果都相同就稱之為 pure function（也就是說每次傳入同樣的 parameter 就會得到同樣的結果）
- 所以從原本是由傳 callback 給 `useHttp` 的到的工廠函式的結果，倒不如直接傳 paramter 給工廠函式的結果的函式
- 函式從 input 拿東西並不算 external state

![](https://hackmd.io/_uploads/Hkav-3Fvi.png)

```javascript=
// src/App.js

function App() {
  const [tasks, setTasks] = useState([]);

  const fetchConfig = {
    method: 'GET',
    requestUrl: 'https://udemy-react-15-190-default-rtdb.firebaseio.com/tasks.json',
  };

  // 不傳 callback 給工廠函式 useHttp
  const { isLoading, error, sendRequest: fetchTasks } = useHttp();


  useEffect(() => {
    const getDataHandler = data => {
      const loadedTasks = Object.entries(data).map(([key, value]) => ({ id: key, ...value }));

      setTasks(loadedTasks);
    };
    // 而是直接傳 callback 給工廠函式之結果，這樣子工廠函式建立的函式就不用往外找，而是直接拿 input 即可
    fetchTasks({ fetchConfig, handler: getDataHandler });
  }, [fetchTasks]);

  const taskAddHandler = task => {
    setTasks(prevTasks => prevTasks.concat(task));
  };

  return (
    <React.Fragment>
      <NewTask onAddTask={taskAddHandler} />
      <Tasks items={tasks} loading={isLoading} error={error} onFetch={fetchTasks} />
    </React.Fragment>
  );
}

```

```javascript=
// src/hooks/use-http.js

// 工廠函式不拿 callback
const useHttp = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  // 而是直接傳 callback 給工廠函式生成的結果
  const sendRequest = useCallback(async ({ fetchConfig, handler }) => {
    const { method, requestUrl, headers, body } = fetchConfig;
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch(requestUrl, {
        method: method ?? 'GET',
        headers: headers ?? {},
        ...(body && { body: JSON.stringify(body) }),
      });

      if (!response.ok) {
        throw new Error('Request failed!');
      }

      const data = await response.json();

      // 直接拿 input，就不需要往外找了
      handler(data);
    } catch (err) {
      setError(err.message || 'Something went wrong!');
    } finally {
      setIsLoading(false);
    }
  }, []);

  return {
    isLoading,
    error,
    sendRequest,
  };
};

```

## Recap

1. 需要注意，每個 custom hook 都是獨立的 state，所以並不會影響其他 components 內叫用 custom hook 的 state（即便其他 components 都叫用同一個 custom hook，但因為 components -> call function，所以不同 components 都會建立新的 scope）
2. custom hook 就是專注處理共同的 state function
3. 別忘了「工廠函式」（factory function）
4. 函式可以透過 parameter 達到靈活性（callback 當然也可以當作 parameter 傳入）


## 課程

[React - The Complete Guide (incl Hooks, React Router, Redux) - sec. 15](https://www.udemy.com/course/react-the-complete-guide-incl-redux/learn/lecture/25599852#questions)