React全局狀態(tài)管理的三種底層機制探究
前言
現(xiàn)代前端框架都是基于組件的方式來開發(fā)頁面。按照邏輯關系把頁面劃分為不同的組件,分別開發(fā)不同的組件,然后把它們一層層組裝起來,把根組件傳入 ReactDOM.render 或者 vue 的 $mount 方法中,就會遍歷整個組件樹渲染成對應的 dom。
組件都支持傳遞一些參數(shù)來定制,也可以在內部保存一些交互狀態(tài),并且會在參數(shù)和狀態(tài)變化以后自動的重新渲染對應部分的 dom。
雖然從邏輯上劃分成了不同的組件,但它們都是同一個應用的不同部分,難免要彼此通信、配合。超過一層的組件之間如果通過參數(shù)通信,那么中間那層組件就要透傳這些參數(shù)。而參數(shù)本來是為了定制組件用的,不應該為了通信而添加一些沒意義的參數(shù)。
所以,對于組件的通信,一般不會通過組件參數(shù)的層層傳遞,而是通過放在全局的一個地方,雙方都從那里存取的方式。
具體的用于全局狀態(tài)管理的方案可能有很多,但是他們的底層無外乎三種機制:props、context、state。
下面,我們分別來探究一下這三種方式是如何做全局狀態(tài)的存儲和傳遞的。
props
我們可以通過一個全局對象來中轉,一個組件向其中存放數(shù)據(jù),另一個組件取出來的方式來通信。
組件里面寫取 store 中數(shù)據(jù)的代碼比較侵入式,總不能每個用到 store 的組件都加一段這些代碼吧。我們可以把這些邏輯抽成高階組件,用它來連接(connect)組件和 store。通過參數(shù)的方式來把數(shù)據(jù)注入到組件中,這樣,對組件來說來源是透明的。
這就是 react-redux 做的事情:
import { connect } from 'react-redux'; function mapStateToProps(state) { return { todos: state.todos } } function mapDispatchToProps(dispatch) { return bindActionCreators({ addTodo }, dispatch) } export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
此外,redux 還提供了中間件機制,可以攔截組件發(fā)送給 store 的 action 來執(zhí)行一系列異步邏輯。
比較流行的中間件有 redux-thunk、redux-saga、redux-obervable,分別支持不同的方式來寫組織異步流程,封裝和復用異步邏輯。
類似的其他全局狀態(tài)管理的庫,比如 mobox、reconcil 等,也是通過 props 的方式注入全局的狀態(tài)到組件中。
context
跨層組件通信一定要用第三方的方案么,不是的,react 本身也提供了 context 機制用于這種通信。
React.createContext 的 api 會返回 Provider 和 Consumer,分別用于提供 state 和取 state,而且也是通過 props 來透明的傳入目標組件的。(這里的 Consumer 也可以換成 useContext 的 api,作用一樣,class 組件用 Provider,function 組件用 useContext)
看起來和 redux 的方案基本沒啥區(qū)別,其實最主要的區(qū)別是 context 沒有執(zhí)行異步邏輯的中間件。
所以 context 這種方案適合沒有異步邏輯的那種全局數(shù)據(jù)通信,而 redux 適合組織復雜的異步邏輯。
案例代碼如下:
const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext(themes.light); function App() { return ( <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }
不知道大家有沒有想過,props、state 改變了,重新渲染組件很正常,context 改變了,又是怎么觸發(fā)渲染的呢?
其實 react 內部做了處理,如果改變了 context 的值,那么會遍歷所有的子組件,找到用到 context 值的組件,觸發(fā)它的更新。
所以,props、state、context 都能夠觸發(fā)重新渲染。
state
redux 和 context 的方案,一個是第三方的,一個是內置的,都是通過 props 來傳入值或者通過 hooks 來取值,但它們都是組件外部的,而 state 是組件內部的,怎么通過 state 來做全局狀態(tài)共享呢?
其實 class 組件的 state 做不到,但是 function 組件的 state 可以,因為它是通過 useState 的 hooks api 創(chuàng)建的,而 useState 可以抽離到自定義 hooks 里,然后不同的 function 組件里引入來用。
import React, { useState } from 'react'; const useGlobalState = (initialValue) => { const [globalState, setGlobalState] = useState(initialValue); return [globalState, setGlobalState]; } function ComponentA() { const [globalState, setGlobalState] = useGlobalState({name: 'aaa'}); setGlobalState({name: bbb}); return <div>{globalState}</div> } function ComponentA() { const [globalState, setGlobalState] = useGlobalState({name: 'aaa'}); return <div>{globalState}</div> }
上面這段代碼可以共享全局狀態(tài)?
確實不可以,因為現(xiàn)在每個組件都是在自己的 fiber.memorizedState 中放了一個新的對象,修改也是修改各自的。
那把這兩個 useState 的初始值指向同一個對象不就行了?
這樣多個組件之間就可以操作同一份數(shù)據(jù)了。
上面的代碼要做下修改:
let globalVal = { name: '' } const useGlobalState = () => { const [globalState, setGlobalState] = useState(globalVal); function updateGlobalState(val) { globalVal = val; setGlobalState(val); } return [globalState, updateGlobalState]; }
這樣,每個組件創(chuàng)建的 state 都指向同一個對象,也能做到全局狀態(tài)的共享。
但這里有個前提,就是只能修改對象的屬性,而不能修改對象本身。
總結
現(xiàn)在前端頁面的開發(fā)方式是把頁面按照邏輯拆成一個個組件,分別開發(fā)每一個組件,然后層層組裝起來,傳入 ReactDOM.render 或者 Vue 的 $mount 來渲染。
組件可以通過 props 來定制,通過 state 來保存交互狀態(tài),這些變了都會自動的重新渲染。除此之外,context 變了也會找到用到 contxt 數(shù)據(jù)的子組件來觸發(fā)重新渲染。
組件之間彼此配合,所以難免要通信,props 是用于定制組件的,不應該用來透傳沒意義的 props,所以要通過全局對象來中轉。
react 本身提供了 context 的方案,createContext 會返回 Provider 和 Consumer,分別用來存放和讀取數(shù)據(jù)。在 function 組件中,還可以用 useContext 來代替 Provider。
context 雖然可以共享全局狀態(tài),但是卻沒有異步邏輯的執(zhí)行機制,當有復雜的異步邏輯的時候,還是得用 redux 這種,它提供了中間件機制用于組織異步流程、封裝復用異步邏輯,比如 redux-saga 中可以把異步邏輯封裝成 saga 來復用。
context 和 redux 都支持通過 props 來注入數(shù)據(jù)到組件中,這樣對組件是透明的、無侵入的。
其實通過 useState 封裝的 自定義 hooks 也可以通過把初始值指向同一個對象的方式來達到全局數(shù)據(jù)共享的目的,但是是有限制的,只能修改對象的屬性,不能修改對象本身。其實用這種還不如用 context,只是提一下可以這樣做。
簡單總結一下就是:context 和 redux 都可以做全局狀態(tài)管理,一個是內置的,一個是第三方的,沒有異步邏輯用 context,有異步邏輯用 redux。
到此這篇關于React全局狀態(tài)管理的三種底層機制的文章就介紹到這了,更多相關React全局狀態(tài)管理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
react使用axios進行api網(wǎng)絡請求的封裝方法詳解
這篇文章主要為大家詳細介紹了react使用axios進行api網(wǎng)絡請求的封裝方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03React Native模塊之Permissions權限申請的實例相機
這篇文章主要介紹了React Native模塊之Permissions權限申請的實例相機的相關資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-09-09從零搭建Webpack5-react腳手架的實現(xiàn)步驟(附源碼)
本文主要介紹了從零搭建Webpack5-react腳手架的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08React?Context?變遷及背后實現(xiàn)原理詳解
這篇文章主要為大家介紹了React?Context?變遷及背后實現(xiàn)原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11next-redux-wrapper使用細節(jié)及源碼分析
這篇文章主要為大家介紹了next-redux-wrapper使用細節(jié)及源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02淺析JS中什么是自定義react數(shù)據(jù)驗證組件
我們在做前端表單提交時,經(jīng)常會遇到要對表單中的數(shù)據(jù)進行校驗的問題。這篇文章主要介紹了js中什么是自定義react數(shù)據(jù)驗證組件,需要的朋友可以參考下2018-10-10