class-state
特性
😀 使用 class 来创建应用状态
😉 不依赖任何前端框架,支持各种前端框架接入
😘 状态变化,创建下一个不可变状态树
😊 支持服务端渲染
🙂 提供React、Vue、Qwik 接入例子
安装
npm install class-state
快速使用
import { createState, connectState, type State } from 'class-state'
// 定义类
class Count {
// 添加使用的方法
public static use (state: State) {
return connectState(state)(this, 'count')
}
// 定义值
public value = 0
// 通过 $ 函数来修改状态,这是一个约定
public $inc () {
this.value++
}
}
// 创建应用状态
const state = createState()
// 使用 Count 类
const count = Count.use(state)
// 调用 $ 函数,更新状态
count.$inc()
// 打印日志输出: 1
console.log(count.value)
// 订阅状态变化
count.$.subscribe(() => {
// TODO
})
基本概念
state
当创建类实例后,会把可枚举的属性都会当成 state,如果有一些对象不想被当成 state,你可以将其设置为不可枚举属性
$函数
state 的变更,只能通过 $函数修改,否则程序会抛出错误,这是一个约定俗成的规范。
class Count {
// 定义值
public value = 0
// ✅ 正确的
public $inc () {
this.value++
}
// ❌ 错误的
public inc () {
this.value++
}
}
StoreContext
类实例创建完成后,会创建一个对应的StoreContext
实例,作为与全局 state 连接的中介,你可以通过访问类实例的$属性获取到
// 在全局 state 存储的路径
count.$.keyPath
// 类实例的状态
count.$.state
// 是否已经连接到 全局 state 中
count.$.connecting
// 获取当前代理的实例
count.$.get()
// 订阅状态变化,并且会返回一个取消订阅的函数
count.$.subscribe(() => {})
// 断开与全局 state 的连接,取消事件监听,释放内存
count.$.dispose()
完整例子
React
Web 和本机用户界面的库
- store.ts
import { createContext, useContext, useSyncExternalStore } from 'react' import { type State, connectState } from 'class-state' // 创建状态的上下文 export const StateContext = createContext<State>({ value: {} }) // 获取状态 export function useState (): State { return useContext(StateContext) } // 定义类 export class Count { // 定义使用方法 public static use (state: State = useState()) { const count = connectState(state)(this, 'count') // 如果使用了服务端渲染,第三个参数不可忽略 return useSyncExternalStore(count.$.subscribe, count.$.get, count.$.get) } // 定义值 public value: number = 0 // 值加加 public $inc () { this.value++ } // 值减减 public $dec () { this.value-- } }
- app.tsx
import { useState } from 'react' import { type State } from 'class-state' import './style.css' import { StateContext, Count } from './store' import { Child } from './child' export function App () { // 创建状态,如果使用了服务端渲染,需要将对应状态传入 const [state] = useState<State>({ value: {} }) // React 的上下文注入是通过组件的形式,这里是获取不到上下文的,所以这里需要传入 state const count = Count.use(state) return ( <StateContext.Provider value={state}> <div> <Child /> <p>Click Count: {count.value}</p> </div> </StateContext.Provider> ) }
- child.tsx
import { Count } from './store' export const Child = () => { const count = Count.use() return ( <div> <button onClick={() => { count.$inc() }}>+</button> <button onClick={() => { count.$dec() }}>-</button> </div> ) }
vue
一个用于构建 Web 用户界面的平易近人、高性能且多功能的框架。
- store.ts
import { type State, connectState } from 'class-state' import { inject } from 'vue' // 定义根组件供应的 key export const STORE_PROVIDE_KEY = Symbol('class-state') // 添加组合式 API 获取状态的方法 export function useState () { return inject(STORE_PROVIDE_KEY) as State } // 定义类 export class Count { // 定义使用方法 public static use (state: State = useState()) { // 连接状态 return connectState(state)(this, 'count') } // 定义值 public value: number = 0 // 值加加 public $inc () { this.value++ } // 值减减 public $dec () { this.value-- } }
- app.vue
<template> <div class="app"> <Child /> <p>{{ count.value }}</p> </div> </template> <script setup lang="ts"> import { provide, reactive } from 'vue'; import { createState, State } from 'class-state'; import { STORE_PROVIDE_KEY, Count } from './store' import Child from './child.vue'; // 创建状态,如果使用了服务端渲染,需要将对应状态传入 const state: State = reactive({ value: {} }) // 在组件中供应状态 provide(STORE_PROVIDE_KEY, state) // 使用应用状态 const count = Count.use(state) </script>
- child.vue
<template> <div> <button @click="count.$inc()">+</button> <button @click="count.$dec()">-</button> </div> </template> <script lang="ts" setup> import { Count } from './store'; // 在子组件中使用 const count = Count.use() </script>
Qwik
Qwik 是一种新型 Web 框架,可以提供任何大小或复杂程度的即时加载 Web 应用程序。您的网站和应用程序可以使用大约 1kb 的 JS 启动(无论应用程序复杂程度如何),并大规模实现一致的性能。
- store.ts
import { createContextId, useContext } from '@builder.io/qwik' import { type State, connectState } from 'class-state' // 定义根组件供应的 key export const PROVIDE_STORE_KEY = createContextId<State>( 'class-state' ) // 使用状态 export function useState (): State { return useContext(PROVIDE_STORE_KEY) } // 定义类 export class Count { // 定义使用方法 public static use (state: State = useState()) { return connectState(state)(this, 'count') } // 定义值 public value: number = 0 // 值加加 public $inc () { this.value++ } // 值减减 public $dec () { this.value-- } }
- app.tsx
import { type State } from 'class-state' import { component$, useStore, useContextProvider } from '@builder.io/qwik' import { Count, PROVIDE_STORE_KEY } from './store' import { Child } from './child' export const App = component$(() => { const state = useStore<State>({ value: {} }) useContextProvider(PROVIDE_STORE_KEY, state) const count = Count.use() return ( <> <div class="app"> <Child /> <p>Click Count: {count.value}</p> </div> </> ) })
- child.tsx
import { component$ } from '@builder.io/qwik' import { useState, Count } from './store' export const Child = component$(() => { // 使用状态 const state = useState() return ( <div> <button onClick$={() => { Count.use(state).$inc() }}>+</button> <button onClick$={() => { Count.use(state).$dec() }}>-</button> </div> ) })
兼容性
运行时需要支持 Proxy
、WeakMap
、Map