Object-oriented state management for react.
npm install react-object-model

Object-oriented state management for react
- Lightweight, based merely on React hooks: useState and useEffect
- Simple and intuitive API: const { name, age } = user.use(['name', 'age'])
- Subscription based, diff subscribed properties, no unnecessary rerender of components

``tsx
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Model } from 'react-object-model'
function Example () {
const { name } = user.use(['name'])
const { value } = counter.use(['value'])
return
class User extends Model
name = ''
age = 0
async logout () {
await delay(2000)
this.set({ name: '', age: 0 })
}
}
let user = new User()
class Counter extends Model
value = 0
reset () {
this.set({ value: 0 })
}
increase () {
this.set({ value: this.value + 1 })
}
async increase_async () {
await delay(2000)
this.set({ value: this.value + 1 })
}
}
let counter = new Counter()
async function delay (milliseconds: number) {
return new Promise( resolve => {
setTimeout(resolve, milliseconds)
})
}
createRoot(
document.querySelector('.root')
).render(
)
`
ts
export class Model {
/* Map /
protected _selectors: Map<({ }) => void, (keyof TModel)[]>
/* 保存上次渲染的状态用于比较 last state /
protected _state: any
constructor () {
Object.defineProperty(this, '_selectors', {
configurable: true,
enumerable: false,
writable: true,
value: new Map()
})
Object.defineProperty(this, '_state', {
configurable: true,
enumerable: false,
writable: true,
value: { }
})
}
/** 像使用 react hooks 那样订阅模型属性 use and watch model's properties like react hooks
use 之后,通过 set 更新属性才会触发组件重新渲染 After use, properties updated by set will trigger rerender
@param selector 订阅属性名称组成的数组 selector array of properties to watch
@returns 模型本身 model self
@example `ts
const { name, age } = user.use(['name', 'age'])
` */
use (selector?: Key[]) {
// React guarantees that dispatch function identity is stable and won’t change on re-renders
const [, rerender] = useState({ })
useEffect(() => {
this._selectors.set(rerender, selector)
return () => { this._selectors.delete(rerender) }
}, [ ])
return this as any as Pick
}
/** 更新模型属性,diff, 重新渲染对应组件 assign properties to model then diff then rerender (when changed)
@param data 属性 data properties
@example `ts
user.set({ name: 'Tom', age: 16 })
` */
set (data: Partial) {
Object.assign(this, data)
this.render()
}
render (diffs?: (keyof TModel)[]) {
const set_diffs = diffs ? new Set(diffs) : null
for (const [rerender, selector] of this._selectors)
if (
!selector ||
selector.find(key =>
set_diffs ?
set_diffs.has(key)
:
this[key as any] !== this._state[key]
)
)
rerender({ })
this._state = { ...this }
}
}
`
2. FormModel
$3
`tsx
import React from 'react'
import ReactDOM from 'react-dom'import { FormModel, FormField } from 'react-object-model/form.js'
class UserForm extends FormModel {
name = new FormField(this.form, 'name', '')
age = new FormField(this.form, 'age', '0')
override async submit (values: UserFormValues) {
await delay(3000)
console.log('submit', values)
}
override validate ({ name, age }: UserFormValues) {
return {
name: name ? undefined : 'name cannot be empty',
age: Number(age) < 18 ? 'age is less than 18' : undefined,
}
}
}
interface UserFormValues {
name: string
age: string
}
let fuser = new UserForm()
// re-render only when form state (hasValidationErrors, submitting) change
function UserFormExample () {
const { form: { hasValidationErrors, submitting, submit } } = fuser.use({ hasValidationErrors: true, submitting: true })
return <>
>
}
// re-render only when name change
function NameInput () {
const { name } = fuser
name.use()
return
auto inject status and message / {...name.item} / label='custom label' />
touched: {String(name.meta.touched)}, error: {name.meta.error}
}// re-render only when age change
function AgeInput () {
const { age } = fuser
age.use()
return
touched: {String(age.meta.touched)}, error: {age.meta.error}
}/* counter for rendered times /
function Counter () {
const rcounter = useRef(0)
return
{++rcounter.current}
}ReactDOM.render( , document.querySelector('.root'))
``