actview 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,118 +1,338 @@
1
- # ActView (MVVM Study Edition)
1
+ # Actview
2
2
 
3
- [简体中文](./README.zh.md) | English
3
+ A lightweight frontend framework with **Vue-like reactivity** and **React-like JSX**, built from scratch with Proxy and a custom JSX factory.
4
4
 
5
- **ActView** is a lightweight MVVM library born for **Campaign / Marketing Single Page Applications**.
5
+ ## Installation
6
6
 
7
- > Target Scenarios: Single-page applications with complex state and logic, requiring strong collaboration between roles.
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
8
11
 
9
- ## Why ActView?
12
+ Build:
13
+
14
+ ```bash
15
+ npm run build
16
+ ```
10
17
 
11
- In Campaign/H5 development, there is often a clear division of labor:
12
- - **Teammate A**: Focuses solely on HTML / CSS (The Slicer)
13
- - **Teammate B**: Focuses solely on Data, Logic, and Reactivity (The Logician)
18
+ ## Reactivity System
14
19
 
15
- **ActView**'s core capability is to "wire" these two together:
16
- **Selector + Configuration → Automatic Data Binding**
20
+ ### ref Primitive Values
17
21
 
18
- Goal: **Low Mental Burden, Fast, Stable, Easy Handoff**.
22
+ ```tsx
23
+ import { ref, computed, watch } from "@actview/core"
19
24
 
20
- ## Core Features
25
+ const count = ref(0)
21
26
 
22
- - **Configuration-Driven Binding**: Non-intrusive to HTML, just provide `selector` + configuration options.
23
- - **Automatic Reactivity**: `ref` / `reactive` data changes automatically update the view.
24
- - **Two-Way Binding Loop**: Quickly write back data via `listeners`.
25
- - **Computed / Watch**: `computed` / `watch` for handling complex logic.
26
- - **JSX / Functional Rendering**: Support returning JSX directly in configuration (`render` function) for complex lists/components.
27
- - **Quick Event Mounting**: Define event callbacks directly in the configuration.
27
+ // Read: count.value
28
+ // Write: count.value = 1
29
+ console.log(count.value) // 0
30
+ count.value++
31
+ console.log(count.value) // 1
32
+ ```
28
33
 
29
- ## Quick Start
34
+ ### reactive — Objects & Arrays
30
35
 
31
- ```bash
32
- npm i
33
- npm run dev
36
+ ```tsx
37
+ const user = reactive({ name: "Actview", age: 3 })
38
+ const list = reactive([{ id: 1, text: "Item A" }])
39
+
40
+ console.log(user.name) // "Actview"
41
+ user.age = 4 // triggers update
42
+
43
+ list.push({ id: 2, text: "Item B" }) // triggers update
34
44
  ```
35
45
 
36
- Build output to `out/`:
46
+ ### computed Derived Values
37
47
 
38
- ```bash
39
- npm run build
48
+ ```tsx
49
+ const name = ref("Actview")
50
+ const version = ref(1)
51
+
52
+ const info = computed(() => `${name.value} v${version.value}`)
53
+
54
+ console.log(info.value) // "Actview v1"
40
55
  ```
41
56
 
42
- ## Core Usage
57
+ ### watch — Side Effects
43
58
 
