在现代前端开发中,组件化开发已成为主流,React、Vue 等框架通过组件封装和复用极大提升了开发效率。而组合模式(Composite Pattern)作为一种结构型设计模式,为组件化开发提供了强大的理论支持。组合模式通过将对象组织成树形结构,以统一的方式处理单个对象和组合对象,完美契合组件化开发中父子组件嵌套的场景。
1. 组合模式的基础
1.1 什么是组合模式?
组合模式是一种结构型设计模式,旨在将对象组织成树形结构,使得客户端可以统一处理单个对象(叶节点)和组合对象(容器节点)。其核心思想是:
- 一致性接口:单个对象和组合对象实现相同的接口。
 - 树形结构:通过递归组合形成层次结构。
 - 透明性:客户端无需区分操作的是单个对象还是组合对象。
 
在组件化开发中,组合模式体现在组件树中,父组件可以包含子组件,子组件也可以是父组件,共同构成复杂的 UI 结构。
1.2 组合模式的组成
组合模式通常包含以下角色:
- Component(组件接口):定义所有组件的公共接口。
 - Leaf(叶节点):实现 Component 接口,表示没有子节点的组件。
 - Composite(组合节点):实现 Component 接口,包含子组件并管理其生命周期。
 - Client(客户端):通过 Component 接口操作组件树。
 
1.3 为什么在组件化开发中使用组合模式?
- 层次结构:UI 天然是树形结构(如 DOM 树、组件树)。
 - 一致性操作:父子组件通过统一接口通信。
 - 灵活性:支持动态添加或移除子组件。
 - 复用性:组合模式便于封装可复用的组件逻辑。
 
2. 组合模式的原理
2.1 基本结构
以下是一个简单的组合模式实现:
            
            
              javascript
              
              
            
          
          // Component 接口
class Component {
  add(child) {
    throw new Error('Method must be implemented');
  }
  remove(child) {
    throw new Error('Method must be implemented');
  }
  render() {
    throw new Error('Method must be implemented');
  }
}
// Leaf 节点
class Leaf extends Component {
  constructor(name) {
    super();
    this.name = name;
  }
  render() {
    return `<div>${this.name}</div>`;
  }
}
// Composite 节点
class Composite extends Component {
  constructor(name) {
    super();
    this.name = name;
    this.children = [];
  }
  add(child) {
    this.children.push(child);
  }
  remove(child) {
    this.children = this.children.filter(c => c !== child);
  }
  render() {
    const childrenHtml = this.children.map(c => c.render()).join('');
    return `<div>${this.name}${childrenHtml}</div>`;
  }
}
// 客户端
const root = new Composite('Root');
const leaf1 = new Leaf('Leaf 1');
const leaf2 = new Leaf('Leaf 2');
const composite1 = new Composite('Composite 1');
root.add(leaf1);
root.add(composite1);
composite1.add(leaf2);
console.log(root.render());
// <div>Root<div>Leaf 1</div><div>Composite 1<div>Leaf 2</div></div></div>
        2.2 组合模式的关键特性
- 递归性 :Composite 节点通过递归调用子节点的 
render方法生成 HTML。 - 一致性 :Leaf 和 Composite 都实现 
render方法,客户端无需区分。 - 动态性:支持运行时添加或移除子节点。
 
2.3 适用场景
组合模式在组件化开发中的典型场景包括:
- UI 组件树:如 React 的组件嵌套。
 - 动态表单:表单字段可以是输入框、选择框或嵌套表单。
 - 菜单系统:菜单项可以是单个链接或包含子菜单的容器。
 
3. 组合模式在 React 中的应用
React 的组件化开发天然契合组合模式,每个组件可以是叶节点(无子组件)或组合节点(包含子组件)。
3.1 基本组件实现
创建一个简单的组件树:
            
            
              javascript
              
              
            
          
          import React from 'react';
const LeafComponent = ({ name }) => <div>{name}</div>;
const CompositeComponent = ({ name, children }) => (
  <div>
    <h2>{name}</h2>
    {children}
  </div>
);
const App = () => (
  <CompositeComponent name="Root">
    <LeafComponent name="Leaf 1" />
    <CompositeComponent name="Composite 1">
      <LeafComponent name="Leaf 2" />
    </CompositeComponent>
  </CompositeComponent>
);
        渲染结果:
            
            
              html
              
              
            
          
          <div>
  <h2>Root</h2>
  <div>Leaf 1</div>
  <div>
    <h2>Composite 1</h2>
    <div>Leaf 2</div>
  </div>
</div>
        3.2 动态组件树
支持动态添加子组件:
            
            
              javascript
              
              
            
          
          import React, { useState } from 'react';
