feat: add Vue ecosystem review support

This commit is contained in:
Bujidao
2026-06-12 19:14:31 +08:00
parent 6865316ab3
commit 86e2a2061a
17 changed files with 89 additions and 59 deletions
+12 -16
View File
@@ -75,7 +75,7 @@ watch(() => count, (newVal) => { ... });
When passing a destructured prop to a composable that needs reactivity, wrap in a getter and use `toValue()` inside the composable:
```ts
useDynamicCount(() => count); // preserves reactivity
useDynamicCount(() => count); // preserves reactivity
```
### Replacing reactive() Objects
@@ -132,7 +132,7 @@ const fullName = computed({
// WRONG: side effect in computed
const displayName = computed(() => {
analytics.track("name-computed"); // side effect
analytics.track("name-computed"); // side effect
return user.value.name;
});
```
@@ -164,23 +164,19 @@ watchEffect(() => {
## Watcher Source Pitfalls
```ts
// WRONG: watching a ref object (never changes)
// CORRECT: watching a ref tracks its value
const u = ref({ name: "Alice" });
watch(u, (val) => {}); // ❌ watches the ref wrapper, not the value
watch(u, (val) => {});
// CORRECT: getter returning .value
// ALSO CORRECT: getter returning .value
watch(() => u.value, (val) => {});
// ALSO WRONG: reactive getter that doesn't track
watch(() => state.name, (val) => {}); // ❌ val is snapshot at setup
// CORRECT: getter that accesses property on reactive object
watch(() => state.name, (val) => {}); // .name access inside getter is tracked
// Wait — careful: `() => state.name` DOES track correctly because the getter
// accesses `.name` on the reactive proxy. The getter is re-evaluated by Vue.
watch(() => state.name, (val) => {}); // .name access inside getter is tracked
// The getter is re-evaluated because it accesses `.name` on the reactive proxy.
// ACTUALLY WRONG case: direct reactive property
watch(state.name, ...); // state.name evaluates to a primitive, not trackable
// WRONG: direct reactive property
watch(state.name, ...); // state.name evaluates to a primitive, not trackable
// CORRECT: getter returning reactive property
watch(() => state.name, (newName) => { ... });
@@ -190,7 +186,7 @@ watch(() => state.name, (newName) => { ... });
Every watcher that creates subscriptions, intervals, or fetch requests must clean up.
**Vue 3.5+**: Use `onWatcherCleanup()` (globally importable from `vue`) for watcher-side-effect cleanup:
**Vue 3.5+**: Use `onWatcherCleanup()` (globally importable from `vue`) for watcher-side-effect cleanup. It must be called synchronously inside the watcher callback:
```ts
import { watch, onWatcherCleanup } from "vue";
@@ -319,7 +315,7 @@ Never initialize state, start timers, or subscribe to external systems in the mo
```ts
// WRONG: module scope side effect
const globalCount = ref(0); // shared across all components
const globalCount = ref(0); // FAIL shared across all components
setInterval(() => globalCount.value++, 1000);
export function useGlobalCount() {
@@ -342,7 +338,7 @@ Use `shallowRef()` for large immutable data structures that are replaced as a wh
```ts
const items = shallowRef<Item[]>([]);
// items.value = await fetchItems(); // replacement works
// items.value[0].name = "new"; // inner mutations are NOT reactive
// items.value[0].name = "new"; // FAIL inner mutations are NOT reactive
```
Use `shallowReactive()` when only top-level properties should be reactive.
+1 -1
View File
@@ -317,7 +317,7 @@ Use `<Teleport>` for modals, tooltips, notifications — content that must escap
```vue
<!-- defer: target can appear after the Teleport in the DOM -->
<Teleport defer target="#container">
<Teleport defer to="#container">
<p>Teleported content</p>
</Teleport>
<div id="container"></div>
+2 -2
View File
@@ -83,7 +83,7 @@ app.directive("render-html", (el, binding) => {
```ts
// CRITICAL: secret leaked to client bundle (Vite)
const apiKey = import.meta.env.VITE_STRIPE_SECRET; // VITE_ prefix = public
const apiKey = import.meta.env.VITE_STRIPE_SECRET; // FAIL VITE_ prefix = public
// CORRECT: server-side only
// vite.config.ts — never pass VITE_ prefixed secrets
@@ -158,7 +158,7 @@ export default defineEventHandler(async (event) => {
```ts
// CRITICAL: session tokens in localStorage
localStorage.setItem("token", jwt); // any XSS can read this
localStorage.setItem("token", jwt); // FAIL any XSS can read this
// CORRECT: httpOnly cookie set by server
// Client never touches the token directly.