44
- ### 1) Data Definition (Ref / Reactive)
59
+ ```tsx
60
+ const count = ref(0)
45
61
 
46
- ```ts
47
- import { ref, reactive } from "./core";
62
+ watch(count, (newVal, oldVal) => {
63
+ console.log(`count: ${oldVal} ${newVal}`)
64
+ })
65
+
66
+ count.value = 1 // logs "count: 0 → 1"
67
+ ```
68
+
69
+ ### How It Works
48
70
 
49
- // Primitive type
50
- const count = ref(0);
71
+ Actview uses **Proxy** to intercept get/set on reactive data. When a ref is read inside a component's render function, the component automatically subscribes to that ref. When the ref changes, only the components that read it are re-rendered.
51
72
 
52
- // Object / Array
53
- const student = reactive({ name: "John Doe", age: 20 });
54
- const list = reactive([{ id: 1, name: "A" }]);
73
+ ```tsx
74
+ const count = ref(0)
75
+
76
+ function Counter() {
77
+ return () => <div>{count.value}</div> // subscribes to `count`
78
+ }
55
79
  ```
56
80
 
57
- ### 2) View Binding (Compile + Option)
81
+ ## Components
58
82
 
59
- View binding is done via `compile(option)`. Teammate A writes HTML (id="count"), Teammate B writes configuration:
83
+ ### Setup Mode (Recommended)
60
84
 
61
- ```ts
62
- import { compile } from "./core/compile";
85
+ The component function runs once (setup), returns a render function that re-runs on each update:
86
+
87
+ ```tsx
88
+ function Counter() {
89
+ const count = ref(0)
90
+
91
+ return () => (
92
+ <div>
93
+ <p>Count: {count.value}</p>
94
+ <button onClick={() => count.value++}>+1</button>
95
+ </div>
96
+ )
97
+ }
98
+ ```
99
+
100
+ - **Setup** runs once: create refs, computed, watch
101
+ - **Render** runs on each update: returns JSX, auto-tracks reactive dependencies
102
+
103
+ ### Direct Mode (Stateless)
104
+
105
+ The component function IS the render function — re-runs on every parent render:
106
+
107
+ ```tsx
108
+ function Greeting(props: { name: string }) {
109
+ return <div>Hello, {props.name}!</div>
110
+ }
111
+ ```
112
+
113
+ No internal state, no isolated reactive boundary. Pure props-in / JSX-out.
114
+
115
+ ### Props
116
+
117
+ ```tsx
118
+ function Welcome(props: { title: string; count: number }) {
119
+ return () => (
120
+ <div>
121
+ <h2>{props.title}</h2>
122
+ <p>Count: {props.count}</p>
123
+ </div>
124
+ )
125
+ }
126
+
127
+ // Usage
128
+ <Welcome title="Hello" count={count.value} />
129
+ ```
130
+
131
+ ## JSX & DOM Rendering
132
+
133
+ Actview uses a **custom JSX factory** (`createElement`) that creates real DOM nodes directly — no virtual DOM.
134
+
135
+ ```tsx
136
+ // JSX → createElement → real DOM
137
+ const element = <div class="card">
138
+ <h3>Title</h3>
139
+ <p>Content</p>
140
+ </div>
141
+ ```
142
+
143
+ ### Reactive Updates (Component-Level)
144
+
145
+ ```tsx
146
+ function Timer() {
147
+ const time = ref(new Date().toLocaleTimeString())
148
+
149
+ setInterval(() => {
150
+ time.value = new Date().toLocaleTimeString()
151
+ }, 1000)
152
+
153
+ return () => <div>Current time: {time.value}</div>
154
+ }
155
+ ```
156
+
157
+ - `time.value` changes → triggers `publish`
158
+ - Only this component's `componentUpdateFn` re-runs
159
+ - Compares old and new DOM nodes, patches only the changed parts
160
+
161
+ ### Fragment
162
+
163
+ ```tsx
164
+ function List() {
165
+ return () => (
166
+ <>
167
+ <h2>Items</h2>
168
+ {items.map(item => <div key={item.id}>{item.text}</div>)}
169
+ </>
170
+ )
171
+ }
172
+ ```
173
+
174
+ Fragment wraps children in a `DocumentFragment` with start/end comment anchors for stable diffing.
175
+
176
+ ### Keyed Reconciliation
63
177
 
