@lark.js/mvc 0.0.6 → 0.0.8
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 +145 -118
- package/dist/compiler.cjs +15366 -0
- package/dist/compiler.d.cts +76 -0
- package/dist/compiler.d.ts +76 -0
- package/dist/compiler.js +15353 -0
- package/dist/index.cjs +32 -774
- package/dist/index.d.cts +4 -69
- package/dist/index.d.ts +4 -69
- package/dist/index.js +32 -772
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -386,15 +386,15 @@ useCountStore.destroy(); // Clears all listeners, removes from registry
|
|
|
386
386
|
|
|
387
387
|
### Comparison
|
|
388
388
|
|
|
389
|
-
| Dimension
|
|
390
|
-
|
|
391
|
-
| Write
|
|
392
|
-
| Read
|
|
393
|
-
| Subscribe
|
|
394
|
-
| View binding | `observeState("keys")`
|
|
395
|
-
| Lifecycle
|
|
396
|
-
| Derived data | Not supported
|
|
397
|
-
| Use case
|
|
389
|
+
| Dimension | State | Store |
|
|
390
|
+
| ------------ | -------------------------------------- | -------------------------------------------------- |
|
|
391
|
+
| Write | `State.set(...)` + `State.digest()` | `store.setState(partial)` or action |
|
|
392
|
+
| Read | `State.get(key)` | `store.getState()` |
|
|
393
|
+
| Subscribe | `observeState` or `on("changed")` | `store.subscribe(listener)` or `bindStore` |
|
|
394
|
+
| View binding | `observeState("keys")` | `bindStore(view, store, selector?)` |
|
|
395
|
+
| Lifecycle | `State.clean` mixin auto-reclaims keys | `store.destroy()` manual teardown |
|
|
396
|
+
| Derived data | Not supported | `computed(deps, fn)` |
|
|
397
|
+
| Use case | Page title, login state, theme | Business entities, forms, complex cross-view state |
|
|
398
398
|
|
|
399
399
|
Selection guide: start with State; upgrade to Store when you need actions, derived data, or fine-grained subscriptions; view-private data always goes through Updater.
|
|
400
400
|
|
|
@@ -409,9 +409,15 @@ import { View } from "@lark.js/mvc";
|
|
|
409
409
|
|
|
410
410
|
export default View.extend({
|
|
411
411
|
template,
|
|
412
|
-
init() {
|
|
413
|
-
|
|
414
|
-
|
|
412
|
+
init() {
|
|
413
|
+
/* ... */
|
|
414
|
+
},
|
|
415
|
+
assign() {
|
|
416
|
+
/* ... */
|
|
417
|
+
},
|
|
418
|
+
render() {
|
|
419
|
+
/* ... */
|
|
420
|
+
},
|
|
415
421
|
});
|
|
416
422
|
```
|
|
417
423
|
|
|
@@ -433,7 +439,7 @@ Both produce equivalent runtime artifacts; the difference is purely in TypeScrip
|
|
|
433
439
|
### Lifecycle
|
|
434
440
|
|
|
435
441
|
- `init(params?)` — Called when the view is first instantiated. `params` comes from query strings on `v-lark`. Read stores and call `this.assign()` to prepare initial data here.
|
|
436
|
-
- `make()` — Called by the merged
|
|
442
|
+
- `make()` — Called by the merged makes pipeline; each mixin's `make` executes in order. Suitable for "run once per instance" initialization.
|
|
437
443
|
- `assign()` — Should be called when data may have changed. Pattern: `this.updater.snapshot()` at the top, `this.updater.set(...)` in the middle, `return this.updater.altered()` at the end. The framework uses `altered()` to determine whether re-render is needed.
|
|
438
444
|
- `render()` — Default implementation is `this.updater.digest()`. Wrapped by `View.wrapMethod`: increments signature on entry, handles pending endUpdate cleanup on exit.
|
|
439
445
|
- Destruction — The framework automatically calls `release(key, true)` to release all `capture`d resources, cleans up event delegation, and sets signature to 0.
|
|
@@ -444,15 +450,15 @@ Both produce equivalent runtime artifacts; the difference is purely in TypeScrip
|
|
|
444
450
|
|
|
445
451
|
Event methods are named `name<eventType>` or `$selector<eventType>`. `View.prepare` scans the prototype at class definition time, parsing methods into three maps (`$evtObjMap` / `$selMap` / `$globalEvtList`) written to the prototype, managed at runtime by `EventDelegator`.
|
|
446
452
|
|
|
447
|
-
| Syntax
|
|
448
|
-
|
|
449
|
-
| `handler<click>`
|
|
450
|
-
| `$selector<click>`
|
|
451
|
-
| `$<click>`
|
|
452
|
-
| `$window<resize>`
|
|
453
|
-
| `$document<keydown>`
|
|
454
|
-
| `handler<click,mousedown>` | Multi-event binding
|
|
455
|
-
| `name<click><ctrl>`
|
|
453
|
+
| Syntax | Meaning |
|
|
454
|
+
| -------------------------- | -------------------------------------------------- |
|
|
455
|
+
| `handler<click>` | Event on the view's root element |
|
|
456
|
+
| `$selector<click>` | Delegated to child elements matching `.selector` |
|
|
457
|
+
| `$<click>` | Empty selector, triggers Frame boundary event only |
|
|
458
|
+
| `$window<resize>` | Delegated to `window` |
|
|
459
|
+
| `$document<keydown>` | Delegated to `document` |
|
|
460
|
+
| `handler<click,mousedown>` | Multi-event binding |
|
|
461
|
+
| `name<click><ctrl>` | Fires only when Ctrl modifier is held |
|
|
456
462
|
|
|
457
463
|
The event callback receives an object `e` that, beyond standard Event fields, provides `e.eventTarget` (the actual hit DOM element) and `e.params` (parsed from the `@event` parameter string). Multiple mixins defining the same event method name are merged into a handler chain called in mixin order.
|
|
458
464
|
|
|
@@ -464,7 +470,15 @@ Event delegation implementation: `EventDelegator` attaches listeners on `documen
|
|
|
464
470
|
|
|
465
471
|
```ts
|
|
466
472
|
const timer = setInterval(tick, 1000);
|
|
467
|
-
this.capture(
|
|
473
|
+
this.capture(
|
|
474
|
+
"myTimer",
|
|
475
|
+
{
|
|
476
|
+
destroy() {
|
|
477
|
+
clearInterval(timer);
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
true,
|
|
481
|
+
);
|
|
468
482
|
```
|
|
469
483
|
|
|
470
484
|
The third parameter `destroyOnRender` when `true` causes automatic destruction and removal on the next render call; when `false` cleanup happens only on view destruction. `release(key, destroy = true)` manually removes an entry.
|
|
@@ -499,16 +513,16 @@ All state parses into a single `Location` object; cache hits skip parsing.
|
|
|
499
513
|
```ts
|
|
500
514
|
import { Router } from "@lark.js/mvc";
|
|
501
515
|
|
|
502
|
-
Router.to("/list", { page: 2 });
|
|
503
|
-
Router.to({ page: 3 });
|
|
504
|
-
Router.to("/list", { page: 2 }, true);
|
|
516
|
+
Router.to("/list", { page: 2 }); // path + params
|
|
517
|
+
Router.to({ page: 3 }); // params only
|
|
518
|
+
Router.to("/list", { page: 2 }, true); // replace mode
|
|
505
519
|
Router.to("/list", { page: 2 }, false, true); // silent, no events
|
|
506
520
|
```
|
|
507
521
|
|
|
508
522
|
```ts
|
|
509
|
-
const loc = Router.parse();
|
|
523
|
+
const loc = Router.parse(); // current Location
|
|
510
524
|
const loc2 = Router.parse("https://x/?a=1#!/path?p=v");
|
|
511
|
-
const diff = Router.diff();
|
|
525
|
+
const diff = Router.diff(); // most recent LocationDiff
|
|
512
526
|
```
|
|
513
527
|
|
|
514
528
|
`Location` provides `path` / `params` / `hash` / `query` / `view` and a `get(key, defaultValue?)` method.
|
|
@@ -580,14 +594,19 @@ const AppService = Service.extend(
|
|
|
580
594
|
fetch(payload.get<string>("url"), {
|
|
581
595
|
method: payload.get<string>("method") || "GET",
|
|
582
596
|
headers: { "Content-Type": "application/json" },
|
|
583
|
-
body: payload.get("data")
|
|
597
|
+
body: payload.get("data")
|
|
598
|
+
? JSON.stringify(payload.get("data"))
|
|
599
|
+
: undefined,
|
|
584
600
|
})
|
|
585
601
|
.then((r) => r.json())
|
|
586
|
-
.then((data) => {
|
|
602
|
+
.then((data) => {
|
|
603
|
+
payload.set(data);
|
|
604
|
+
callback();
|
|
605
|
+
})
|
|
587
606
|
.catch(() => callback());
|
|
588
607
|
},
|
|
589
608
|
20, // cacheMax
|
|
590
|
-
5,
|
|
609
|
+
5, // cacheBuffer
|
|
591
610
|
);
|
|
592
611
|
|
|
593
612
|
AppService.add([
|
|
@@ -597,7 +616,10 @@ AppService.add([
|
|
|
597
616
|
url: "/api/users/:id",
|
|
598
617
|
cache: 30_000,
|
|
599
618
|
before(payload) {
|
|
600
|
-
payload.set(
|
|
619
|
+
payload.set(
|
|
620
|
+
"url",
|
|
621
|
+
payload.get<string>("url").replace(":id", payload.get<string>("id")),
|
|
622
|
+
);
|
|
601
623
|
},
|
|
602
624
|
after(payload) {
|
|
603
625
|
const data = payload.get("data");
|
|
@@ -629,18 +651,18 @@ export default View.extend({
|
|
|
629
651
|
});
|
|
630
652
|
```
|
|
631
653
|
|
|
632
|
-
| Method
|
|
633
|
-
|
|
634
|
-
| `service.all(attrs, done)`
|
|
635
|
-
| `service.one(attrs, done)`
|
|
636
|
-
| `service.save(attrs, done)` | Same as `all` but skips cache, always makes a fresh request
|
|
637
|
-
| `service.enqueue(task)`
|
|
638
|
-
| `service.dequeue(...args)`
|
|
639
|
-
| `service.destroy()`
|
|
654
|
+
| Method | Behavior |
|
|
655
|
+
| --------------------------- | ---------------------------------------------------------------------------------- |
|
|
656
|
+
| `service.all(attrs, done)` | Fetch all endpoints; callback `(errors, p1, p2, ...)` when all complete |
|
|
657
|
+
| `service.one(attrs, done)` | Fetch all endpoints; callback `(error, payload, isLast, index)` on each completion |
|
|
658
|
+
| `service.save(attrs, done)` | Same as `all` but skips cache, always makes a fresh request |
|
|
659
|
+
| `service.enqueue(task)` | Add to serial queue |
|
|
660
|
+
| `service.dequeue(...args)` | Take one item and execute |
|
|
661
|
+
| `service.destroy()` | Destroy instance and cancel pending callbacks |
|
|
640
662
|
|
|
641
663
|
### Caching and Deduplication
|
|
642
664
|
|
|
643
|
-
`Cache` implements an LFU-style bounded cache: sorted by `(frequency, lastTimestamp)`, evicting `bufferSize` entries via single-pass partial selection (O(n
|
|
665
|
+
`Cache` implements an LFU-style bounded cache: sorted by `(frequency, lastTimestamp)`, evicting `bufferSize` entries via single-pass partial selection (O(n\*k), k typically 5) when capacity exceeds `maxSize + bufferSize`. `del` immediately removes from the `entries` array and `lookup` Map.
|
|
644
666
|
|
|
645
667
|
`_pendingCacheKeys` tracks in-flight requests per `(endpoint, params)` key. Concurrent calls to the same key are added to a callback chain; a single request completes and invokes all callbacks, avoiding redundant network round-trips.
|
|
646
668
|
|
|
@@ -652,23 +674,21 @@ Template files use the `.html` extension and are compiled at build time by `lark
|
|
|
652
674
|
|
|
653
675
|
### Expression Operators
|
|
654
676
|
|
|
655
|
-
| Syntax
|
|
656
|
-
|
|
657
|
-
| `{{=variable}}` | HTML-escaped output (escapes `& < > " ' \``)
|
|
658
|
-
| `{{!variable}}` | Raw output, use with caution (potential XSS)
|
|
677
|
+
| Syntax | Meaning |
|
|
678
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
679
|
+
| `{{=variable}}` | HTML-escaped output (escapes `& < > " ' \``) |
|
|
680
|
+
| `{{!variable}}` | Raw output, use with caution (potential XSS) |
|
|
659
681
|
| `{{@variable}}` | Reference lookup: stores the JS value in refData and produces a token, used with `@event` to pass live references |
|
|
660
|
-
| `{{:variable}}` | Two-way binding marker; renders equivalently to `=`
|
|
682
|
+
| `{{:variable}}` | Two-way binding marker; renders equivalently to `=` |
|
|
661
683
|
|
|
662
684
|
### Control Flow
|
|
663
685
|
|
|
664
686
|
```html
|
|
665
|
-
{{if condition}}...{{else if other}}...{{else}}...{{/if}}
|
|
666
|
-
{{forOf list as item}}
|
|
667
|
-
{{forOf list as
|
|
668
|
-
{{
|
|
669
|
-
|
|
670
|
-
{{for (let i = 0; i < n; i++)}} ... {{/for}}
|
|
671
|
-
{{set localVar = expr}}
|
|
687
|
+
{{if condition}}...{{else if other}}...{{else}}...{{/if}} {{forOf list as item}}
|
|
688
|
+
... {{/forOf}} {{forOf list as item idx}} {{=idx}}: {{=item.name}} {{/forOf}}
|
|
689
|
+
{{forOf list as {name, age} idx last first}} ... {{/forOf}} {{forIn object as
|
|
690
|
+
value key}} ... {{/forIn}} {{for (let i = 0; i < n; i++)}} ... {{/for}} {{set
|
|
691
|
+
localVar = expr}}
|
|
672
692
|
```
|
|
673
693
|
|
|
674
694
|
`forOf` requires the `as` keyword. `{{forOf list item}}` is a compile-time error; the correct form is `{{forOf list as item}}`.
|
|
@@ -695,11 +715,11 @@ With query strings, parameters are translated into the first argument of the chi
|
|
|
695
715
|
|
|
696
716
|
### VDOM Optimization Hints
|
|
697
717
|
|
|
698
|
-
| Attribute | Purpose
|
|
699
|
-
|
|
700
|
-
| `ldk`
|
|
701
|
-
| `lak`
|
|
702
|
-
| `lvk`
|
|
718
|
+
| Attribute | Purpose |
|
|
719
|
+
| --------- | -------------------------------------------------------------------------- |
|
|
720
|
+
| `ldk` | diff key: when old and new ldk match, the entire subtree's diff is skipped |
|
|
721
|
+
| `lak` | attribute key: skips attribute diff but children continue to diff |
|
|
722
|
+
| `lvk` | view key: assign optimization marker |
|
|
703
723
|
|
|
704
724
|
Marking large static subtrees with `ldk` can completely skip rendering work. This is currently the framework's only "fine-grained skip diff" mechanism; the compiler does not automatically mark fully static subtrees.
|
|
705
725
|
|
|
@@ -709,22 +729,22 @@ Marking large static subtrees with `ldk` can completely skip rendering work. Thi
|
|
|
709
729
|
|
|
710
730
|
### Typed API
|
|
711
731
|
|
|
712
|
-
| API
|
|
713
|
-
|
|
714
|
-
| `Frame.get(id)`
|
|
715
|
-
| `Frame.getAll()`
|
|
716
|
-
| `Frame.getRoot()`
|
|
717
|
-
| `Frame.createRoot(id)`
|
|
718
|
-
| `Frame.root(id)`
|
|
719
|
-
| `new Frame(containerId)`
|
|
720
|
-
| `frame.invoke(name, args?)`
|
|
721
|
-
| `frame.children()`
|
|
722
|
-
| `frame.parent(level?)`
|
|
723
|
-
| `frame.mountFrame(id, viewPath, params?)`
|
|
724
|
-
| `frame.unmountFrame(id)`
|
|
725
|
-
| `frame.mountZone(id?)` / `frame.unmountZone(id?)` | Batch mount/unmount all `v-lark` child nodes in a zone
|
|
726
|
-
| `Frame.on("add" \| "remove", handler)`
|
|
727
|
-
| `frame.on("created" \| "alter", handler)`
|
|
732
|
+
| API | Description |
|
|
733
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
734
|
+
| `Frame.get(id)` | Look up Frame by DOM id |
|
|
735
|
+
| `Frame.getAll()` | All Frames as `Map<string, Frame>` |
|
|
736
|
+
| `Frame.getRoot()` | Current root Frame; returns `undefined` if not created |
|
|
737
|
+
| `Frame.createRoot(id)` | Idempotent root creation (`Framework.boot` calls this) |
|
|
738
|
+
| `Frame.root(id)` | `@deprecated` alias, forwards to `createRoot` |
|
|
739
|
+
| `new Frame(containerId)` | Independent Frame instance for micro-frontend / embedded widget scenarios |
|
|
740
|
+
| `frame.invoke(name, args?)` | Call the owning view's method; if view not mounted, pushes to `invokeList`, flushed by `View.runInvokes(frame)` after mounting |
|
|
741
|
+
| `frame.children()` | Child Frame id array (order not guaranteed) |
|
|
742
|
+
| `frame.parent(level?)` | Ancestor Frame, defaults to one level up |
|
|
743
|
+
| `frame.mountFrame(id, viewPath, params?)` | Explicitly create a child Frame |
|
|
744
|
+
| `frame.unmountFrame(id)` | Unmount a specific child Frame |
|
|
745
|
+
| `frame.mountZone(id?)` / `frame.unmountZone(id?)` | Batch mount/unmount all `v-lark` child nodes in a zone |
|
|
746
|
+
| `Frame.on("add" \| "remove", handler)` | Frame instance lifecycle events (static emitter) |
|
|
747
|
+
| `frame.on("created" \| "alter", handler)` | All child Frames rendered / child content changed (instance emitter) |
|
|
728
748
|
|
|
729
749
|
Frame instances enter `frameCache` object pool upon destruction, caching up to `MAX_FRAME_POOL = 64`; beyond that threshold they are GC'd. Do not retain Frame references after unmounting as the object may be reused.
|
|
730
750
|
|
|
@@ -741,22 +761,27 @@ Framework.boot({
|
|
|
741
761
|
rootId: "app",
|
|
742
762
|
projectName: "host-app",
|
|
743
763
|
crossConfigs: [
|
|
744
|
-
{
|
|
764
|
+
{
|
|
765
|
+
projectName: "remote-app",
|
|
766
|
+
source: "remote_app@//cdn.example.com/remote-app/remoteEntry.js",
|
|
767
|
+
},
|
|
745
768
|
],
|
|
746
769
|
require: async (names: string[]) => {
|
|
747
770
|
await __webpack_init_sharing__("default");
|
|
748
771
|
const container = __webpack_share_scopes__["default"];
|
|
749
|
-
return Promise.all(
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
772
|
+
return Promise.all(
|
|
773
|
+
names.map(async (name) => {
|
|
774
|
+
const slash = name.indexOf("/");
|
|
775
|
+
const remote = slash > -1 ? name.substring(0, slash) : name;
|
|
776
|
+
const mod = slash > -1 ? name.substring(slash + 1) : "./index";
|
|
777
|
+
const rc = (window as Record<string, unknown>)[remote];
|
|
778
|
+
if (!rc) return undefined;
|
|
779
|
+
await rc.init(container);
|
|
780
|
+
const factory = await rc.get(`./${mod}`);
|
|
781
|
+
const raw = factory();
|
|
782
|
+
return raw && raw.__esModule ? raw.default : raw;
|
|
783
|
+
}),
|
|
784
|
+
);
|
|
760
785
|
},
|
|
761
786
|
});
|
|
762
787
|
```
|
|
@@ -785,7 +810,9 @@ Host:
|
|
|
785
810
|
```js
|
|
786
811
|
new ModuleFederationPlugin({
|
|
787
812
|
name: "host_app",
|
|
788
|
-
remotes: {
|
|
813
|
+
remotes: {
|
|
814
|
+
"remote-app": "remote_app@//cdn.example.com/remote-app/remoteEntry.js",
|
|
815
|
+
},
|
|
789
816
|
shared: { "@lark.js/mvc": { singleton: true, requiredVersion: "^1.0.0" } },
|
|
790
817
|
});
|
|
791
818
|
```
|
|
@@ -811,17 +838,17 @@ new ModuleFederationPlugin({
|
|
|
811
838
|
|
|
812
839
|
After `Framework.boot` completes, the following are attached to `window`:
|
|
813
840
|
|
|
814
|
-
| Global
|
|
815
|
-
|
|
816
|
-
| `window.__lark_Framework`
|
|
817
|
-
| `window.__lark_State`
|
|
818
|
-
| `window.__lark_Router`
|
|
819
|
-
| `window.__lark_Frame`
|
|
820
|
-
| `window.__lark_View`
|
|
821
|
-
| `window.__lark_registerViewClass`
|
|
822
|
-
| `window.__lark_invalidateViewClass`
|
|
823
|
-
| `window.__lark_getViewClassRegistry` | Function
|
|
824
|
-
| `window.__lark_Debug`
|
|
841
|
+
| Global | Value | Purpose |
|
|
842
|
+
| ------------------------------------ | ----------------------------- | ----------------------------------- |
|
|
843
|
+
| `window.__lark_Framework` | Framework object | Direct access |
|
|
844
|
+
| `window.__lark_State` | State object | Direct access |
|
|
845
|
+
| `window.__lark_Router` | Router object | Direct access |
|
|
846
|
+
| `window.__lark_Frame` | Frame class | Direct access |
|
|
847
|
+
| `window.__lark_View` | View class | Direct access |
|
|
848
|
+
| `window.__lark_registerViewClass` | Function | HMR: re-register View class |
|
|
849
|
+
| `window.__lark_invalidateViewClass` | Function | HMR: remove View from registry |
|
|
850
|
+
| `window.__lark_getViewClassRegistry` | Function | HMR: read registry |
|
|
851
|
+
| `window.__lark_Debug` | boolean, must be set manually | Enable Safeguard Proxy debug checks |
|
|
825
852
|
|
|
826
853
|
### Safeguard Debug Mode
|
|
827
854
|
|
|
@@ -928,15 +955,15 @@ The `lark-visual` sub-project in this repository is the paired visual DevTools t
|
|
|
928
955
|
|
|
929
956
|
Similarities: templates compile to functions; reactivity via Proxy; derived data with `computed`; microtask batching; component-level granular updates.
|
|
930
957
|
|
|
931
|
-
| Dimension
|
|
932
|
-
|
|
933
|
-
| Component abstraction | SFC / function components / Options
|
|
934
|
-
| Render output
|
|
935
|
-
| Template syntax
|
|
936
|
-
| Dependency tracking
|
|
937
|
-
| Compile optimizations | PatchFlag / hoistStatic / cacheHandler | Only `ldk` / `lak` / `lvk` user-manual markers
|
|
938
|
-
| Micro-frontend
|
|
939
|
-
| Scheduling
|
|
958
|
+
| Dimension | Vue 3 | Lark |
|
|
959
|
+
| --------------------- | -------------------------------------- | ----------------------------------------------------- |
|
|
960
|
+
| Component abstraction | SFC / function components / Options | Class inheritance `View.extend` / `defineView` |
|
|
961
|
+
| Render output | VNode patch | HTML string parsed to real DOM + diff |
|
|
962
|
+
| Template syntax | `v-if` / `v-for` / `:bind` | `{{if}}` / `{{forOf}}` / `@event` / `v-lark` |
|
|
963
|
+
| Dependency tracking | Automatic effect tracking | subscribe + bindStore + computed |
|
|
964
|
+
| Compile optimizations | PatchFlag / hoistStatic / cacheHandler | Only `ldk` / `lak` / `lvk` user-manual markers |
|
|
965
|
+
| Micro-frontend | Third-party (qiankun / wujie etc.) | Built-in `CrossSite` + `FrameworkConfig.require` |
|
|
966
|
+
| Scheduling | Microtask batching + nextTick | Microtask batching + `Framework.task` sliceable queue |
|
|
940
967
|
|
|
941
968
|
The key difference is render output: Vue uses virtual node patching; Lark generates HTML strings, parses them via innerHTML into a temporary div, then diffs the resulting real DOM. The advantage is that context-sensitive tags (`<table>` / `<select>` / `<svg>`) are handled by the native parser nearly for free; the disadvantage is the absence of PatchFlag-style compile-time annotations (only user-manual `ldk` / `lak` / `lvk`).
|
|
942
969
|
|
|
@@ -944,17 +971,17 @@ The key difference is render output: Vue uses virtual node patching; Lark genera
|
|
|
944
971
|
|
|
945
972
|
Similarities: unidirectional data flow; immutable write-back style; async protection (`wrapAsync` is analogous to `useEffect` cleanup + AbortController); microtask batching; global error boundary (`FrameworkConfig.error` + `funcWithTry`).
|
|
946
973
|
|
|
947
|
-
| Dimension
|
|
948
|
-
|
|
949
|
-
| Component abstraction | Function components + Hooks
|
|
950
|
-
| State encapsulation
|
|
951
|
-
| Side effects
|
|
952
|
-
| Render interruption
|
|
953
|
-
| Compile optimization
|
|
954
|
-
| Server rendering
|
|
955
|
-
| Cross-platform
|
|
956
|
-
| Event system
|
|
957
|
-
| Route guards
|
|
974
|
+
| Dimension | React 19 | Lark |
|
|
975
|
+
| --------------------- | ---------------------------------------- | ------------------------------------------------------------ |
|
|
976
|
+
| Component abstraction | Function components + Hooks | Class inheritance `View.extend` / `defineView` |
|
|
977
|
+
| State encapsulation | `useState` / `useReducer` | View instance fields, `create()` store, `State` |
|
|
978
|
+
| Side effects | `useEffect` / `useLayoutEffect` | `init` / `make` + `capture` / `release` |
|
|
979
|
+
| Render interruption | Fiber time-slicing, Suspense, Transition | Synchronous digest, not interruptible |
|
|
980
|
+
| Compile optimization | React Compiler (auto-memo) | Template compile-time only; no runtime auto-memo |
|
|
981
|
+
| Server rendering | RSC, streaming SSR | Not supported (design trade-off) |
|
|
982
|
+
| Cross-platform | React Native / DOM | Web DOM only |
|
|
983
|
+
| Event system | Synthetic Event | `document.body` capture-phase delegation + selector matching |
|
|
984
|
+
| Route guards | Third-party router libraries | Built-in `Router.beforeEach(asyncGuard)` + two-phase change |
|
|
958
985
|
|
|
959
986
|
The key difference is scheduling: React 19's Concurrent mode can interrupt and restart renders by lane priority. Lark's `Updater.digest()` is synchronous (though the internal `digestingQueue` supports re-entry) and never yields the main thread. For large lists or frequent updates, Lark has no time-slicing mechanism, which may cause long tasks; the advantage is predictable behavior and simpler debugging.
|
|
960
987
|
|