@@ -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();
380654const { 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