64
- compile({
65
- selector: "#count",
66
- // Text binding: automatically responds to count.value changes
67
- text: () => `Current Count: ${count.value}`,
68
- // Visibility binding
69
- show: () => count.value > 0,
70
- // Event binding
71
- listeners: [
72
- { type: "click", callback: () => count.value++ }
73
- ]
74
- });
178
+ Child nodes with a `key` prop are matched by identity, preserving DOM state (e.g., input focus):
179
+
180
+ ```tsx
181
+ function SortableList() {
182
+ const items = reactive([
183
+ { id: 1, text: "A" },
184
+ { id: 2, text: "B" },
185
+ ])
186
+
187
+ return () => (
188
+ <>
189
+ {items.map(item => (
190
+ <div key={item.id}>
191
+ <input placeholder={item.text} />
192
+ </div>
193
+ ))}
194
+ <button onClick={() => items.sort(() => Math.random() - 0.5)}>
195
+ Shuffle
196
+ </button>
197
+ </>
198
+ )
199
+ }
75
200
  ```
76
201
 
77
- ### 3) Complex Rendering (Render + JSX)
202
+ Without keys, shuffling would misplace input values. With keys, each input stays with its matching `div`.
78
203
 
79
- For lists or complex structures, use JSX directly:
204
+ ## Slots
205
+
206
+ Named slots use `<template slot="xxx">` syntax. The template's content is extracted and passed as `props.xxx` to the component. Content not wrapped in `<template slot>` becomes `props.children` (default slot).
207
+
208
+ ### Defining a Component with Slots
80
209
 
81
210
  ```tsx
82
- compile({
83
- selector: "#list",
84
- render: () => (
85
- <ul>
86
- {list.map(item => <li>{item.name}</li>)}
87
- </ul>
211
+ function Card() {
212
+ return (props: any) => (
213
+ <div class="card">
214
+ {props.header && <div class="card-header">{props.header}</div>}
215
+ <div class="card-body">{props.children}</div>
216
+ {props.footer && <div class="card-footer">{props.footer}</div>}
217
+ </div>
88
218
  )
89
- });
219
+ }
220
+ ```
221
+
222
+ ### Using Slots
223
+
224
+ ```tsx
225
+ <Card>
226
+ <template slot="header">
227
+ <span>📌</span>
228
+ <strong>Title</strong>
229
+ </template>
230
+
231
+ <p>Main body content — becomes props.children.</p>
232
+
233
+ <template slot="footer">
234
+ <span>Footer info</span>
235
+ </template>
236
+ </Card>
90
237
  ```
91
238
 
92
- ### 4) Logic Reuse (Computed / Watch)
239
+ How it works inside `createElement`:
240
+
241
+ 1. Children passed to the component are iterated
242
+ 2. Elements matching `child instanceof HTMLTemplateElement && child.getAttribute("slot")` have their content extracted into the named prop
243
+ 3. Other children are collected into `props.children`
93
244
 
