@lankajs/vue 0.1.0
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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/chunk-PLDDJCW6.js +49 -0
- package/dist/chunk-PLDDJCW6.js.map +1 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +43 -0
- package/dist/testing.js +84179 -0
- package/dist/testing.js.map +1 -0
- package/package.json +56 -0
- package/skills/lanka-vue/SKILL.md +171 -0
- package/skills/lanka-vue/reference.md +255 -0
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lankajs/vue",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "module: One composable — `useLankaVM` — and the access tracking core already does.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/lankajs/lanka.git",
|
|
10
|
+
"directory": "modules/bindings/vue"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/lankajs/lanka/tree/main/modules/bindings/vue#readme",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./testing": {
|
|
21
|
+
"types": "./dist/testing.d.ts",
|
|
22
|
+
"default": "./dist/testing.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"README.md",
|
|
30
|
+
"skills"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"lanka": "^2.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@testing-library/vue": "^8.1.0",
|
|
37
|
+
"@lankajs/tool-testing": "^2.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"vue": "^3.5.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@testing-library/vue": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"lint": "eslint src _playground --max-warnings=0",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:coverage": "vitest run --coverage",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"bench": "vitest bench --run",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lanka-vue
|
|
3
|
+
description: Read a lanka ViewModel from a Vue component with useLankaVM, or declare a Pinia-shaped composable with defineLankaComposable and destructure it through lankaVMToRefs. Use when writing or reviewing a Vue or Nuxt screen in a lanka application, when a template shows a value that never updates, when a destructured field stops tracking, when a module-level read leaks a subscription, or when reviewing code that imports `@lankajs/vue`.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: lankajs
|
|
7
|
+
package: @lankajs/vue
|
|
8
|
+
version: "0.1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# @lankajs/vue
|
|
12
|
+
|
|
13
|
+
One call to read a ViewModel, plus the Pinia spelling for a codebase that
|
|
14
|
+
expects one. `reference.md` beside this file is the full guide.
|
|
15
|
+
|
|
16
|
+
> [!NOTE]
|
|
17
|
+
> Only what the framework or a gate refuses is binding. Everything else here is a
|
|
18
|
+
> recommendation you can adapt.
|
|
19
|
+
|
|
20
|
+
## Pick the call
|
|
21
|
+
|
|
22
|
+
| The situation | Use |
|
|
23
|
+
| ----------------------------------------- | ------------------------------------------------ |
|
|
24
|
+
| a component reads a ViewModel | `useLankaVM(todoVM)` — a `ShallowRef` |
|
|
25
|
+
| it needs one derived value | `useLankaVM(todoVM, (s) => s.todos.length)` |
|
|
26
|
+
| the codebase reads like Pinia | `defineLankaComposable(todoVM)`, at module level |
|
|
27
|
+
| destructuring a composable's fields | `lankaVMToRefs(todos)` |
|
|
28
|
+
| outside a component — a handler, a module | `todoVM.getState()` |
|
|
29
|
+
| a component test | `renderWithLanka` from `@lankajs/vue/testing` |
|
|
30
|
+
|
|
31
|
+
```vue
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
import { useLankaVM } from "@lankajs/vue";
|
|
34
|
+
import { todoVM } from "./todoVM";
|
|
35
|
+
|
|
36
|
+
const state = useLankaVM(todoVM);
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<p v-if="state.isLoading">loading</p>
|
|
41
|
+
<ul v-else @click="state.load()">
|
|
42
|
+
<li v-for="todo in state.todos" :key="todo.id">{{ todo.title }}</li>
|
|
43
|
+
</ul>
|
|
44
|
+
</template>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It answers a **`ShallowRef`** — Vue's own idea of reactivity, which is the one
|
|
48
|
+
thing the shelf does not make uniform. So `state.todos` in a template and
|
|
49
|
+
`state.value.todos` in a script. Flattening the ref would be a second reactivity
|
|
50
|
+
system fighting the first, and every `watch` you wrote would stop seeing changes.
|
|
51
|
+
|
|
52
|
+
## The Pinia spelling
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// todosVM.ts — at module level, the way `defineStore` is declared
|
|
56
|
+
import { defineLankaComposable } from "@lankajs/vue";
|
|
57
|
+
|
|
58
|
+
export const useTodosVM = defineLankaComposable(todosVM);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
const todos = useTodosVM();
|
|
64
|
+
const { rows, isLoading } = lankaVMToRefs(todos);
|
|
65
|
+
</script>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`todos.rows` in the script and in the template, no `.value` anywhere, and
|
|
69
|
+
`todos.load()` for an action.
|
|
70
|
+
|
|
71
|
+
- **It answers a FUNCTION, and each CALL builds its own reader** inside the
|
|
72
|
+
calling component's scope, with its own subscription and its own recording.
|
|
73
|
+
A module-level reader would subscribe at import time, outside any scope, and
|
|
74
|
+
two components reading different keys would wake each other.
|
|
75
|
+
- **Two components get two objects** where Pinia answers one. The ViewModel
|
|
76
|
+
behind them is the same and there is no second copy of the state; what differs
|
|
77
|
+
is the recording, which belongs to whoever did the reading.
|
|
78
|
+
- **`$stop` is the one meta member**, `$`-prefixed so it cannot collide with a
|
|
79
|
+
state key. A component scope calls it for you.
|
|
80
|
+
- **Destructuring loses reactivity, exactly as in Pinia.** `const { rows } =
|
|
81
|
+
todos` reads once. `lankaVMToRefs` is `storeToRefs` under a name this shelf
|
|
82
|
+
uses; it leaves actions out on purpose, because an action is stable for the
|
|
83
|
+
life of the ViewModel and a ref would make every call site write
|
|
84
|
+
`load.value()`.
|
|
85
|
+
|
|
86
|
+
## What re-renders
|
|
87
|
+
|
|
88
|
+
Without a selector the ref carries a value that RECORDS which keys you read, and
|
|
89
|
+
the next change repaints only if one of those moved. With a selector, the
|
|
90
|
+
selector decides and tracking is bypassed.
|
|
91
|
+
|
|
92
|
+
> [!WARNING]
|
|
93
|
+
> **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
|
|
94
|
+
> inside a derived getter is invisible to it, so a change to that key repaints
|
|
95
|
+
> nothing and the screen freezes with no error. Set
|
|
96
|
+
> `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT read the
|
|
97
|
+
> underlying keys in the template "for the side effect": that is dead code, and a
|
|
98
|
+
> refactor or a lint autofix removes it. In development the framework announces
|
|
99
|
+
> the mismatch by ViewModel and key name.
|
|
100
|
+
|
|
101
|
+
## Releasing the subscription
|
|
102
|
+
|
|
103
|
+
Inside a component or an `effectScope`, `onScopeDispose` does it and you do
|
|
104
|
+
nothing. Called OUTSIDE one — a module-level read, a test — there is no scope to
|
|
105
|
+
attach to, so the returned ref carries `stop()` and you own it:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const state = useLankaVM(todoVM);
|
|
109
|
+
state.stop();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Testing
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { renderWithLanka } from "@lankajs/vue/testing";
|
|
116
|
+
|
|
117
|
+
renderWithLanka(TodoScreen, {
|
|
118
|
+
fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Every call gets a fresh instance and disposes the previous one.
|
|
123
|
+
|
|
124
|
+
## A selector that builds an object
|
|
125
|
+
|
|
126
|
+
A selector answering a fresh object is never identical to its own last answer,
|
|
127
|
+
so the reader wakes for EVERY change in the ViewModel — including the keys the
|
|
128
|
+
selector exists to ignore. Hold it:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { createLankaShallowHold } from "lanka/viewmodel";
|
|
132
|
+
|
|
133
|
+
const hold = createLankaShallowHold<{ title: string }>();
|
|
134
|
+
const mission = useLankaVM(missionVM, (s) => hold({ title: s.title }));
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
One hold per reader, declared in `setup` — never at module level and never
|
|
138
|
+
shared between two components. A selector answering a **primitive** needs none
|
|
139
|
+
of this. The comparison is one level deep: own keys, same count, `Object.is` on
|
|
140
|
+
each value, arrays included.
|
|
141
|
+
|
|
142
|
+
## Never do these
|
|
143
|
+
|
|
144
|
+
- **Never pass an object-building selector without a hold.** The reader then
|
|
145
|
+
wakes for every change in the ViewModel, selector or no selector.
|
|
146
|
+
- **Never destructure a composable directly.** `const { rows } = todos` reads
|
|
147
|
+
once and never updates again — right on the first paint, wrong after it.
|
|
148
|
+
- **Never call `defineLankaComposable` inside a component.** It is a declaration,
|
|
149
|
+
like `defineStore`; the call it returns is what a component uses.
|
|
150
|
+
- **Never read `state.value` in a template or drop `.value` in a script.** That
|
|
151
|
+
is Vue's rule about refs, and this binding does not bend it.
|
|
152
|
+
- **Never leave a `useLankaVM` call outside a scope unstopped.** No scope means
|
|
153
|
+
no `onScopeDispose`, and the subscription outlives the reader.
|
|
154
|
+
- **Never expect `"use client"` here.** That is a React Server Components
|
|
155
|
+
mechanism; Nuxt renders this package on the server as ordinary code.
|
|
156
|
+
|
|
157
|
+
## Symptom → cause
|
|
158
|
+
|
|
159
|
+
| What you see | What it is |
|
|
160
|
+
| ------------------------------------------------- | --------------------------------------------- |
|
|
161
|
+
| a screen repainting for changes it never selected | an object selector with no hold |
|
|
162
|
+
| the first paint is right, nothing updates | a destructure without `lankaVMToRefs` |
|
|
163
|
+
| `[object Object]` in a template | `state` where the script needed `state.value` |
|
|
164
|
+
| the screen never updates, no error | the tracking blind spot — a derived getter |
|
|
165
|
+
| two components waking on each other's keys | one reader shared instead of one call each |
|
|
166
|
+
| a growing subscription count in a test | a `useLankaVM` outside a scope, never stopped |
|
|
167
|
+
|
|
168
|
+
## More
|
|
169
|
+
|
|
170
|
+
`reference.md` — the full guide: the ref rules, the composable in detail, and
|
|
171
|
+
what this package deliberately is not.
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<!-- Generated from modules/bindings/vue/GUIDE.md by scripts/skills.mjs. Edit the guide. -->
|
|
2
|
+
|
|
3
|
+
> **`@lankajs/vue@0.1.0`** — this document describes that version.
|
|
4
|
+
>
|
|
5
|
+
> Install: `npm install @lankajs/vue vue zustand` (the peers are not optional; only npm adds a missing one for you).
|
|
6
|
+
>
|
|
7
|
+
> Complete code, compiled and run in CI: [modules/bindings/vue/_playground/playground.test.ts](https://github.com/lankajs/lanka/blob/main/modules/bindings/vue/_playground/playground.test.ts)
|
|
8
|
+
|
|
9
|
+
# @lankajs/vue — user guide
|
|
10
|
+
|
|
11
|
+
How a Vue component reads a lanka ViewModel.
|
|
12
|
+
|
|
13
|
+
## You will learn
|
|
14
|
+
|
|
15
|
+
- the one call this package publishes, and what it answers
|
|
16
|
+
- when a component re-renders and when it deliberately does not
|
|
17
|
+
- why a selector that builds an object needs a hold, and when it needs nothing
|
|
18
|
+
- what to do about a ViewModel that derives what the screen shows
|
|
19
|
+
- how to test a Vue component with a live framework behind it
|
|
20
|
+
|
|
21
|
+
## When to reach for this
|
|
22
|
+
|
|
23
|
+
Reach for it the moment a Vue component has to read a lanka ViewModel — that is
|
|
24
|
+
the whole job, and there is no other supported way to do it. Install this one
|
|
25
|
+
package and no other binding: the five are alternatives, not layers.
|
|
26
|
+
|
|
27
|
+
You do NOT need it to reach the rest of the framework. Gateways, scenarios and
|
|
28
|
+
the locator are plain calls with no view in them, and `viewModel.getState()`
|
|
29
|
+
works anywhere, including on a server.
|
|
30
|
+
|
|
31
|
+
> [!NOTE]
|
|
32
|
+
> Everything below is how this package is _meant_ to be used, not how it must
|
|
33
|
+
> be. The framework bends at the seams it publishes — see
|
|
34
|
+
> [ARCHITECTURE.md](https://github.com/lankajs/lanka/blob/main/ARCHITECTURE.md) for what is checked and what is
|
|
35
|
+
> merely advice.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @lankajs/vue vue zustand
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> [!IMPORTANT]
|
|
44
|
+
> `vue` is already in your project; `zustand` is `lanka`'s own peer. npm adds a
|
|
45
|
+
> missing peer for you and pnpm does not, so the line names all of them.
|
|
46
|
+
|
|
47
|
+
## The one call
|
|
48
|
+
|
|
49
|
+
`useLankaVM` is a composable. Every member of `modules/bindings/` publishes that same
|
|
50
|
+
name, so moving a screen from one framework to another rewrites the view and not
|
|
51
|
+
the vocabulary.
|
|
52
|
+
|
|
53
|
+
```vue
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import { useLankaVM } from "@lankajs/vue";
|
|
56
|
+
import { todoVM } from "./todoVM";
|
|
57
|
+
|
|
58
|
+
const state = useLankaVM(todoVM);
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<p v-if="state.isLoading">loading</p>
|
|
63
|
+
<ul v-else @click="state.load()">
|
|
64
|
+
<li v-for="todo in state.todos" :key="todo.id">{{ todo.title }}</li>
|
|
65
|
+
</ul>
|
|
66
|
+
</template>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
It answers **a `ShallowRef`** — the one thing this shelf does not make uniform,
|
|
70
|
+
because that is Vue's own idea of reactivity and a binding that hid it
|
|
71
|
+
would be a second reactivity system fighting the first.
|
|
72
|
+
|
|
73
|
+
## The Vue spelling, if you prefer it
|
|
74
|
+
|
|
75
|
+
`useLankaVM` answers a `ShallowRef`, which is the honest shape for Vue's
|
|
76
|
+
reactivity and the one every other binding on the shelf parallels. It is not how
|
|
77
|
+
a Pinia codebase reads, so this package publishes that too — declaration and all:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// todosVM.ts — at module level, the way `defineStore` is declared
|
|
81
|
+
import { defineLankaComposable } from "@lankajs/vue";
|
|
82
|
+
|
|
83
|
+
export const useTodosVM = defineLankaComposable(todosVM);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<script setup lang="ts">
|
|
88
|
+
const todos = useTodosVM();
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<template>
|
|
92
|
+
<li v-for="row in todos.rows" :key="row">{{ row }}</li>
|
|
93
|
+
</template>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`todos.rows` in the script and in the template, no `.value` anywhere, and
|
|
97
|
+
`todos.load()` for an action.
|
|
98
|
+
|
|
99
|
+
`$stop` is the one member the composable adds, and the `$` prefix is Pinia's
|
|
100
|
+
convention for Pinia's reason: the keys belong to the application, and a meta
|
|
101
|
+
member sharing that namespace collides the day somebody adds a `stop` of their
|
|
102
|
+
own. A component scope calls it for you; you need it only for a reader built
|
|
103
|
+
outside one.
|
|
104
|
+
|
|
105
|
+
**It is still a ViewModel, and it is called one.** Pinia's noun for the thing a
|
|
106
|
+
component reads is "store", and this reads the way one does — but what holds the
|
|
107
|
+
state, the actions and the scenario bindings is the ViewModel, and naming it
|
|
108
|
+
after the shape it wears would hide where the work lives.
|
|
109
|
+
|
|
110
|
+
**It answers a FUNCTION, and that is not only for the look of it.** A reader
|
|
111
|
+
built at module level would open its subscription at IMPORT time, outside any
|
|
112
|
+
component scope — nothing would release it, and every component would share ONE
|
|
113
|
+
recording, so two components reading different keys would wake each other. Each
|
|
114
|
+
CALL builds a reader inside the calling component's scope, with its own
|
|
115
|
+
subscription and its own recording, and Vue releases it when that component goes.
|
|
116
|
+
|
|
117
|
+
Where it differs from Pinia: `useTodosVM()` in two components answers two
|
|
118
|
+
objects, where Pinia answers one. The ViewModel behind them is the same one and
|
|
119
|
+
there is no second copy of the state — what differs is the recording, which
|
|
120
|
+
belongs to whoever did the reading.
|
|
121
|
+
|
|
122
|
+
**Destructuring loses reactivity, exactly as it does in Pinia.**
|
|
123
|
+
`const { rows } = todos` reads once and stops tracking:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const { rows, isLoading } = lankaVMToRefs(todos); // rows.value in script, {{ rows }} in template
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Actions are left out of `lankaVMToRefs` deliberately: an action is a stable
|
|
130
|
+
function for the life of the ViewModel, so `const { load } = todos` was already
|
|
131
|
+
correct and a ref would make every call site write `load.value()`.
|
|
132
|
+
|
|
133
|
+
## What re-renders, and what does not
|
|
134
|
+
|
|
135
|
+
Without a selector you get a value that RECORDS which keys you read. The next
|
|
136
|
+
change re-renders only if one of those moved:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// reads `todos`; a change to `isLoading` alone repaints nothing
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
With a selector, the selector decides and tracking is bypassed:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const count = useLankaVM(todoVM, (state) => state.todos.length);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> [!WARNING]
|
|
149
|
+
> **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
|
|
150
|
+
> inside a derived getter — an action calling `get()` — is invisible to it, so a
|
|
151
|
+
> change to that key re-renders nothing and the screen freezes with no error.
|
|
152
|
+
>
|
|
153
|
+
> Set `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT patch
|
|
154
|
+
> it in the view by reading the underlying keys "for the side effect": that is
|
|
155
|
+
> dead code, and a refactor or a lint autofix removes it.
|
|
156
|
+
>
|
|
157
|
+
> In development the framework announces the mismatch by ViewModel and key name.
|
|
158
|
+
|
|
159
|
+
## A selector that builds its answer
|
|
160
|
+
|
|
161
|
+
`useLankaVM(vm, (state) => ({ … }))` is safe — nothing loops — but on its own it
|
|
162
|
+
wakes the component for **every** change in the ViewModel, including the keys
|
|
163
|
+
the selector exists to ignore. The reason is identity: that object is new on
|
|
164
|
+
every call, and a binding compares selections with `Object.is`.
|
|
165
|
+
|
|
166
|
+
`createLankaShallowHold` is the comparison that fixes it. It answers the
|
|
167
|
+
PREVIOUS object while nothing in the selection moved, one level deep — own keys,
|
|
168
|
+
same count, `Object.is` on each value, arrays included:
|
|
169
|
+
|
|
170
|
+
```vue
|
|
171
|
+
<script setup lang="ts">
|
|
172
|
+
import { createLankaShallowHold } from "lanka/viewmodel";
|
|
173
|
+
import { useLankaVM } from "@lankajs/vue";
|
|
174
|
+
|
|
175
|
+
const hold = createLankaShallowHold<{ title: string; status: string }>();
|
|
176
|
+
const mission = useLankaVM(missionVM, (state) =>
|
|
177
|
+
hold({ title: state.title, status: state.status }),
|
|
178
|
+
);
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<h1>{{ mission.title }} — {{ mission.status }}</h1>
|
|
183
|
+
</template>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
One hold per reader, created in `setup` beside the read — never at module level
|
|
187
|
+
and never shared between two components, because the answer it holds belongs to
|
|
188
|
+
whoever selected it.
|
|
189
|
+
|
|
190
|
+
A selector answering a **primitive** needs none of this: `(state) => state.title`
|
|
191
|
+
compares equal to itself and was always free. A selection with a **nested**
|
|
192
|
+
object wants a selector that picks the leaves — comparing deeper would mean
|
|
193
|
+
walking a state of unknown size on every read, which is the cost a selector was
|
|
194
|
+
taken to avoid.
|
|
195
|
+
|
|
196
|
+
## Releasing the subscription
|
|
197
|
+
|
|
198
|
+
Inside a component or an `effectScope`, `onScopeDispose` releases the
|
|
199
|
+
subscription and you do nothing. Called OUTSIDE one — a module-level read, a
|
|
200
|
+
test — there is no scope to attach to, so the returned ref carries `stop()` and
|
|
201
|
+
you own it:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
const state = useLankaVM(todoVM);
|
|
205
|
+
// …
|
|
206
|
+
state.stop();
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## A template unwraps the ref, a script does not
|
|
210
|
+
|
|
211
|
+
`state.todos` in a template, `state.value.todos` in a script. That is Vue's own
|
|
212
|
+
rule about refs, and this package does not bend it: flattening the ref would be a
|
|
213
|
+
second reactivity system fighting the first, and every `watch` you wrote would
|
|
214
|
+
stop seeing changes.
|
|
215
|
+
|
|
216
|
+
## Testing
|
|
217
|
+
|
|
218
|
+
`@lankajs/vue/testing` renders a component with a bootstrapped framework, so a component
|
|
219
|
+
test needs no bootstrap preamble of its own:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { renderWithLanka } from "@lankajs/vue/testing";
|
|
223
|
+
|
|
224
|
+
renderWithLanka(TodoScreen, {
|
|
225
|
+
fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Every call gets a FRESH instance and disposes the previous one, so a test never
|
|
230
|
+
inherits its neighbour's subscriptions.
|
|
231
|
+
|
|
232
|
+
## What this package is not
|
|
233
|
+
|
|
234
|
+
It is a subscription and a render trigger, and nothing else. The recording of
|
|
235
|
+
which keys you read, the comparison that decides whether a change is worth a
|
|
236
|
+
render, and the blind-spot warning are all in `lanka` itself — which is why the
|
|
237
|
+
behaviour you see is the framework's rather than this package's reading of it,
|
|
238
|
+
and why `lankaViewBindingConformance` can hold every binding to one list.
|
|
239
|
+
|
|
240
|
+
If this package ever needs more than the ViewModel port gives it, the port has
|
|
241
|
+
the defect and the fix belongs in `lanka`, for every framework at once.
|
|
242
|
+
|
|
243
|
+
## Recap
|
|
244
|
+
|
|
245
|
+
- `useLankaVM(todoVM)` is the one call, and every binding publishes that name.
|
|
246
|
+
- It answers a `ShallowRef`: `state.todos` in a template, `state.value.todos` in a script. That difference is Vue's, and the shelf does not hide it.
|
|
247
|
+
- Without a selector you get a value that records which keys you read, and only those keys wake the ref.
|
|
248
|
+
- A key reached only through a derived getter is invisible to tracking: set `enableAccessTrackingOptimization: false` on that ViewModel.
|
|
249
|
+
- `defineLankaComposable` gives the Pinia spelling — `todos.rows`, no `.value` — and `lankaVMToRefs` keeps reactivity through a destructure.
|
|
250
|
+
- Inside a component or an `effectScope` the subscription is released for you; outside one, `stop()` is yours to call.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
Maintaining this package: [SKILL.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/vue/SKILL.md) · What it is:
|
|
255
|
+
[README.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/vue/README.md) · Repository map: [../../../README.md](https://github.com/lankajs/lanka/blob/main/README.md)
|