useState

发布时间 2023-06-20 14:19:46作者: 随漫人

what is hook?

  • hook就是JavaScript函数,这个函数可以帮你勾入React State以及生命周期等特性;useState=>钩入状态 useEffect=>钩入生命周期;
  • 只能在函数顶层去调用hook,不能在if语句和for循环里面去调用hook;
  • 普通函数里面不能使用hooks;
  • 自定义hooks,use开头,可以使用react提供的其他hooks,必须是use开头。

why use hooks?

  • 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

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

要用 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)

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

 1 function A () {
 2     const [count, setCount] = useState(4);
 3     setCount(count + 1);
 4     setCount(count + 1);
 5     console.log('A: ', count) // ?
 6 }
 7 function B () {
 8     const [count, setCount] = useState(4);
 9     setCount(prev => prev + 1);
10     setCount(prev => prev + 1);
11     console.log('B: ', count) // ?
12 }
13 // Answer
14 // A: 5
15 // B 6

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

另外 Pass the state 是每一次都會重新跑,而 Pass the function 只會跑第一次,直接來看 範例 比較快     https://codepen.io/hannahpun/pen/VwjxPWM

這就是狀態的延遲初始化,每当 React 重新渲染组件时,都会执行useState(initialState)。 如果初始状态是原始值(数字,布尔值等),则不会有性能问题。

当初始状态需要昂贵的性能方面的操作时,可以通过为useState(computeInitialState)提供一个函数来使用状态的延迟初始化

getInitialState()仅在初始渲染时执行一次,以获得初始状态。在以后的组件渲染中,不会再调用getInitialState(),从而跳过昂贵的操作。

1 function init () {
2     console.log('run function');
3     return 4;
4 }
5 // Run Everytime
6 const [count, setCount] = useState(4);
7 const [count, setCount] = useState(init());
8 // Run only the very first time when your component render
9 const [count, setCount] = useState(() => init());

若用object    數據的不可變性

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

过时状态

闭包是一个从外部作用域捕获变量的函数。

闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。 由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。

来看看一个过时的状态是如何表现出来的。组件<DelayedCount>延迟3秒计数按钮点击的次数。

 1 function DelayedCount() {
 2   const [count, setCount] = useState(0);
 3 
 4   const handleClickAsync = () => {
 5     setTimeout(function delay() {
 6       setCount(count + 1);
 7     }, 3000);
 8   }
 9 
10   return (
11     <div>
12       {count}
13       <button onClick={handleClickAsync}>Increase async</button>
14     </div>
15   );
16 }

count 变量不能正确记录实际点击次数,有些点击被吃掉。

delay() 是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。

为了解决这个问题,使用函数方法来更新count状态:

 1 function DelayedCount() {
 2   const [count, setCount] = useState(0);
 3 
 4   const handleClickAsync = () => {
 5     setTimeout(function delay() {
 6       setCount(count => count + 1);
 7     }, 3000);
 8   }
 9 
10   return (
11     <div>
12       {count}
13       <button onClick={handleClickAsync}>Increase async</button>
14     </div>
15   );
16 }