const LeafComponent = ({ name }) => <div>{name}</div>;
const CompositeComponent = ({ name, children }) => (
  <div>
    <h2>{name}</h2>
    {children}
  </div>
);
const App = () => {
  const [components, setComponents] = useState([
    { id: 1, type: 'leaf', name: 'Leaf 1' },
    {
      id: 2,
      type: 'composite',
      name: 'Composite 1',
      children: [{ id: 3, type: 'leaf', name: 'Leaf 2' }],
    },
  ]);
  const renderComponent = comp => {
    if (comp.type === 'leaf') {
      return <LeafComponent key={comp.id} name={comp.name} />;
    }
    return (
      <CompositeComponent key={comp.id} name={comp.name}>
        {comp.children?.map(renderComponent)}
      </CompositeComponent>
    );
  };
  return <div>{components.map(renderComponent)}</div>;
};
        3.3 组件组合与 Props 传递
通过 Props 传递数据和方法:
            
            
              javascript
              
              
            
          
          import React from 'react';
const LeafComponent = ({ name, onClick }) => (
  <div onClick={() => onClick(name)}>{name}</div>
);
const CompositeComponent = ({ name, onClick, children }) => (
  <div>
    <h2 onClick={() => onClick(name)}>{name}</h2>
    {children}
  </div>
);
const App = () => {
  const handleClick = name => console.log(`Clicked: ${name}`);
  return (
    <CompositeComponent name="Root" onClick={handleClick}>
      <LeafComponent name="Leaf 1" onClick={handleClick} />
      <CompositeComponent name="Composite 1" onClick={handleClick}>
        <LeafComponent name="Leaf 2" onClick={handleClick} />
      </CompositeComponent>
    </CompositeComponent>
  );
};
        4. 组合模式在 Vue 中的应用
Vue 的组件化机制同样支持组合模式,组件通过插槽(Slots)和动态组件实现树形结构。
4.1 基本组件实现
            
            
              javascript
              
              
            
          
          // LeafComponent.vue
<template>
  <div>{{ name }}</div>
</template>
<script>
export default {
  props: ['name'],
};
</script>
// CompositeComponent.vue
<template>
  <div>
    <h2>{{ name }}</h2>
    <slot />
  </div>
</template>
<script>
export default {
  props: ['name'],
};
</script>
// App.vue
<template>
  <CompositeComponent name="Root">
    <LeafComponent name="Leaf 1" />
    <CompositeComponent name="Composite 1">
      <LeafComponent name="Leaf 2" />
    </CompositeComponent>
  </CompositeComponent>
</template>
<script>
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default {
  components: { LeafComponent, CompositeComponent },
};
</script>
        4.2 动态组件树
使用动态组件渲染:
            
            
              javascript
              
              
            
          
          // App.vue
<template>
  <div>
    <component
      v-for="comp in components"
      :key="comp.id"
      :is="comp.type === 'leaf' ? LeafComponent : CompositeComponent"
      :name="comp.name"
    >
      <template v-if="comp.children">
        <component
          v-for="child in comp.children"
          :key="child.id"
          :is="child.type === 'leaf' ? LeafComponent : CompositeComponent"
          :name="child.name"
        />
      </template>
    </component>
  </div>
</template>
<script>
import { defineComponent } from 'vue';
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default defineComponent({
  components: { LeafComponent, CompositeComponent },
  data() {
    return {
      components: [
        { id: 1, type: 'leaf', name: 'Leaf 1' },
        {
          id: 2,
          type: 'composite',
          name: 'Composite 1',
          children: [{ id: 3, type: 'leaf', name: 'Leaf 2' }],
        },
      ],
    };
  },
});
</script>
        4.3 事件传递
通过事件实现父子通信:
            
            
              javascript
              
              
            
          
          // LeafComponent.vue
<template>
  <div @click="$emit('custom-click', name)">{{ name }}</div>
</template>
<script>
export default {
  props: ['name'],
  emits: ['custom-click'],
};
</script>
// CompositeComponent.vue
<template>
  <div>
    <h2 @click="$emit('custom-click', name)">{{ name }}</h2>
    <slot />
  </div>
</template>
<script>
export default {
  props: ['name'],
  emits: ['custom-click'],
};
</script>
// App.vue
<template>
  <CompositeComponent name="Root" @custom-click="handleClick">
    <LeafComponent name="Leaf 1" @custom-click="handleClick" />
    <CompositeComponent name="Composite 1" @custom-click="handleClick">
      <LeafComponent name="Leaf 2" @custom-click="handleClick" />
    </CompositeComponent>
  </CompositeComponent>
</template>
<script>
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default {
  components: { LeafComponent, CompositeComponent },
  methods: {
    handleClick(name) {
      console.log(`Clicked: ${name}`);
    },
  },
};
</script>
        5. 组合模式在 TypeScript 中的实现
