可观察存储器 - 前端应用程序(Angular、React、Vue.js等)的国家管理
可观察存储器(Observative Store)是一个前端状态管理库,在前端应用程序中提供了简单而强大的状态管理方法. 前端国家管理变得如此复杂,以至于我们许多人花费更多的时间来制定国家管理规范,而不是其他应用. 可观察商店有一个总体目标——"保持简单".
可观测商店的目标是提供一个小型、简单和一致的方法,在任何前端应用程序(Angular、React、Vue.js等)中管理状态,同时实现许多 关键目标 由更为复杂的国家管理解决方案提供。 虽然许多前端框架/图书馆提供状态管理功能,但许多可能过于复杂,只能与目标框架/图书馆相通. 可观察存储器很简单,可以和任何前端JavaScript代码库一起使用.

可观察存储的关键目标 :
- 简单点!
- 国家唯一的真相来源
- 存储状态不可改变
- 向任何订阅者提供状态更改通知
- 音轨状态变化历史
- 以启动所需的最低数量代码容易理解
- 用 JavaScript 或 TypeScript (Angular, React, Vue 等) 构建的任何前端工程的工程
- 与 Redux DevTools 集成( 目前支持的 Angular 和 React)
兼容性
- 角 17 + 变相 — 使用可观察存储器 页:1 (单位:千美元)
npm install @codewithdan/observable-store(中文(简体) ) - 角数 14-16 — 使用可观察存储器 v2.2.15 (中文(简体) ) (单位:千美元)
npm install @codewithdan/[email protected](中文(简体) ) - 反应,Vue,和香草JS ——v3与任何现代设置合作
开发设置
在本地运行此 repo 的样本 :
-
首先构建核心模块:
npm run build -
然后进入任何样本并安装+开始:
cd samples/angular-store npm install npm start
使用可观察存储的步骤
以下是一个简单的开始使用可观察商店的例子. 请注意,如果您正在使用TypeScript,您可以通过使用接口或类来提供存储状态的额外细节(下面可以找到其他实例).
-
安装可观察存储软件包 :
npm install @codewithdan/observable-store -
安装 RxJS - 如果您的工程还没有引用它, 需要对等依赖 :
npm install rxjs -
创建扩展的类
ObservableStore设置super()在您的班级的构造器中( e)查看可观察存储设置) (中文(简体) ). 虽然这显示了纯粹的JavaScript方法,可观察Store也接受代表存储类型的通用. 详见下文"角"的例子.export class CustomersStore extends ObservableStore { constructor() { super({ /* add settings here */ }); } } -
使用
setState(state, action)。 。 。 .addCustomerToStore(newCustomer) { this.setState({ customer: newCustomer }, 'add_customer'); } -
使用
getState()。 。 。 .getCustomerFromStore() { this.getState().customer; } -
使用商店的
stateChanged可见.// Create CustomersStore object or have it injected if platform supports that init() { this.storeSub = this.customersStore.stateChanged.subscribe(state => { if (state) { this.customer = state.customer; } }); } // Note: Would need to unsubscribe by calling this.storeSub.unsubscribe() // as the target object is destroyed -
访问存储状态历史
CustomersStore通过呼叫stateHistory财产(此处假定:trackStateHistory设置设定为true(中文(简体) )console.log(this.stateHistory);
API 和设置
运行样本
打开 samples 在 Github Repo 上可用的文件夹,并遵循所提供样本项目在readme 文件中提供的指令.
样本应用程序
使用带有角的可观察存储器
见 samples github repo 中的文件夹,用于使用 Angular 的可观察存储器.
-
使用 Angular CLI 或其他选项创建角应用程序.
-
安装
@codewithdan/observable-store数字 :npm install @codewithdan/observable-store -
添加一个界面或模型对象,代表您要添加到您的存储中的数据的形状. 以下是存储客户状态的接口示例:
export interface StoreState { customers: Customer[]; customer: Customer; } -
添加一个服务(如果您愿意,您可以选择称之为商店),扩展可观测站. 将显示您存储数据形状的接口或模型类传递给 T , 如下所示:
@Injectable({ providedIn: 'root' }) export class CustomersService extends ObservableStore<StoreState> { } -
在构造器中添加一个调用到
super()。商店允许您打开或关闭存储状态变化的跟踪trackStateHistory属性。 见列表 可观察存储设置。 。 。 .constructor() { super({ trackStateHistory: true }); } -
将函数添加到您的服务/ 仓库中, 以获取、 存储、 排序、 过滤或执行您想要的任何动作 。 更新商店电话
setState()然后通过正在发生的动作 以及商店状态。 让州里的人离开店里getState()。请注意,存储数据是不可改变的getState()总是返回存储数据的克隆。 这里有一个简单的例子:@Injectable({ providedIn: 'root' }) export class CustomersService extends ObservableStore<StoreState> { sorterService: SorterService; constructor(sorterService: SorterService) { const initialState = { customers: [], customer: null } super({ trackStateHistory: true }); this.setState(initialState, 'INIT_STATE'); this.sorterService = sorterService; } get() { const { customers } = this.getState(); if (customers) { return of(customers); } // call server and get data // assume async call here that returns observable return asyncData; } add(customer: Customer) { let state = this.getState(); state.customers.push(customer); this.setState({ customers: state.customers }, 'ADD_CUSTOMER'); } remove() { let state = this.getState(); state.customers.splice(state.customers.length - 1, 1); this.setState({ customers: state.customers }, 'REMOVE_CUSTOMER'); } sort(property: string = 'id') { let state = this.getState(); const sortedState = this.sorterService.sort(state.customers, property); this.setState({ customers: sortedState }, 'SORT_CUSTOMERS'); } }虽然字符串用于前几个示例中的动作,但是如果想要有一组动作列表从中选择,也可以使用字符串enums(TypeScript特性):
export enum CustomersStoreActions { AddCustomer = 'ADD_CUSTOMER', RemoveCustomer = 'REMOVE_CUSTOMER', GetCustomers = 'GET_CUSTOMERS', SortCustomers = 'SORT_CUSTOMERS' } // Example of using the enum in a store add(customer: Customer) { let state = this.getState(); state.customers.push(customer); this.setState({ customers: state.customers }, CustomersStoreActions.AddCustomer); } -
如果您想要查看商店的所有更改, 您可以访问
stateHistory属性 :console.log(this.stateHistory); -
州历史产出的一个例子如下:
// example stateHistory output [ { "action": "INIT_STATE", "beginState": null, "endState": { "customers": [ { "id": 1545847909628, "name": "Jane Doe", "address": { "street": "1234 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } } ], "customer": null } }, { "action": "ADD_CUSTOMER", "beginState": { "customers": [ { "id": 1545847909628, "name": "Jane Doe", "address": { "street": "1234 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } } ], "customer": null }, "endState": { "customers": [ { "id": 1545847909628, "name": "Jane Doe", "address": { "street": "1234 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } }, { "id": 1545847921260, "name": "Fred", "address": { "street": "1545847921260 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } } ], "customer": null } } ] -
任何组件都可以通过注入存储器并订阅存储器来通知存储状态的变化
stateChanged可观测 :customers: Customer[]; storeSub: Subscription; constructor(private customersService: CustomersService) { } ngOnInit() { // If using async pipe (recommend renaming customers to customers$) // this.customers$ = this.customersService.stateChanged; // Can subscribe to stateChanged observable of the store this.storeSub = this.customersService.stateChanged.subscribe(state => { if (state) { this.customers = state.customers; } }); // Can call service/store to get data directly // It won't fire when the store state changes though in this case //this.storeSub = this.customersService.get().subscribe(custs => this.customers = custs); }
Unsubscribe when the component is destroyed to avoid memory leaks:
``` typescript
ngOnDestroy() {
if (this.storeSub) {
this.storeSub.unsubscribe();
}
}
```
使用可观察存储和反应
见 samples/react-store github repo 中的完整示例文件夹.
-
使用 维特 或另一个工具 :
npm create vite@latest my-app -- --template react -
安装
@codewithdan/observable-store和 RxJS 数据 :npm install @codewithdan/observable-store rxjs -
创建扩展的存储类
ObservableStore数字 :import { ObservableStore } from '@codewithdan/observable-store'; class CustomersStore extends ObservableStore { constructor() { super({ trackStateHistory: true }); } fetchCustomers() { return fetch('/customers.json') .then(response => response.json()) .then(customers => { this.setState({ customers }, 'GET_CUSTOMERS'); return customers; }); } getCustomers() { const state = this.getState(); if (state && state.customers) { return Promise.resolve(state.customers); } return this.fetchCustomers(); } } export default new CustomersStore(); -
使用带有钩子的组件中的商店. 订阅
stateChanged在一个useEffect并在卸载上清理:import { useState, useEffect } from 'react'; import CustomersStore from '../stores/CustomersStore'; function CustomersList() { const [customers, setCustomers] = useState([]); useEffect(() => { // Subscribe to store state changes const sub = CustomersStore.stateChanged.subscribe(state => { if (state && state.customers) { setCustomers(state.customers); } }); // Trigger the initial data fetch CustomersStore.getCustomers(); // Cleanup subscription on unmount return () => sub.unsubscribe(); }, []); return ( <ul> {customers.map(cust => ( <li key={cust.id}>{cust.name}</li> ))} </ul> ); } export default CustomersList;
使用带有 JavaScript 的可观察存储器
见 samples/javascript-demo github repo 中的完整示例文件夹。 可观察 Store 与 plain JavaScript 工作——不需要框架.
-
创建工程 维特 或任何捆绑器,然后安装可观察存储 :
npm install @codewithdan/observable-store rxjs -
创建存储类并直接使用 :
import { ObservableStore } from '@codewithdan/observable-store'; class CustomersStore extends ObservableStore { constructor() { super({ trackStateHistory: true, logStateChanges: true }); } addCustomer(customer) { const state = this.getState(); const customers = state?.customers ? [...state.customers, customer] : [customer]; this.setState({ customers }, 'ADD_CUSTOMER'); } } const store = new CustomersStore(); // Subscribe to state changes store.stateChanged.subscribe(state => { if (state) { console.log('Customers:', state.customers); } }); // Update state store.addCustomer({ id: 1, name: 'Jane Doe' });
存储 API
Observable Store提供了一个简单的API,可用于获取/设置状态,订阅以存储状态变化,以及访问状态历史. 如果您是新到 TypeScript 通用软件,则 T 下面的 API 显示为您的商店状态 .
| 职能 | 说明 |
|---|---|
dispatchState(stateChanges: Partial<T>, dispatchGlobalState: boolean = true) : void | 发送商店状态 不修改商店状态。 可以发送服务状态以及全球商店状态. 若为 dispatchGlobalState 错误, 那么全球状态将不会被发送到订阅者( 默认为 true) (中文(简体) ). |
getState(deepCloneReturnedState: boolean = true) : T | 取取所出店州. 如果使用 TypeScript( 可选的) , 那么商店创建时定义的状态类型将返回而不是 any。深 Clone Returned State 布尔参数(默认为真实)可用于确定返回状态是否被深克隆。 如果设置为虚假,则会返回对商店状态的引用,并且由用户来保证状态不会从商店外部改变. 如果只存储只读缓存数据,而且必须尽快检索,而不作任何克隆,则将其设定为虚假可能有用. |
getStateProperty<TProp>(propertyName: string, deepCloneReturnedState: boolean = true) : TProp | 从商店状态取回特定属性, 其效率可能比Gee State () 更高, 因为只有定义属性值会被返回(和克隆) 而不是整个商店值 。 如果使用 TypeScript (可选), 那么函数调用时使用的通用属性类型将是返回类型 . |
getStateSliceProperty<TProp>(propertyName: string, deepCloneReturnedState: boolean = true): TProp | 如果有的话 stateSliceSelector 已设定,将首先搜索特定的切片。 从商店状态取回特定属性, 其效率可能比Gee State () 更高, 因为只有定义属性值会被返回(和克隆) 而不是整个商店值 。 如果使用 TypeScript (可选), 那么函数调用时使用的通用属性类型将是返回类型 . |
logStateAction(state: any, action: string): void | 在州史上添加自定义状态值和动作. 假设 trackStateHistory 设置被设置在存储或使用全局设置. |
resetStateHistory(): void | 将商店的状态历史重置为空数组 . |
setState(state: Partial<T> | stateFunc<T>, action?: string, dispatchState: boolean = true, deepCloneState: boolean = true) : T | 设库州. 通过要更新的状态以及正在发生的动作. 状态值可以是对象或函数(见下文示例). 最新的商店状态被返回,任何商店的订阅者都会被通知状态变化. 这个 dispatchState 参数可以设置为 false 如果您不想向订阅者发送状态更改通知。 这个 deepCloneState 参数(默认是真实的)可以用来确定状态是否会被深克隆后再被添加到存储中. 在只读缓存数据被存储的情况下,将其设为虚假可能有用,并且必须尽快被添加到存储中而无需克隆. |
destroy(): void | 从全球商店取消此服务注册并完成状态调度. 当某项服务被破坏时(例如,在Angular's)就叫它 ngOnDestroy)防止内存出漏. |
static addExtension(extension: ObservableStoreExtension) | 曾为可观测站添加扩展. 延期必须执行 ObservableStoreExtension 接口. |
static clearState(dispatchState: boolean = true): void | 清除/关闭所有使用它的服务的商店状态 。 如果 dispatchState 真实(默认). |
static initializeState(state: any) | 用来初始化商店状态. 如果调用此选项并存储状态已经存在, 则会丢出一个错误, 因此在应用程序首次加载时应该设置 。 当存储状态初始化时, 不向存储用户发送通知 . |
static resetState(state, dispatchState: boolean = true) | 曾将店面状态重新设定为从可观测站衍生出的所有服务的预期值. 如果发送状态参数属实(默认值),则向订阅者发送状态变化通知和全球状态变化通知. |
| 属性 | 说明 |
|---|---|
globalStateChanged: Observable<any> | 订阅全局存储变化, 即存储状态的任何一片变化 。 全球商店可能包括由特定服务管理的"n' s片状态". 这个地产表示任何"n"片状态的变化. 返回包含当前存储状态的 RxJS 监视器 . |
globalStateWithPropertyChanges: Observable<StateWithPropertyChanges<any>> | 订阅全局商店变化,即商店中任意一片状态的变化,同时也包括所改变的属性. 全球商店可能包括由特定服务管理的"n' s片状态". 这个地产表示任何"n"片状态的变化. 加入时 globalStateWithPropertyChanges 您将返回包含 state (具有当前存储状态)和 stateChanges (其中包含存储中更改的单个属性/数据). |
stateChanged: Observable<T> | 订阅以存储由 Service 更新的特定状态片段的更改 。 如果商店中包含“ n” 的状态片段, 每个状态片段由“ n” 服务之一管理, 那么, 其它状态片段的变化将不会在 State Changed stream 中生成值 。 返回包含当前存储状态的 RxJS 观测器( 或指定状态SlicesSelector) . |
stateWithPropertyChanges: Observable<StateWithPropertyChanges<T>> | 订阅以存储由 Service 更新的特定状态片段的更改, 并包含更改的属性 。 加入时 stateWithPropertyChanges 您将返回包含 state (拥有当前存储状态的一分块)和 stateChanges (其中包含存储中更改的单个属性/数据). |
stateHistory: StateHistory<T>[] | 夺取州史. 假设 trackStateHistory 设置设置在商店上. |
static allStoreServices: any[] | 提供与可观测站互动的所有服务. 用于需要访问特定服务的扩展. |
static globalSettings: ObservableStoreGlobalSettings | 在整个 ObservableStore 应用程序中获取/设置全局设置。 见 可观察存储设置 详情见下文。 注意全局设置只能设定一次作为应用程序的首载. |
static isStoreInitialized: boolean | 用于确定该商店的状态是否目前已初始化. 如果存在多种情况,使商店可能已经初始化,例如在单元测试期间,或商店清空后,则这样做是有用的. |
注意TypeScript类型用于描述以上参数和返回类型. TypeScript 虽然不需要使用可观察存储 .
传递函数到设置状态( C)
以下是将一个函数传递给 setState()。这允许在设置新状态时直接访问以前的状态.
this.setState(prevState => {
return { customers: this.sorterService.sort(prevState.customers, property) };
}, 'SORT_CUSTOMERS');
存储设置( 每个服务)
可观察存储器设置可以在存储器初始化(当一个服务中调用超级())时被通过. 这使得您可以控制您在扩展商店的应用程序中的每项服务的运作方式.
| 设置 | 说明 |
|---|---|
trackStateHistory: boolean | 确定商店状态是否会被跟踪(默认为虚假). 初始化可观察存储器时通过(见上文实例). 何时 true,你可以通过调用 stateHistory 属性. |
logStateChanges: boolean | 记录浏览器控制台的任何存储状态变化( 默认为假) . |
stateSliceSelector: function | 用于选择该特定服务所管理的商店片段的功能 。 如果指定, 则返回特定的状态切片 。 如果没有指定,则返回总状态(默认为无效). |
通过设置到商店的例子 :
export class CustomersStore extends ObservableStore {
constructor() {
super({ /* add settings here */ });
}
}
使用状态SliceSelector () 函数
这个 stateSliceSelector() 函数可以用来返回由服务处管理的存储状态的“切片”。 例如,如果客户服务公司管理一个 customers 收集资料和a selectedCustomer 只返回对象 selectedCustomer 对象到订阅者(而不是 customers 财务报告和审定财务报表 selectedCustomer(通过创建 stateSliceSelector() 函数.
定义为您在通过时初始化服务 settings 对象 super() 在服务建筑师.
export class CustomersService extends ObservableStore<StoreState> {
constructor() {
super({ stateSliceSelector: state => {
return {
customer: state.selectedCustomer
// return other parts of the store here too if desired
};
}
});
}
}
全局存储设置
如果需要,您可以为整个应用程序在全球设置以下可观察存储设置 。 详情请查看 可观察存储设置 节。 这使得您可以一次性定义设置, 所有扩展可观察存储的服务都会自动取取这些设置 。 您可以在服务级别上覆盖这些属性, 当您希望某个特定服务拥有更多的记录( 例如) 而其它服务则没有时, 这样做也很好 .
trackStateHistorylogStateChanges
全局存储设置在应用程序时定义 ONCE 第一个初始化 在使用该商店之前:
ObservableStore.globalSettings = { /* pass settings here */ };
扩展
可观察 Store 现在支持扩展 。 这些可以在应用程序首次通过调用加载时添加 ObservableStore.addExtension()。 。 。 .
Redux DevTools 扩展
第一个内置扩展增加 Redux 开发工具 集成到使用可观察存储的应用程序中。 扩展名可见于 @codewithdan/observable-store-extensions 软件包.

说明 __devTools 存储属性 :
启用 Redux DevTools 扩展后, 将使用名为属性的路径信息添加到您的仓库 __devTools。此属性用于启用 Redux DevTools 时间旅行特性,并可用于将不同的动作状态与给定的路由连接起来,同时手动查看使用 DevTools 的存储数据。 如果 Redux DevTools 扩展未启用(例如生产情景),则 __devTools 属性不会被添加到您的商店中 .
将 Angular 与 Redux DevTools 整合
参见 samples/angular-store 文件夹.
安装扩展包 :
npm install @codewithdan/observable-store-extensions
将以下内容添加到 main.ts 和确保你设置 trackStateHistory 改为 true数字 :
import { ObservableStore } from '@codewithdan/observable-store';
import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
...
ObservableStore.globalSettings = {
trackStateHistory: true
};
ObservableStore.addExtension(new ReduxDevToolsExtension());
安装 Redux DevTools 扩展 在浏览器中运行您的 Angular 应用程序,并打开 Redux DevTools 扩展名.
与 Redux DevTools 集成反应
参见 samples/react-store 文件夹.
安装扩展包 :
npm install @codewithdan/observable-store-extensions
在您的应用程序的切入点中添加以下内容( 例如 ), main.jsx),并确保设置 trackStateHistory 改为 true数字 :
import { ObservableStore } from '@codewithdan/observable-store';
import { ReduxDevToolsExtension } from '@codewithdan/observable-store-extensions';
ObservableStore.globalSettings = {
trackStateHistory: true
};
ObservableStore.addExtension(new ReduxDevToolsExtension());
安装 Redux DevTools 扩展 在浏览器中,运行您的 React 应用程序,并打开 Redux DevTools 扩展名.
Redux 开发工具与生产
当你可以在生产中启用Redux DevTools扩展时,通常建议您去除. 可以通过自定义构建进程或通过检查您代码运行的环境来实现 .
角实例
import { isDevMode } from '@angular/core';
if (isDevMode()) {
ObservableStore.addExtension(new ReduxDevToolsExtension());
}
反应示例
if (import.meta.env.DEV) {
ObservableStore.addExtension(new ReduxDevToolsExtension());
}
变动
见 变动情况 为完整版本的历史.
项目建设
# Build both modules (observable-store + extensions)
npm run build
# Run tests
npm test