基于React Context实现一个简单的状态管理的示例代码

目录
  • 前言
  • 封装一个父组件用来包裹其他子组件
  • 子组件如何获取数据呢
    • class Component 方式
    • context.Consumer
    • useContext
  • 总结
  • 参考

前言

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

reduxmobxrecoil

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

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

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

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 <MyContext.Provider value={value}>
        {children}
    </MyContext.Provider>
}

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(
    <ContextProvider><App /></ContextProvider>,
    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

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

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 (
    <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

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

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 (
    <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 都会响应改变,获取到最新的值并渲染展示。

总结

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

当然 Context 是可以嵌套多层的,同学们可以自行尝试

参考

到此这篇关于基于React Context实现一个简单的状态管理的示例代码的文章就介绍到这了,更多相关React Context状态管理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • React 数据共享useContext的实现

    因为疫情, 最近接手一个新项目.是React的.上次写React已经过去1年多了. 虽然捡起来也不是什么难事,不过技术这个东西,长时间不用就容易忘记. 为了保证这个项目根其他平行项目的技术栈统一. 采用的是 Nextjs .styled-components.useContext .react-query.ts 今天不做重点介绍项目,还是说说在项目中碰到的问题. 这个足足折腾了差不多2个小时,我一直在排查其问题. 最后这个问题也是比较的诡异. ReferenceError: Cannot acc

  • React中的Context应用场景分析

    Context定义和目的 Context 提供了一种在组件之间共享数据的方式,而不必显式地通过组件树的逐层传递 props. 应用场景 哪些数据会需要共享? Context 设计目的是为了共享那些对于一个组件树而言是**"全局"的数据**,例如当前认证的用户.主题或首选语言. 使用步骤 1. 创建并初始化Context const MyContext = createContex(defaultValue); 创建一个 Context 对象.当 React 渲染一个订阅了这个 Cont

  • 聊一聊我对 React Context 的理解以及应用

    前言 Context被翻译为上下文,在编程领域,这是一个经常会接触到的概念,React中也有. 在React的官方文档中,Context被归类为高级部分(Advanced),属于React的高级API,但官方并不建议在稳定版的App中使用Context. The vast majority of applications do not need to use content. If you want your application to be stable, don't use context

  • react的context和props详解

    目录 一.context 1. 使用场景 2. 使用步骤 3. 总结 二.props深入 1. children 属性 2. props 校验 3. props校验使用步骤 4. props校验约束规则 5. props默认值 总结 一.context 1. 使用场景 设想一个场景,假如我们要给子孙组件传值,应该怎么办呢? 如果使用props一层一层往下 传递的话,特别的繁琐! 更好的办法:使用context来帮助我们跨组件传递数据 2. 使用步骤 调用 React.createContext(

  • 使用react context 实现vue插槽slot功能

    首先来看下vue的slot的实现 <base-layout>组件,具名插槽name定义以及默认插槽 <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot n

  • 基于React Context实现一个简单的状态管理的示例代码

    目录 前言 封装一个父组件用来包裹其他子组件 子组件如何获取数据呢 class Component 方式 context.Consumer useContext 总结 参考 前言 在大多数情况下,我们开发项目都需要一个状态管理,方便我们在全局共享状态库,在React生态里比较流行的几个库 redux.mobx.recoil 但是对于小项目,我们完全可以自己封装一个状态管理,减少一个包的安装就可以减小打包以后的项目体积. 主要分两步: 封装一个顶层组件提供数据 子组件获取数据和更新数据 封装一个父

  • C++实现一个简单的线程池的示例代码

    目录 一.设计 二.参数选择 三.类设计 一.设计 线程池应该包括 保存线程的容器,保存任务的容器. 为了能保证避免线程对任务的竞态获取,需要对任务队列进行加锁. 为了使得工作线程感知任务的到来,需要使用条件变量来唤醒工作线程. 任务容器中的任务管理. 任务的处理API. 二.参数选择 使用数组存放线程,链表存放任务. 三.类设计 线程池类 template<typename T> class threadpool { public: threadpool(int thread_num,int

  • vue2.0+vue-router构建一个简单的列表页的示例代码

    一: 环境搭建 使用vue-cli脚手架工具构建 安装 vue-cli npm install -g vue-cli 使用vue-cli初始化项目 vue init demo1 进到目录 cd demo1 安装依赖 npm install 开始运行 npm run dev 浏览器访问http://localhost:8080 1.首先会打开首页 也就是我们看到的index.html文件 2.使用webpack打包之后默认加载main.js文件并将其引入到index.html文件中 二: 开发 在

  • python实现一个简单的udp通信的示例代码

    什么是 Socket? Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯. python创建套接字 socket.socket([family[, type[, proto]]]) 参数解析: family: AF_UNIX(适用跨机通信) 或 AF_INET(IPv4)(适用本机通信) type: 套接字类型,可以根据是面向连接的还是非连接分为 SOCK_STREAM(TCP) 或 SO

  • php实现简单的权限管理的示例代码

    今天主要来实现一个权限管理系统,它主要是为了给不同的用户设定不同的权限,从而实现不同权限的用户登录之后使用的功能不一样,首先先看下数据库 总共有5张表,qx_user,qx_rules和qx_juese 3张表与另外2张表形成"w"型的关系,也是比较常见的一种权限数据库的方式,首先先做权限的设定,也就是管理层给不同用户设定不同权限. guanli.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/

  • 详解几十行代码实现一个vue的状态管理

    介绍 采用集中式存储管理应用的所有组件的状态, 就能实现组件间数据共享 实现 逻辑图 从图上有两条线: Vue.use(vuec), 与 new Vuec.center(options) 第一条线Vue.use(vuec)安装插件 使用Vue.use(vuec)时, 会执行vuec的install方法,会注入参数Vue 所以vuec是这样的, // index.js import {Center, install} from './center' export default {Center,

  • 用React Native制作一个简单的游戏引擎

    简介 今天我们将学习如何使用React Native制作一个游戏.因为我们使用的是React Native,这个游戏将是跨平台的,这意味着你可以在Android.iOS和网络上玩同一个游戏.然而,今天我们将只关注移动设备.所以我们开始吧. 开始吧 要制作任何游戏,我们需要一个循环,在我们玩的时候更新我们的游戏.这个循环被优化以顺利运行游戏,为此我们将使用 React Native游戏引擎 . 首先让我们用以下命令创建一个新的React Native应用. npx react-native ini

  • 基于C语言编写一个简单的抽卡小游戏

    目录 效果图展示 开始的界面 输入1 输入10 输入0 实现代码 test4.26.c 许愿.c game.h 下载 小奔最近学了C语言不少的东西,但是想用学到的东西来搞一个小游戏. 不过小奔就不做那些猜数字等小游戏了,虽然很经典,但是可以尝试一下其他比较好玩的. 小奔喜欢玩原神,但它抽卡系统的中奖概率太低了,所以就类似做一个它的抽卡系统吧,不过没有保底功能哦(小奔还不想搞,还要学习新的知识,不过以后熟练了就可能会搞一个),是全角色抽卡,只有角色没有武器的,可以十连抽,没有保底功能,抽中的概率只

  • Golang 基于 flag 库实现一个简单命令行工具

    目录 前言 flag 库 FlagSet 需求拆解 实现 weather flag 天气数据打印 获取源数据 数据转换 运行效果 小结 前言 Golang 标准库中的 flag 库提供了解析命令行选项的能力,我们可以基于此来开发命令行工具. 假设我们想做一个命令行工具,我们通过参数提供[城市],它自动能够返回当前这个[城市]的天气状况.这样一个简单的需求,今天我们就来试一下,看怎样实现. flag 库 Package flag implements command-line flag parsi

  • python实现一个简单的贪吃蛇游戏附代码

    前言: 不知道有多少同学跟我一样,最初接触编程的动机就是为了自己做个游戏玩? 今天要给大家分享的是一个 pygame 写的“贪吃蛇”小游戏: “贪吃蛇”这个小游戏在编程学习中的常客,因为: 简单,最基本的游戏元素只需要蛇和食物两个就可以进行了.(打飞机还需要三个元素呢,想想分别是什么?)方向的话只要上下左右4个固定方向就可以了.有基本的数据结构和面向对象的思想在其中.游戏开发本身就会用到很多面向对象的概念,而蛇的身体又是一个天然的“链表”结构,太适合用来练习数据结构了.另外比较有趣的一点是,Py

随机推荐