TypeScript 的类型系统为组合模式提供更强的类型安全。
5.1 基本实现
            
            
              typescript
              
              
            
          
          interface Component {
  add(child: Component): void;
  remove(child: Component): void;
  render(): string;
}
class Leaf implements Component {
  constructor(private name: string) {}
  add() {}
  remove() {}
  render(): string {
    return `<div>${this.name}</div>`;
  }
}
class Composite implements Component {
  private children: Component[] = [];
  constructor(private name: string) {}
  add(child: Component): void {
    this.children.push(child);
  }
  remove(child: Component): void {
    this.children = this.children.filter(c => c !== child);
  }
  render(): string {
    const childrenHtml = this.children.map(c => c.render()).join('');
    return `<div>${this.name}${childrenHtml}</div>`;
  }
}
const root = new Composite('Root');
const leaf1 = new Leaf('Leaf 1');
const leaf2 = new Leaf('Leaf 2');
const composite1 = new Composite('Composite 1');
root.add(leaf1);
root.add(composite1);
composite1.add(leaf2);
console.log(root.render());
        5.2 React + TypeScript
            
            
              typescript
              
              
            
          
          import React, { FC, ReactNode } from 'react';
interface LeafProps {
  name: string;
  onClick: (name: string) => void;
}
const LeafComponent: FC<LeafProps> = ({ name, onClick }) => (
  <div onClick={() => onClick(name)}>{name}</div>
);
interface CompositeProps {
  name: string;
  onClick: (name: string) => void;
  children?: ReactNode;
}
const CompositeComponent: FC<CompositeProps> = ({ name, onClick, children }) => (
  <div>
    <h2 onClick={() => onClick(name)}>{name}</h2>
    {children}
  </div>
);
const App: FC = () => {
  const handleClick = (name: string) => console.log(`Clicked: ${name}`);
  return (
    <CompositeComponent name="Root" onClick={handleClick}>
      <LeafComponent name="Leaf 1" onClick={handleClick} />
      <CompositeComponent name="Composite 1" onClick={handleClick}>
        <LeafComponent name="Leaf 2" onClick={handleClick} />
      </CompositeComponent>
    </CompositeComponent>
  );
};
        5.3 Vue + TypeScript
            
            
              typescript
              
              
            
          
          // LeafComponent.vue
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  props: {
    name: { type: String, required: true },
  },
  emits: ['custom-click'],
  setup(props, { emit }) {
    return () => (
      <div onClick={() => emit('custom-click', props.name)}>{props.name}</div>
    );
  },
});
</script>
// CompositeComponent.vue
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  props: {
    name: { type: String, required: true },
  },
  emits: ['custom-click'],
  setup(props, { emit, slots }) {
    return () => (
      <div>
        <h2 onClick={() => emit('custom-click', props.name)}>{props.name}</h2>
        {slots.default?.()}
      </div>
    );
  },
});
</script>
// App.vue
<script lang="ts">
import { defineComponent } from 'vue';
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default defineComponent({
  components: { LeafComponent, CompositeComponent },
  setup() {
    const handleClick = (name: string) => console.log(`Clicked: ${name}`);
    return { handleClick };
  },
});
</script>
<template>
  <CompositeComponent name="Root" @custom-click="handleClick">
    <LeafComponent name="Leaf 1" @custom-click="handleClick" />
    <CompositeComponent name="Composite 1" @custom-click="handleClick">
      <LeafComponent name="Leaf 2" @custom-click="handleClick" />
    </CompositeComponent>
  </CompositeComponent>
</template>
        6. 组合模式的性能优化
6.1 React 优化
使用 React.memo 避免不必要的重渲染:
            
            
              javascript
              
              
            
          
          import React, { memo } from 'react';
const LeafComponent = memo(({ name, onClick }) => (
  <div onClick={() => onClick(name)}>{name}</div>
));
const CompositeComponent = memo(({ name, onClick, children }) => (
  <div>
    <h2 onClick={() => onClick(name)}>{name}</h2>
    {children}
  </div>
));
        使用 useCallback 稳定回调:
            
            
              javascript
              
              
            
          
          import React, { useCallback } from 'react';
const App = () => {
  const handleClick = useCallback(name => console.log(`Clicked: ${name}`), []);
  return (
    <CompositeComponent name="Root" onClick={handleClick}>
      <LeafComponent name="Leaf 1" onClick={handleClick} />
      <CompositeComponent name="Composite 1" onClick={handleClick}>
        <LeafComponent name="Leaf 2" onClick={handleClick} />
      </CompositeComponent>
    </CompositeComponent>
  );
};
        6.2 Vue 优化
使用 defineComponent 和 reactive 优化响应式数据:
            
            
              javascript
              
              
            
          
          // App.vue
