@expressive/mvc

中文文档
0.94.5发布于 2026/11/14MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 3022

Class-based reactive state management - framework-agnostic core of Expressive MVC.

原始使用文档
文档目录7 节
  1. Model
  2. Instructions
  3. Lifecycle
  4. Context & composition
  5. Events
  6. Framework-agnostic components
  7. License

@expressive/mvc

Class-based reactive state management - framework-agnostic core.

NPM Coverage


The core of Expressive MVC: reactive primitives built around plain classes, with no framework dependency. Provides the Model class, instructions, context, and the renderer-agnostic Component that adapters like @expressive/react build on.

Building a React app? Install @expressive/react alongside this package - the adapter declares @expressive/mvc as a peer dependency, so both belong in your package.json. Import Model, Component, and every instruction from this package, and import the adapter once from your entry so the host registers. The same holds for @expressive/dom, the renderer without React.

Installed alone, it serves host-agnostic code - a shared domain package, a Node service, or a new adapter:

npm install @expressive/mvc

Model

A Model is a class whose fields are reactive - read to subscribe, assign to update. Getters are cached computed values.

import { Model } from '@expressive/mvc';

class Counter extends Model {
  count = 0;
  increment() {
    this.count++;
  }

  get doubled() {
    return this.count * 2;     // recomputes only when count changes
  }
}

const counter = Counter.new();

// effect runs now, then again whenever read values change
counter.get(({ count }) => {
  console.log(`count is ${count}`);
});

counter.increment();           // -> "count is 1"

Instances run and test anywhere - no renderer required.

Instructions

Field initializers that change how a property behaves:

ref()a mutable reference (e.g. a DOM node) that's still reactive
set()computed values, smart setters, side-effects on assignment
get()dependency injection - pull another Model from context
map()a shallow-reactive Map
has()an owned reactive collection - an ordered list or a spawned pool
import { Model, set, has, map } from '@expressive/mvc';

class Cart extends Model {
  items = has<Item>();                      // reactive collection
  products = map<string, Product>();        // reactive keyed collection
  coupon = set('', code => apply(code));    // side-effect on assignment
}

Lifecycle

class Timer extends Model {
  seconds = 0;

  new() {                              // runs once on activation
    const id = setInterval(() => this.seconds++, 1000);
    return () => clearInterval(id);    // returned cleanup runs on destroy
  }
}

const timer = Timer.new();   // construct + activate
timer.set(null);             // destroy - runs cleanup, freezes state

Under a host that renders on the server, new() still runs there but its cleanup never does - so client resources like this interval go in mount() instead, which runs only on the client; see the host's docs.

Context & composition

Instances find each other through context, and compose by holding one another:

import { Model, get } from '@expressive/mvc';

class Session extends Model {
  user = 'guest';
}

class Profile extends Model {
  session = get(Session);    // injected from the nearest provider
  address = new Address();   // nested instance - its changes bubble up
}

<Component for={Model}> - part of this package - places a Model in a tree; a host such as @expressive/react renders it, and get() pulls the instance back out.

Events

Any property or arbitrary key can be dispatched and listened to:

const off = counter.set('refresh', () => reload());   // listen
counter.set('refresh');                               // dispatch
off();                                                // stop listening

Framework-agnostic components

@expressive/mvc ships its own JSX runtime, so you can author components - including reusable libraries - against the core and let any host render them:

// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@expressive/mvc"
  }
}

Component - a Model usable in JSX - lives here, along with <Component for>: props, subcomponents, suspense and context placement are all core. A host only renders them; for how that looks in an app, see @expressive/react.


To render state in a UI, add a host: @expressive/react or @expressive/dom.

Full guide and API reference → github.com/gabeklein/expressive

License

MIT

版本与发布记录

@expressive/mvc 共发布 236 个版本,首次发布于 2021/06/22;过去 12 个月发布了 32 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.94.5latest2026/11/14今天
0.94.42026/11/131 天前
0.94.32026/11/113 天前
0.94.22026/11/103 天前
0.94.12026/11/059 天前
0.94.02026/11/0212 天前

其他发布通道:next → 0.0.2,canary → 0.52.2-alpha.74。

关于 @expressive/mvc 的常见问题

@expressive/mvc 是什么?

Class-based reactive state management - framework-agnostic core of Expressive MVC.

如何安装 @expressive/mvc?

在项目目录运行 npm install @expressive/mvc,或使用 pnpm add / yarn add / bun add。

@expressive/mvc 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@expressive/mvc 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@expressive/mvc 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@expressive/mvc 可以商用吗?

@expressive/mvc 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@expressive/mvc 的下载量有多少?

根据 npm 官方下载统计,@expressive/mvc 在 2026/10/14 – 2026/11/12 这 30 天内被下载 3,022 次,日均约 101 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@expressive/mvc 还在维护吗?

最新版本 0.94.5 发布于 2026/11/14(今天);至今共发布 236 个版本,其中过去 12 个月发布了 32 个;首次发布于 2021/06/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。