摘要:用戶(hù)點(diǎn)擊改變?nèi)譅顟B(tài)崔然渲染整顆組件樹(shù)有沒(méi)有解決方案呢當(dāng)然有創(chuàng)建一個(gè)只接收的新組件,并將組件中的邏輯都移到組件中。最終的示例使用全局狀態(tài)和生成全局狀態(tài)和崔然完整示例見(jiàn)結(jié)論在和出現(xiàn)之前,缺乏自帶的全局狀態(tài)管理能力。
React 16.3 版本,正式推了出官方推薦的 context API —— 一種跨層級(jí)的數(shù)據(jù)傳遞方法。React 16.8 版本,推出了全新的 hooks 功能,將原本只有 class 組件才有的狀態(tài)管理功能和生命周期函數(shù)功能,賦予了 function 組件。Hooks 配合 context 一起使用,為 react 狀態(tài)管理提供了一種新的選擇。這可能會(huì)減少開(kāi)發(fā)者對(duì) redux 等狀態(tài)管理庫(kù)的依賴(lài)。
本文首先會(huì)對(duì)官方的 context 作簡(jiǎn)單介紹,并搭建一個(gè)十分簡(jiǎn)單的使用全局狀態(tài)的應(yīng)用。然后再對(duì) hooks 的基本 API useState useEffect 做基本介紹。接著使用 useContext hooks 對(duì)應(yīng)用進(jìn)行重構(gòu),讓 context 的使用變得更優(yōu)雅。再使用 useReducer hooks 來(lái)管理多個(gè)狀態(tài)。最后,待充分理解 hooks 和 context 之后,我們將它們搭配起來(lái)用,對(duì)整個(gè)應(yīng)用進(jìn)行狀態(tài)管理。
Context 概述React 中存在一個(gè)眾所周知的難題,那就是如何管理全局狀態(tài)。即便是最基礎(chǔ)的全局狀態(tài)跨越層級(jí)傳遞,也是非常麻煩。此時(shí),首選的解決方案就是使用狀態(tài)管理庫(kù),如 redux。Redux 本身是一個(gè) API 非常少的狀態(tài)管理工具,其底層也是用 context 實(shí)現(xiàn)的。在一些狀態(tài)管理不是那么復(fù)雜,但是又有跨越層級(jí)傳遞數(shù)據(jù)的需求時(shí),不妨考慮使用 context 直接實(shí)現(xiàn)。
例如,一個(gè) Page 組件包含全局狀態(tài) user ,需要經(jīng)過(guò)多次props的傳遞,層級(jí)很深的 Avatar 組件才能使用它。
Context :跨層級(jí)傳遞數(shù)據(jù)// ... render ... // ... render ... // ... render ...
Context 提供了一種方法,解決了全局?jǐn)?shù)據(jù)傳遞的問(wèn)題,使得組件之間不用顯式地通過(guò) props 傳遞數(shù)據(jù)。
React.createContext: 創(chuàng)建一個(gè) Context 對(duì)象,該對(duì)象擁有 Provider 和 Consumer 屬性。
Context.Provider: 接受一個(gè) value 參數(shù),在 value 參數(shù)更新的時(shí)候通知 Consumer。
Context.Consumer: 訂閱 value 參數(shù)的改變。一旦 value 參數(shù)改變,就會(huì)觸發(fā)它的回調(diào)函數(shù)。
使用 context 重構(gòu)的之后,跨層級(jí)傳遞數(shù)據(jù)就變得容易很多:
// 創(chuàng)建一個(gè) context const UserContext = React.createContext(); class App extends React.Component { state = { user: "崔然" }; setUser = user => { this.setState({ user }); }; render() { // 設(shè)置 context 當(dāng)前值為 {user, setUser} return (避免全局渲染); } } // ... Page render ... // ... PageLayout render ... // ... NavigationBar render ... // 無(wú)論組件有多深,都可以**直接**讀取 user 值 { ({user, setUser}) => }
但是,在使用 context 時(shí),有些寫(xiě)代碼的小技巧,需要特別注意。不然在全局狀態(tài)改變時(shí), Provider 的所有后代組件都會(huì)重新渲染。例如,用戶(hù)點(diǎn)擊 Avatar 組件后,將 崔然 更新為 CuiRan,這時(shí)會(huì)調(diào)用根組件的 setUser 方法。根組件 setState({ user }) 更新?tīng)顟B(tài),會(huì)導(dǎo)致整顆組件樹(shù)重新渲染。
const Avatar = ({ user, setUser }) => { // 用戶(hù)點(diǎn)擊改變?nèi)譅顟B(tài) returnsetUser("CuiRan")}>{user}; }; class App extends React.Component { state = { user: "崔然" }; setUser = user => { this.setState({ user }); }; // ... 渲染整顆組件樹(shù) }
有沒(méi)有解決方案呢?當(dāng)然有!
創(chuàng)建一個(gè)只接收 props.children的新組件 AppProvider ,并將 App 組件中的邏輯都移到 AppProvider組件中。通過(guò)備注的 console 日志可以看到,該方式避免了不必要的渲染。
const Avatar = ({ user, setUser }) => { // 用戶(hù)點(diǎn)擊改變?nèi)譅顟B(tài) returnsetUser("CuiRan")}>{user}; }; // 將 App 邏輯移到 AppProvider const UserContext = React.createContext(); class AppProvider extends React.Component { state = { user: "崔然" }; setUser = user => { this.setState({ user }); }; render() { return ({this.props.children} ); } } // APP 只保留根組件最基本的 JSX 嵌套 const App = () => (); // ... Page not render ... // ... PageLayout not render ... // ... NavigationBar not render ... // Consumer 監(jiān)聽(tīng)到 Provider value 的改變 {/* **only** Avatar render */ } {({user, setUser}) => }
為什么?為什么把 App 上的全局狀態(tài)及設(shè)置狀態(tài)的方法移到 AppProvider 上,就能避免不必要的渲染?在 props.children 方案中:
// 1. App 本身沒(méi)有全局狀態(tài)改變,因此不會(huì)重渲染 const App = () => ( ); // 2. Provider value 變化,因此會(huì)觸發(fā) Consumer 的監(jiān)聽(tīng)函數(shù)。 { /* 3. this.props.children 只是 的引用 但并不會(huì)調(diào)用 ,即調(diào)用 createElement("Page") */ } {this.props.children}
雖然,context 解決了數(shù)據(jù)跨層級(jí)傳輸?shù)膯?wèn)題,但是還遺留了一些問(wèn)題:
Consumer 的回調(diào)取值的寫(xiě)法
單個(gè)狀態(tài)和狀態(tài)改變很好傳遞,但是多個(gè)狀態(tài)和對(duì)應(yīng)的狀態(tài)改變傳遞依舊不方便。
多個(gè)全局狀態(tài),如何管理?
沒(méi)關(guān)系,且看 hooks 閃亮登場(chǎng),將這些問(wèn)題一一擊破。
Hooks 概述考慮到有些朋友不是很了解 hooks,本文先介紹一下 hooks 的基本用法 。Hooks 讓我們可以在 function 組件中使用狀態(tài)和生命周期函數(shù),并賦予了一些更強(qiáng)大的功能。這也意味著,在 React 16.8 之后,我們?cè)俨恍枰獙?xiě) class 組件。再?gòu)?qiáng)調(diào)一次,我們?cè)俨恍枰獙?xiě) class 組件!
useState: 允許在 function 組件中,聲明和改變狀態(tài)。在此之前,只有 class 組件可以。
useEffect:允許在 function 組件中,抽象地使用 React 的生命周期函數(shù)。開(kāi)發(fā)者可以使用更函數(shù)式的、更清晰的 hooks 的方式。
使用 hooks 對(duì)帶有本地狀態(tài)的 Avatar 組件進(jìn)行重構(gòu)說(shuō)明:
import React, { useState, useEffect } from "react"; const Avatar = ({ user, setUser }) => { // 創(chuàng)建 user 狀態(tài)和修改狀態(tài)的函數(shù) const [user, setUser] = useState("崔然"); // 默認(rèn) componentDidMount/componentDidUpdate 時(shí)會(huì)觸發(fā)回調(diào) // 也可以使用第二個(gè)參數(shù),指定觸發(fā)時(shí)機(jī) useEffect(() => { document.title = `當(dāng)前用戶(hù):${user}`; }); // 使用 setUser 改變狀態(tài) returnsetUser("CuiRan")}>{user}; };
接著,我們繼續(xù)了解 context 的 hooks 用法 —— userContext 。
useContext:更優(yōu)雅的 context在 react 引入 hooks 后,使得 context 的消費(fèi)更簡(jiǎn)單了,開(kāi)發(fā)者可以很優(yōu)雅地直接獲取。下面我們使用 useContext 對(duì) User 組件進(jìn)行重構(gòu)。
// 重構(gòu)前 const User = () => { return ({({ user, setUser }) => ); }; // 重構(gòu)后 const User = () => { // 直接獲取,不用回調(diào) const { user, setUser } = useContext(UserContext); return} ; };
就是這么簡(jiǎn)單!無(wú)論 context 包含什么,是數(shù)字、字符串,還是對(duì)象、函數(shù),都可以通過(guò)useContext訪問(wèn)它。
useReducer:自帶的狀態(tài)管理當(dāng)組件同時(shí)使用多個(gè)useState方法時(shí),需要一個(gè)一個(gè)的聲明。狀態(tài)多了,就一大溜的聲明。比如:
const Avatar = ({ user, setUser }) => { const [user, setUser] = useState("崔然"); const [age, setAge] = useState("18"); const [gender, setGender] = useState("女"); const [city, setCity] = useState("北京"); // more ... };
useReducer 實(shí)際是 useState 的一個(gè)變種,解決了上述多個(gè)狀態(tài),需要多次使用 useState 的問(wèn)題。
當(dāng)你看到 useReducer 時(shí),是不是非常熟悉?想起了redux 中的 reducer 函數(shù)。對(duì)!React 提供的 useReducer 函數(shù),它就是使用 (use) reducer 函數(shù)作為參數(shù)。useReducer 接受的 reducer 參數(shù),本質(zhì)和 redux 的是一樣的。然后 useReducer 會(huì)返回 state 和 dispath 方法,返回的 dispath ,本質(zhì)上和 redux 的也是一樣的。
讓我們使用 useReducer 將帶有本地狀態(tài)的 Avatar 組件重構(gòu)一下:
const reducer = (state, action) => { switch (action.type) { case "CHANGE_USER": return { ...state, user: action.user }; case "CHANGE_AGE": return { ...state, age: action.age }; // more ... default: return state; }}; const Avatar = ({ user, setUser }) => { const [state, dispatch] = useReducer( reducer, { user: "崔然", age: 18 } ); return ( <>dispatch({ type: "CHANGE_USER", user: "CuiRan" })}> {state.user}dispatch({ type: "CHANGE_AGE", age: 17 })}> {state.age}> )};
更進(jìn)一步地,將 useReducer和直接對(duì)比 redux 試試,你會(huì)發(fā)現(xiàn)它們之間驚人的相似:
// react hooks const [state, dispatch] = useReducer(reducer, [initialArg]); // redux const store = createStore(reducer, [initialArg]) const state = store.getState() const dispatch = store.dispatch
還記得我們?cè)?context 中介紹的 provider 和 consumer 嗎?再聯(lián)想一下,它們的作用不就是和 react-redux 中的 provider 和 connect 一模一樣 —— 將數(shù)據(jù)跨層級(jí)的進(jìn)行傳遞!
// react hooks// ... 跨層級(jí)傳遞 ... const { state, dispacth } = useContext(GolbleContext); // react-redux // ... 跨層級(jí)傳遞 ... connect(mapStateToProps, actionCreators)(ConsumerComponent)
到現(xiàn)在為止,react 可謂是自帶了大半個(gè) redux 的 API 了。那么我們不就可以把 redux 的狀態(tài)管理思路直接搬過(guò)來(lái)即可。
最后,只需要將全局狀態(tài)放到在 App 組件的頂層。最終的示例:
const Avatar = ({{ state, dispatch }) => {// ...}) // 使用全局狀態(tài)和 dispatch const User = () => { const { state, dispatch } = useContext(UserContext); return; }; // 生成全局狀態(tài)和 dispatch const reducer = (state, action) => {// ...}; const AppProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, { user: "崔然", age: 18 }); return ( {children} )};
完整示例見(jiàn):https://github.com/jiangleo/h...
結(jié)論在 hooks 和 context 出現(xiàn)之前,react 缺乏自帶的全局狀態(tài)管理能力。即便很小的應(yīng)用,一旦要用到全局狀態(tài),要么使用 props 多層級(jí)的進(jìn)行傳輸,要么就只能引入 redux 等第三方狀態(tài)管理工具。
在 hooks 和 context 出現(xiàn)之后,react 自身提供了一種簡(jiǎn)單的全局狀態(tài)管理的能力。如果你的項(xiàng)目比較簡(jiǎn)單,只有少部分狀態(tài)需要提升到全局,大部分組件依舊通過(guò)本地狀態(tài)來(lái)進(jìn)行管理。這時(shí),使用 hooks + context 進(jìn)行狀態(tài)管理的是強(qiáng)烈推薦的。打蒼蠅,用不著大炮。
此外,我們也觀察到,社區(qū)中一些新型的基于 hooks + context 的狀態(tài)管理庫(kù)正在快速崛起,比如 easy-peasy、constate。另一方面,成熟的 redux 也在 7.x 版本,開(kāi)始引入 hooks API 開(kāi)始升級(jí)。我們也會(huì)持續(xù)保持關(guān)注,探索 hooks 時(shí)代狀態(tài)管理的最佳實(shí)踐。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/105348.html
摘要:要求通過(guò)要求數(shù)據(jù)變更函數(shù)使用裝飾或放在函數(shù)中,目的就是讓狀態(tài)的變更根據(jù)可預(yù)測(cè)性單向數(shù)據(jù)流。同一份數(shù)據(jù)需要響應(yīng)到多個(gè)視圖,且被多個(gè)視圖進(jìn)行變更需要維護(hù)全局狀態(tài),并在他們變動(dòng)時(shí)響應(yīng)到視圖數(shù)據(jù)流變得復(fù)雜,組件本身已經(jīng)無(wú)法駕馭。今天是 520,這是本系列最后一篇文章,主要涵蓋 React 狀態(tài)管理的相關(guān)方案。 前幾篇文章在掘金首發(fā)基本石沉大海, 沒(méi)什么閱讀量. 可能是文章篇幅太長(zhǎng)了?掘金值太低了? ...
摘要:首發(fā)自我的博客,歡迎注如要運(yùn)行本文的代碼,請(qǐng)先確認(rèn)自己的版本已支持出來(lái)已經(jīng)有段時(shí)間了,本文不對(duì)的具體用法作介紹,而是使用實(shí)現(xiàn)一個(gè)簡(jiǎn)易的基于的使用實(shí)現(xiàn)初版自帶了供我們使用,它接受兩個(gè)參數(shù),一是函數(shù),二是初始,并返回和函數(shù),如下這個(gè)函數(shù)自己實(shí)現(xiàn) 首發(fā)自我的github博客,歡迎star 注:如要運(yùn)行本文的代碼,請(qǐng)先確認(rèn)自己的react版本已支持hooks react hooks出來(lái)已經(jīng)有段...
摘要:可以在不改變組件層級(jí)的前提下將帶有狀態(tài)的邏輯抽離出來(lái)。因此在中增加了一個(gè)特性,允許傳入的函數(shù)再返回一個(gè)函數(shù),這個(gè)返回函數(shù)的執(zhí)行時(shí)機(jī)是下一次觸發(fā)這個(gè)前,以及組件卸載前。當(dāng)?shù)诙€(gè)參數(shù)為空數(shù)組時(shí),返回函數(shù)進(jìn)行清理工作只會(huì)在組件卸載時(shí)執(zhí)行。 hooks是什么 hooks是react16.8版本中新增的特性,它讓我們能夠在不寫(xiě)class的情況下使用狀態(tài)和其他react特性。也就是說(shuō)現(xiàn)在我們可以在...
摘要:所以我們做的事情其實(shí)就是,聲明了一個(gè)狀態(tài)變量,把它的初始值設(shè)為,同時(shí)提供了一個(gè)可以更改的函數(shù)。 你還在為該使用無(wú)狀態(tài)組件(Function)還是有狀態(tài)組件(Class)而煩惱嗎? ——擁有了hooks,你再也不需要寫(xiě)Class了,你的所有組件都將是Function。 你還在為搞不清使用哪個(gè)生命周期鉤子函數(shù)而日夜難眠嗎? ——擁有了Hooks,生命周期鉤子函數(shù)可以先丟一邊了。 你在還...
摘要:本文是學(xué)習(xí)了年新鮮出爐的提案之后,針對(duì)異步請(qǐng)求數(shù)據(jù)寫(xiě)的一個(gè)案例。注意,本文假設(shè)了你已經(jīng)初步了解的含義了,如果不了解還請(qǐng)移步官方文檔。但不要忘記和上下文對(duì)象可以看做是寫(xiě)法的以及三個(gè)鉤子函數(shù)的組合。 本文是學(xué)習(xí)了2018年新鮮出爐的React Hooks提案之后,針對(duì)異步請(qǐng)求數(shù)據(jù)寫(xiě)的一個(gè)案例。注意,本文假設(shè)了:1.你已經(jīng)初步了解hooks的含義了,如果不了解還請(qǐng)移步官方文檔。(其實(shí)有過(guò)翻譯...
閱讀 2084·2021-11-24 09:39
閱讀 1942·2019-08-30 15:55
閱讀 2228·2019-08-30 15:53
閱讀 680·2019-08-29 13:16
閱讀 1056·2019-08-26 12:20
閱讀 2443·2019-08-26 11:58
閱讀 3228·2019-08-26 10:19
閱讀 3385·2019-08-23 18:31