@virid/vue 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +322 -1
  2. package/README.zh.md +304 -1
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1 +1,322 @@
1
- todo
1
+ # @virid/vue
2
+
3
+ `@virid/vue` is the UI adapter for `@virid/core`, responsible for delivering data processed by systems to Vue for display. In this architecture, Vue acts strictly as a **"Data Projection Layer"** and is not responsible for handling complex business logic. Its purpose is to establish a controlled, unidirectional communication tunnel between Vue's reactivity system and the `@virid/core` kernel.
4
+
5
+ ## 🌟 Core Design Philosophy
6
+
7
+ In `@virid/vue`, Vue components no longer hold business state directly. Instead, they delegate all authority and functionality to a **Controller** via the `useController` hook. You will not—and should not—use most of Vue's built-in APIs, such as `ref`, `computed`, `watch`, `emit`, `provide`, or `inject`, state management tools like Pinia also should no longer be used.
8
+
9
+ - **Physically Isolated Modification Rights:** The Controller acts as the intermediary between Vue and Components. While Vue components can directly observe Component data, any operation that leads to a state change must be converted into a **Message** and sent to a **System** for processing.
10
+ - **Enforced Read-Only (Deep Shield):** In `@virid/vue`, "read-only" is more than just a suggestion. Through the **createDeepShield** mechanism, all data not owned by the current context is forced into a "Deep Read-Only" state. All write operations are prohibited, and even methods cannot be called unless explicitly marked with the `@Safe()` decorator. This physically eliminates the possibility of UI components accidentally polluting external states.
11
+ - **Full Vue Ecosystem Compatibility:** Controllers are pure classes. When combined with `@OnHook` and `@Use` decorators, they can perceive the Vue lifecycle and utilize any hooks from the Vue ecosystem without being tightly coupled to a specific DOM structure.
12
+
13
+ ## 🔌Enable plugins
14
+
15
+ ```ts
16
+ import { createVirid } from '@virid/core'
17
+ import { VuePlugin } from '@virid/vue'
18
+ const app = createVirid()
19
+ app.use(VuePlugin, {})
20
+ ```
21
+
22
+ ## 🛠️ @virid/vue Core API Overview
23
+
24
+ ### 1. Vue Adapter Decorators
25
+
26
+ #### `@Responsive(shallow?: boolean)`
27
+
28
+ - **Function:** Marks a class property as reactive. This decorator is available for both `Controllers` and `Components`.
29
+ - **Logic:** `@virid/vue` transforms any class property marked with `@Responsive()` into a reactive state upon instantiation. A key advantage is that **you do not need to use `.value`** to access or modify the data.
30
+ - **Example:**
31
+
32
+ ```ts
33
+ // Marking a property in a global Component as reactive.
34
+ // Functionally similar to a Pinia store, but with full Dependency Injection support.
35
+ @Component()
36
+ export class SettingComponent {
37
+ @Responsive()
38
+ public counter: number = 0;
39
+
40
+ // Passing 'true' will wrap the property with a ShallowRef instead of a standard Ref.
41
+ // @Responsive(true)
42
+ // public counter: number = 0;
43
+ }
44
+
45
+ export class SettingSystem {
46
+ /**
47
+ * Updates settings when a specific message is dispatched.
48
+ */
49
+ @System({
50
+ messageClass: ChangeCounterMessage
51
+ })
52
+ static LoadSetting(settings: SettingComponent) {
53
+ // Note: No need for .value. Direct assignment works perfectly.
54
+ settings.counter += 1;
55
+ }
56
+ }
57
+
58
+ // Marking a property in a Vue UI Controller as reactive.
59
+ @Controller()
60
+ export class PageController {
61
+ @Responsive()
62
+ public currentPageIndex: number = 0;
63
+ }
64
+
65
+ // Inside a Vue component, use useController to retrieve the instance.
66
+ import { useController } from '@virid/vue';
67
+ import { PageController } from './controllers';
68
+
69
+ const pct = useController(PageController);
70
+
71
+ // Usage in template:
72
+ // <div>Current Page: {{ pct.currentPageIndex }}</div>
73
+ ```
74
+
75
+ #### `@Project()`
76
+
77
+ - **Function:** The most powerful projection mechanism in `@virid/vue`. it allows you to pull data from any `Component` or derive new values from local `@Responsive()` properties while maintaining full reactivity.
78
+ - **Logic:** `@Project` is conceptually similar to Vue's `computed`, but far more powerful. The resulting data is **strictly read-only**, ensuring that the projection layer cannot accidentally modify the source of truth.
79
+ - **Example:**
80
+
81
+ ```ts
82
+ @Controller()
83
+ export class PageController {
84
+ // Define a local reactive property first
85
+ @Responsive()
86
+ public currentPageIndex: number = 0;
87
+
88
+ // Usage 1: Using a 'get' accessor to remap local reactive data
89
+ @Project()
90
+ get nextPageIndex() {
91
+ // nextPageIndex behaves like computed(() => this.currentPageIndex + 1)
92
+ return this.currentPageIndex + 1;
93
+ }
94
+
95
+ // Usage 2: Using an arrow function to remap data
96
+ // The first argument of the function is the Controller instance itself.
97
+ // No initialization required; @virid/vue ensures previousPageIndex is available on the instance.
98
+ @Project<PageController>(i => i.currentPageIndex - 1)
99
+ public previousPageIndex!: number;
100
+
101
+ // Usage 3: Pulling data directly from a global Component
102
+ // Arg 1: The Component constructor type.
103
+ // Arg 2: An arrow function receiving the Component instance.
104
+ // If SettingComponent.counter is decorated with @Responsive(),
105
+ // then currentCounter will also be reactive.
106
+ @Project(SettingComponent, i => i.counter)
107
+ public currentCounter!: number;
108
+ }
109
+ ```
110
+
111
+ ------
112
+
113
+ #### `@Inherit()`
114
+
115
+ - **Function:** Enables data sharing across different Controllers, bypassing component hierarchy entirely. The **enforced read-only** nature ensures that one Controller can never modify the data owned by another.
116
+ - **Logic:** `@Inherit()` is used to share local, non-global variables between Controllers—ideal for data that needs to be shared but doesn't belong in a global `Component` (e.g., specific UI states).
117
+ - **Example:**
118
+
119
+ ```ts
120
+ // --- In PageController.ts ---
121
+ @Controller()
122
+ export class PageController {
123
+ @Responsive()
124
+ public currentPageIndex: number = 0;
125
+ }
126
+
127
+ // --- In Page.vue ---
128
+ import { useController } from '@virid/vue';
129
+ import { PageController } from './controllers';
130
+ // Instantiate and assign a unique ID
131
+ const pct = useController(PageController, { id: "page-controller" });
132
+
133
+ // --- In OtherController.ts ---
134
+ @Controller()
135
+ export class OtherController {
136
+ /**
137
+ * Use @Inherit with three arguments:
138
+ * 1. The target Controller constructor.
139
+ * 2. The ID registered during useController.
140
+ * 3. An arrow function to pick the data.
141
+ * * Virid automatically creates 'myPageIndex' on this instance and keeps it reactive.
142
+ * It is protected as read-only; OtherController cannot modify the source data.
143
+ * If the PageController with this ID is destroyed, myPageIndex becomes null.
144
+ */
145
+ @Inherit(PageController, "page-controller", (i) => i.currentPageIndex)
146
+ public myPageIndex!: number | null;
147
+ }
148
+ ```
149
+
150
+ #### `@Watch()`
151
+
152
+ - **Function:** An enhanced version of Vue's `watch` utility. It can observe any `Component` or local properties marked with `@Responsive()`.
153
+ - **Logic:** Provides a mechanism for triggering side effects based on state changes.
154
+ - **Example:**
155
+
156
+ ```ts
157
+ @Controller()
158
+ export class OtherController {
159
+ @Inherit(PageController, "page-controller", (i) => i.currentPageIndex)
160
+ public myPageIndex!: number | null;
161
+
162
+ // Usage 1: Watch data derived from @Inherit(), @Project(), or @Responsive().
163
+ @Watch<OtherController>(i => i.myPageIndex, { immediate: true })
164
+ onPageIndexChange() {
165
+ // Logic here...
166
+ }
167
+
168
+ // Usage 2: Watch data on a global Component directly, regardless of where it is used.
169
+ // Both usage styles support Vue's standard WatchOptions as the final argument.
170
+ @Watch(SettingComponent, i => i.counter, { deep: true })
171
+ onCounterChange() {
172
+ // Logic here...
173
+ }
174
+ }
175
+ ```
176
+
177
+ ------
178
+
179
+ #### `@OnHook(lifecycle)`
180
+
181
+ - **Function:** Vue lifecycle bridging. It allows Vue to trigger your Controller's methods at the appropriate lifecycle stages.
182
+ - **Logic:** In addition to standard Vue lifecycles, it introduces a unique **`onSetup`** hook. Methods marked with `onSetup` are executed immediately after the Controller is instantiated and its data is prepared.
183
+ - **Example:**
184
+
185
+ ```ts
186
+ @Controller()
187
+ export class OtherController {
188
+ @OnHook("onMounted")
189
+ public onMounted() {
190
+ // Called after the Vue component is mounted
191
+ }
192
+
193
+ @OnHook("onSetup")
194
+ public onSetup() {
195
+ // Called immediately after Controller initialization, prior to onMounted
196
+ }
197
+ }
198
+ ```
199
+
200
+ ------
201
+
202
+ #### `@Use()`
203
+
204
+ - **Function:** Provides seamless compatibility with any Hook in the Vue ecosystem, binding them directly to the Controller instance.
205
+ - **Logic:** The return value of the provided function is bound to the class property. You can interact with these external hooks as if they were native members of your class.
206
+ - **Example:**
207
+
208
+ ```ts
209
+ @Controller()
210
+ export class OtherController {
211
+ // Access vue-router state via 'this.route'
212
+ @Use(() => useRoute())
213
+ public route!: ReturnType<typeof useRoute>;
214
+
215
+ // Bind a template reference
216
+ @Use(() => useTemplateRef("html"))
217
+ public htmlElement!: ReturnType<typeof useTemplateRef>;
218
+ }
219
+ ```
220
+
221
+ ------
222
+
223
+ #### `@Env()`
224
+
225
+ - **Function:** Captures data provided by a parent component via `defineProps` and binds it to the Controller.
226
+ - **Logic:** When calling `useController`, pass the `props` into the `context`. The Controller will then automatically synchronize with those values.
227
+ - **Example:**
228
+
229
+ ```ts
230
+ // --- Inside your Vue Component ---
231
+ const props = defineProps<{
232
+ pageIndex: number
233
+ maxPageLength: number
234
+ messageType: Newable<BaseMessage>
235
+ }>();
236
+
237
+ // Pass props to the context during creation
238
+ const sct = useController(ScrubberController, {
239
+ context: props
240
+ });
241
+
242
+ // --- Inside ScrubberController.ts ---
243
+ @Controller()
244
+ export class ScrubberController {
245
+ // These properties will automatically stay reactive!
246
+ @Env()
247
+ public pageIndex!: number;
248
+
249
+ @Env()
250
+ public maxPageLength!: number;
251
+
252
+ @Env()
253
+ public messageType!: Newable<BaseMessage>;
254
+ }
255
+ ```
256
+
257
+ ### 2. Controller Messaging
258
+
259
+ In `@virid/vue`, Controllers are not passive observers; they can actively listen for messages of interest and trigger internal callbacks.
260
+
261
+ #### `@Listener()`
262
+
263
+ - **Function:** The `@Listener()` decorator empowers a Controller to perceive environmental changes autonomously. It transitions the Controller from a passive receiver to an active listener.
264
+
265
+ - **Lifecycle:** Listeners are automatically tied to the Controller's lifecycle. They are registered upon instantiation and disposed of when the Controller is destroyed, requiring no manual cleanup.
266
+
267
+ - **Logic:** Provide the specific message type to the `messageClass` parameter within the `@Listener` decorator.
268
+
269
+ - **Example:**
270
+
271
+ ```ts
272
+ export class PageChangeMessage extends SingleMessage {
273
+ constructor(public pageIndex: number) {
274
+ super();
275
+ }
276
+ }
277
+
278
+ @Controller()
279
+ export class PlaylistPageController {
280
+ @Responsive()
281
+ public pageIndex: number = 0;
282
+
283
+ /**
284
+ * Use @Listener to capture specific messages.
285
+ * Note: This only provides access to the message instance itself;
286
+ * it does not support Component injection.
287
+ * Wherever PageChangeMessage.send(newPage) is called—regardless of
288
+ * component hierarchy—onPageChange will be triggered.
289
+ */
290
+ @Listener({
291
+ messageClass: PageChangeMessage
292
+ })
293
+ public onPageChange(message: PageChangeMessage) {
294
+ this.pageIndex = message.pageIndex;
295
+ }
296
+ }
297
+ ```
298
+
299
+ ------
300
+
301
+ ## 🛡️ Physical Read-Only Shield (Deep Shield)
302
+
303
+ In `@virid/vue`, "prohibiting the modification of external data" is not a suggestion—it is an absolute law.
304
+
305
+ To ensure determinism, all external data retrieved via `@Project` or `@Inherit` is automatically wrapped in a **recursive physical shield**. This shield intercepts all **Write (Set)** operations and **Illegal Method Calls**, providing detailed error messages regarding the violation path and cause.
306
+
307
+ ### 1. Interception Behavior
308
+
309
+ - **Assignment Interception:** Any attempt to modify an object property will immediately throw an exception.
310
+ - **Mutation Interception:** Calling any method that could modify the original data (e.g., `Array.push`, `Map.set`, `Set.add`) is strictly forbidden.
311
+ - **Deep Recursion:** The shield is applied recursively and cached lazily. No matter how deep the data nesting is, all descendant nodes are protected, and the protection is activated only upon access.
312
+
313
+ ### 2. Safe Method Whitelist
314
+
315
+ To avoid breaking UI rendering logic, non-side-effect utility methods are permitted:
316
+
317
+ | **Category** | **Allowed "Safe" Methods/Properties** |
318
+ | ------------------ | ------------------------------------------------------------ |
319
+ | **Base Protocols** | `Symbol.iterator`, `toString`, `valueOf`, `toJSON`, `constructor`, etc. |
320
+ | **Array** | `length`, `map`, `filter`, `reduce`, `slice`, `find`, `includes`, `at`, `join`, `concat`, etc. |
321
+ | **Set / Map** | `has`, `get`, `keys`, `values`, `entries`, `forEach`, `size`. |
322
+ | **String** | `length`, `slice`, `includes`, `split`, `replace`, `trim`, `toUpperCase`, etc. |
package/README.zh.md CHANGED
@@ -1 +1,304 @@
1
- todo
1
+ # @virid/vue
2
+
3
+ `@virid/vue` 是 ` @virid/core` 的UI 适配器,负责将`system`处理完成的数据交付给`Vue`显示。`Vue`在此过程中仅仅是是一个 **“数据投影层”**,不负责处理任何复杂的逻辑,其宗旨是在 `Vue` 的响应式系统与 `@virid/core`核心之间建立一条受控的、单向的通讯隧道。
4
+
5
+ ## 🌟 核心设计理念
6
+
7
+ 在 `@virid/vue` 中,`Vue` 组件不再直接持有业务状态,而是通过 `useController` 将**所有权限和功能**委托给一个 **Controller**。**你不会,也不应该**使用`Vue`提供的绝大部分API,例如`ref`,`computed`,`watch` `emit`,`privode`, `inject`等,也不应该再使用`Pinia`之类的状态管理工具。
8
+
9
+ - **物理隔离的修改权**:`Controller` 充当 `Vue` 与 `Component` 之间的中介。Vue 组件可以直接观察 `Component` 数据,但所有导致状态变更的操作必须转化为 `Message` 发送给 System 处理。
10
+ - **强制只读**:在`@virid/vue`中,只读不仅仅只是建议,过 `createDeepShield` 机制,**所有非自身所有**的数据,都被强制转化为 **“深度只读”** 禁止任何写操作,甚至连方法也无法任意调用,除非被@Safe装饰器标记。在物理层面杜绝了 UI 组件意外污染非自身的可能性。
11
+ - **Vue生态全适配**:`Controller` 本身是纯粹的类,配合 `@OnHook` 与`@Use`装饰器,它可以感知 `Vue` 的生命周期并使用所有`Vue`生态的钩子,但又不与特定的 DOM 结构绑定。
12
+
13
+ ## 🔌启用插件
14
+
15
+ ```ts
16
+ import { createVirid } from '@virid/core'
17
+ import { VuePlugin } from '@virid/vue'
18
+ const app = createVirid()
19
+ app.use(VuePlugin, {})
20
+ ```
21
+
22
+ ## 🛠️ @virid/vue 核心 API 概览
23
+
24
+ ### 1. Vue适配装饰器
25
+
26
+ #### `@Responsive(shallow?: boolean)`
27
+
28
+ - **功能**:将类属性标记为响应式的,该装饰器在`Component`上也可用。
29
+ - **逻辑**:`@virid/vue`会在任何`Controller`或`Component`实例化时,将标记为`@Responsive()`的类属性变为响应式的。并且,**不需要使用.value来访问**。
30
+ - **示例:**
31
+
32
+ ```ts
33
+ //将一个全局的Component中的属性标记为响应式的
34
+ //这在功能上类似于一个pinia store
35
+ //但是,你仍然可以使用依赖注入功能
36
+ @Component()
37
+ export class SettingComponent {
38
+ @Responsive()
39
+ public counter:number=0
40
+ //传入true,virid/vue将会用ShadowRef来包装而不是使用Ref
41
+ //@Responsive(true)
42
+ //public counter:number=0
43
+ }
44
+
45
+ export class SettingSystem {
46
+ /**
47
+ * *消息发送时更新设置
48
+ */
49
+ @System({
50
+ messageClass: ChangeCounterMessage
51
+ })
52
+ static LoadSetting(settings: SettingComponent) {
53
+ //注意⚠️,不需要使用.value。直接赋值即可
54
+ settings.counter +=1
55
+ }
56
+ }
57
+
58
+ ```
59
+
60
+ ```ts
61
+ //将一个vue组件的Controller中的属性标记为响应式的
62
+ @Controller()
63
+ export class PageController {
64
+ @Responsive()
65
+ public currentPageIndex:number=0
66
+ }
67
+
68
+ //在vue组件中,使用useContoller来获得控制器
69
+ import { useController } from '@virid/vue'
70
+ import { PageController } from './controllers'
71
+ const pct = useController(PageController)
72
+ //然后,你可以直接使用
73
+ <div>当前页面是:{{pct.currentPageIndex}}<div>
74
+ ```
75
+
76
+ #### `@Project()`
77
+
78
+ - **功能**:`@virid/vue`最强大的投影机制,可以从**任意**`component`中拉取数据或从自身被`@Responsive()`标记的属性中生成新数,且保留响应式。
79
+ - **逻辑**:`@Project`类似于`vue`中的`compouted`,但是远比`compouted`更为强大,其得到的数据是**强制只读**的,并且可以从任意`Component`中拉取数据
80
+ - **示例:**
81
+
82
+ ```ts
83
+ @Controller()
84
+ export class PageController {
85
+ //先确保自己有一个响应式数据
86
+ @Responsive()
87
+ public currentPageIndex:number=0
88
+ //用法1:使用get来从自身的响应式数据中重新映射新数据并保持响应式
89
+ @Project()
90
+ get nextPageIndex(){
91
+ // nextPageIndex就等于compouted(()=>this.currentPageIndex+1)
92
+ return this.currentPageIndex + 1;
93
+ }
94
+
95
+ // 用法2:不使用get而是使用一个箭头函数来重新映射数据,函数接受的第一个参数是controller实例自身
96
+ // 不需要初始化,@virid/vue将会保证previousPageIndex一定会出现在Controller实例上
97
+ @Project<PageController>(i=>.currentPageIndex - 1)
98
+ public previousPageIndex!:number;
99
+
100
+ // 用法3:直接从某个component上拉取数据,并嫁接到自身'
101
+ // 第一个参数是Component的构造函数类型,第二个参数是箭头函数,参数为前一个构造函数制定的Component类型的实例
102
+ // 不需要初始化,@virid/vue将会保证currentCounter一定会出现在Controller实例上
103
+ // 并且,如果SettingComponent中的counter是被 @Responsive()装饰的
104
+ // 那么,currentCounter也会具有响应式
105
+ @Project(SettingComponent, i=>.counter)
106
+ public currentCounter!:number;
107
+ }
108
+ ```
109
+
110
+ #### `@Inherit()`
111
+
112
+ - **功能**:跨`controller`共享数据,**无视任何组件层级**,同时**强制只读**保证了其他`controller`永远无法更改另一个`controller`的数据。
113
+ - **逻辑**:`@Inherit()`用于`Controller`之间共享一些局部的,非全局的的变量。用于处理那些需要共享但是不需要放在`Component`中存储的数据。
114
+ - **示例:**
115
+
116
+ ```ts
117
+ //在文件PageController中
118
+ //将一个vue组件的Controller中的属性标记为响应式的
119
+ @Controller()
120
+ export class PageController {
121
+ @Responsive()
122
+ public currentPageIndex:number=0
123
+ }
124
+
125
+ //然后,使用useController来在.vue文件中实例化,并同时指定一个id
126
+ //在Page.vue中
127
+ import { useController } from '@virid/vue'
128
+ import { PageController } from './controllers'
129
+ const pct = useController(PageController,{id:"page-controller"})
130
+
131
+ ```
132
+
133
+ ```ts
134
+ //在另一个Controller中
135
+ @Controller()
136
+ export class OtherController {
137
+ // 使用Inherit,并传入三个参数,分别为其他controller的构造函数、使用useController时候注册的id,一个箭头函数
138
+ // virid将为你的Controller自动创建一个myPageIndex属性,并且,将保持响应式
139
+ // 因此,你可以像使用自己的变量一样使用它
140
+ // 并且,myPageIndex是只读保护的,OtherController内绝对无法更改其他Controller内的数据
141
+ // 当对应id的PageController销毁之后,myPageIndex将自动断开连接变为null
142
+ @Inherit(PageController,"page-controller",(i)=>i.currentPageIndex)
143
+ public myPageIndex!: number|null
144
+ }
145
+ ```
146
+
147
+ #### `@Watch()`
148
+
149
+ - **功能**:提供vue中的watch功能的增强版,可以监听**任意**`component`或自身被`@Responsive()`标记的属性。
150
+ - **逻辑**:提供副作用触发机制。
151
+ - **示例:**
152
+
153
+ ```ts
154
+ //在另一个Controller中
155
+ @Controller()
156
+ export class OtherController {
157
+ @Inherit(PageController,"page-controller",(i)=>i.currentPageIndex)
158
+ public myPageIndex!: number|null
159
+ // 用法1:对于任何使用@Inherit()或者@Project()或@Responsive()得来的数据,你仍然可以使用Watch来监听
160
+ @Watch<OtherController>(i=>i.myPageIndex,{immediate:true})
161
+ onPageIndexChange(){
162
+ //....
163
+ }
164
+ // 用法2:你可以直接监听component上的数据变化,不管component在任何地方
165
+ // 二者都提供一个与vue的watch相同的配置参数对象在最后一个位置
166
+ @Watch(SettingComponent,i=>i.counter,{deep:true})
167
+ onCounterChange(){
168
+ //....
169
+ }
170
+ }
171
+ ```
172
+
173
+ #### `@OnHook(“OnMounted”|"onUnounted"|"onUpdate"|"onActivated"|"onDeactivated"|"onSetup")`
174
+
175
+ - **功能**:`Vue`生命周期桥接,让`Vue`在合适的生命周期调用你的`controller`函数。
176
+ - **逻辑**:除了`Vue`组件自身的生命周期,还提供了一个新的`onSetup`生命周期,被标记该生命周期的成员函数将会在`controller`创建时候调用。
177
+ - **示例:**
178
+
179
+ ```ts
180
+ @Controller()
181
+ export class OtherController {
182
+ @OnHook("onMounted")
183
+ public onMounted(){
184
+ //这个函数将会在组件挂载之后调用
185
+ }
186
+ @OnHook("onSetup")
187
+ public onSetup(){
188
+ //这个函数将会在Controller完成数据准备后立刻调用,先与onMounted
189
+ }
190
+ }
191
+ ```
192
+
193
+ #### `@Use()`
194
+
195
+ - **功能**:无缝兼容所有的`Vue`生态中的`hook`,将其绑定到`controller`自己身上。
196
+ - **逻辑**:使用`@Use()`,其返回值将会直接绑定到对应的类属性上,因此你可以像操作自身成员一样操作所有`hook`的返回值
197
+ - **示例:**
198
+
199
+ ```ts
200
+ @Controller()
201
+ export class OtherController {
202
+ // 轻松获得vue-router中的route,然后你可以像操作自己的 route一样使用this.route来访问
203
+ @Use(()=>useRoute())
204
+ public route!:ReturnType<typeof useRoute>;
205
+ // 获取html元素
206
+ @Use(()=>useTemplateRef("html"))
207
+ public htmlElement!:ReturnType<typeof useTemplateRef>;
208
+ }
209
+ ```
210
+
211
+ #### `@Env()`
212
+
213
+ - **功能**:接受父组件使用`defineProps`提供给自己的数据,并绑定到自己身上。
214
+ - **逻辑**:在使用`useController`的时,将`defineProps`的数据传给`context`,`controller`即可自动获得其上的数据
215
+ - **示例:**
216
+
217
+ ```ts
218
+ // 定义一个props
219
+ const props = defineProps<{
220
+ pageIndex: number
221
+ maxPageLength: number
222
+ messageType: Newable<BaseMessage>
223
+ }>()
224
+ //创建时将其传递给context
225
+ const sct = useController(ScrubberController, {
226
+ context: props
227
+ })
228
+
229
+
230
+ //在你的controller上,直接使用这些数据!他们将会保持响应式
231
+ @Controller()
232
+ export class ScrubberController {
233
+ @Env()
234
+ public pageIndex!: number
235
+ @Env()
236
+ public maxPageLength!: number
237
+ @Env()
238
+ public messageType!: Newable<BaseMessage>
239
+ }
240
+ ```
241
+
242
+ ------
243
+
244
+ ### 2. Conrtoller消息
245
+
246
+ `@virid/vue`中的`Controller`不是盲目的,也可以监听自己在意的消息并触发回调。
247
+
248
+ #### `@Listener()`
249
+
250
+ - **特性**:`@Listener()`使得`Controller`能够自主感知环境变化,使其从被动接受变为主动监听,且将会随着`controller`的生命周期被一同卸载,**无需手动卸载监听**。
251
+
252
+ - **逻辑**:对于**任何消息类型**,指定`@Listener`的`messageClass`参数即可
253
+
254
+ - **示例:**
255
+
256
+ ```ts
257
+ export class PageChangeMessage extends SingleMessage {
258
+ constructor(public pageIndex: number) {
259
+ super()
260
+ }
261
+ }
262
+
263
+ @Controller()
264
+ export class PlaylistPageController {
265
+ // 当前的页面
266
+ @Responsive()
267
+ public pageIndex: number = 0
268
+ // 使用@Listener来收听自己在意的消息。注意,只能获得消息本身,无法注入component
269
+ // 在任何地方,不管是子组件还是父组件还是兄弟组件,只要有人发出了PageChangeMessage.send(newPage)
270
+ // onPageChange就会被自动调用
271
+ @Listener({
272
+ messageClass: PageChangeMessage
273
+ })
274
+ public onPageChange(message: PageChangeMessage) {
275
+ this.pageIndex = message.pageIndex
276
+ }
277
+ }
278
+ ```
279
+
280
+ ## 🛡️ 物理级只读护盾 (Deep Shield)
281
+
282
+ 在 `@virid/vue` 中,**“禁止修改父组件数据”不是一种建议,而是一种铁律。**
283
+
284
+ 为了确保确定性,所有通过 `@Project` 或 `@Inherit` 获取的外部数据,都会被自动套上一层递归的物理护盾。 该护盾会拦截所有的 **写操作 (Set)** 以及 **非法方法调用**并**详细指出原因及其访问的路径**。
285
+
286
+ ### 1. 拦截行为
287
+
288
+ - **赋值拦截**:尝试修改对象的属性将直接触发异常。
289
+ - **变异方法拦截**:禁止调用任何可能修改原数据的函数(如 `Array.push`, `Map.set`, `Set.add`)。
290
+ - **深度递归**:护盾是递归生效的且惰性缓存的,无论数据嵌套多深,其后代节点均受保护,且只有访问时才生效。
291
+
292
+ ### 2. 安全方法白名单
293
+
294
+ 为了不影响 UI 层的渲染逻辑,放行了所有**无副作用**的工具方法:
295
+
296
+ | **分类** | **允许调用的安全方法** |
297
+ | ------------- | ------------------------------------------------------------ |
298
+ | **基础协议** | `Symbol.iterator`, `toString`, `valueOf`, `toJSON`, `constructor` 等。 |
299
+ | **Array** | `length`, `map`, `filter`, `reduce`, `slice`, `find`, `includes`, `at`, `join`, `concat` 等。 |
300
+ | **Set / Map** | `has`, `get`, `keys`, `values`, `entries`, `forEach`, `size`。 |
301
+ | **String** | `length`, `slice`, `includes`, `split`, `replace`, `trim`, `toUpperCase` 等。 |
302
+
303
+
304
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@virid/vue",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Vue adapter for virid, projecting logic sovereignty to reactive UI",
6
6
  "author": "Ailrid",
7
7
  "license": "Apache 2.0",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git+https://github.com/ArisuYuki/virid.git"
17
+ "url": "git+https://github.com/Ailrid/virid.git"
18
18
  },
19
19
  "main": "./dist/index.cjs",
20
20
  "module": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "dependencies": {},
37
37
  "peerDependencies": {
38
38
  "vue": "^3.4.0",
39
- "@virid/core": "0.1.0"
39
+ "@virid/core": "0.1.1"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsup --config tsup.config.ts",