94
245
  ```ts
95
- import { computed, watch } from "./core";
246
+ // Pseudocode of the slot resolution
247
+ for (const child of allChildren) {
248
+ if (child instanceof HTMLTemplateElement && child.getAttribute("slot")) {
249
+ const slotName = child.getAttribute("slot")!
250
+ mergedProps[slotName] = Array.from(child.content.childNodes)
251
+ } else {
252
+ defaultChildren.push(child)
253
+ }
254
+ }
255
+ mergedProps.children = defaultChildren
256
+ ```
257
+
258
+ ## Router
259
+
260
+ Actview includes a client-side router with nested route support.
261
+
262
+ ### Setup
263
+
264
+ ```tsx
265
+ import { Router } from "@actview/router"
266
+ import { Home } from "./pages/home"
267
+ import { About } from "./pages/about"
268
+
269
+ const routes = [
270
+ { path: "/", component: Home },
271
+ { path: "/about", component: About },
272
+ { path: "/admin", component: AdminLayout, children: [
273
+ { path: "/admin/users", component: Users },
274
+ { path: "/admin/settings", component: Settings },
275
+ ]},
276
+ ]
277
+
278
+ new Router({ routes })
279
+ ```
280
+
281
+ ### Navigation
282
+
283
+ ```tsx
284
+ import { useRouter } from "@actview/router"
285
+
286
+ function NavBar() {
287
+ const router = useRouter()
96
288
 
97
- // Computed Property
98
- const info = computed(() => `${student.name} (${student.age} years old)`);
289
+ return () => (
290
+ <nav>
291
+ <a onClick={() => router.push("/")}>Home</a>
292
+ <a onClick={() => router.push("/about")}>About</a>
293
+ </nav>
294
+ )
295
+ }
296
+ ```
297
+
298
+ ### RouterView
299
+
300
+ ```tsx
301
+ import { RouterView } from "@actview/router"
302
+
303
+ function App() {
304
+ return () => (
305
+ <div>
306
+ <NavBar />
307
+ <RouterView /> {/* renders matched route component */}
308
+ </div>
309
+ )
310
+ }
311
+ ```
312
+
313
+ For nested routes, place `<RouterView />` inside the parent layout component — it automatically renders the child route at the current depth.
314
+
315
+ ## Architecture
99
316
 
100
- // Watcher
101
- watch(() => student.age, (newVal, oldVal) => {
102
- console.log("Age changed:", oldVal, "->", newVal);
103
- });
104
317
  ```
318
+ packages/
319
+ core/ Reactivity (ref, reactive, computed, watch) + EventBus
320
+ jsx/ Custom JSX factory (createElement, mountComponent, diffElement)
321
+ router/ Client-side router with nested route support
322
+ actview/ Package re-exports
323
+ ```
324
+
325
+ ### Diff Algorithm
326
+
327
+ `diffElement` compares two real DOM nodes directly (no virtual DOM):
105
328
 
106
- ## Directory Structure & Principle
329
+ 1. **Type mismatch** replace
330
+ 2. **Text node** → update `textContent`
331
+ 3. **Component boundary** → skip (component manages its own subtree)
332
+ 4. **Same element** → sync attributes → reconcile children (keyed or index-based)
107
333
 
108
- - `src/core`: Core implementation
109
- - `ref.ts` / `reactive.ts`: Reactivity system based on Proxy
110
- - `compile.ts`: View binder based on jQuery selectors
111
- - `computed.ts` / `watch.ts`: Dependency collection and side effect handling
112
- - `jsx/`: Custom JSX runtime (No Virtual DOM, returns real DOM directly)
113
- - `src/types`: Type definitions
334
+ Component boundary isolation means a re-rendering parent does not diff into child components. Each component has its own `componentUpdateFn` that runs independently when its reactive dependencies change.
114
335
 
115
- ## Known Limitations
336
+ ## License
116
337
 
117
- - `render` strategy is "clear and re-insert", no Diff algorithm, suitable for display-heavy Campaign UIs.
118
- - Intended for learning and small-scale campaign pages only.
338
+ MIT
package/README.zh.md CHANGED
@@ -1,119 +1,339 @@
1
- # ActView (MVVM 学习版)
1
+ # Actview
2
2
 
3
- [English](./README.md) | 简体中文
3
+ 轻量级前端框架,融合 **Vue 风格的响应式系统** 与 **React 风格的 JSX**,基于 Proxy 和自定义 JSX 工厂从零实现。
4
4
 
5
- **ActView** 是为**活动/营销单页**而生的轻量级 MVVM 库。
5
+ ## 安装
6
6
 
7
- > 目标场景:单页应用、状态复杂、逻辑复杂,且有强分工协作需求。
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
8
11
 
9
- ## 为什么叫 ActView?
12
+ 构建:
13
+
14
+ ```bash
15
+ npm run build
16
+ ```
10
17
 
11
- 在活动页(Campaign/H5)开发中,常有明确分工:
12
- - **A 同学**:只写 HTML / CSS(切图仔)
13
- - **B 同学**:只管数据、逻辑、响应式(逻辑仔)
18
+ ## 响应式系统
14
19
 
