首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >在 React 中进行事件驱动的状态管理

在 React 中进行事件驱动的状态管理

作者头像
疯狂的技术宅
发布于 2020-06-29 08:33:59
发布于 2020-06-29 08:33:59
2.8K00
代码可运行
举报
文章被收录于专栏:京程一灯京程一灯
运行总次数:0
代码可运行

每日前端夜话第357篇

正文共:3740 字

预计阅读时间:10 分钟

自 Hook 被引入 React 以来,Context API 与 Hook 库在应用状态管理中被一起使用。但是把 Context API 和 Hooks(许多基于 Hooks 的状态管理库建立在其基础上)组合的用法对于大规模应用来说可能效率不高。

由于必须创建一个自定义的 Hook 才能启用对状态及其方法的访问,然后才能在组件中使用它,所以在实际开发中很繁琐。这违反了 Hook 的真正目的:简单。但是对于较小的应用,Redux 可能会显得太重了。

今天,我们将讨论 Context API 的替代方法:Storeon。Storeon 是一个微型的、事件驱动的 React 状态管理库,其原理类似于 Redux。用 Redux DevTools 可以查看并可视化状态操作。Storeon 内部使用 Context API 来管理状态,并采用事件驱动的方法进行状态操作。

Store

store 是在应用程序状态下存储的数据的集合。它是通过从 Storeon 库导入的 createStoreon() 函数创建的。

createStoreon() 函数接受模块列表,其中每个模块都是一个接受 store 参数并绑定其事件监听器的函数。这是一个store 的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { createStoreon } from 'storeon/react'
// todos module
const todos = store => {
  store.on(event, callback)
}

export default const store = createStoreon([todos])

模块化

Storeon 中的 store 是模块化的,也就是说,它们是独立定义的,并且没有被绑定到 Hook 或组件。每个状态及其操作方法均在被称为模块的函数中定义。这些模块被传递到 createStoreon() 函数中,然后将其注册为全局 store。

store 有三种方法:

  1. store.get() – 用于检索状态中的当前数据。
  2. store.on(event, callback) – 用于把事件侦听器注册到指定的事件名称。
  3. store.dispatch(event, data) – 用于发出事件,并根据定义的事件要求将可选数据传递进来。

Events

Storeon 是基于事件的状态管理库,状态更改由状态模块中定义的事件发出。Storeon 中有三个内置事件,它们以 @ 开头。其他事件不带 @ 前缀定义。三个内置事件是:

  1. @init – 在应用加载时触发此事件。它用于设置应用的初始状态,并执行传递给它的回调中的所有内容。
  2. @dispatch – 此事件在每个新动作上触发。这对于调试很有用。
  3. @changed – 当应用状态发生更改时,将触发此事件。

注意store.on(event,callback) 用于在我们的模块中添加事件监听器。

演示程序

为了演示在 Storeon 中如何执行应用程序状态操作,我们将构建一个简单的 notes 程序。还会用 Storeon 的另一个软件包把状态数据保存在 localStorage 中。

假设你具有 JavaScript 和 React 的基本知识。你可以在 https://github.com/Youngestdev/storeon-app 上找到本文中使用的代码。

设置

在深入探讨之前,让我们先勾勒出 Notes 程序所需的项目结构和依赖项的安装。从创建项目文件夹开始。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mkdir storeon-app && cd storeon-app
mkdir {src,public,src/Components}
touch public/{index.html, style.css} && touch src/{index,store,Components/Notes}.js

接下来,初始化目录并安装所需的依赖项。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
npm init -y
npm i react react-dom react-scripts storeon @storeon/localstorage uuidv4

接下来就是在 index.js文件中编写父组件了。

`index.js`

这个文件负责渲染我们的笔记组件。首先导入所需的包。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react'
import { render } from 'react-dom';

function App() {
  return (
    <>
      Hello!
    </>
  );
}
const root = document.getElementById('root');
render(<App />, root);

接下来通过在 store.js 中编写用于状态的初始化和操作的代码来构建 store。

`store.js`

此文件负责处理应用中的状态和后续状态管理操作。我们必须创建一个模块来存储状态以及支持事件,以处理操作变更。

首先,从 Storeon 导入 createStoreon 方法和唯一随机ID生成器 UUID。

createStoreon 方法负责将我们的 状态 注册到全局 store 。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { createStoreon } from 'storeon';
import { v4 as uuidv4 } from 'uuid'
import { persistState } from '@storeon/localstorage';

