UNPKG

@guanghechen/viewmodel

Version:
267 lines (212 loc) 7.03 kB
<header> <h1 align="center"> <a href="https://github.com/guanghechen/sora/tree/@guanghechen/viewmodel@2.0.0/packages/viewmodel#readme">@guanghechen/viewmodel</a> </h1> <div align="center"> <a href="https://www.npmjs.com/package/@guanghechen/viewmodel"> <img alt="Npm Version" src="https://img.shields.io/npm/v/@guanghechen/viewmodel.svg" /> </a> <a href="https://www.npmjs.com/package/@guanghechen/viewmodel"> <img alt="Npm Download" src="https://img.shields.io/npm/dm/@guanghechen/viewmodel.svg" /> </a> <a href="https://www.npmjs.com/package/@guanghechen/viewmodel"> <img alt="Npm License" src="https://img.shields.io/npm/l/@guanghechen/viewmodel.svg" /> </a> <a href="#install"> <img alt="Module Formats: cjs, esm" src="https://img.shields.io/badge/module_formats-cjs%2C%20esm-green.svg" /> </a> <a href="https://github.com/nodejs/node"> <img alt="Node.js Version" src="https://img.shields.io/node/v/@guanghechen/viewmodel" /> </a> <a href="https://github.com/facebook/jest"> <img alt="Tested with Jest" src="https://img.shields.io/badge/tested_with-jest-9c465e.svg" /> </a> <a href="https://github.com/prettier/prettier"> <img alt="Code Style: prettier" src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square" /> </a> </div> </header> <br/> Reactive viewmodel implementation with observable state management and computed properties. ## Install - npm ```bash npm install --save @guanghechen/viewmodel ``` - yarn ```bash yarn add @guanghechen/viewmodel ``` ## Usage | Name | Description | | :-----------------: | :-------------------------------------------------------: | | `ViewModel` | Abstract base class for reactive view models | | `State` | Observable state with React-like setState API | | `Computed` | Computed values derived from other observables | | `ObservableMap` | Observable immutable map with change notifications | | `ObservableSet` | Observable immutable set with change notifications | ## Examples ### Basic ViewModel ```typescript import { ViewModel, State } from '@guanghechen/viewmodel' import { Subscriber } from '@guanghechen/subscriber' class CounterViewModel extends ViewModel { public readonly count$ = new State<number>(0) public readonly message$ = new State<string>('Hello') constructor() { super() this.registerDisposable(this.count$) this.registerDisposable(this.message$) } public increment(): void { this.count$.updateState(prev => prev + 1) } public setMessage(msg: string): void { this.message$.updateState(msg) } } const viewModel = new CounterViewModel() // Subscribe to state changes const subscriber = new Subscriber({ onNext: (count) => console.log('Count:', count) }) viewModel.count$.subscribe(subscriber) viewModel.increment() // Output: "Count: 1" viewModel.increment() // Output: "Count: 2" ``` ### Computed Properties ```typescript import { ViewModel, State, Computed } from '@guanghechen/viewmodel' class UserViewModel extends ViewModel { public readonly firstName$ = new State<string>('John') public readonly lastName$ = new State<string>('Doe') public readonly fullName$: Computed<string> constructor() { super() this.registerDisposable(this.firstName$) this.registerDisposable(this.lastName$) // Create computed property this.fullName$ = Computed.fromObservables( [this.firstName$, this.lastName$], ([first, last]) => `${first} ${last}` ) } public setFirstName(name: string): void { this.firstName$.updateState(name) } public setLastName(name: string): void { this.lastName$.updateState(name) } } const userVM = new UserViewModel() console.log(userVM.fullName$.getSnapshot()) // "John Doe" userVM.setFirstName('Jane') console.log(userVM.fullName$.getSnapshot()) // "Jane Doe" ``` ### Observable Collections ```typescript import { ViewModel, ObservableMap, ObservableSet } from '@guanghechen/viewmodel' import { Map, Set } from 'immutable' class TodoViewModel extends ViewModel { public readonly todos$ = new ObservableMap<string, Todo>(Map()) public readonly tags$ = new ObservableSet<string>(Set()) constructor() { super() this.registerDisposable(this.todos$) this.registerDisposable(this.tags$) } public addTodo(id: string, todo: Todo): void { this.todos$.set(id, todo) } public removeTodo(id: string): void { this.todos$.delete(id) } public addTag(tag: string): void { this.tags$.add(tag) } public addMultipleTags(tags: string[]): void { this.tags$.addAll(tags) } } interface Todo { title: string completed: boolean } ``` ### State with React-like API ```typescript import { State } from '@guanghechen/viewmodel' const userState = new State({ name: 'John', age: 30 }) // React-like setState with function userState.setState(prev => ({ ...prev, age: prev.age + 1 })) // Direct state update userState.updateState({ name: 'Jane', age: 25 }) // Subscribe to changes const unsubscribe = userState.subscribeStateChange(() => { console.log('State changed:', userState.getSnapshot()) }) // React integration helpers const snapshot = userState.getSnapshot() // For client-side const serverSnapshot = userState.getServerSnapshot() // For SSR ``` ### ViewModel Ticker (Advanced) ```typescript import { ViewModel, State } from '@guanghechen/viewmodel' import { Subscriber } from '@guanghechen/subscriber' class DashboardViewModel extends ViewModel { public readonly users$ = new State<number>(0) public readonly orders$ = new State<number>(0) public readonly revenue$ = new State<number>(0) constructor() { super() this.registerDisposable(this.users$) this.registerDisposable(this.orders$) this.registerDisposable(this.revenue$) } public observeMetrics(): void { // Create ticker that combines multiple observables const metricsTicker = this.ticker(['users$', 'orders$', 'revenue$']) const subscriber = new Subscriber({ onNext: () => { const metrics = { users: this.users$.getSnapshot(), orders: this.orders$.getSnapshot(), revenue: this.revenue$.getSnapshot() } console.log('Metrics updated:', metrics) } }) metricsTicker.ticker.subscribe(subscriber) } public updateMetrics(users: number, orders: number, revenue: number): void { this.users$.updateState(users) this.orders$.updateState(orders) this.revenue$.updateState(revenue) } } ``` ## Reference - [homepage][homepage] [homepage]: https://github.com/guanghechen/sora/tree/@guanghechen/viewmodel@2.0.0/packages/viewmodel#readme