15
- **ActView** 的核心能力就是把这两者“接”起来:
16
- **选择器 + 配置 → 自动绑定数据**
20
+ ### ref — 基础类型
17
21
 
18
- 目标:**低心智负担、快、稳、好交接**。
22
+ ```tsx
23
+ import { ref, computed, watch } from "@actview/core"
19
24
 
20
- ## 核心特性
25
+ const count = ref(0)
21
26
 
22
- - **配置驱动绑定**:不侵入 HTML,只需提供 `selector` + 配置项。
23
- - **自动响应式**:`ref` / `reactive` 数据变更自动更新视图。
24
- - **双向绑定闭环**:通过 `listeners` 快速回写数据。
25
- - **计算属性/侦听器**:`computed` / `watch` 处理复杂逻辑。
26
- - **JSX / 函数渲染**:支持在配置中直接返回 JSX(`render` 函数),处理复杂列表/组件。
27
- - **事件快速挂载**:配置里直接写事件回调。
27
+ // 读取: count.value
28
+ // 写入: count.value = 1
29
+ console.log(count.value) // 0
30
+ count.value++
31
+ console.log(count.value) // 1
32
+ ```
28
33
 
29
- ## 快速开始
34
+ ### reactive — 对象与数组
30
35
 
31
- ```bash
32
- npm i
33
- npm run dev
36
+ ```tsx
37
+ const user = reactive({ name: "Actview", age: 3 })
38
+ const list = reactive([{ id: 1, text: "条目 A" }])
39
+
40
+ console.log(user.name) // "Actview"
41
+ user.age = 4 // 触发更新
42
+
43
+ list.push({ id: 2, text: "条目 B" }) // 触发更新
34
44
  ```
35
45
 
36
- 构建产物输出到 `out/`:
46
+ ### computed — 计算属性
37
47
 
38
- ```bash
39
- npm run build
48
+ ```tsx
49
+ const name = ref("Actview")
50
+ const version = ref(1)
51
+
52
+ const info = computed(() => `${name.value} v${version.value}`)
53
+
54
+ console.log(info.value) // "Actview v1"
40
55
  ```
41
56
 
42
- ## 核心用法
57
+ ### watch — 侦听器
43
58
 
