You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中拆分相似逻辑组件且避免代码重复的正确方式

这个场景太常见啦!既要拆分组件保持职责清晰,又不想重复写代码,其实用「组件复用+插槽/Props控制差异」的思路就完美解决,下面给你具体的实现方案:

核心思路:拆分出公共基础组件,通过插槽/Props注入差异内容

把两个页面共享的所有逻辑(数据请求、搜索/筛选方法、分页处理等)和通用UI(搜索框、结果列表、分页器等)都抽离到一个基础组件里,然后把分类页独有的上下div做成“可插拔”的内容,由上层的搜索页/分类页组件来决定是否注入这些内容。

方案一:用插槽(Slots)处理UI差异(适合Vue、Svelte等支持插槽的框架)

1. 先写公共基础组件(比如BaseSearchResults.vue)

这个组件负责所有共享的逻辑和UI,同时预留好差异部分的插槽:

<template>
  <!-- 分类页顶部的插槽,默认不显示 -->
  <slot name="top-extra"></slot>

  <!-- 共享的搜索区域 -->
  <div class="search-bar">
    <input v-model="searchQuery" @input="handleSearch" placeholder="搜索..." />
    <!-- 其他共享的筛选控件 -->
  </div>

  <!-- 共享的结果列表 -->
  <div class="results-list">
    <div v-for="item in results" :key="item.id" class="result-item">
      <!-- 结果项内容 -->
    </div>
  </div>

  <!-- 共享的分页器 -->
  <div class="pagination">
    <!-- 分页逻辑 -->
  </div>

  <!-- 分类页底部的插槽,默认不显示 -->
  <slot name="bottom-extra"></slot>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const searchQuery = ref('')
const results = ref([])

// 共享的搜索/数据请求方法
const handleSearch = async () => {
  // 请求数据逻辑
}

// 共享的初始化逻辑
onMounted(() => {
  handleSearch()
})

// 其他共享方法(比如分页、排序)
</script>

2. 搜索页组件(SearchPage.vue)

直接复用基础组件,不需要填充插槽,同时保留跳转判断逻辑:

<template>
  <BaseSearchResults />
</template>

<script setup>
import BaseSearchResults from './BaseSearchResults.vue'
import { useRouter } from 'vue-router'
import { onMounted } from 'vue'

const router = useRouter()

onMounted(() => {
  // 如果当前搜索参数匹配某个分类,跳转到分类页
  if (isSearchMatchCategory()) {
    router.push({ name: 'CategoryPage', params: { category: matchedCategory } })
  }
})

// 辅助判断函数
const isSearchMatchCategory = () => {
  // 实现你的匹配逻辑
  return false
}
</script>

3. 分类页组件(CategoryPage.vue)

复用基础组件,同时填充两个插槽来添加独有内容:

<template>
  <BaseSearchResults>
    <template #top-extra>
      <div class="category-top-banner">
        <h2>{{ categoryInfo.name }}</h2>
        <!-- 分类面包屑、描述等独有内容 -->
      </div>
    </template>

    <template #bottom-extra>
      <div class="category-bottom-recommend">
        <h3>相关分类推荐</h3>
        <!-- 推荐列表等独有内容 -->
      </div>
    </template>
  </BaseSearchResults>
</template>

<script setup>
import BaseSearchResults from './BaseSearchResults.vue'
import { ref, onMounted } from 'vue'

const categoryInfo = ref({})

onMounted(() => {
  // 获取分类详情的独有逻辑
  fetchCategoryInfo()
})

const fetchCategoryInfo = async () => {
  // 请求分类数据
}
</script>

方案二:用Props控制差异(适合React、Vue等所有框架)

如果你的框架对插槽支持有限(比如React更常用Props传递),可以用Props来控制是否显示差异内容,或者直接传递差异内容的组件:

1. 公共基础组件(React版BaseSearchResults.jsx)

import { useState, useEffect } from 'react';

const BaseSearchResults = ({ topContent, bottomContent }) => {
  const [searchQuery, setSearchQuery] = useState('');
  const [results, setResults] = useState([]);

  // 共享的搜索方法
  const handleSearch = async () => {
    // 请求数据逻辑
  };

  useEffect(() => {
    handleSearch();
  }, []);

  return (
    <div>
      {/* 渲染传入的顶部内容 */}
      {topContent && <div>{topContent}</div>}

      {/* 共享的搜索区域 */}
      <div className="search-bar">
        <input
          value={searchQuery}
          onChange={(e) => setSearchQuery(e.target.value)}
          placeholder="搜索..."
        />
      </div>

      {/* 共享的结果列表 */}
      <div className="results-list">
        {results.map(item => (
          <div key={item.id} className="result-item">{item.content}</div>
        ))}
      </div>

      {/* 共享的分页器 */}
      <div className="pagination">
        {/* 分页逻辑 */}
      </div>

      {/* 渲染传入的底部内容 */}
      {bottomContent && <div>{bottomContent}</div>}
    </div>
  );
};

export default BaseSearchResults;

2. 搜索页组件(SearchPage.jsx)

import BaseSearchResults from './BaseSearchResults';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

const SearchPage = () => {
  const navigate = useNavigate();

  useEffect(() => {
    // 判断搜索参数是否匹配分类,跳转至分类页
    if (isSearchMatchCategory()) {
      navigate(`/category/${matchedCategory}`);
    }
  }, []);

  const isSearchMatchCategory = () => {
    // 实现你的匹配逻辑
    return false;
  };

  return <BaseSearchResults />;
};

export default SearchPage;

3. 分类页组件(CategoryPage.jsx)

import BaseSearchResults from './BaseSearchResults';
import { useEffect, useState } from 'react';

const CategoryPage = () => {
  const [categoryInfo, setCategoryInfo] = useState(null);

  useEffect(() => {
    fetchCategoryInfo().then(data => setCategoryInfo(data));
  }, []);

  // 定义顶部和底部的独有内容
  const topContent = (
    <div className="category-top-banner">
      <h1>{categoryInfo?.name}</h1>
      {/* 面包屑等内容 */}
    </div>
  );

  const bottomContent = (
    <div className="category-bottom-recommend">
      <h3>相关分类推荐</h3>
      {/* 推荐列表等内容 */}
    </div>
  );

  return <BaseSearchResults topContent={topContent} bottomContent={bottomContent} />;
};

export default CategoryPage;

额外优化:提取共享逻辑到组合式函数/Hook

如果两个页面的逻辑非常复杂,还可以把共享的逻辑(比如数据请求、搜索逻辑、分页逻辑)进一步抽离到**组合式函数(Vue)或者自定义Hook(React)**里,让组件更轻量化:

比如Vue的组合式函数useSearchResults.js:

import { ref, onMounted } from 'vue'

export function useSearchResults() {
  const searchQuery = ref('')
  const results = ref([])

  const handleSearch = async () => {
    // 共享的请求逻辑
  }

  onMounted(() => {
    handleSearch()
  })

  return {
    searchQuery,
    results,
    handleSearch
  }
}

然后在BaseSearchResults里直接使用:

<script setup>
import { useSearchResults } from './useSearchResults'

const { searchQuery, results, handleSearch } = useSearchResults()
</script>

这样不管是基础组件还是其他需要用到搜索逻辑的地方,都能复用这段代码。


内容的提问来源于stack exchange,提问作者Igor Zinchenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 14:57:55