拆分與重構 React Component:優化 Projects 頁面組件結構
Hugo ChunHo Lin (1chooo)
Day ONE ⚡️
在我的網頁中 1chooo.com 有一個展示我所有作品集的頁面 – Hugo’s Portfolio ,過去我把所有的功能都寫在同一個 component – Projects 之中,但是我發現這頁面中的有些元素我會重複使用,因此就造成了 copy-paste programming 的情況,這樣會讓程式碼變得雜亂,也更不容易維護,因此我決定把這個 component 劃分成更小的 components,並且以這篇文章記錄我的修改過程。
首先我們可以先透過頁面的元素來分析我們該如何區分 component
我們可以觀察到把這個 component 中的元素分成幾個部分:
- Filter: 可以用來選擇要顯示的作品類別
- Select: 一個下拉式選單,也可以用來選擇要顯示的作品類別,是在手機版時的顯示樣貌
- ProjectList: 用來顯示所有的作品
因此我們可以往這個方向去修改,我們把原本的 Projects
component 分成三個 components,分別是 FilterList
, FilterSelectBox
, ProjectItem
,這樣我們就可以把這些 components 獨立開來,並且可以更容易地維護。
改寫好的 Source Code 如下:
filter-list
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React from 'react'; import { projectTags } from '@/config/portfolio'; import { handleItemClick } from '@/utils/filter-utils';
interface FilterListProps { selectedValue: string; setSelectedValue: React.Dispatch<React.SetStateAction<string>>; }
const FilterList: React.FC<FilterListProps> = ({ selectedValue, setSelectedValue }) => ( <ul className="filter-list"> {projectTags.map((tag) => ( <li className="filter-item" key={tag}> <button className={`filter-btn ${selectedValue === tag ? 'active' : ''}`} onClick={() => handleItemClick(tag, setSelectedValue)} > {tag} </button> </li> ))} </ul> );
export default FilterList;
|
filter-select-box
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import React from 'react'; import { MdExpandMore } from "react-icons/md"; import { projectTags } from '@/config/portfolio'; import { handleItemClick } from '@/utils/filter-utils';
interface FilterSelectBoxProps { selectedValue: string; isSelectActive: boolean; setIsSelectActive: React.Dispatch<React.SetStateAction<boolean>>; setSelectedValue: React.Dispatch<React.SetStateAction<string>>; }
const FilterSelectBox: React.FC<FilterSelectBoxProps> = ({ selectedValue, isSelectActive, setIsSelectActive, setSelectedValue, }) => ( <div className="filter-select-box"> <button className={`filter-select ${isSelectActive ? 'active' : ''}`} onClick={() => setIsSelectActive(!isSelectActive)} > <div className="select-value"> {selectedValue || 'Select category'} </div> <div className="select-icon"> <MdExpandMore /> </div> </button> {isSelectActive && ( <ul className="select-list"> {projectTags.map((tag: string) => ( <li className="select-item" key={tag}> <button onClick={() => { handleItemClick(tag, setSelectedValue); setIsSelectActive(false); }} > {tag} </button> </li> ))} </ul> )} </div> );
export default FilterSelectBox;
|
project-item
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import React from 'react'; import { LuEye } from "react-icons/lu"; import Image from 'next/image'; import { Project } from '@/config/portfolio';
interface ProjectItemProps { project: Project; }
const ProjectItem: React.FC<ProjectItemProps> = ({ project }) => ( <li className="project-item active" data-filter-item data-category={project.category.toLowerCase()} > <a href={project.link} target="_blank" rel="noopener noreferrer" > <figure className="project-img"> <div className="project-item-icon-box"> <LuEye /> </div> <Image src={project.imageUrl} alt={project.title} loading="lazy" /> </figure> <h3 className="project-title">{project.title}</h3> <p className="project-category">{project.category}</p> </a> </li> );
export default ProjectItem;
|
最後我們把這些 components 組合到 projects
(可見本次修改 commit ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import React, { useState, useEffect } from 'react'; import { projectsData } from '@/config/portfolio'; import { filterCategory } from '@/utils/filter-utils'; import FilterList from './filter-list'; import FilterSelectBox from './filter-select-box'; import ProjectItem from './project-item';
const Projects: React.FC = () => { const [selectedValue, setSelectedValue] = useState('All'); const [isSelectActive, setIsSelectActive] = useState(false);
useEffect(() => { setSelectedValue('All'); }, []);
const filteredProjects = filterCategory(selectedValue, projectsData);
return ( <section className="projects"> <FilterList selectedValue={selectedValue} setSelectedValue={setSelectedValue} /> <FilterSelectBox selectedValue={selectedValue} isSelectActive={isSelectActive} setIsSelectActive={setIsSelectActive} setSelectedValue={setSelectedValue} /> <ul className="project-list"> {filteredProjects.map((project, index) => ( <ProjectItem project={project} key={index} /> ))} </ul> </section> ); };
export default Projects;
|
這樣我們就把原本的 Projects
component 分成三個 components,這樣我們就可以更容易地維護這個 component 了。如果其他頁面需要類似風格的 Item 可以沿用,也可以繼續擴充 Portfolio 頁面的元素,例如加入頁碼功能,這樣如果 project 過多時,就可以分頁顯示,這樣就可以讓頁面更加的乾淨,也能有更好的 component 實踐!