44
- ### 1) 数据定义 (Ref / Reactive)
59
+ ```tsx
60
+ const count = ref(0)
45
61
 
46
- ```ts
47
- import { ref, reactive } from "./core";
62
+ watch(count, (newVal, oldVal) => {
63
+ console.log(`count: ${oldVal} ${newVal}`)
64
+ })
65
+
66
+ count.value = 1 // 输出 "count: 0 → 1"
67
+ ```
68
+
69
+ ### 原理
48
70
 
49
- // 基础类型
50
- const count = ref(0);
71
+ Actview 使用 **Proxy** 拦截响应式数据的读写。组件渲染函数中读取的 ref 会自动建立订阅关系。数据变更时,仅依赖该数据的组件重新渲染。
51
72
 
52
- // 对象/数组
53
- const student = reactive({ name: "张三", age: 20 });
54
- const list = reactive([{ id: 1, name: "A" }]);
73
+ ```tsx
74
+ const count = ref(0)
75
+
76
+ function Counter() {
77
+ return () => <div>{count.value}</div> // 订阅 count
78
+ }
55
79
  ```
56
80
 
57
- ### 2) 视图绑定 (Compile + Option)
81
+ ## 组件
58
82
 
59
- 视图绑定通过 `compile(option)` 完成。A 同学写好 HTML (id="count"),B 同学写配置:
83
+ ### Setup 模式(推荐)
60
84
 
61
- ```ts
62
- import { compile } from "./core/compile";
85
+ 组件函数执行一次(setup),返回渲染函数(render),每次更新时重新执行渲染函数:
86
+
87
+ ```tsx
88
+ function Counter() {
89
+ const count = ref(0)
90
+
91
+ return () => (
92
+ <div>
93
+ <p>Count: {count.value}</p>
94
+ <button onClick={() => count.value++}>+1</button>
95
+ </div>
96
+ )
97
+ }
98
+ ```
99
+
100
+ - **Setup 阶段**:创建 ref、computed、watch 等,只执行一次
101
+ - **Render 阶段**:返回 JSX,自动追踪响应式依赖,每次更新重新执行
102
+
103
+ ### 直接模式(无状态)
104
+
105
+ 组件函数本身就是渲染函数,父组件每次渲染都会重新执行:
106
+
107
+ ```tsx
108
+ function Greeting(props: { name: string }) {
109
+ return <div>你好,{props.name}!</div>
110
+ }
111
+ ```
112
+
113
+ 无内部状态,无独立响应式边界,纯 props 输入 / JSX 输出。
114
+
115
+ ### Props
116
+
117
+ ```tsx
118
+ function Welcome(props: { title: string; count: number }) {
119
+ return () => (
120
+ <div>
121
+ <h2>{props.title}</h2>
122
+ <p>Count: {props.count}</p>
123
+ </div>
124
+ )
125
+ }
126
+
127
+ // 使用
128
+ <Welcome title="Hello" count={count.value} />
129
+ ```
130
+
131
+ ## JSX & DOM 渲染
132
+
133
+ Actview 使用 **自定义 JSX 工厂**(`createElement`)直接创建真实 DOM,无虚拟 DOM:
134
+
135
+ ```tsx
136
+ // JSX → createElement → 真实 DOM
137
+ const element = <div class="card">
138
+ <h3>标题</h3>
139
+ <p>内容</p>
140
+ </div>
141
+ ```
142
+
143
+ ### 响应式更新(组件级)
144
+
145
+ ```tsx
146
+ function Timer() {
147
+ const time = ref(new Date().toLocaleTimeString())
148
+
149
+ setInterval(() => {
150
+ time.value = new Date().toLocaleTimeString()
151
+ }, 1000)
152
+
153
+ return () => <div>当前时间: {time.value}</div>
154
+ }
155
+ ```
156
+
157
+ - `time.value` 变化 → 触发 `publish`
158
+ - 仅该组件的 `componentUpdateFn` 重新执行
159
+ - 对比新旧 DOM,仅更新变化的节点
160
+
161
+ ### Fragment
162
+
163
+ ```tsx
164
+ function List() {
165
+ return () => (
166
+ <>
167
+ <h2>列表</h2>
168
+ {items.map(item => <div key={item.id}>{item.text}</div>)}
169
+ </>
170
+ )
171
+ }
172
+ ```
173
+
174
+ Fragment 用 `DocumentFragment` 包含子节点,配合首尾锚点注释节点实现稳定 diff。
175
+
176
+ ### Keyed Reconciliation
63
177
 
64
- compile({
65
- selector: "#count",
66
- // 绑定文本:自动响应 count.value 变化
67
- text: () => `当前计数: ${count.value}`,
68
- // 绑定显隐
69
- show: () => count.value > 0,
70
- // 绑定事件
71
- listeners: [
72
- { type: "click", callback: () => count.value++ }
73
- ]
74
- });
178
+ 子节点带有 `key` 属性时按标识符匹配,保留 DOM 状态(如输入框内容):
179
+
180
+ ```tsx
181
+ function SortableList() {
182
+ const items = reactive([
183
+ { id: 1, text: "A" },
184
+ { id: 2, text: "B" },
185
+ ])
186
+
187
+ return () => (
188
+ <>
189
+ {items.map(item => (
190
+ <div key={item.id}>
191
+ <input placeholder={item.text} />
192
+ </div>
193
+ ))}
194
+ <button onClick={() => items.sort(() => Math.random() - 0.5)}>
195
+ 随机排序
196
+ </button>
197
+ </>
198
+ )
199
+ }
75
200
  ```