<script lang="ts">
import { defineComponent, reactive } from 'vue';
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default defineComponent({
  components: { LeafComponent, CompositeComponent },
  setup() {
    const state = reactive({
      components: [
        { id: 1, type: 'leaf', name: 'Leaf 1' },
        {
          id: 2,
          type: 'composite',
          name: 'Composite 1',
          children: [{ id: 3, type: 'leaf', name: 'Leaf 2' }],
        },
      ],
    });
    const handleClick = (name: string) => console.log(`Clicked: ${name}`);
    return { state, handleClick };
  },
});
</script>
<template>
  <div>
    <component
      v-for="comp in state.components"
      :key="comp.id"
      :is="comp.type === 'leaf' ? LeafComponent : CompositeComponent"
      :name="comp.name"
      @custom-click="handleClick"
    >
      <template v-if="comp.children">
        <component
          v-for="child in comp.children"
          :key="child.id"
          :is="child.type === 'leaf' ? LeafComponent : CompositeComponent"
          :name="child.name"
          @custom-click="handleClick"
        />
      </template>
    </component>
  </div>
</template>
        7. 组合模式在动态表单中的实战
7.1 React 动态表单
实现一个动态表单,支持输入框和嵌套表单:
            
            
              javascript
              
              
            
          
          import React, { useState } from 'react';
const InputField = ({ name, value, onChange }) => (
  <div>
    <label>{name}</label>
    <input value={value} onChange={e => onChange(name, e.target.value)} />
  </div>
);
const FormGroup = ({ name, children, onChange }) => (
  <div>
    <h3>{name}</h3>
    {React.Children.map(children, child =>
      React.cloneElement(child, { onChange })
    )}
  </div>
);
const DynamicForm = () => {
  const [formData, setFormData] = useState({});
  const handleChange = (name, value) => {
    setFormData(prev => ({ ...prev, [name]: value }));
  };
  return (
    <FormGroup name="User Info" onChange={handleChange}>
      <InputField name="name" value={formData.name || ''} />
      <FormGroup name="Address">
        <InputField name="street" value={formData.street || ''} />
        <InputField name="city" value={formData.city || ''} />
      </FormGroup>
    </FormGroup>
  );
};
        7.2 Vue 动态表单
            
            
              javascript
              
              
            
          
          // InputField.vue
<template>
  <div>
    <label>{{ name }}</label>
    <input :value="value" @input="$emit('update', name, $event.target.value)" />
  </div>
</template>
<script>
export default {
  props: ['name', 'value'],
  emits: ['update'],
};
</script>
// FormGroup.vue
<template>
  <div>
    <h3>{{ name }}</h3>
    <slot />
  </div>
</template>
<script>
export default {
  props: ['name'],
};
</script>
// DynamicForm.vue
<template>
  <FormGroup name="User Info">
    <InputField
      name="name"
      :value="formData.name"
      @update="updateField"
    />
    <FormGroup name="Address">
      <InputField
        name="street"
        :value="formData.street"
        @update="updateField"
      />
      <InputField
        name="city"
        :value="formData.city"
        @update="updateField"
      />
    </FormGroup>
  </FormGroup>
</template>
<script>
import { reactive } from 'vue';
import InputField from './InputField.vue';
import FormGroup from './FormGroup.vue';
export default {
  components: { InputField, FormGroup },
  setup() {
    const formData = reactive({});
    const updateField = (name, value) => {
      formData[name] = value;
    };
    return { formData, updateField };
  },
};
</script>
        8. 组合模式在菜单系统中的实战
8.1 React 菜单
实现一个可嵌套的菜单系统:
            
            
              javascript
              
              
            
          
          import React, { useState } from 'react';
const MenuItem = ({ name, onClick }) => (
  <li onClick={() => onClick(name)}>{name}</li>
);
const Menu = ({ name, children, onClick }) => {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <div onClick={() => setIsOpen(!isOpen)}>{name}</div>
      {isOpen && <ul>{children}</ul>}
    </div>
  );
};
const MenuSystem = () => {
  const handleClick = name => console.log(`Clicked: ${name}`);
  return (
    <Menu name="Root" onClick={handleClick}>
      <MenuItem name="Item 1" onClick={handleClick} />
      <Menu name="Submenu 1" onClick={handleClick}>
        <MenuItem name="Item 2" onClick={handleClick} />
        <MenuItem name="Item 3" onClick={handleClick} />
      </Menu>
    </Menu>
  );
};
        8.2 Vue 菜单
            
            
              javascript
              
              
            
          
          // MenuItem.vue
<template>
  <li @click="$emit('custom-click', name)">{{ name }}</li>
</template>
<script>
export default {
  props: ['name'],
  emits: ['custom-click'],
};
</script>
// Menu.vue
<template>
  <div>
    <div @click="isOpen = !isOpen">{{ name }}</div>
    <ul v-if="isOpen">
      <slot />
    </ul>
  </div>
</template>
<script>
import { ref } from 'vue';
export default {
  props: ['name'],
  emits: ['custom-click'],
  setup() {
    const isOpen = ref(false);
    return { isOpen };
  },
};
</script>
// MenuSystem.vue
<template>
  <Menu name="Root" @custom-click="handleClick">
    <MenuItem name="Item 1" @custom-click="handleClick" />
    <Menu name="Submenu 1" @custom-click="handleClick">
      <MenuItem name="Item 2" @custom-click="handleClick" />
      <MenuItem name="Item 3" @custom-click="handleClick" />
    </Menu>
  </Menu>
