State Hook
State Hook是一個在函數組件中使用的函數, 該函數名字是useState, 用於在函數組件中提供狀態
讓React的函數組件能夠像類組件一樣擁有state
useState
函數有一個參數, 這個參數的值表示狀態的默認值
在我們引入react的時候順帶先引入一下stateHook
import React, { useState } from 'react';
useState
函數的返回值是一個數組, 該數組一定包含兩項
- 數組的第一項爲狀態的值
- 數組的第二項是函數
useState的基本使用方法如下
import React, { useState } from 'react';
export default function App(props) {
// 在函數組件中state可以是一個原始類型
// 使用一個狀態, 該狀態的默認值是0, 調用useState後返回一個數組
// 數組的第一項是默認值, 數組的第二項是一個函數用來改變這個state
const arr = useState(0);
const number = arr[0];
const setNumber = arr[1];
return (
<div>
{/* 點擊了按鈕以後調用setNumber, setNumber接收一個參數就是要將number改變爲何值 */}
<span onClick = { () => { setNumber(number + 1) } }> + </span>
<span>{ number }</span>
<span onClick = { () => { setNumber(number - 1) } }> - </span>
</div>
)
}
我們是否有發現用hook來給函數組件裝飾了以後實現同樣得功能函數組件會比類組件寫法簡潔很多
其實我們還可以更加簡潔, 如果大家學習了ES6的解構的話, 我相信對上方代碼的某一塊能夠思考到這塊
const arr = useState(0);
const number = arr[0];
const setNumber = arr[1];
// 上方代碼的這一塊如果我們用解構的話是可以濃縮爲一行如下
const [number, setNumber] = useState(0); // 效果是一模一樣的, 未來我們也會普遍使用這種方式進行書寫
一個函數組件可以擁有多個state, 這種寫法非常有利於橫向切分關注點.
這是什麼意思呢, 也就是說在函數組件中我們可以多次調用useState從而返回多個不同的state, 而在類組件中我們必須所有的狀態都書寫在state這個對象中, 如果數據多了會讓state解構變得特別複雜且不容易閱讀
下方我們就通過useState生成了兩個state
import React, { useState } from 'react';
export default function App(props) {
const [number, setNumber] = useState(0); // 定義一個狀態默認值爲0 來控制頁面中展示的數字值
const [isVisible, setVisible] = useState(true); // 定義一個狀態默認值爲true, 來控制類名爲wrapper的div是否顯示
return (
<div>
<div style = {{
display: isVisible ? 'block' : 'none'
}} className = 'wrapper'>
{/* 點擊了按鈕以後調用setNumber, setNumber接收一個參數就是要將number改變爲何值 */}
<span onClick={() => { setNumber(number + 1) }}> + </span>
<span>{number}</span>
<span onClick={() => { setNumber(number - 1) }}> - </span>
</div>
<button onClick = { () => { setVisible(!isVisible) } }>顯示/隱藏</button>
</div>
)
}
瞭解了useState的基本操作, 我們來看看他的原理
不知道朋友們有沒有一個疑問, 就是函數組件每次渲染都會重新進行調用把函數體重新走一次, 但是useState的值卻沒有一直被賦予初值, 這是爲什麼呢? 我們帶着這個問題來看看原理
看了上方的圖以後, 大家可能想值得那這個狀態怎麼被清空呢, 清空方式只有一個那就是該函數組件被卸載, 所以注意: 如果函數組件被卸載則表格被清空那麼調用useState會被賦初值, 所以爲了避免出現bug, 根據react渲染原理, 我們要儘量用style來控制元素的消失和隱藏
使用useState的注意點
- useState最好寫到函數的起始位置, 主要是便於閱讀
- useState嚴禁出現在代碼塊(判斷和循環等)中
- useState返回的函數(數組的第二項), 這個函數的引用是不會變化的(優化性能)
- 如果使用函數改變數據, 若數據和之前的數據完全相等(使用Object.is), 則不會重新渲染, 由於Object.is是淺比較, 所以如果狀態是一個對象的時候要小心操作了
- 如果使用函數改變數據, 傳入的值不會和原來的數據進行合併而是直接進行替換(跟setState完全不一樣), 所以在修改對象的時候, 我們要先將之前的對象保存下來
export default function App(props) {
const [obj, setObj] = useState({
name: '小明',
age: 18
})
return (
<>
{obj.name}, {obj.age}
<button onClick={() => {
setObj({
...obj, // 先將之前的對象進行展開
age: 20
})
}}>change</button>
</>
)
}
- 不要直接去改變state的值
- 如果要實現強制刷新組件的情況: 如果是類組件我們都會使用forceUpdate, 在函數組件中, 我們可以用useState來實現, 使用useState的改變state的函數傳入一個空對象, 因爲每次傳入一個空對象的地址不一樣所以一定會刷新
export default function App(props) {
// const [number, setNumber] = useState(0); // 定義一個狀態默認值爲0 來控制頁面中展示的數字值
// const [isVisible, setVisible] = useState(true); // 定義一個狀態默認值爲true, 來控制類名爲wrapper的div是否顯示
// let element = isVisible ? <ChildA /> : null;
const [, forceUpdate] = useState({})
return (
<>
<button onClick={() => {
forceUpdate({})
}}>強制刷新</button>
</>
)
}
- 如果某些狀態之間沒有必然的聯繫, 應該分化爲不同的狀態而非合併成一個對象
- 和類組件一樣, 函數組件的狀態更改在某些時候是一步的(dom事件下), 如果是異步的更改, 則多個狀態的更改會合並, 此時不能信任之前的狀態, 而應該使用回調函數的方式改變狀態
export default function App(props) {
const [num, setNum] = useState(0);
return (
<>
<span>{num}</span>
<button onClick={() => {
setNum(num + 1) // 不會立馬運行會等到事件完全走完以後一起運行, 同時多個更改會合併到一起執行
setNum(num + 1) // 因爲是一步的, 運行到第二個setNum的時候num的基礎值還是0
}}>num++</button>
</>
)
}
// 如果我們確實需要進行對num的兩次更改請務必使用回調函數的方式
export default function App(props) {
const [num, setNum] = useState(0);
return (
<>
<span>{num}</span>
<button onClick={() => {
setNum(curNum => curNum + 1) // 傳入的函數會在事件走完以後按照順序依次執行
setNum(curNum => curNum + 1)
}}>num++</button>
</>
)
}
- 就是之前標紅的, 因爲組件被卸載會導致狀態表清空, 所以當我們需要頻繁的隱藏顯示一個組件的時候最好使用style的display的方式來進行操作, 同時這種操作從渲染原理層面來說也更利於效率的提升