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
相关产品推荐
相关产品推荐