</template>
<script>
import Menu from './Menu.vue';
import MenuItem from './MenuItem.vue';
export default {
  components: { Menu, MenuItem },
  methods: {
    handleClick(name) {
      console.log(`Clicked: ${name}`);
    },
  },
};
</script>
        9. 组合模式与状态管理
9.1 Redux 集成
在 React 中结合 Redux 管理组件树状态:
            
            
              javascript
              
              
            
          
          import React from 'react';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';
const initialState = {
  components: [
    { id: 1, type: 'leaf', name: 'Leaf 1' },
    {
      id: 2,
      type: 'composite',
      name: 'Composite 1',
      children: [{ id: 3, type: 'leaf', name: 'Leaf 2' }],
    },
  ],
};
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'UPDATE_NAME':
      return {
        ...state,
        components: state.components.map(comp =>
          comp.id === action.payload.id
            ? { ...comp, name: action.payload.name }
            : comp
        ),
      };
    default:
      return state;
  }
};
const store = createStore(reducer);
const LeafComponent = ({ id, name }) => {
  const dispatch = useDispatch();
  const handleClick = () =>
    dispatch({ type: 'UPDATE_NAME', payload: { id, name: `${name} Updated` } });
  return <div onClick={handleClick}>{name}</div>;
};
const CompositeComponent = ({ id, name, children }) => {
  const dispatch = useDispatch();
  const handleClick = () =>
    dispatch({ type: 'UPDATE_NAME', payload: { id, name: `${name} Updated` } });
  return (
    <div>
      <h2 onClick={handleClick}>{name}</h2>
      {children}
    </div>
  );
};
const App = () => {
  const components = useSelector(state => state.components);
  const renderComponent = comp => {
    if (comp.type === 'leaf') {
      return <LeafComponent key={comp.id} id={comp.id} name={comp.name} />;
    }
    return (
      <CompositeComponent key={comp.id} id={comp.id} name={comp.name}>
        {comp.children?.map(renderComponent)}
      </CompositeComponent>
    );
  };
  return <div>{components.map(renderComponent)}</div>;
};
const Root = () => (
  <Provider store={store}>
    <App />
  </Provider>
);
        9.2 Vuex 集成
在 Vue 中结合 Vuex:
            
            
              javascript
              
              
            
          
          // store.js
import { createStore } from 'vuex';
export default createStore({
  state: {
    components: [
      { id: 1, type: 'leaf', name: 'Leaf 1' },
      {
        id: 2,
        type: 'composite',
        name: 'Composite 1',
        children: [{ id: 3, type: 'leaf', name: 'Leaf 2' }],
      },
    ],
  },
  mutations: {
    UPDATE_NAME(state, { id, name }) {
      state.components = state.components.map(comp =>
        comp.id === id ? { ...comp, name } : comp
      );
    },
  },
  actions: {
    updateName({ commit }, { id, name }) {
      commit('UPDATE_NAME', { id, name });
    },
  },
});
// App.vue
<template>
  <div>
    <component
      v-for="comp in $store.state.components"
      :key="comp.id"
      :is="comp.type === 'leaf' ? LeafComponent : CompositeComponent"
      :id="comp.id"
      :name="comp.name"
    >
      <template v-if="comp.children">
        <component
          v-for="child in comp.children"
          :key="child.id"
          :is="child.type === 'leaf' ? LeafComponent : CompositeComponent"
          :id="child.id"
          :name="child.name"
        />
      </template>
    </component>
  </div>
</template>
<script>
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default {
  components: { LeafComponent, CompositeComponent },
};
</script>
// LeafComponent.vue
<template>
  <div @click="updateName">{{ name }}</div>
</template>
<script>
export default {
  props: ['id', 'name'],
  methods: {
    updateName() {
      this.$store.dispatch('updateName', {
        id: this.id,
        name: `${this.name} Updated`,
      });
    },
  },
};
</script>
// CompositeComponent.vue
<template>
  <div>
    <h2 @click="updateName">{{ name }}</h2>
    <slot />
  </div>
</template>
<script>
export default {
  props: ['id', 'name'],
  methods: {
    updateName() {
      this.$store.dispatch('updateName', {
        id: this.id,
        name: `${this.name} Updated`,
      });
    },
  },
};
</script>
        10. 组合模式与微前端
10.1 Qiankun 集成
在微前端中,组合模式用于管理子应用:
            
            
              javascript
              
              
            
          
          import { registerMicroApps, start } from 'qiankun';
