Skip to content

Commit aad34a4

Browse files
committed
feat: update skill prompt
1 parent d04127e commit aad34a4

File tree

2 files changed

+590
-26
lines changed

2 files changed

+590
-26
lines changed

packages/mcp/refactor_prompt_en.md

Lines changed: 295 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,283 @@ function useDataLoader() {
344344
- Potential for code reuse
345345
- Completeness of test coverage
346346

347-
## 7. Common Patterns and Anti-patterns
347+
## 7. Data Flow Management Principles
348348

349-
### 7.1 Recommended Patterns
349+
In complex Composition API development, clear data flow is key to maintainability. This section introduces 5 principles to ensure refactored code follows best practices for data flow management.
350+
351+
### 7.1 Principle One: Adopt Unidirectional Data Flow Pipeline Pattern
352+
353+
Avoid data jumping across multiple refs. Instead, use `computed` to build a single-directional pipeline. Data should flow in a single direction, not form cycles.
354+
355+
#### **Anti-pattern Example: Data Jumping Horizontally**
356+
```ts
357+
// 🚫 Multiple refs dependent on each other, hard to trace
358+
const searchText = ref('');
359+
const filters = ref({});
360+
const sortOrder = ref('asc');
361+
362+
// watch A modifies B, B modifies C...
363+
watch(searchText, () => {
364+
filters.value = computeFilters();
365+
});
366+
367+
watch(filters, () => {
368+
sortOrder.value = 'asc'; // Manual reset
369+
fetchData();
370+
});
371+
372+
watch(sortOrder, () => {
373+
fetchData(); // Redundant request
374+
});
375+
```
376+
377+
#### **Best Practice Example: Vertical Pipeline Flow**
378+
```ts
379+
// ✅ Clear unidirectional pipeline
380+
const searchText = ref('');
381+
const pageNum = ref(1);
382+
383+
// Step 1: Normalize/Clean input
384+
const normalizedText = computed(() => searchText.value.trim().toLowerCase());
385+
386+
// Step 2: Trigger async operation
387+
const { data, loading } = useDataFetching(normalizedText, pageNum);
388+
389+
// Step 3: Business filtering/sorting (don't modify source data)
390+
const processedData = computed(() => {
391+
if (!data.value) { return []; }
392+
return data.value
393+
.filter(item => matchesText(item, normalizedText.value))
394+
.sort((a, b) => a.priority - b.priority);
395+
});
396+
397+
// Final output to template
398+
return { processedData, loading };
399+
```
400+
401+
#### **Self-Check Questions**
402+
- Can the data flow be drawn as a **non-returning arrow sequence**?
403+
- Are there any **cycles** (A changes B, B changes A)?
404+
- Can you **read the flow directly from top to bottom**?
405+
406+
### 7.2 Principle Two: Explicitly Mark Side Effect Entries
407+
408+
Don't hide side effects scattered throughout the code. Use clear method names and debug hooks to mark them explicitly.
409+
410+
#### **Side Effect Naming Convention**
411+
```ts
412+
function useUserSearch() {
413+
const state = ref({});
414+
415+
// ✅ Verb-based naming: indicates this method modifies data
416+
const handleSearch = async (query: string) => {};
417+
const handlePageChange = (page: number) => {};
418+
const updateFilters = (filters: any) => {};
419+
420+
// ❌ Avoid ambiguous names like:
421+
// const process = () => { ... };
422+
// const change = () => { ... };
423+
424+
return {
425+
state: readonly(state),
426+
handleSearch,
427+
handlePageChange,
428+
updateFilters,
429+
};
430+
}
431+
```
432+
433+
### 7.3 Principle Three: Distinguish "Source of Truth" from "Derived State"
434+
435+
**Source of Truth**: User clicks, URL parameters, WebSocket messages, external API returns
436+
**Derived State**: State derived via `computed` or `watch` (loading, filteredList, disabledButton, etc.)
437+
438+
#### **Wrong: Treating Derived State as Source**
439+
```ts
440+
// 🚫 Manually modifying loading when userType changes
441+
watch(userType, () => {
442+
loading.value = true; // This is derived state, shouldn't be manually modified
443+
fetchData();
444+
});
445+
```
446+
447+
#### **Correct: Only Modify Source, Let Derived State Auto-Propagate**
448+
```ts
449+
// ✅ Only source userType and query are refs
450+
const userType = ref('');
451+
const query = ref('');
452+
453+
// Everything else is computed (derived state)
454+
const loading = computed(() => {
455+
return isFetching.value || isProcessing.value;
456+
});
457+
458+
const filteredUsers = computed(() => {
459+
return users.value.filter(u => u.type === userType.value && u.name.includes(query.value));
460+
});
461+
462+
const isEmpty = computed(() => filteredUsers.value.length === 0);
463+
464+
// Side effects only trigger on source changes
465+
watch([userType, query], async () => {
466+
// Automatically triggers loading
467+
}, { debounce: 300 });
468+
```
469+
470+
### 7.4 Principle Four: Interface Design Reflects Data Flow
471+
472+
Through clear interface structure, users can immediately see which are read-only (states, derived values) and which are action entry points (methods).
473+
474+
#### **Layered Interface Design**
475+
```ts
476+
export function useUserSearch() {
477+
// === Internal states (hidden) ===
478+
const _page = ref(1);
479+
const _cache = new Map();
480+
const _isRequesting = ref(false);
481+
482+
// === Source of truth (observable but modify via methods) ===
483+
const queryText = ref('');
484+
const filters = ref({});
485+
486+
// === Derived states (read-only) ===
487+
const isLoading = computed(() => _isRequesting.value);
488+
const users = computed(() => _cache.get(cacheKey.value) || []);
489+
const hasMore = computed(() => users.value.length < totalCount.value);
490+
const pageInfo = computed(() => ({
491+
current: _page.value,
492+
hasMore: hasMore.value,
493+
}));
494+
495+
// === Action methods (data modification entry points) ===
496+
const search = async (text: string) => {
497+
queryText.value = text;
498+
_page.value = 1;
499+
await _loadData();
500+
};
501+
502+
const nextPage = async () => {
503+
if (!hasMore.value) { return; }
504+
_page.value++;
505+
await _loadData();
506+
};
507+
508+
const resetFilters = () => {
509+
filters.value = {};
510+
};
511+
512+
// === Clear return interface ===
513+
return {
514+
// States (read-only)
515+
users: readonly(users),
516+
isLoading: readonly(isLoading),
517+
pageInfo: readonly(pageInfo),
518+
519+
// Source of truth (observable)
520+
queryText,
521+
filters,
522+
523+
// Actions (data modification)
524+
search,
525+
nextPage,
526+
resetFilters,
527+
};
528+
}
529+
```
530+
531+
#### **Interface Checklist**
532+
- [ ] Are all states wrapped with `readonly()` or computed?
533+
- [ ] Are all methods verb-named (search, update, reset)?
534+
- [ ] Is the return value clearly divided into: states, sources, actions?
535+
- [ ] Are there unnecessary internal details exposed?
536+
537+
### 7.5 Principle Five: Limit Modification Entry Points, Reduce Direct Assignments
538+
539+
Good Hooks only allow callers to modify data through explicit methods, avoiding direct ref exposure that leads to uncontrolled mutations.
540+
541+
#### **Anti-pattern: Exposing Too Many Refs**
542+
```ts
543+
// 🚫 This causes data flow chaos
544+
function useTodo() {
545+
const todos = ref([]);
546+
const selectedId = ref(null);
547+
const filter = ref('all');
548+
const loading = ref(false);
549+
550+
// ... Expose all refs directly, allowing arbitrary modifications
551+
return { todos, selectedId, filter, loading };
552+
}
553+
554+
// Callers can modify at will, making changes untraceable
555+
todos.value = newList; // Direct assignment, may skip validation
556+
selectedId.value = 123; // Who knows what side effects this triggers?
557+
```
558+
559+
#### **Best Practice: Strict Control via Methods**
560+
```ts
561+
// ✅ Only expose necessary methods, all modifications are traceable
562+
function useTodo() {
563+
const todos = ref<Todo[]>([]);
564+
const selectedId = ref<number | null>(null);
565+
const filter = ref<Filter>('all');
566+
const loading = ref(false);
567+
568+
// Validation + modification methods
569+
const loadTodos = async () => {
570+
loading.value = true;
571+
try {
572+
const data = await fetchTodos(filter.value);
573+
todos.value = data; // Single modification entry point
574+
}
575+
finally {
576+
loading.value = false;
577+
}
578+
};
579+
580+
const selectTodo = (id: number) => {
581+
// Can add validation logic
582+
if (todos.value.find(t => t.id === id)) {
583+
selectedId.value = id;
584+
}
585+
};
586+
587+
const setFilter = (newFilter: Filter) => {
588+
if (newFilter !== filter.value) {
589+
filter.value = newFilter;
590+
// Auto-reload
591+
loadTodos();
592+
}
593+
};
594+
595+
// Return read-only states + control methods
596+
return {
597+
todos: readonly(todos),
598+
selectedId: readonly(selectedId),
599+
filter: readonly(filter),
600+
loading: readonly(loading),
601+
loadTodos,
602+
selectTodo,
603+
setFilter,
604+
};
605+
}
606+
```
607+
608+
### 7.6 Data Flow Clarity Self-Check
609+
610+
After completing refactoring, use these questions to verify data flow clarity:
611+
612+
1. **Source Identification** - Can you clearly list all data sources (props, refs, store)?
613+
2. **Flow Tracing** - From source to rendering output, can you draw the complete flow with arrows?
614+
3. **Modification Entry** - Are all data modifications made through explicit methods?
615+
4. **Cycle Detection** - Are there any A→B→A dependency cycles?
616+
5. **Redundancy Detection** - Are there multiple watches doing the same thing?
617+
6. **Granularity Assessment** - Is a component watching too many fine-grained state changes?
618+
619+
---
620+
621+
## 8. Common Patterns and Anti-patterns
622+
623+
### 8.1 Recommended Patterns
350624

351625
#### **Layered Architecture Pattern**
352626
```ts
@@ -380,7 +654,7 @@ const { selectedItems, toggleSelection } = useSelection();
380654
const { exportData } = useDataExport(filteredData);
381655
```
382656

383-
### 7.2 Anti-patterns to Avoid
657+
### 8.2 Anti-patterns to Avoid
384658

385659
#### **God Object Anti-pattern**
386660
```ts
@@ -420,27 +694,35 @@ function useFeature(
420694
}
421695
```
422696

423-
## 8. Refactoring Checklist
697+
## 9. Refactoring Checklist
424698

425-
### 8.1 Structural Check
699+
### 9.1 Structural Check
426700
- [ ] VHO node count < 15
427701
- [ ] No articulation points exist
428702
- [ ] Isolated node groups < 3
429703
- [ ] Dependency chain depth < 4 layers
430704

431-
### 8.2 Design Check
705+
### 9.2 Design Check
432706
- [ ] All Composables have single responsibility
433707
- [ ] Interfaces follow minimal exposure principle
434708
- [ ] Lifecycle ownership is appropriate
435709
- [ ] State ownership decisions are correct
436710

437-
### 8.3 Quality Check
438-
- [ ] Type safety is complete
439-
- [ ] Test coverage is sufficient
440-
- [ ] No significant performance degradation
441-
- [ ] Readability and maintainability improved
442-
443-
### 8.4 Business Check
711+
### 9.3 Data Flow Check (NEW)
712+
- [ ] Data flow is a unidirectional pipeline with no cycles
713+
- [ ] Only refs as sources are manually modified, computed values are never directly assigned
714+
- [ ] All side effect methods use verb-based naming (handle/update/reset)
715+
- [ ] Composable return values clearly separate into: states, derived values, methods
716+
- [ ] No cascading modifications across multiple watches (watch A modifies B, watch B modifies C)
717+
- [ ] Complete source→transform→output chain is directly readable from code
718+
719+
### 9.4 Quality Check
720+
- [ ] Type safety is complete
721+
- [ ] Test coverage is sufficient
722+
- [ ] No significant performance degradation
723+
- [ ] Readability and maintainability improved
724+
725+
### 9.5 Business Check
444726
- [ ] All original functionality preserved
445727
- [ ] New features easy to implement
446728
- [ ] Good code reusability

0 commit comments

Comments
 (0)