1. 程式人生 > 程式設計 >React全域性狀態管理的三種底層機制探究

React全域性狀態管理的三種底層機制探究

目錄
  • 前言
  • props
  • context
  • state
  • 總結

前言

現代前端框架都是基於元件的方式來開發頁面。按照邏輯關係把頁面劃分為不同的元件,分別開發不同的元件,然後把它們一層層組裝起來,把根元件傳入 ReactDOM.render 或者 的 $mount 方法中,就會遍歷整個元件樹渲染成對應的 dom。

元件都支援傳遞一些引數來定製,也可以在內部儲存一些互動狀態,並且會在引數和狀態變化以後自動的重新渲染對應部分的 dom。

雖然從邏輯上劃分成了不同的元件,但它們都是同一個應用的不同部分,難免要彼此通訊、配合。超過一層的元件之間如果通過引數通訊,那麼中間那層元件就要透傳這些引數。而引數本來是為了定製元件用的,不應該為了通訊而新增一些沒意義的引數。

所以,對於元件的通訊,一般不會通過元件引數的層層傳遞,而是通過放在全域性的一個地方,雙方都從那裡存取的方式。

具體的用於全域性狀態管理的方案可能有很多,但是他們的底層無外乎三種機制:props、context、state。

下面,我們分別來探究一下這三種方式是如何做全域性狀態的儲存和傳遞的。

props

我們可以通過一個全域性物件來中轉,一個元件向其中存放資料,另一個元件取出來的方式來通訊。

React全域性狀態管理的三種底層機制探究

元件裡面寫取 store 中資料的程式碼比較侵入式,總不能每個用到 store 的元件都加一段這些程式碼吧。我們可以把這些邏輯抽成高階元件,用它來連線(connect)元件和 store。通過引數的方式來把資料注入到元件中,這樣,對元件來說來源是透明的。

React全域性狀態管理的三種底層機制探究

這就是 react-redux 做的事情:

import { connect } from 'react-redux';

function mapStateToProps(state) {
    return { todos: state.http://www.cppcns.comtodos }
}
  
function mapDispatchToProps(dispatch) {
    return bindActionCreators({ addTodo },dispatch)
}
  
export default connect(mapStateToProps,mapDispatchToProps)(TodoApp)

此外,redux 還提供了中介軟體機制,可以攔截元件傳送給 store 的 action 來執行一系列非同步邏輯。

React全域性狀態管理的三種底層機制探究

比較流行的中介軟體有 redux-thunk、redux-saga、redux-obervable,分別支援不同的方式來寫組織非同步流程,封裝和複用非同步邏輯。

類似的其他全域性狀態管理的庫,比如 mobox、reconcil 等,也是通過 props 的方式注入全域性的狀態到元件中。

context

跨層元件通訊一定要用第三方的方案麼,不是的,react 本身也提供了 context 機制用於這種通訊。

React全域性狀態管理的三種底層機制探究

React.createContext 的 api 會返回 Provider 和 Consumer,分別用於提供www.cppcns.com state 和取 state,而且也是通過 props 來透明的傳入目標元件的。(這裡的 Consumer 也可以換成 useContext 的 api,作用一樣,class 元件用 Provider,function 元件用 useContext)

看起來和 redux 的方案基本沒啥區別,其實最主要的區別是 context 沒有執行非同步邏輯的中介軟體。

所以 context 這種方案適合沒有非同步邏輯的那種全域性資料通訊,而 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 改變了,又是怎麼觸發渲染的呢?

其實 react 內部做了處理,如果改變了 context 的值,那麼會遍歷所有的子元件,找到用到 context 值的元件,觸發它的更新。

所以,props、state、context 都能夠觸發重新渲染。

state

redux 和 context 的方案,一個是第三方的,一個是內建的,都是通過 props 來傳入值或者通過 hooks 來取值,但它們都是元件外部的,而 state 是元件內部的,怎麼通過 state 來做全域性狀態共享呢?

其實 class 元件的 state 做不到,但是 function 元件的 state 可以,因為它是通過 useState 的 hooks api 建立的,而 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() {
    consthttp://www.cppcns.com [globalState,setGlobalState] = useGlobalState({name: 'aaa'});
 
    return <div>{globalState}</div>
}

上面這段程式碼可以共享全域性狀態?

確實不可以,因為現在每個元件都是在自己的 fiber.memorizedState 中放了一個新的物件,修改也是修改各自的。

React全域性狀態管理的三種底層機制探究

那把這兩個 useState 的初始值指向同一個物件不就行了?

樣多個元件之間就可以操作同一份資料了。

React全域性狀態管理的三種底層機制探究

上面的程式碼要做下修改:

let globalVal  = {
    name: ''
}

const useGlobalState = () => {
    const [globalState,setGlobalState] = useState(globalVal);

    function updateGlobalState(val) {
        globalVal = val;
        setGlobalState(val);
    }

    return [globalState,updateGlobalState];
}

這樣,每個元件建立的 state 都指向同一個物件,也能做到全域性狀態的共享。

但這裡有個前提,就是隻能修改物件的屬性,而不能修改物件本身。

總結

現在前端頁面的開發方式是把頁面按照邏輯拆成一個個元件,分別開發每一個元件,然後層層組裝起來,傳入 ReactDOM.render 或者 Vue 的 $mount 來渲染。

元件可以通過 props 來定製,通過 state 來儲存互動狀態,這些變了都會自動的重新渲染。除此之外,context 變了也會找到用到 contxt 資料的子元件來觸發重新渲染。

元件之間彼此配合,所以難免要通訊,props 是用於定製元件的,不應該用來透傳沒意義的 props,所以要通過全域性物件來中轉。

react 本身提供了 context 的方案,createContext 會返回 Provider 和 Consumer,分別用來存放和讀取資料。在 function 元件中,還可以用 useContext 來代替 Provider。

context 雖然可以共享全域性狀態,但是卻沒有非同步邏輯的執行機制,當有複雜的非同步邏輯的時候,還是得用 redux 這種,它提供了中介軟體機制用於組織非同步流程、封裝複用非同步邏輯,比如 redux-saga 中可以把非同步邏輯封裝成 saga 來複用。
context 和 redux 都支援通過 props 來注入資料到元件中,這樣對元件是透明的、無侵入的。

其實通過 useState 封裝的 自定義 hooks 也可以通過把初始值指向同一個物件的方式來達到全域性資料共享的目的,但是是有限制的,只能修改物件的屬性,不能修改物件本身。其實用這種還不如用 context,只是提一下可以這樣做。

簡單總結一下就是:context 和 redux 都可以做全域性狀態管理,一個是內建的,一個是第三方的,沒有非同步邏輯用 context,有非同步邏輯用 redux。

到此這篇關於React全域性狀態管理的三種底層機制的文章就介紹到這了,更多相關React全域性狀態管理內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!