class MicroAppComponent {
  constructor(name, config) {
    this.name = name;
    this.config = config;
  }
  register() {
    registerMicroApps([this.config]);
  }
  start() {
    start();
  }
}
class MicroAppComposite {
  constructor(name) {
    this.name = name;
    this.children = [];
  }
  add(child) {
    this.children.push(child);
  }
  remove(child) {
    this.children = this.children.filter(c => c !== child);
  }
  register() {
    this.children.forEach(child => child.register());
  }
  start() {
    start();
  }
}
const root = new MicroAppComposite('Root');
const reactApp = new MicroAppComponent('reactApp', {
  name: 'reactApp',
  entry: '//localhost:3001',
  container: '#reactContainer',
  activeRule: '/react',
});
const vueApp = new MicroAppComponent('vueApp', {
  name: 'vueApp',
  entry: '//localhost:3002',
  container: '#vueContainer',
  activeRule: '/vue',
});
root.add(reactApp);
root.add(vueApp);
root.register();
root.start();
        10.2 React 微前端
            
            
              javascript
              
              
            
          
          import React from 'react';
import { loadMicroApp } from 'qiankun';
const MicroAppLeaf = ({ name, config }) => {
  React.useEffect(() => {
    const app = loadMicroApp(config);
    return () => app.unmount();
  }, [config]);
  return <div id={config.container.replace('#', '')} />;
};
const MicroAppComposite = ({ name, children }) => (
  <div>
    <h2>{name}</h2>
    {children}
  </div>
);
const App = () => (
  <MicroAppComposite name="Root">
    <MicroAppLeaf
      name="React App"
      config={{
        name: 'reactApp',
        entry: '//localhost:3001',
        container: '#reactContainer',
        activeRule: '/react',
      }}
    />
    <MicroAppLeaf
      name="Vue App"
      config={{
        name: 'vueApp',
        entry: '//localhost:3002',
        container: '#vueContainer',
        activeRule: '/vue',
      }}
    />
  </MicroAppComposite>
);
        11. 测试组合模式
11.1 React 测试
使用 Jest 和 Testing Library:
            
            
              javascript
              
              
            
          
          import { render, screen } from '@testing-library/react';
import App from './App';
describe('App', () => {
  it('renders component tree', () => {
    render(<App />);
    expect(screen.getByText('Root')).toBeInTheDocument();
    expect(screen.getByText('Leaf 1')).toBeInTheDocument();
    expect(screen.getByText('Composite 1')).toBeInTheDocument();
    expect(screen.getByText('Leaf 2')).toBeInTheDocument();
  });
});
        11.2 Vue 测试
            
            
              javascript
              
              
            
          
          import { mount } from '@vue/test-utils';
import App from './App.vue';
describe('App', () => {
  it('renders component tree', () => {
    const wrapper = mount(App);
    expect(wrapper.text()).toContain('Root');
    expect(wrapper.text()).toContain('Leaf 1');
    expect(wrapper.text()).toContain('Composite 1');
    expect(wrapper.text()).toContain('Leaf 2');
  });
});
        12. 组合模式与错误处理
12.1 React 错误边界
            
            
              javascript
              
              
            
          
          import React from 'react';
class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) {
      return <div>Something went wrong</div>;
    }
    return this.props.children;
  }
}
const LeafComponent = ({ name }) => {
  if (!name) throw new Error('Name is required');
  return <div>{name}</div>;
};
const App = () => (
  <ErrorBoundary>
    <CompositeComponent name="Root">
      <LeafComponent name="Leaf 1" />
      <CompositeComponent name="Composite 1">
        <LeafComponent name="" />
      </CompositeComponent>
    </CompositeComponent>
  </ErrorBoundary>
);
        12.2 Vue 错误处理
            
            
              javascript
              
              
            
          
          // App.vue
<template>
  <div v-if="error">Something went wrong</div>
  <CompositeComponent v-else name="Root">
    <LeafComponent name="Leaf 1" />
    <CompositeComponent name="Composite 1">
      <LeafComponent name="" />
    </CompositeComponent>
  </CompositeComponent>
</template>
<script>
import { ref } from 'vue';
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default {
  components: { LeafComponent, CompositeComponent },
  setup() {
    const error = ref(false);
    return { error };
  },
  errorCaptured() {
    this.error = true;
    return false;
  },
};
</script>
// LeafComponent.vue
<template>
  <div>{{ name }}</div>
</template>
<script>
export default {
  props: ['name'],
  mounted() {
    if (!this.name) throw new Error('Name is required');
  },
};
</script>
        13. 组合模式与模块化
13.1 CommonJS
            
            
              javascript
              
              
            
          
          // LeafComponent.js
module.exports = function LeafComponent({ name }) {
  return `<div>${name}</div>`;
};
// CompositeComponent.js
module.exports = function CompositeComponent({ name, children }) {
  return `<div><h2>${name}</h2>${children.join('')}</div>`;
};
// App.js
const LeafComponent = require('./LeafComponent');
const CompositeComponent = require('./CompositeComponent');
const render = () => {
  const leaf1 = LeafComponent({ name: 'Leaf 1' });
  const leaf2 = LeafComponent({ name: 'Leaf 2' });
  const composite1 = CompositeComponent({
    name: 'Composite 1',
    children: [leaf2],
  });
  return CompositeComponent({ name: 'Root', children: [leaf1, composite1] });
};
console.log(render());
        13.2 ES Modules
            
            
              javascript
              
              
            
          
          // LeafComponent.mjs
