基于 React Context 实现一个简单的状态管理

前言

在大多数情况下,我们开发项目都需要一个状态管理,方便我们在全局共享状态库,在React生态里比较流行的几个库

redux、mobx、recoil

但是对于小项目,我们完全可以自己封装一个状态管理,减少一个包的安装就可以减小打包以后的项目体积。 主要分两步:

1.封装一个顶层组件提供数据
2.子组件获取数据和更新数据

封装一个父组件用来包裹其他子组件

stores/index.js 文件中首先需要调用 createContext

export const MyContext = React.createContext({list: [], data: null, time: Date.now()}); 

createContext 中的参数是默认值,只有当组件所处的树中没有匹配到 Provider 时,其参数才会生效。

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中匹配离自身最近的Provider,并从中读取到当前的 context 值。

context 可以设置一个displayName 的属性, 可以方便在React DevTool 对该context调试。

MyContext.displayName = 'MyManagementDisplayName'; 

Provider 接收一个 value 属性,传递给消费组件。 Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。

这里我们封装一个父组件用来包裹其他子组件。

import { createContext, useReducer } from 'react';

// 纯函数reducer
function reducer(state, action) {// action包括 具体的类型type,// 除了 `type` 之外,action 对象的结构其实完全取决于你自己。// 这里使用了payload代表dipatch传过来的数据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 {children}
}

export default ContextProvider; 

这里用到了useReducer, 用过redux的同学一定非常熟悉,这是因为redux的作者 dan abramov 加入了react开发团队, 是react的主要开发者。 第一个参数是一个处理数据的纯函数,第二个参数是 initialValue。 useReducer还有另一种用法可以接受函数作为第三个参数,可以惰性地创建初始 state,这不是本文的重点,感兴趣的同学可以自行查询文档学习。

在入口文件index.js中 用 ContextProvider 包裹 App 组件

import ReactDOM from 'react-dom';
import App from './App';
import './styles/index.less';

import ContextProvider from './stores';
ReactDOM.render(,document.getElementById('root')
); 

子组件如何获取数据呢

有3种方式

  • Class Component 内获取(本方法仅能订阅 1 个 context)
  • context.Consumer
  • useContext

class Component 方式

import {MyContext} from '@/store';

class MyClass extends React.Component {static contextType = MyContext;// 引入的MyContext 赋值给静态属性 contextType后,// React可以让你使用 `this.context` 来获取最近 Context 上的值。componentDidMount() {let value = this.context;/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */}componentDidUpdate() {let value = this.context;/* ... */}componentWillUnmount() {let value = this.context;/* ... */}render() {let value = this.context;/* 基于 MyContext 组件的值进行渲染 */}
} 

context.Consumer

{value => /* 基于 context 值进行渲染* /}
 

useContext

这是使用 hook 方式, 也是目前最流行的用法,后面的例子主要使用这个方式来演示。 因为我们要在很多需要全局状态的子组件使用,所以我们可以封装一下。

在 hooks/useStores.js

import {MyContext} from '@/stores';
import React from 'react';

// 封装代码以复用
const useStores = () => React.useContext(MyContext);

export default useStores; 

下面我们通过两个组件,分别演示 获取数据并展示更新全局数据

views/footer/index.js在此组件里获取全局数据并展示

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 (
time now is {time}
list is{list.map((item) => ({item.num}))}
); } export default Footer;

views/header/index.js

我们在此组件里更新全局数据

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});})// 更新数据,订阅状态的组件都会获取更新通知并取到最新数据dispatch({ type: "list", payload });};return (
); } export default Header;

点击 header 中的按钮,footer 里的 time list 都会响应改变,获取到最新的值并渲染展示。

总结

我们通过封装顶层组件提供全局数据,子组件获取和更新数据, 完全基于 React 实现了一个简单的状态管理。

当然 Context 是可以嵌套多层的,同学们可以自行尝试(全文完)

你可能感兴趣的:(react.js,javascript,前端)