76
201
 
77
- ### 3) 复杂渲染 (Render + JSX)
202
+ key 时随机排序会导致输入框内容错位,有 key 时每个输入框跟随其 `div`。
78
203
 
79
- 对于列表或复杂结构,直接用 JSX:
204
+ ## 插槽
205
+
206
+ 命名插槽使用 `<template slot="xxx">` 语法。模板内的内容会被提取为 `props.xxx` 传入组件。未包裹在 `<template slot>` 中的内容归入 `props.children`(默认插槽)。
207
+
208
+ ### 定义带插槽的组件
80
209
 
81
210
  ```tsx
82
- compile({
83
- selector: "#list",
84
- render: () => (
85
- <ul>
86
- {list.map(item => <li>{item.name}</li>)}
87
- </ul>
211
+ function Card() {
212
+ return (props: any) => (
213
+ <div class="card">
214
+ {props.header && <div class="card-header">{props.header}</div>}
215
+ <div class="card-body">{props.children}</div>
216
+ {props.footer && <div class="card-footer">{props.footer}</div>}
217
+ </div>
88
218
  )
89
- });
219
+ }
220
+ ```
221
+
222
+ ### 使用插槽
223
+
224
+ ```tsx
225
+ <Card>
226
+ <template slot="header">
227
+ <span>📌</span>
228
+ <strong>标题</strong>
229
+ </template>
230
+
231
+ <p>主体内容 — 会成为 props.children。</p>
232
+
233
+ <template slot="footer">
234
+ <span>底部信息</span>
235
+ </template>
236
+ </Card>
90
237
  ```
91
238
 
92
- ### 4) 逻辑复用 (Computed / Watch)
239
+ ### 原理
240
+
241
+ 在 `createElement` 中,传递给组件的子节点会被遍历:
242
+
243
+ 1. 命中的 `<template slot="xxx">` 提取其 `content.childNodes` 作为命名 prop
244
+ 2. 其余节点归入 `props.children`
93
245
 
94
246
  ```ts
95
- import { computed, watch } from "./core";
247
+ // 插槽解析伪代码
248
+ for (const child of allChildren) {
249
+ if (child instanceof HTMLTemplateElement && child.getAttribute("slot")) {
250
+ const slotName = child.getAttribute("slot")!
251
+ mergedProps[slotName] = Array.from(child.content.childNodes)
252
+ } else {
253
+ defaultChildren.push(child)
254
+ }
255
+ }
256
+ mergedProps.children = defaultChildren
257
+ ```
258
+
259
+ ## 路由
260
+
261
+ Actview 内置客户端路由,支持嵌套。
262
+
263
+ ### 配置
264
+
265
+ ```tsx
266
+ import { Router } from "@actview/router"
267
+ import { Home } from "./pages/home"
268
+ import { About } from "./pages/about"
269
+
270
+ const routes = [
271
+ { path: "/", component: Home },
272
+ { path: "/about", component: About },
273
+ { path: "/admin", component: AdminLayout, children: [
274
+ { path: "/admin/users", component: Users },
275
+ { path: "/admin/settings", component: Settings },
276
+ ]},
277
+ ]
278
+
279
+ new Router({ routes })
280
+ ```
281
+
282
+ ### 导航
283
+
284
+ ```tsx
285
+ import { useRouter } from "@actview/router"
96
286
 
97
- // 计算属性
98
- const info = computed(() => `${student.name} (${student.age}岁)`);
287
+ function NavBar() {
288
+ const router = useRouter()
99
289
 
100
- // 侦听器
101
- watch(() => student.age, (newVal, oldVal) => {
102
- console.log("年龄变了:", oldVal, "->", newVal);
103
- });
290
+ return () => (
291
+ <nav>
292
+ <a onClick={() => router.push("/")}>首页</a>
293
+ <a onClick={() => router.push("/about")}>关于</a>
294
+ </nav>
295
+ )
296
+ }
297
+ ```
298
+
299
+ ### RouterView
300
+
301
+ ```tsx
302
+ import { RouterView } from "@actview/router"
303
+
304
+ function App() {
305
+ return () => (
306
+ <div>
307
+ <NavBar />
308
+ <RouterView /> {/* 渲染匹配当前路由的组件 */}
309
+ </div>
310
+ )
311
+ }
104
312
  ```
