基于React Context實現(xiàn)一個簡單的狀態(tài)管理的示例代碼
前言
在大多數(shù)情況下,我們開發(fā)項目都需要一個狀態(tài)管理,方便我們在全局共享狀態(tài)庫,在React生態(tài)里比較流行的幾個庫
但是對于小項目,我們完全可以自己封裝一個狀態(tài)管理,減少一個包的安裝就可以減小打包以后的項目體積。 主要分兩步:
- 封裝一個頂層組件提供數(shù)據(jù)
- 子組件獲取數(shù)據(jù)和更新數(shù)據(jù)
封裝一個父組件用來包裹其他子組件
stores/index.js 文件中首先需要調(diào)用 createContext
export const MyContext = React.createContext({list: [], data: null, time: Date.now()});createContext 中的參數(shù)是默認值,只有當組件所處的樹中沒有匹配到 Provider 時,其參數(shù)才會生效。
每個 Context 對象都會返回一個 Provider React 組件,它允許消費組件訂閱 context 的變化。
創(chuàng)建一個 Context 對象。當 React 渲染一個訂閱了這個 Context 對象的組件,這個組件會從組件樹中匹配離自身最近的Provider,并從中讀取到當前的 context 值。
context 可以設置一個displayName 的屬性, 可以方便在React DevTool 對該context調(diào)試。
MyContext.displayName = 'MyManagementDisplayName';
Provider 接收一個 value 屬性,傳遞給消費組件。 Context 能讓你將這些數(shù)據(jù)向組件樹下所有的組件進行“廣播”,所有的組件都能訪問到這些數(shù)據(jù),也能訪問到后續(xù)的數(shù)據(jù)更新。
這里我們封裝一個父組件用來包裹其他子組件。
import { createContext, useReducer } from 'react';
// 純函數(shù)reducer
function reducer(state, action) {
// action包括 具體的類型type,
// 除了 `type` 之外,action 對象的結構其實完全取決于你自己。
// 這里使用了payload代表dipatch傳過來的數(shù)據(jù)
switch(action.type) {
case 'list':
return ({...state, list: action.payload});
case 'data':
return ({...state, data: action.payload});
case 'time':
return ({...state, time: action.payload});
default:
return state;
}
}
const list = [{num: 0, key: 0}, {num: 1, key: 1}, {num: 2, key: 2}];
export const MyContext = createContext({list: [], data: null, time: Date.now()});
function ContextProvider({children}) {
const [state, dispatch] = useReducer(
reducer,
{list: list, data: null, time: Date.now()}
);
const value = {
state,
dispatch
}
return <MyContext.Provider value={value}>
{children}
</MyContext.Provider>
}
export default ContextProvider;這里用到了useReducer, 用過redux的同學一定非常熟悉,這是因為redux的作者 dan abramov 加入了react開發(fā)團隊, 是react的主要開發(fā)者。 第一個參數(shù)是一個處理數(shù)據(jù)的純函數(shù),第二個參數(shù)是 initialValue。 useReducer還有另一種用法可以接受函數(shù)作為第三個參數(shù),可以惰性地創(chuàng)建初始 state,這不是本文的重點,感興趣的同學可以自行查詢文檔學習。
在入口文件index.js中 用 ContextProvider 包裹 App 組件
import ReactDOM from 'react-dom';
import App from './App';
import './styles/index.less';
import ContextProvider from './stores';
ReactDOM.render(
<ContextProvider><App /></ContextProvider>,
document.getElementById('root')
);子組件如何獲取數(shù)據(jù)呢
有3種方式
- Class Component 內(nèi)獲取(本方法僅能訂閱 1 個 context)
- context.Consumer
- useContext
class Component 方式
import {MyContext} from '@/store';
class MyClass extends React.Component {
static contextType = MyContext;
// 引入的MyContext 賦值給靜態(tài)屬性 contextType后,
// React可以讓你使用 `this.context` 來獲取最近 Context 上的值。
componentDidMount() {
let value = this.context;
/* 在組件掛載完成后,使用 MyContext 組件的值來執(zhí)行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 組件的值進行渲染 */
}
}context.Consumer
<context.Consumer>
{value => /* 基于 context 值進行渲染* /}
</context.Consumer>useContext
這是使用 hook 方式, 也是目前最流行的用法,后面的例子主要使用這個方式來演示。 因為我們要在很多需要全局狀態(tài)的子組件使用,所以我們可以封裝一下。
在 hooks/useStores.js
import {MyContext} from '@/stores';
import React from 'react';
// 封裝代碼以復用
const useStores = () => React.useContext(MyContext);
export default useStores;下面我們通過兩個組件,分別演示 獲取數(shù)據(jù)并展示 和 更新全局數(shù)據(jù)
views/footer/index.js
在此組件里獲取全局數(shù)據(jù)并展示
import { useEffect } from 'react';
import useStores from '../../hooks/useStores';
function Footer() {
const { state } = useStores();
const { time, list } = state;
useEffect(() => {
console.log('Footer page rendered!!!')
})
return (
<div style={{ height: 200 }}>
<div>time now is {time}</div>
<div>
list is
{list.map((item) => (
<span
style={{
background: 'pink',
padding: '0 10px',
border: '1px solid',
marginRight: '10px'
}}
key={item.key}
>
{item.num}
</span>
))}
</div>
</div>
);
}
export default Footer;views/header/index.js
我們在此組件里更新全局數(shù)據(jù)
import useStores from '../../hooks/useStores';
import { Link } from 'react-router-dom';
import { useEffect } from 'react';
function Header() {
// 解構獲取 dispatch 方法
const { dispatch } = useStores();
const handleList = () => {
const payload = [...new Array(3)].map(() => {
const key = Math.random();
const num = Math.floor(key * 100);
return ({
key, num
});
})
// 更新數(shù)據(jù),訂閱狀態(tài)的組件都會獲取更新通知并取到最新數(shù)據(jù)
dispatch({ type: "list", payload });
};
return (
<div style={{ height: 100 }}>
<button onClick={() => dispatch({ type: 'time', payload: Date.now() })}>
time
</button>
<button onClick={handleList}>list</button>
</div>
);
}
export default Header;
點擊 header 中的按鈕,footer 里的 time list 都會響應改變,獲取到最新的值并渲染展示。
總結
我們通過封裝頂層組件提供全局數(shù)據(jù),子組件獲取和更新數(shù)據(jù), 完全基于 React 實現(xiàn)了一個簡單的狀態(tài)管理。
當然 Context 是可以嵌套多層的,同學們可以自行嘗試
參考
到此這篇關于基于React Context實現(xiàn)一個簡單的狀態(tài)管理的示例代碼的文章就介紹到這了,更多相關React Context狀態(tài)管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
React?Server?Component混合式渲染問題詳解
React?官方對?Server?Comopnent?是這樣介紹的:?zero-bundle-size?React?Server?Components,這篇文章主要介紹了React?Server?Component:?混合式渲染,需要的朋友可以參考下2022-12-12
React Hook 監(jiān)聽localStorage更新問題
這篇文章主要介紹了React Hook 監(jiān)聽localStorage更新問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10

