1. 程式人生 > 實用技巧 >React函式式元件值之useState()

React函式式元件值之useState()

  react hooks 是 React 16.8 的新增特性。 它可以讓我們在函式元件中使用 state 、生命週期以及其他 react 特性,而不僅限於 class 元件。react hooks 的出現,標示著 react 中不會在存在無狀態元件了,只有類元件和函式元件。

  狀態是隱藏在元件中的資訊,元件可以在父元件不知道的情況下修改其狀態。相比類元件,函式元件足夠簡單,要使函式元件具有狀態管理,可以useState() Hook。

一、基礎用法

  定義一個state,以及更新 state 的函式。在初始渲染期間,返回的狀態(state) 與傳入的第一個引數 (initialState) 值相同。setState函式用於更新 state。它接收一個新的 state 值並將元件的一次重新渲染加入佇列。在後續的重新渲染中,useState 返回的第一個值將始終是更新後最新的 state。

1 const [state, setState] = useState(initialState);
2 setState(newState);

例子:

1   function App () {
2     const [ count, setCount ] = useState(0)
3     return (
4       <div>
5         點選次數: { count } 
6         <button onClick={() => { setCount(count + 1)}}>點我</button>
7       </div>
8
) 9 }

  當我們在使用 useState 時,修改值時傳入同樣的值,元件不會重新渲染,這點和類元件setState保持一致。

二、初始值與初始函式

  useState 支援我們在呼叫的時候直接傳入一個值,來指定 state 的預設值,比如這樣 useState(0), useState({ a: 1 }), useState([ 1, 2 ]),還支援我們傳入一個函式,來通過邏輯計算出預設值,比如這樣

 1 function App (props) {
 2     const [ count, setCount ] = useState(() => {
 3
return props.count || 0 4 }) 5 return ( 6 <div> 7 點選次數: { count } 8 <button onClick={() => { setCount(count + 1)}}>點我</button> 9 </div> 10 ) 11 }

  useState 中的函式只會在初始化的時候執行一次。

三、函式式更新

  如果新的 state 需要通過使用先前的 state 計算得出,那麼可以將函式傳遞給 setState。該函式將接收先前的 state,並返回一個更新後的值。下面的計數器元件示例展示了 setState 的兩種用法:

 1 function Counter() {
 2   const [count, setCount] = useState(0);
 3   function handleClick() {
 4     setCount(count + 1)
 5   }
 6   function handleClickFn() {
 7     setCount((prevCount) => {
 8       return prevCount + 1
 9     })
10   }
11   return (
12     <>
13       Count: {count}
14       <button onClick={handleClick}>+</button>
15       <button onClick={handleClickFn}>+</button>
16     </>
17   );
18 }

  handleClick和handleClickFn一個是通過一個新的 state 值更新,一個是通過函式式更新返回新的 state。現在這兩種寫法沒有任何區別,但是如果是非同步更新的話,區別就顯現出來了。

 1 function Counter() {
 2   const [count, setCount] = useState(0);
 3   function handleClick() {
 4     setTimeout(() => {
 5       setCount(count + 1)
 6     }, 3000);
 7   }
 8   function handleClickFn() {
 9     setTimeout(() => {
10       setCount((prevCount) => {
11         return prevCount + 1
12       })
13     }, 3000);
14   }
15   return (
16     <>
17       Count: {count}
18       <button onClick={handleClick}>+</button>
19       <button onClick={handleClickFn}>+</button>
20     </>
21   );
22 }

  當設定為非同步更新,點選按鈕延遲到3s之後去呼叫setCount函式,當快速點選按鈕時,也就是說在3s多次去觸發更新,但是隻有一次生效,因為 count 的值是沒有變化的。而當使用函式式更新 state 的時候,這種問題就沒有了,因為它可以獲取之前的 state 值,也就是程式碼中的 prevCount 每次都是最新的值。

  其實這個特點和類元件中 setState 類似,可以接收一個新的 state 值更新,也可以函式式更新。如果新的 state 需要通過使用先前的 state 計算得出,那麼就要使用函式式更新。因為setState更新可能是非同步,當你在事件繫結中操作 state 的時候,setState更新就是非同步的。

  一般操作state,因為涉及到 state 的狀態合併,react 認為當你在事件繫結中操作 state 是非常頻繁的,所以為了節約效能 react 會把多次 setState 進行合併為一次,最後在一次性的更新 state,而定時器裡面操作 state 是不會把多次合併為一次更新的。

四、使用優化

  在 React 應用中,當某個元件的狀態發生變化時,它會以該元件為根,重新渲染整個元件子樹。

 1 function Child({ onButtonClick, data }) {
 2   console.log('Child Render')
 3   return (
 4     <button onClick={onButtonClick}>{data.number}</button>
 5   )
 6 }
 7 
 8 function App() {
 9   const [number, setNumber] = useState(0)
10   const [name, setName] = useState('hello') // 表單的值
11   const addClick = () => setNumber(number + 1)
12   const data = { number }
13   return (
14     <div>
15       <input type="text" value={name} onChange={e => setName(e.target.value)} />
16       <Child onButtonClick={addClick} data={data} />
17     </div>
18   )
19 }

  上述程式碼中,子元件引用了number相關資料,但是當name相關資料發生變化,也會重繪整個元件,子元件雖然沒有任何變化,也會重繪。為了避免不必要的子元件的重渲染,需要使用useMemo和useCallback的Hook。

 1 function Child({ onButtonClick, data }) {
 2   console.log('Child Render')
 3   return (
 4     <button onClick={onButtonClick}>{data.number}</button>
 5   )
 6 }
 7 
 8 Child = memo(Child)
 9 
10 function App() {
11   const [number, setNumber] = useState(0)
12   const [name, setName] = useState('hello') // 表單的值
13   const addClick = useCallback(() => setNumber(number + 1), [number])
14   const data = useMemo(() => ({ number }), [number])
15   return (
16     <div>
17       <input type="text" value={name} onChange={e => setName(e.target.value)} />
18       <Child onButtonClick={addClick} data={data} />
19     </div>
20   )
21 }
22 
23 export default App;

  把“建立”函式和依賴項陣列作為引數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算。如果沒有提供依賴項陣列,useMemo 在每次渲染時都會計算新的值。

  useCallback返回一個 memoized 回撥函式。useCallback(fn, deps) 相當於 useMemo(() => fn, deps)。

  useCallback 和 useMemo 引數相同,第一個引數是函式,第二個引數是依賴項的陣列。主要區別是 React.useMemo 將呼叫 fn 函式並返回其結果,而 React.useCallback 將返回 fn 函式而不呼叫它。