105
313
 
106
- ## 目录结构与原理
314
+ 嵌套路由时,在父布局组件中放置 `<RouterView />`,自动渲染当前深度的子路由。
315
+
316
+ ## 架构
317
+
318
+ ```
319
+ packages/
320
+ core/ 响应式系统 (ref, reactive, computed, watch) + EventBus
321
+ jsx/ 自定义 JSX 工厂 (createElement, mountComponent, diffElement)
322
+ router/ 客户端路由,支持嵌套
323
+ actview/ 统一导出
324
+ ```
325
+
326
+ ### Diff 算法
327
+
328
+ `diffElement` 直接比较两个真实 DOM 节点(无虚拟 DOM):
107
329
 
108
- - `src/core`:核心实现
109
- - `ref.ts` / `reactive.ts`:基于 Proxy 的响应式系统
110
- - `compile.ts`:基于 jQuery 选择器的视图绑定器
111
- - `computed.ts` / `watch.ts`:依赖收集与副作用处理
112
- - `jsx/`:自定义 JSX 运行时(无虚拟 DOM,直接返回真实 DOM)
113
- - `src/types`:类型定义
330
+ 1. **类型不同** → 替换
331
+ 2. **文本节点** 更新 `textContent`
332
+ 3. **组件边界** → 跳过(组件自己管理子树)
333
+ 4. **相同元素** → 同步属性 → 协调子节点(key 匹配或索引匹配)
114
334
 
115
- ## 已知限制
335
+ 组件边界隔离意味着父组件重新渲染时不会深入到子组件内部。每个组件拥有独立的 `componentUpdateFn`,仅在自身依赖的响应式数据变化时执行。
116
336
 
117
- - `render` 策略是“清空并重新插入”,无 Diff 算法,适合活动页展示型 UI。
118
- - 仅供学习与小型活动页使用。
337
+ ## License
119
338
 
339
+ MIT
package/index.cjs CHANGED
@@ -1,10 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const core = require("@actview/core");
4
+ const router = require("@actview/router");
4
5
  Object.keys(core).forEach((k) => {
5
6
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
6
7
  enumerable: true,
7
8
  get: () => core[k]
8
9
  });
9
10
  });
11
+ Object.keys(router).forEach((k) => {
12
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
13
+ enumerable: true,
14
+ get: () => router[k]
15
+ });
16
+ });
10
17
  //# sourceMappingURL=index.cjs.map
package/index.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
package/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from '@actview/core';
2
+ export * from '@actview/router';
2
3
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA"}
package/index.mjs CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "@actview/core";
2
+ export * from "@actview/router";
2
3
  //# sourceMappingURL=index.mjs.map
package/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "actview",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Configuration-driven MVVM born for Campaign/Marketing single pages: selector + config for automatic data binding, supporting reactive/ref, computed, watch/watchEffect, JSX rendering, and quick event mounting.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
@@ -36,8 +36,9 @@
36
36
  "url": ""
37
37
  },
38
38
  "dependencies": {
39
- "@actview/core": "^1.0.2",
40
- "@actview/jsx": "^1.0.1"
39
+ "@actview/core": "^1.0.4",
40
+ "@actview/jsx": "^1.0.4",
41
+ "@actview/router": "^1.0.4"
41
42
  },
42
43
  "publishConfig": {
43
44
  "access": "public"