let note = store => {}

我们将状态存储在数组变量 notes 中,该变量包含以下格式的注释:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  id: 'note id',
  item: 'note item'
},

接下来,我们将用两个注释(在首次启动程序时会显示)来初始化状态,从而首先填充注释模块。然后,定义状态事件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let note = store => {
  store.on('@init', () => ({
    notes: [
      { id: uuidv4(), item: 'Storeon is a React state management library and unlike other state management libraries that use Context, it utilizes an event-driven approach like Redux.' },
      { id: uuidv4(), item: 'This is a really short note. I have begun to study the basic concepts of technical writing and I'\'m optimistic about becoming one of the best technical writers.' },
    ]
  });
  store.on('addNote', ({ notes }, note) => {
    return {
      notes: [...notes, { id: uuidv4(), item: note }],
    }
  });
  store.on('deleteNote', ({ notes }, id) => ({
    notes: notes.filter(note => note.id !== id),
  });
}

在上面的代码中,我们定义了状态,并用两个简短的注释填充了状态,并定义了两个事件和一个从 dispatch(event, data) 函数发出事件后将会执行的回调函数。

addNote 事件中,我们返回添加了新 note 的更新后的状态对象,在 deleteNote 事件中把 ID 传递给调度方法的 note 过滤掉。

最后,把模块分配给可导出变量 store ,将其注册为全局 store,以便稍后将其导入到上下文 provider 中,并将状态存储在 localStorage 中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const store = createStoreon([
  notes,
  // Store state in localStorage
  persistState(['notes']),
]);
export default store;

接下来,在 Notes.js 中编写 Notes 应用组件。

`Notes.js`

此文件包含 Notes 程序的组件。我们将从导入依赖项开始。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import { useStoreon } from 'storeon/react';

接下来,编写组件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const Notes = () => {
  const { dispatch, notes } = useStoreon('notes');
  const [ value, setValue ] = React.useState(''); 
}

在上面的代码的第二行中,useStoreon() hook 的返回值设置为可破坏的对象。useStoreon() hook 使用模块名称作为其参数,并返回状态和调度方法以发出事件。

接下来定义在组件中发出状态定义事件的方法 。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const Notes = () => {
...
  const deleteNote = id => {
    dispatch('deleteNote', id)
  };
  const submit = () => {
    dispatch('addNote', value);
    setValue('');
  };
  const handleInput = e => {
    setValue(e.target.value);
  };
}

让我们回顾一下上面定义的三种方法:

  1. deleteNote(id) – 此方法在触发时调度deleteNote事件。
  2. submit() – 该方法通过传递输入状态的值来调度addNote事件,该状态在Notes组件中本地定义。
  3. handleInput() – 此方法将本地状态的值设置为用户输入。

Next, we’ll build the main interface of our app and export it. 接下来,我们将构建应用程序的主界面并将其导出。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const Notes = () => {
  ...
  return (
    <section>
      <header>Quick Notes</header>

      <div className='addNote'>
        <textarea onChange={handleInput} value={value} />
        <button onClick={() => submit()}> Add A Note </button>
      </div>

      <ul>
        {notes.map(note => (
          <li key={note.id}>
            <div className='todo'>
              <p>{note.item}</p>
              <button onClick={() => deleteNote(note.id)}>Delete note</button>
            </div>
          </li>
        ))}
      </ul>
    </section>
  );
}

这样就构成了我们的Notes组件。接下来为我们的应用和index.html文件编写样式表。

`index.html`

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="style.css">
    <title>Storeon Todo App</title>
</head>

<body>
    <div id="root"></div>
</body>

</html>

接下来,填充style.css文件。

`style.css`

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

section {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  width: 300px;
  margin: auto;
}

header {
  text-align: center;
  font-size: 24px;
  line-height: 40px;
}

ul {
  display: block;
}

.todo {
  display: block;
  margin: 12px 0;
  width: 300px;
  padding: 16px;
  box-shadow: 0 8px 12px 0 rgba(0, 0, 0, 0.3);
  transition: 0.2s;
  word-break: break-word;
}

li {
  list-style-type: none;
  display: block;
}

textarea {
  border: 1px double;
  box-shadow: 1px 1px 1px #999;
  height: 100px;
  margin: 12px 0;
  width: 100%;
  padding: 5px 10px;
}

button {
  margin: 8px 0;
  border-radius: 5px;
  padding: 10px 25px;
}

.box:hover {
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
}

运行

现在我们已经成功编写了组件和样式表,但是还没有更新 index.js 中的父组件来渲染 Notes 组件。接下来让我们渲染 Notes 组件。

`index.js`

要访问我们的全局 store,必须导入 store 和 Storeon store 上下文组件。我们还将导入 notes 组件来进行渲染。

用以下代码替换组件的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import { render } from 'react-dom';
import { StoreContext } from 'storeon/react';
import Notes from './Components/Notes';
import store from '../src/store';

function App() {
  return (
    <>
      <StoreContext.Provider value={store}>
        <Notes />
      </StoreContext.Provider>
    </>
  );
}

const root = document.getElementById('root');
render(<App />, root);

在第 8-10 行,调用 store 上下文提供程序组件,并将 notes 组件作为使用者传递。store 上下文提供程序组件将全局 store 作为其上下文值。

接下来把 package.json 文件中的脚本部分编辑为以下内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"scripts": {
  "start": "react-scripts start",
}

然后运行我们的程序:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
npm run start

让我们继续添加和删除注释:

Storeon devtools

Storeon 与 Redux 有着相似的属性,可以在 Redux DevTools 中可视化和监视状态的更改。为了可视化 Storeon 程序中的状态,我们将导入 devtools 包,并将其作为参数添加到我们 store.js 文件的 createStoreon() 方法中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
...
import { storeonDevtools } from 'storeon/devtools';
...
const store = createStoreon([
  ...,
  process.env.NODE_ENV !== 'production' && storeonDevtools,
]);

这是用 Redux DevTools 可视化状态变化的演示:

总结

Storeon 是一个非常有用的状态管理库,它用事件驱动和 Redux 改编的模块化样式来管理状态。你可以在 https://github.com/Youngestdev/storeon-app 上找到本文中的代码。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端先锋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
[oeasy]python0079_控制序列_光标位置设置_ESC_逃逸字符_CSI
| 转义序列 | 中文含义 | 英文含义 | ascii序号 | | --- | --- | --- | --- | | \b | 退格 | backspace | 8 | | \t | 制表键 | tab | 9 | | \n | 换行 | line feed | 10 | | \r | 回车 | carriage | 13 |
oeasy
2023/08/05
4300
[oeasy]python0079_控制序列_光标位置设置_ESC_逃逸字符_CSI
[oeasy]python0072_自定义小动物变色_cowsay_color_boxes_asciiart
修改颜色回忆上次内容上次搞的是 颜色 前景颜色 总共有 7 种基本色还有什么 好玩的 么?🤔可以 给小动物 上色 吗?🤔配合先将cowsay结果 输出重定向sudo apt install cowsaycowsay -f turtle "oeasy" > t.py 我想要 更换 所说话的颜色可能么? 先输出重定向 然后封进三引号再加上开头和结尾修改文件解决 行尾转义字符(escaping character)\ :%s/\\$/\\\\/g : 执行命令行模式% 对所有行执行命令s 执行的是替换命令\\
oeasy
2023/02/04
3310
[oeasy]python0072_自定义小动物变色_cowsay_color_boxes_asciiart
[oeasy]python0040_换行与回车的不同_通用换行符_universal_newlines
换行回车回忆上次内容区分概念 terminal终端 主机网络中 最终的 端点TeleTYpewriter 电传打印机终端硬件shell 终端硬件基础上的 软件壳子Console 控制台 主机旁边 的 控制面板存储文件 的 时候 我 在文件里 打了回车\n系统 将0x0a存入字节 进文件换行 自动就有 回车功能 了吗?🤔回忆一下 被忽略的 \r\r \r 也是一个 ascii字符 \是 转义字符\r是 转义序列 什么是 转义 呢? 转义转义 转化含义把原来 两个字符 : \和r转化为 \r 这样一个字符 没错
oeasy
2023/01/07
4K0
[oeasy]python0040_换行与回车的不同_通用换行符_universal_newlines
Python 基础(四):字符串
在之前的文章【Python 基础(一):入门必备知识】中我们已经提到了:字符串是 Python 的一种数据类型,它可以通过单引号 '、双引号 "、三引号 ''' 或 """ 来定义,本节我们来详细介绍一下。
Python小二
2020/08/17
4640
Python 字符串
字符串 转义字符串 转义字符 描述 (在行尾时) 续行符 \ 反斜杠符号 ' 单引号 " 双引号 \a 响铃 \b 退格(Backspace) \e 转义 \000 空 \n 换行 \v 纵向制表符 \t 横向制表符 \r 回车 \f 换页 \oyy 八进制数,yy代表的字符,例如:\o12代表换行 \xyy 十六进制数,yy代表的字符,例如:\x0a代表换行 \other 其它的字符以普通格式输出
hankleo
2020/09/16
3940
[oeasy]python0074_修改字体背景颜色_background_color_背景色
修改背景色回忆上次内容上次将asciiart和颜色一起来玩 7 种基本色变化多端不过到目前为止 改的 都是前景色背景色可以修改吗?重温参数具体动手试试print("\033[40moeasy\33[0mgo")print("\033[41moeasy\33[0mgo")print("\033[42moeasy\33[0mgo")print("\033[43moeasy\33[0mgo")print("\033[44moeasy\33[0mgo")print("\033[45moeasy\33[0mgo"
oeasy
2023/02/07
1.2K0
[oeasy]python0074_修改字体背景颜色_background_color_背景色
[oeasy]python0037_字符画艺术_asciiview_自制小动物_imagick_asciiart
​牛说(cowsay)回忆上次内容 我们狂飙了一路 从用shell 直接执行 python程序到用shell 循环执行 python程序 循环体中 把 python的 输出结果 用管道 交给了 figlet 把 figlet的 输出结果 用管道 交给了 cowsay 把 cowsay的 输出结果 用管道 交给了 lolcat 最后 提权 直接运行 shell程序 这一路真的好远啊! python3 是脚本解释器shell 也是脚本解释器 其实我们是 在shell中 利用 python3的 输出结果用she
oeasy
2023/01/03
9670
[oeasy]python0037_字符画艺术_asciiview_自制小动物_imagick_asciiart
[oeasy]python0071_字符串类型_str_string_下标运算符_中括号
oeasy
2023/07/07
1970
[oeasy]python0071_字符串类型_str_string_下标运算符_中括号
[oeasy]python0072_修改字体前景颜色_foreground_color_font
修改颜色回忆上次内容m 可以改变字体样式 0-9 之间设置的都是字体效果0 重置为默认1 变亮2 变暗3 斜体4 下划线5 慢闪6 快闪7 前景背景互换8 隐藏9 中划线叠加效果 \33[1;3moeasy;分割取消效果 21 取消 122 取消 223 取消 3一直到 290 是全部取消,回到默认最后发现 真的可以 设置颜色???👁颜色是重要的不同颜色 可以提示出 信息重要性的级别颜色本身也是信息 OFF_INT = 2147483647ERROR_INT = 40000WARN_INT = 3000
oeasy
2023/02/03
9150
[oeasy]python0072_修改字体前景颜色_foreground_color_font
[oeasy]python0043_八进制_oct_octal_october_octave
八进制(oct)回忆上次内容什么是 转义? 转义转义 转化含义\ 是 转义字符\n、\r是 转义序列还有什么 转义序列 吗? \a是 响铃\b 退格键\t 水平制表符 tab键\v、\f 实现喂纸不回车通过 16进制数值 转义 \xhh输出 (hh)16进制对应的ascii字符如果我们不输入x 会发生什么呢?为什么会输出 S 呢?🤔查询文档查询主题 STRINGS查询结果表示方法 \xhh 是 16进制 表示方法\ooo 是 8进制 表示方法去试试从 16进制 到 8进制16进制表示法 没有问题那
oeasy
2023/01/10
4150
[oeasy]python0043_八进制_oct_octal_october_octave
[oeasy]python0066_控制序列_光标位置设置_ESC_逃逸字符_CSI
光标位置回忆上次内容上次讲了 三引号的输出三引号中 回车和引号 都会 被原样输出\ 还是需要从 \\转义黑暗森林 快被摸排清了 还有哪个 转义序列 没 研究过吗?🤔\e是 干什么的?🤔回忆转义转义转义 转化含义\反斜杠(backslash)加了之后字符就不是原来的意思了 转义么转义转义 转化含义所以\反斜杠这个字符 也叫做转义字符Escape character\b 这两个字符的序列算是一个转义序列 Escape sequence\ 这个转义字符会让 \b转义序列 转义为 Backspace 这个含义B
oeasy
2023/01/29
1.6K0
[oeasy]python0066_控制序列_光标位置设置_ESC_逃逸字符_CSI
Python转义字符
有时我们并不想让转义字符生效,我们只想显示字符串原来的意思,这就要用r和R来定义原始字符串。如:
狼啸风云
2019/03/20
4.3K0
[oeasy]python0074_设置高亮色_color_highlight_ansi_控制终端颜色
更多颜色回忆上次内容上次我们搞的还是颜色 FG foreground 前景色 30-37BG background 背景色 40-47这些 都可以和字体样式 结合起来难道 就这几种颜色 吗??🤔有点少啊!有些颜色 也和想象不一致 金黄色 也不够黄啊?!明确概念\是 转义字符escape character\和其他字符 可以构成转义序列\t\n\r\e 也是转义序列 \e 这个转义序列转义到 escape 这样的状态从正常的输出退出进入 control sequences 控制序列控制序列 不直接输出到屏
oeasy
2023/02/08
6240
[oeasy]python0074_设置高亮色_color_highlight_ansi_控制终端颜色
[oeasy]python0053_ 续行符_line_continuation_python行尾续行
续行符与三引号回忆上次内容上次还是转义序列类型英文符号\abell响铃\bbackspace退格\ttab水平制表符\vvertical tab垂直制表符换行不回车\\backslash反斜杠\"double quote双引号\’single quote单引号\xhh具体字符输出(hh)16 进制对应的ascii 字符\ooo具体字符输出(nnn)8 进制对应的ascii 字符黑暗森林已经渐渐清晰上图中提到的续行符 line continuation character 是哪个字符呢?神奇的-反斜杠\\是
oeasy
2023/01/18
1.2K0
[oeasy]python0053_ 续行符_line_continuation_python行尾续行
[oeasy]python0052_ raw格式字符串_单引号_双引号_反引号_ 退格键
转义字符回忆上次内容最近玩的是\n、\r 之外的转义序列 \a是 ␇ (bell)\t是 水平制表符\v是 换行不回车通过 16 进制数值转义 \xhh把(hh)16 进制对应的 ascii 字符输出通过 8 进制数值转义 \ooo把(nnn)8 进制对应的 ascii 字符输出这次加了 转义输出 反斜杠本身 \\ 输出 \总是转义 还是挺麻烦的能否直接输出原样输出呢?搜索帮助找到raw stringrawr的含义是 raw原始原样如果是有r就原样输出为什么raw就是原始原样呢?raw生的食物 没有
oeasy
2023/01/17
1.9K0
[oeasy]python0052_ raw格式字符串_单引号_双引号_反引号_ 退格键
[oeasy]python0020换行字符_feed_line_lf_反斜杠n_B语言_安徒生童话
Basic Combined Programming Language(BCPL)
oeasy
2022/11/25
1.1K0
[oeasy]python0020换行字符_feed_line_lf_反斜杠n_B语言_安徒生童话
Python 字符串
字符串是 Python 中最常用的数据类型。我们可以使用引号(‘或”)来创建字符串。 单引号双引号都可以。
小小工匠
2021/08/16
2770
[oeasy]python0022_框架标题的制作_banner_结尾字符串_end
​结尾字符串(end)回忆上次内容​python3​​ 的程序是一个 5.3M 的可执行文件​​python3​​ 里面存的是 cpu 指令可以执行的那种我们可以把指令对应的汇编找到​​objdump -d ~/python3 > python3.asm​​汇编语句是和当前机器架构的指令集相关的​​uname -a​​可以查询指令集我们执行的过程其实是系统先执行​​python3​​这个可执行文件在内存中构建解释器将参数​​hello.py​​ 放入解释器​python3​​解释器 对于​​hello.py
oeasy
2022/12/01
6530
[oeasy]python0022_框架标题的制作_banner_结尾字符串_end
第五讲:Python数据类型之String
在Python 中,字符串是最常用的数据类型,我们可以使用引号(‘或”)来创建字符串。
Wu_Candy
2022/07/04
5530
第五讲:Python数据类型之String
Python3基础数据-字符串
字符串是 Python 中最常用的数据类型。我们可以使用引号('或")来创建字符串。 创建字符串很简单,只要为变量分配一个值即可。例如:
用户5522200
2019/06/02
6560
推荐阅读
相关推荐
[oeasy]python0079_控制序列_光标位置设置_ESC_逃逸字符_CSI
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档