avatarHannah Lin

总结

本文是 React Hooks 系列文章中的第一篇,介绍了 useState 和 useEffect 这两个最基本的 Hooks。

摘要

作者首先介绍了为什么需要使用 Hooks,然后列出了使用 Hooks 的一些规则,例如只能在 React Function 中呼叫 Hook,只在最上层呼叫 Hook,等等。接着作者介绍了 useState,并提供了一些使用实例和注意事项。之后,作者介绍了 useEffect,并介绍了 useEffect 的两个参数,以及不同的依赖项对 useEffect 的执行时机的影响。最后,作者简单介绍了在 TypeScript 中如何定义 useState 的类型。

观点

  • useState 和 useEffect 是 React Hooks 中最基本的两个 Hooks
  • useState 可以用来更新状态,可以用来更新单个状态也可以用来更新多个状态
  • useEffect 可以用来在组件渲染之后执行一些副作用,可以在不同的依赖项下有不同的执行时机
  • useState 和 useEffect 在 TypeScript 中可以定义类型,使用更加方便和安全。

[React Hook 筆記] 從最基本的useState, useEffect 開始

(新增 typing 於 2023/11/7)

React Hook 系列文

1. 從最基本的 Hook 開始 useState, useEffect 2. Memorized Hook- useMemo, useCallback 3. useRef 4. useContext 5. useReducer 6. useLayoutEffect 7. Custom Hooks

本來不想寫這篇的,因為網路上實在太多關於 useStateuseEffect 文章,但還是想要把一些重點寫出來

🔖 文章索引
1. 用 React Hooks 的規則
2. useState
3. useEffect
4. useState/useEffect in typeScript

Why use hooks?

自己寫 React 也兩年了,針對 Hook 跟舊的 Class 寫法跟做比較。

  • Keep state logic close to where it is used: 不需要像 Class 再分 View 跟 Lifecycle 的檔案
  • Often requires less code and less prop passing: 可以用更少的程式碼撰寫
  • Can be more granular: 只需要更新 specific dependencies 變動就好
  • Improves reliability and allows for logical grouping of functionality
  • 藉由 custom hook 更容易 reuse

想轉 Hooks 的成本也蠻低的,不需要整個專案都 migrate 到 Hooks,他可以跟 Class 並存,所以若手上有 React 案子可以先從小的 components 先試試看 Hooks。

用 React Hooks 的規則

使用 Hooks 前有一些要遵守的規則

  • 只能在 React Function 中呼叫 Hook class component 是不能用的
// Class-based Component
class App extends React.component {
  render () { return </> }
}
// Function Component
function App () {
}
  • 只在最上層呼叫 。不要在迴圈、條件式或是巢狀的 function 內呼叫 Hook,這樣你才能確保每一次 component render 時 Hook 被呼叫順序都要是一樣的
function Appp () {
    ✅ const [state, setState] = useState()
    ❌ if (true) { useState() }
    ❌ function B () { useState() }
    ❌ for(xx) { useState() }  
}

useState

useState 會回傳一個包含兩個值的 array,第一個值是 state、第二個值是用來更新 state 的函式。每當 state 值改變,就會觸發 re-render

const [appleCount, setAppleCount] = useState(1);
console.log(appleCount); //1
setAppleCount(prev => prev + 1);
console.log(appleCount) // 2

以上程式碼就代表 appleCount這個變數的初始值是 1 ,只有用 setAppleCount這個更新函式更新 appleCount 才會觸發 re-render

State Hook 可以用不止一次

function ExampleWithManyStates() {
  // 宣告多個 state 變數!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

要用 setCount(count + 1) 還是 setCount(prev => prev + 1) ?

更新 state 的函式可以丟兩種參數

  • Pass the state, Run Everytime. eg. setCount(count + 1)
  • Pass the function, Run only the very first time when your component render. eg. setCount(prev => prev + 1)

兩種都可以用,但需要了解其中有什麼不同,若懶得了解那建議用後者可以減少出錯機會。以下先出一道題

function A () {
    const [count, setCount] = useState(4);
    setCount(count + 1);
    setCount(count + 1);
    console.log('A: ', count) // ?
}
function B () {
    const [count, setCount] = useState(4);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    console.log('B: ', count) // ?
}
// Answer
// A: 5
// B 6

有沒有答對呢? 在 A 裡面第二個 setCount會覆蓋第一個,因為他們抓的 count 都是 4,但若是用 function version 來設定 state 就會記住 prevState 再去做運算

另外 Pass the state 是每一次都會重新跑,而 Pass the function 只會跑第一次,直接來看 範例 比較快

function init () {
    console.log('run function');
    return 4;
}
// Run Everytime
const [count, setCount] = useState(4);
const [count, setCount] = useState(init());
// Run only the very first time when your component render
const [count, setCount] = useState(() => init());
https://codepen.io/hannahpun/pen/VwjxPWM

若用 Object 呢

const [state, setState] = useState({count: 4, name: 'blue'});
setState(prevState => {...prevSate, count: prevSate.count + 1}; console.log(state); // {count: 5, name: 'blue'}
setState(prevState => {count: prevSate.count + 1}; 
console.log(state); // {count: 5} name 消失,因為他會整個覆蓋掉

useEffect

任何會產生 side Effect 的行為都應該 Effect Hook 裡執行。他和 componentDidMountcomponentDidUpdate,與 componentWillUnmount 有著同樣的宗旨,但整合進一個單一的 API。

useEffect 有兩個參數,第一個參數是 Effect function,第二個則是 depandancy array。 根據不同 depandancy 決定何時要執行 Effect function

Once

useEffect(() => {
    // Just run the first time
    console.log('render')
  }, [])

after every render

記得是 after rendering 才會執行若你想要 before rendering 執行基本上是辦不到的 [延伸閱讀: React Hook to Run Code After Render]

useEffect(() => {
    // run after every rendering
    console.log('render')
})

state/props change

useEffect(() => {
    // When title or name changed will render
    console.log('render')
}, [title, name])

cleanup

useEffect(() => {
   return () => {
       // Cleanup whatever we did last time
   }
}, [])

可以來範例玩玩,我有串 API

https://codepen.io/hannahpun/pen/abmGGbP

Typing useState

Typing hooks 其實並不難,一般來說在初始化的時候,TS 就會幫你自動定義型別了

const [appleCount, setAppleCount] = useState(1); // type is number
setAppleCount("2"); // error: string not assignable to number!

但實務上,很多 state 得初始值都是 nullundefined。這時就要使用 generic (允許你可以使用多個 types)。例如 state 一開始是 null ,set state 後才會是 string

// title is string or null
const [title, setTitle] = useState<string | null>(null) // type is string or null
setTitle("hllo orld") // success

// score is number or undefined
const [score, setScore] = useState<number | undefined>(undefined) // type is number or undefined
setScore(100) // success

若 state value 是個複雜的 object,generic 也可以寫成 typeinterface

interface Member {
  username: string,
  age?: number
}
const [member, setMember] = useState<Member | null>(null)

{member?.username} // type is string

Typing useEffect

恭喜你,不需要管 useEffect的 type,因為他不會回傳任何值。You can write this hook as normal.

React
React Hook
Recommended from ReadMedium