export function LeafComponent({ name }) {
  return `<div>${name}</div>`;
}
// CompositeComponent.mjs
export function CompositeComponent({ name, children }) {
  return `<div><h2>${name}</h2>${children.join('')}</div>`;
}
// App.mjs
import { LeafComponent } from './LeafComponent.mjs';
import { CompositeComponent } from './CompositeComponent.mjs';
const render = () => {
  const leaf1 = LeafComponent({ name: 'Leaf 1' });
  const leaf2 = LeafComponent({ name: 'Leaf 2' });
  const composite1 = CompositeComponent({
    name: 'Composite 1',
    children: [leaf2],
  });
  return CompositeComponent({ name: 'Root', children: [leaf1, composite1] });
};
console.log(render());
        14. 组合模式与性能分析
14.1 性能测试
            
            
              javascript
              
              
            
          
          const start = performance.now();
const root = new Composite('Root');
for (let i = 0; i < 1000; i++) {
  root.add(new Leaf(`Leaf ${i}`));
}
root.render();
const end = performance.now();
console.log(`Rendering took ${end - start}ms`);
        14.2 React Profiler
            
            
              javascript
              
              
            
          
          import { Profiler } from 'react';
const App = () => (
  <Profiler id="App" onRender={(id, phase, actualDuration) => {
    console.log(`${id} ${phase} took ${actualDuration}ms`);
  }}>
    <CompositeComponent name="Root">
      <LeafComponent name="Leaf 1" />
      <CompositeComponent name="Composite 1">
        <LeafComponent name="Leaf 2" />
      </CompositeComponent>
    </CompositeComponent>
  </Profiler>
);
        15. 组合模式与 Node.js 集成
15.1 服务端渲染
            
            
              javascript
              
              
            
          
          const express = require('express');
const app = express();
class LeafComponent {
  constructor(name) {
    this.name = name;
  }
  render() {
    return `<div>${this.name}</div>`;
  }
}
class CompositeComponent {
  constructor(name) {
    this.name = name;
    this.children = [];
  }
  add(child) {
    this.children.push(child);
  }
  render() {
    const childrenHtml = this.children.map(c => c.render()).join('');
    return `<div><h2>${this.name}</h2>${childrenHtml}</div>`;
  }
}
app.get('/', (req, res) => {
  const root = new CompositeComponent('Root');
  const leaf1 = new LeafComponent('Leaf 1');
  const composite1 = new CompositeComponent('Composite 1');
  const leaf2 = new LeafComponent('Leaf 2');
  root.add(leaf1);
  root.add(composite1);
  composite1.add(leaf2);
  res.send(root.render());
});
app.listen(3000);
        15.2 API 集成
            
            
              javascript
              
              
            
          
          const express = require('express');
const axios = require('axios');
const app = express();
class LeafComponent {
  constructor(data) {
    this.data = data;
  }
  render() {
    return `<div>${this.data.name}</div>`;
  }
}
class CompositeComponent {
  constructor(name) {
    this.name = name;
    this.children = [];
  }
  add(child) {
    this.children.push(child);
  }
  render() {
    const childrenHtml = this.children.map(c => c.render()).join('');
    return `<div><h2>${this.name}</h2>${childrenHtml}</div>`;
  }
}
app.get('/', async (req, res) => {
  const { data } = await axios.get('https://api.example.com/users');
  
  const root = new CompositeComponent('Users');
  data.forEach(user => root.add(new LeafComponent(user)));
  res.send(root.render());
});
app.listen(3000);
        16. 组合模式与事件处理
16.1 React 事件冒泡
            
            
              javascript
              
              
            
          
          const LeafComponent = ({ name, onClick }) => (
  <div onClick={() => onClick(name)}>{name}</div>
);
const CompositeComponent = ({ name, onClick, children }) => (
  <div onClick={() => onClick(name)}>
    <h2>{name}</h2>
    {children}
  </div>
);
const App = () => {
  const handleClick = name => console.log(`Clicked: ${name}`);
  return (
    <CompositeComponent name="Root" onClick={handleClick}>
      <LeafComponent name="Leaf 1" onClick={handleClick} />
      <CompositeComponent name="Composite 1" onClick={handleClick}>
        <LeafComponent name="Leaf 2" onClick={handleClick} />
      </CompositeComponent>
    </CompositeComponent>
  );
};
        16.2 Vue 事件冒泡
            
            
              javascript
              
              
            
          
          // LeafComponent.vue
<template>
  <div @click="$emit('custom-click', name)">{{ name }}</div>
