做者:Dmitri Pavlutinhtml
譯者:前端小智前端
來源:dmitripavlutin.comreact
阿里雲最近在作活動,低至2折,真心以爲很划算了,能夠點擊本條內容或者連接進行參與: promotion.aliyun.com/ntms/yunpar…git
騰訊雲最近在作活動,百款雲產品低至 1 折,能夠點擊本條內容或者連接進行參與github
狀態是隱藏在組件中的信息,組件能夠在父組件不知道的狀況下修改其狀態。我更偏心函數組件,由於它們足夠簡單,要使函數組件具備狀態管理,能夠useState()
Hook。數組
本文會逐步講解如何使用useState()
Hook。此外,還會介紹一些常見useState()
坑。微信
useState()
進行狀態管理無狀態的函數組件沒有狀態,以下所示(部分代碼):閉包
import React from 'react';
function Bulbs() {
return <div className="bulb-off" />;
}
複製代碼
能夠找 codesandbox 嘗試一下。less
運行效果:async
這時,要如何添加一個按鈕來打開/關閉燈泡呢? 爲此,我們須要具備狀態的函數組件,也就是有狀態函數組件。
useState()
是實現燈泡開關狀態的 Hoook,將狀態添加到函數組件須要4
個步驟:啓用狀態、初始化、讀取和更新。
要將<Bulbs>
轉換爲有狀態組件,須要告訴 React:從'react'
包中導入useState
鉤子,而後在組件函數的頂部調用useState()
。
大體以下所示:
import React, { useState } from 'react';
function Bulbs() {
... = useState(...);
return <div className="bulb-off" />;
}
複製代碼
在Bulbs
函數的第一行調用useState()
(暫時不要考Hook的參數和返回值)。 重要的是,在組件內部調用 Hook 會使該函數成爲有狀態的函數組件。
啓用狀態後,下一步是初始化它。
始時,燈泡關閉,對應到狀態應使用false
初始化 Hook:
import React, { useState } from 'react';
function Bulbs() {
... = useState(false);
return <div className="bulb-off" />;
}
複製代碼
useState(false)
用false
初始化狀態。
啓用和初始化狀態以後,如何讀取它?來看看useState(false)
返回什麼。
當 hook useState(initialState)
被調用時,它返回一個數組,該數組的第一項是狀態值
const stateArray = useState(false);
stateArray[0]; // => 狀態值
複製代碼
我們讀取組件的狀態
function Bulbs() {
const stateArray = useState(false);
return <div className={stateArray[0] ? 'bulb-on' : 'bulb-off'} />;
}
複製代碼
<Bulbs>
組件狀態初始化爲false
,能夠打開 codesandbox 看看效果。
useState(false)
返回一個數組,第一項包含狀態值,該值當前爲false
(由於狀態已用false
初始化)。
我們可使用數組解構來將狀態值提取到變量on
上:
import React, { useState } from 'react';
function Bulbs() {
const [on] = useState(false);
return <div className={on ? 'bulb-on' : 'bulb-off'} />;
}
複製代碼
on
狀態變量保存狀態值。
狀態已經啓用並初始化,如今能夠讀取它了。可是如何更新呢?再來看看useState(initialState)
返回什麼。
####1.4 更新狀態
用值更新狀態
我們已經知道,useState(initialState)
返回一個數組,其中第一項是狀態值,第二項是一個更新狀態的函數。
const [state, setState] = useState(initialState);
// 將狀態更改成 'newState' 並觸發從新渲染
setState(newState);
// 從新渲染`state`後的值爲`newState`
複製代碼
要更新組件的狀態,請使用新狀態調用更新器函數setState(newState)
。組件從新渲染後,狀態接收新值newState
。
當點擊開燈
按鈕時將燈泡開關狀態更新爲true
,點擊關燈
時更新爲 false
。
import React, { useState } from 'react';
function Bulbs() {
const [on, setOn] = useState(false);
const lightOn = () => setOn(true);
const lightOff = () => setOn(false);
return (
<>
<div className={on ? 'bulb-on' : 'bulb-off'} />
<button onClick={lightOn}>開燈</button>
<button onClick={lightOff}>關燈</button>
</>
);
}
複製代碼
打開 codesandbox 自行嘗試一下。
單擊開燈按鈕時,lightOn()
函數將on
更新爲true
: setOn(true)
。單擊關燈時也會發生相同的狀況,只是狀態更新爲false
。
狀態一旦改變,React 就會從新渲染組件,on
變量獲取新的狀態值。
狀態更新做爲對提供一些新信息的事件的響應。這些事件包括按鈕單擊、HTTP 請求完成等,確保在事件回調或其餘 Hook 回調中調用狀態更新函數。
使用回調更新狀態
當使用前一個狀態計算新狀態時,可使用回調更新該狀態:
const [state, setState] = useState(initialState);
...
setState(prevState => nextState);
...
複製代碼
下面是一些事例:
// Toggle a boolean
const [toggled, setToggled] = useState(false);
setToggled(toggled => !toggled);
// Increase a counter
const [count, setCount] = useState(0);
setCount(count => count + 1);
// Add an item to array
const [items, setItems] = useState([]);
setItems(items => [...items, 'New Item']);
複製代碼
接着,經過這種方式從新實現上面電燈的示例:
import React, { useState } from 'react';
function Bulbs() {
const [on, setOn] = useState(false);
const lightSwitch = () => setOn(on => !on);
return (
<>
<div className={on ? 'bulb-on' : 'bulb-off'} />
<button onClick={lightSwitch}>開燈/關燈</button>
</>
);
}
複製代碼
打開 codesandbox 自行嘗試一下。
setOn(on => !on)
使用函數更新狀態。
調用useState()
Hook 來啓用函數組件中的狀態。
useState(initialValue)
的第一個參數initialValue
是狀態的初始值。
[state, setState] = useState(initialValue)
返回一個包含2
個元素的數組:狀態值和狀態更新函數。
使用新值調用狀態更新器函數setState(newState)
更新狀態。或者,可使用一個回調setState(prev => next)
來調用狀態更新器,該回調將返回基於先前狀態的新狀態。
調用狀態更新器後,React 確保從新渲染組件,以使新狀態變爲當前狀態。
經過屢次調用useState()
,一個函數組件能夠擁有多個狀態。
function MyComponent() {
const [state1, setState1] = useState(initial1);
const [state2, setState2] = useState(initial2);
const [state3, setState3] = useState(initial3);
// ...
}
複製代碼
須要注意的,要確保對useState()
的屢次調用在渲染之間始終保持相同的順序(後面會講)。
咱們添加一個按鈕添加燈泡
,並添加一個新狀態來保存燈泡數量,單擊該按鈕時,將添加一個新燈泡。
新的狀態count
包含燈泡的數量,初始值爲1
:
import React, { useState } from 'react';
function Bulbs() {
const [on, setOn] = useState(false);
const [count, setCount] = useState(1);
const lightSwitch = () => setOn(on => !on);
const addBulbs = () => setCount(count => count + 1);
const bulb = <div className={on ? 'bulb-on' : 'bulb-off'} />;
const bulbs = Array(count).fill(bulb);
return (
<>
<div className="bulbs">{bulbs}</div>
<button onClick={lightSwitch}>開/關</button>
<button onClick={addBulbs}>添加燈泡</button>
</>
);
}
複製代碼
打開演示,而後單擊添加燈泡按鈕:燈泡數量增長,單擊開/關按鈕可打開/關閉燈泡。
[count, setCount] = useState(1)
管理燈泡數量。多個狀態能夠在一個組件中正確工做。
每當 React 從新渲染組件時,都會執行useState(initialState)
。 若是初始狀態是原始值(數字,布爾值等),則不會有性能問題。
當初始狀態須要昂貴的性能方面的操做時,能夠經過爲useState(computeInitialState)
提供一個函數來使用狀態的延遲初始化,以下所示:
function MyComponent({ bigJsonData }) {
const [value, setValue] = useState(function getInitialState() {
const object = JSON.parse(bigJsonData); // expensive operation
return object.initialValue;
});
// ...
}
複製代碼
getInitialState()
僅在初始渲染時執行一次,以得到初始狀態。在之後的組件渲染中,不會再調用getInitialState()
,從而跳過昂貴的操做。
如今我們基本已經初步掌握瞭如何使用useState()
,儘管如此,我們必須注意在使用useState()
時可能遇到的常見問題。
useState()
在使用useState()
Hook 時,必須遵循 Hook 的規則
僅頂層調用 Hook :不能在循環,條件,嵌套函數等中調用useState()
。在多個useState()
調用中,渲染之間的調用順序必須相同。
僅從React 函數調用 Hook:必須僅在函數組件或自定義鉤子內部調用useState()
。
來看看useState()
的正確用法和錯誤用法的例子。
有效調用useState()
useState()
在函數組件的頂層被正確調用
function Bulbs() {
// Good
const [on, setOn] = useState(false);
// ...
}
複製代碼
以相同的順序正確地調用多個useState()
調用:
function Bulbs() {
// Good
const [on, setOn] = useState(false);
const [count, setCount] = useState(1);
// ...
複製代碼
useState()
在自定義鉤子的頂層被正確調用
function toggleHook(initial) {
// Good
const [on, setOn] = useState(initial);
return [on, () => setOn(!on)];
}
function Bulbs() {
const [on, toggle] = toggleHook(false);
// ...
}
複製代碼
useState()
的無效調用
在條件中調用useState()
是不正確的:
function Switch({ isSwitchEnabled }) {
if (isSwitchEnabled) {
// Bad
const [on, setOn] = useState(false);
}
// ...
}
複製代碼
在嵌套函數中調用useState()
也是不對的
function Switch() {
let on = false;
let setOn = () => {};
function enableSwitch() {
// Bad
[on, setOn] = useState(false);
}
return (
<button onClick={enableSwitch}>
Enable light switch state
</button>
);
}
複製代碼
閉包是一個從外部做用域捕獲變量的函數。
閉包(例如事件處理程序,回調)可能會從函數組件做用域中捕獲狀態變量。 因爲狀態變量在渲染之間變化,所以閉包應捕獲具備最新狀態值的變量。不然,若是閉包捕獲了過期的狀態值,則可能會遇到過期的狀態問題。
來看看一個過期的狀態是如何表現出來的。組件<DelayedCount>
延遲3
秒計數按鈕點擊的次數。
function DelayedCount() {
const [count, setCount] = useState(0);
const handleClickAsync = () => {
setTimeout(function delay() {
setCount(count + 1);
}, 3000);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
</div>
);
}
複製代碼
打開演示,快速屢次點擊按鈕。count
變量不能正確記錄實際點擊次數,有些點擊被吃掉。
delay()
是一個過期的閉包,它從初始渲染(使用0
初始化時)中捕獲了過期的count
變量。
爲了解決這個問題,使用函數方法來更新count
狀態:
function DelayedCount() {
const [count, setCount] = useState(0);
const handleClickAsync = () => {
setTimeout(function delay() {
setCount(count => count + 1);
}, 3000);
}
return (
<div>
{count}
<button onClick={handleClickAsync}>Increase async</button>
</div>
);
}
複製代碼
如今etCount(count => count + 1)
在delay()
中正確更新計數狀態。React 確保將最新狀態值做爲參數提供給更新狀態函數,過期閉包的問題解決了。
打開演示,快速單擊按鈕。 延遲過去後,count
能正確表示點擊次數。
useState()
用於管理簡單狀態。對於複雜的狀態管理,可使用useReducer()
hook。它爲須要多個狀態操做的狀態提供了更好的支持。
假設須要編寫一個最喜歡的電影列表。用戶能夠添加電影,也能夠刪除已有的電影,實現方式大體以下:
import React, { useState } from 'react';
function FavoriteMovies() {
const [movies, setMovies] = useState([{ name: 'Heat' }]);
const add = movie => setMovies([...movies, movie]);
const remove = index => {
setMovies([
...movies.slice(0, index),
...movies.slice(index + 1)
]);
}
return (
// Use add(movie) and remove(index)...
);
}
複製代碼
嘗試演示:添加和刪除本身喜歡的電影。
狀態列表須要幾個操做:添加和刪除電影,狀態管理細節使組件混亂。
更好的解決方案是將複雜的狀態管理提取到reducer
中:
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'add':
return [...state, action.item];
case 'remove':
return [
...state.slice(0, action.index),
...state.slice(action.index + 1)
];
default:
throw new Error();
}
}
function FavoriteMovies() {
const [state, dispatch] = useReducer(reducer, [{ name: 'Heat' }]);
return (
// Use dispatch({ type: 'add', item: movie })
// and dispatch({ type: 'remove', index })...
);
}
複製代碼
reducer
管理電影的狀態,有兩種操做類型:
"add"
將新電影插入列表
"remove"
從列表中按索引刪除電影
嘗試演示並注意組件功能沒有改變。可是這個版本的<FavoriteMovies>
更容易理解,由於狀態管理已經被提取到reducer
中。
還有一個好處:能夠將reducer
提取到一個單獨的模塊中,並在其餘組件中重用它。另外,即便沒有組件,也能夠對reducer
進行單元測試。
這就是關注點分離的威力:組件渲染UI並響應事件,而reducer
執行狀態操做。
考慮這樣一個場景:我們想要計算組件渲染的次數。
一種簡單的實現方法是初始化countRender
狀態,並在每次渲染時更新它(使用useEffect()
hook)
import React, { useState, useEffect } from 'react';
function CountMyRenders() {
const [countRender, setCountRender] = useState(0);
useEffect(function afterRender() {
setCountRender(countRender => countRender + 1);
});
return (
<div>I've rendered {countRender} times</div>
);
}
複製代碼
useEffect()
在每次渲染後調用afterRender()
回調。可是一旦countRender
狀態更新,組件就會從新渲染。這將觸發另外一個狀態更新和另外一個從新渲染,依此類推。
可變引用useRef()
保存可變數據,這些數據在更改時不會觸發從新渲染,使用可變的引用改造一下<CountMyRenders>
:
import React, { useRef, useEffect } from 'react';
function CountMyRenders() {
const countRenderRef = useRef(1);
useEffect(function afterRender() {
countRenderRef.current++;
});
return (
<div>I've rendered {countRenderRef.current} times</div>
);
}
複製代碼
打開演示並單擊幾回按鈕來觸發從新渲染。
每次渲染組件時,countRenderRef
可變引用的值都會使countRenderRef.current ++
遞增。 重要的是,更改不會觸發組件從新渲染。
要使函數組件有狀態,請在組件的函數體中調用useState()
。
useState(initialState)
的第一個參數是初始狀態。返回的數組有兩項:當前狀態和狀態更新函數。
const [state, setState] = useState(initialState);
複製代碼
使用 setState(newState)
來更新狀態值。 另外,若是須要根據先前的狀態更新狀態,可使用回調函數setState(prevState => newState)
。
在單個組件中能夠有多個狀態:調用屢次useState()
。
當初始狀態開銷很大時,延遲初始化很方便。使用計算初始狀態的回調調用useState(computeInitialState)
,而且此回調僅在初始渲染時執行一次。
必須確保使用useState()
遵循 Hook 規則。
當閉包捕獲過期的狀態變量時,就會出現過期狀態的問題。能夠經過使用一個回調來更新狀態來解決這個問題,這個回調會根據先前的狀態來計算新的狀態。
最後,您將使用useState()
來管理一個簡單的狀態。爲了處理更復雜的狀態,一個更好的的選擇是使用useReducer()
hook。
原文:dmitripavlutin.com/react-usest…
代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
由於篇幅的限制,今天的分享只到這裏。若是你們想了解更多的內容的話,能夠去掃一掃每篇文章最下面的二維碼,而後關注我們的微信公衆號,瞭解更多的資訊和有價值的內容。