在 React 中進行事件驅(qū)動的狀態(tài)管理「實踐」
前言
自 Hook 被引入 React 以來,Context API 與 Hook 庫在應用狀態(tài)管理中被一起使用。但是把 Context API 和 Hooks(許多基于 Hooks 的狀態(tài)管理庫建立在其基礎(chǔ)上)組合的用法對于大規(guī)模應用來說可能效率不高。
由于必須創(chuàng)建一個自定義的 Hook 才能啟用對狀態(tài)及其方法的訪問,然后才能在組件中使用它,所以在實際開發(fā)中很繁瑣。這違反了 Hook 的真正目的:簡單。但是對于較小的應用,Redux 可能會顯得太重了。
今天,我們將討論 Context API 的替代方法:Storeon。Storeon 是一個微型的、事件驅(qū)動的 React 狀態(tài)管理庫,其原理類似于 Redux。用 Redux DevTools 可以查看并可視化狀態(tài)操作。Storeon 內(nèi)部使用 Context API 來管理狀態(tài),并采用事件驅(qū)動的方法進行狀態(tài)操作。
Store
store 是在應用程序狀態(tài)下存儲的數(shù)據(jù)的集合。它是通過從 Storeon 庫導入的 createStoreon() 函數(shù)創(chuàng)建的。
createStoreon() 函數(shù)接受模塊列表,其中每個模塊都是一個接受 store 參數(shù)并綁定其事件監(jiān)聽器的函數(shù)。這是一個store 的例子:
- import { createStoreon } from 'storeon/react'
- // todos module
- const todos = store => { store.on(event, callback)
- }export default const store = createStoreon([todos])
模塊化
Storeon 中的 store 是模塊化的,也就是說,它們是獨立定義的,并且沒有被綁定到 Hook 或組件。每個狀態(tài)及其操作方法均在被稱為模塊的函數(shù)中定義。這些模塊被傳遞到 createStoreon() 函數(shù)中,然后將其注冊為全局 store。
store 有三種方法:
- store.get() – 用于檢索狀態(tài)中的當前數(shù)據(jù)。
- store.on(event, callback) – 用于把事件偵聽器注冊到指定的事件名稱。
- store.dispatch(event, data) – 用于發(fā)出事件,并根據(jù)定義的事件要求將可選數(shù)據(jù)傳遞進來。
Events
Storeon 是基于事件的狀態(tài)管理庫,狀態(tài)更改由狀態(tài)模塊中定義的事件發(fā)出。Storeon 中有三個內(nèi)置事件,它們以 @ 開頭。其他事件不帶 @ 前綴定義。三個內(nèi)置事件是:
- @init – 在應用加載時觸發(fā)此事件。它用于設置應用的初始狀態(tài),并執(zhí)行傳遞給它的回調(diào)中的所有內(nèi)容。
- @dispatch – 此事件在每個新動作上觸發(fā)。這對于調(diào)試很有用。
- @changed – 當應用狀態(tài)發(fā)生更改時,將觸發(fā)此事件。
注意:store.on(event,callback) 用于在我們的模塊中添加事件監(jiān)聽器。
演示程序
為了演示在 Storeon 中如何執(zhí)行應用程序狀態(tài)操作,我們將構(gòu)建一個簡單的 notes 程序。還會用 Storeon 的另一個軟件包把狀態(tài)數(shù)據(jù)保存在 localStorage 中。
假設你具有 JavaScript 和 React 的基本知識。你可以在https://github.com/Youngestdev/storeon-app 上找到本文中使用的代碼。
設置
在深入探討之前,讓我們先勾勒出 Notes 程序所需的項目結(jié)構(gòu)和依賴項的安裝。從創(chuàng)建項目文件夾開始。
- mkdir storeon-app && cd storeon-app
- mkdir {src,public,src/Components}
- touch public/{index.html, style.css} && touch src/{index,store,Components/Notes}.js
接下來,初始化目錄并安裝所需的依賴項。
- npm init -y
- npm i react react-dom react-scripts storeon @storeon/localstorage uuidv4
接下來就是在 index.js文件中編寫父組件了。
`index.js`
這個文件負責渲染我們的筆記組件。首先導入所需的包。
- import React from 'react'
- import { render } from 'react-dom';
- function App() {
- return (
- <>
- Hello!
- </>
- );
- }
- const root = document.getElementById('root');
- render(<App />, root);
接下來通過在 store.js 中編寫用于狀態(tài)的初始化和操作的代碼來構(gòu)建 store。
`store.js`
此文件負責處理應用中的狀態(tài)和后續(xù)狀態(tài)管理操作。我們必須創(chuàng)建一個模塊來存儲狀態(tài)以及支持事件,以處理操作變更。
首先,從 Storeon 導入 createStoreon 方法和唯一隨機ID生成器 UUID。
createStoreon 方法負責將我們的 狀態(tài) 注冊到全局 store 。
- import { createStoreon } from 'storeon';
- import { v4 as uuidv4 } from 'uuid'
- import { persistState } from '@storeon/localstorage';
- let note = store => {}
我們將狀態(tài)存儲在數(shù)組變量 notes 中,該變量包含以下格式的注釋:
- {
- id: 'note id',
- item: 'note item'
- },
接下來,我們將用兩個注釋(在首次啟動程序時會顯示)來初始化狀態(tài),從而首先填充注釋模塊。然后,定義狀態(tài)事件。
- 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) => ({
- });16}
在上面的代碼中,我們定義了狀態(tài),并用兩個簡短的注釋填充了狀態(tài),并定義了兩個事件和一個從 dispatch(event, data) 函數(shù)發(fā)出事件后將會執(zhí)行的回調(diào)函數(shù)。
在 addNote 事件中,我們返回添加了新 note 的更新后的狀態(tài)對象,在 deleteNote 事件中把 ID 傳遞給調(diào)度方法的 note 過濾掉。
最后,把模塊分配給可導出變量 store ,將其注冊為全局 store,以便稍后將其導入到上下文 provider 中,并將狀態(tài)存儲在 localStorage 中。
- const store = createStoreon([
- notes, // Store state in localStorage
- persistState(['notes']),
- ]);export default store;
接下來,在 Notes.js 中編寫 Notes 應用組件。
`Notes.js`
此文件包含 Notes 程序的組件。我們將從導入依賴項開始。
- import React from 'react';
- import { useStoreon } from 'storeon/react';
接下來,編寫組件。
- const Notes = () => {
- const { dispatch, notes } = useStoreon('notes');
- const [ value, setValue ] = React.useState('');
- }
在上面的代碼的第二行中,useStoreon() hook 的返回值設置為可破壞的對象。useStoreon() hook 使用模塊名稱作為其參數(shù),并返回狀態(tài)和調(diào)度方法以發(fā)出事件。
接下來定義在組件中發(fā)出狀態(tài)定義事件的方法 。
- const Notes = () => {
- ... const deleteNote = id => {
- dispatch('deleteNote', id)
- }; const submit = () => {
- dispatch('addNote', value);
- setValue('');
- }; const handleInput = e => {
- setValue(e.target.value); };}
讓我們回顧一下上面定義的三種方法:
- deleteNote(id) – 此方法在觸發(fā)時調(diào)度deleteNote事件。
- submit() – 該方法通過傳遞輸入狀態(tài)的值來調(diào)度addNote事件,該狀態(tài)在Notes組件中本地定義。
- handleInput() – 此方法將本地狀態(tài)的值設置為用戶輸入。
Next, we’ll build the main interface of our app and export it.接下來,我們將構(gòu)建應用程序的主界面并將其導出。
- 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>
- );24}25
這樣就構(gòu)成了我們的Notes組件。接下來為我們的應用和index.html文件編寫樣式表。
`index.html`
- <!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`
- * {
- 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);
- }
運行
現(xiàn)在我們已經(jīng)成功編寫了組件和樣式表,但是還沒有更新 index.js 中的父組件來渲染 Notes 組件。接下來讓我們渲染 Notes 組件。
`index.js`
要訪問我們的全局 store,必須導入 store 和 Storeon store 上下文組件。我們還將導入 notes 組件來進行渲染。
用以下代碼替換組件的內(nèi)容:
- 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 行,調(diào)用 store 上下文提供程序組件,并將 notes 組件作為使用者傳遞。store 上下文提供程序組件將全局 store 作為其上下文值。
接下來把 package.json 文件中的腳本部分編輯為以下內(nèi)容:
- "scripts": {
- "start": "react-scripts start",
- }
然后運行我們的程序:
- npm run start
讓我們繼續(xù)添加和刪除注釋:
Storeon devtools
Storeon 與 Redux 有著相似的屬性,可以在 Redux DevTools 中可視化和監(jiān)視狀態(tài)的更改。為了可視化 Storeon 程序中的狀態(tài),我們將導入 devtools 包,并將其作為參數(shù)添加到我們 store.js 文件的 createStoreon() 方法中。
- ...
- import { storeonDevtools } from 'storeon/devtools';
- ...const store = createStoreon([ ..., process.env.NODE_ENV !== 'production' && storeonDevtools,
- ]);
這是用 Redux DevTools 可視化狀態(tài)變化的演示:
總結(jié)
Storeon 是一個非常有用的狀態(tài)管理庫,它用事件驅(qū)動和 Redux 改變的模塊化樣式來管理狀態(tài)。你可以在 https://github.com/Youngestdev/storeon-app 上找到本文中的代碼。