</template>
<script>
export default {
  props: ['name'],
  emits: ['custom-click'],
};
</script>
// CompositeComponent.vue
<template>
  <div @click="$emit('custom-click', name)">
    <h2>{{ name }}</h2>
    <slot />
  </div>
</template>
<script>
export default {
  props: ['name'],
  emits: ['custom-click'],
};
</script>
// App.vue
<template>
  <CompositeComponent name="Root" @custom-click="handleClick">
    <LeafComponent name="Leaf 1" @custom-click="handleClick" />
    <CompositeComponent name="Composite 1" @custom-click="handleClick">
      <LeafComponent name="Leaf 2" @custom-click="handleClick" />
    </CompositeComponent>
  </CompositeComponent>
</template>
<script>
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default {
  components: { LeafComponent, CompositeComponent },
  methods: {
    handleClick(name) {
      console.log(`Clicked: ${name}`);
    },
  },
};
</script>
        17. 组合模式与插件系统
17.1 React 插件
            
            
              javascript
              
              
            
          
          const withPlugin = (Component, plugin) => props => (
  <Component {...props} {...plugin} />
);
const loggingPlugin = {
  onClick: name => console.log(`Plugin logged: ${name}`),
};
const LeafComponent = ({ name, onClick }) => (
  <div onClick={() => onClick(name)}>{name}</div>
);
const CompositeComponent = ({ name, onClick, children }) => (
  <div>
    <h2 onClick={() => onClick(name)}>{name}</h2>
    {children}
  </div>
);
const LoggedLeaf = withPlugin(LeafComponent, loggingPlugin);
const LoggedComposite = withPlugin(CompositeComponent, loggingPlugin);
const App = () => (
  <LoggedComposite name="Root">
    <LoggedLeaf name="Leaf 1" />
    <LoggedComposite name="Composite 1">
      <LoggedLeaf name="Leaf 2" />
    </LoggedComposite>
  </LoggedComposite>
);
        17.2 Vue 插件
            
            
              javascript
              
              
            
          
          // Plugin.js
export const loggingPlugin = {
  install(app) {
    app.config.globalProperties.$logClick = name =>
      console.log(`Plugin logged: ${name}`);
  },
};
// App.vue
<template>
  <CompositeComponent name="Root">
    <LeafComponent name="Leaf 1" />
    <CompositeComponent name="Composite 1">
      <LeafComponent name="Leaf 2" />
    </CompositeComponent>
  </CompositeComponent>
</template>
<script>
import { createApp } from 'vue';
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
import { loggingPlugin } from './Plugin';
const app = createApp({
  components: { LeafComponent, CompositeComponent },
});
app.use(loggingPlugin);
app.mount('#app');
</script>
// LeafComponent.vue
<template>
  <div @click="$logClick(name)">{{ name }}</div>
</template>
<script>
export default {
  props: ['name'],
};
</script>
// CompositeComponent.vue
<template>
  <div>
    <h2 @click="$logClick(name)">{{ name }}</h2>
    <slot />
  </div>
</template>
<script>
export default {
  props: ['name'],
};
</script>
        18. 组合模式与配置管理
18.1 React 配置
            
            
              javascript
              
              
            
          
          const createComponent = (type, config) => {
  const components = {
    leaf: LeafComponent,
    composite: CompositeComponent,
  };
  const Component = components[type];
  return props => <Component {...props} {...config} />;
};
const themedLeaf = createComponent('leaf', { className: 'theme-dark' });
const themedComposite = createComponent('composite', { className: 'theme-dark' });
const App = () => (
  <themedComposite name="Root">
    <themedLeaf name="Leaf 1" />
    <themedComposite name="Composite 1">
      <themedLeaf name="Leaf 2" />
    </themedComposite>
  </themedComposite>
);
        18.2 Vue 配置
            
            
              javascript
              
              
            
          
          // ComponentFactory.js
export const createComponent = (type, config) => {
  const components = {
    leaf: 'LeafComponent',
    composite: 'CompositeComponent',
  };
  return {
    name: components[type],
    props: config,
    setup(props, { slots }) {
      return () => (
        <components[type] {...props} v-slots={slots} />
      );
    },
  };
};
// App.vue
<template>
  <ThemedComposite name="Root">
    <ThemedLeaf name="Leaf 1" />
    <ThemedComposite name="Composite 1">
      <ThemedLeaf name="Leaf 2" />
    </ThemedComposite>
  </ThemedComposite>
</template>
<script>
import { createComponent } from './ComponentFactory';
import LeafComponent from './LeafComponent.vue';
import CompositeComponent from './CompositeComponent.vue';
export default {
  components: {
    LeafComponent,
    CompositeComponent,
    ThemedLeaf: createComponent('leaf', { class: 'theme-dark' }),
    ThemedComposite: createComponent('composite', { class: 'theme-dark' }),
  },
};
</script>