组件结构 - 在React中构建可重用和可维护的组件
在 React 中构建可重用和可维护的组件不仅仅是编写代码;还需要编写代码。它涉及采用最佳实践并遵循合理的架构原则。
通过仔细构建我们的组件,遵守单一职责原则,并采用原子设计和组件组合等概念,我们可以创建更加模块化、更易于测试和更易于维护的代码。
这种方法可以实现更高效的开发过程,并最终产生高质量、可扩展的 React 应用程序。
(更|多优质内|容:java567 点 c0m)
让我们考虑一个在 React 中实现的 Todo 应用程序的示例。
// ❌ Bad code with multiple responsibilities
import React, { useState } from 'react';
const TodoApp = () => {
// Handling state ❌
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
// Handle input change ❌
const handleInputChange = (e) => {
setNewTodo(e.target.value);
};
// Handle todo logic ❌
const handleAddTodo = () => {
if (newTodo.trim() !== '') {
const updatedTodos = [...todos, newTodo];
setTodos(updatedTodos);
setNewTodo('');
}
};
const handleDeleteTodo = (index) => {
const updatedTodos = todos.filter((_, i) => i !== index);
setTodos(updatedTodos);
};
const handleCompleteTodo = (index) => {
const updatedTodos = todos.map((todo, i) => {
if (i === index) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
setTodos(updatedTodos);
};
// ❌ It doesn't provide a clear separation of smaller reusable components.
return (
<div>
<h1>Todo App</h1>
<input type="text"
value={newTodo} onChange={handleInputChange} />
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span>
<button onClick={() => handleDeleteTodo(index)}>Delete</button>
<button onClick={() => handleCompleteTodo(index)}>
{todo.completed ? 'Mark Incomplete' : 'Mark Complete'}
</button>
</li>
))}
</ul>
</div>
);
};
上面的代码库包含一个组件,它处理从呈现 UI到处理数据和状态管理的所有事务。这种单一的方法导致缺乏关注点分离,并且违反了SRP和原子设计原则。
为了改进代码,我们可以遵循SRP和原子设计原则:
单一职责原则(SRP)
该原则指出,类或组件应该有单一的职责或单一的更改原因。通过使组件专注于特定任务,您可以提高代码的可读性、可维护性和可重用性。
它促进将复杂的功能分解为更小、更集中的部分,更容易理解、测试和维护。
它鼓励组件有明确和具体的职责,增强其可重用性和可维护性。
它通过让组件专注于特定任务来帮助避免紧密耦合的组件。
让我们分解一下整体,
TodoInput:将输入处理逻辑提取到单独的useTodoInput自定义挂钩和组件中TodoInput。
负责处理用户输入并添加新的待办事项。
TodoList:将待办事项列表处理逻辑提取到单独的useTodoList自定义挂钩和组件中TodoList。
负责呈现待办事项列表。
TodoItem:将各个待办事项的渲染逻辑移至单独的TodoItem组件中。
负责渲染单个待办事项。
通过将状态和事件处理逻辑分离到自定义挂钩或组件中,我们确保每个组件都有以下单一职责。
待办事项输入
useTodoInput自定义挂钩可以使用 useState 挂钩管理输入状态并处理输入更改事件
使用TodoInput.js
// ✅ Responsible for manage state and UI events
import { useState } from "react";
const useTodoInput = (onAddTodo) => {
const [inputValue, setInputValue] = useState("");
const [disabled, setDisabled] = useState(true);
const handleSubmit = (e) => {
e.preventDefault();
onAddTodo(inputValue);
clearInput();
};
const handleInputChange = (e) => {
const value = e.target.value;
setInputValue(value);
setDisabled(value.trim() === "");
};
const clearInput = () => {
setInputValue("");
setDisabled(true);
};
return {
disabled,
inputValue,
handleInputChange,
handleSubmit
};
};
export { useTodoInput };
通过使用自定义钩子,我们可以以可重用和模块化的方式封装状态和事件处理逻辑,从而提高代码的可重用性和可维护性。
TodoInput.jsx
将与输入字段、“添加待办事项”按钮和待办事项列表相关的 JSX 代码移至单独的 JSX 文件中。
// TodoInput.jsx
// ✅ Responsible for rendering TodoInput UI
const TodoInput = ({ onAddTodo }) => {
const {
disabled,
inputValue,
handleInputChange,
handleSubmit
} = useTodoInput(onAddTodo);
return (
<form className="todo-input" onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Add a todo"
/>
<button
className={`add-button ${disabled ? "disabled" : ""}`}
disabled={disabled}
type="submit"
>
Add
</button>
</form>
);
};
通过将 JSX 代码分成单独的文件,我们可以提高代码的组织性和可读性,从而更容易维护和理解组件结构。
像这样我们需要拆分TodoItem和TodoList。
这种重构方法通过为每个组件分配单一职责来遵守 SRP,利用自定义挂钩进行状态和事件处理,并将 JSX 代码分离为可重用的组件,从而提高 React 应用程序的模块化性和可维护性。
最后,组件结构如下所示,
// ✅ Component Stucture
components/
├── todo-input/
│ ├── TodoInput.jsx
│ ├── useTodoInput.js
│ └── TodoInput.css
├── todo-item/
│ ├── TodoItem.jsx
│ └── TodoItem.css
├── todo-list/
│ ├── TodoList.jsx
│ ├── useTodoList.js
│ └── TodoList.css
└── ...
您可以在codesandbox 中查看整个代码库。
我们可以使用原子设计原则进一步重构这个代码库。
原子设计原理
原子设计是一种根据组件的抽象级别和复杂性以分层方式设计和组织组件的方法。
它将组件分为五个级别:原子、分子、有机体、模板和页面,每个级别都有特定的职责。
原子:在最低级别,原子代表最小和最基本的 UI 元素,例如按钮、输入或图标。
他们有单一的职责,专注于其视觉外观和基本功能。
分子:分子是原子的组合,它们共同作用以创建更复杂的 UI 元素。
它们的责任级别稍高,代表一组相关的原子。
有机体:有机体由分子和原子组成,代表用户界面更大、更独立的部分。
它们具有更复杂的行为,可能包括状态管理和交互逻辑。
模板:模板是有机体的特定排列,为页面或部分提供基本结构。
它们定义了 UI 的整体布局和组成。
页面:页面是用真实数据填充模板的实例,创建供用户交互的实际内容。
让我们以同一个待办事项应用程序为例。我将使用原子设计模式给出高级代码设计:
原子
Atoms 包含小型、可重用的 UI 组件,例如Button和Input。
// ✅ Atoms
// Button.jsx
const Button = ({ onClick, children }) => {
return (
<button className="button" onClick={onClick}>
{children}
</button>
);
};
//Input.jsx
const Input = ({ value, onChange }) => {
return (
<input className="input" type="text" value={value} onChange={onChange} />
);
};
每个原子都有自己的 JavaScript 文件 ( Button.jsx, Input.jsx) 和 CSS 文件 ( Button.css, Input.css)。
分子
分子目录包含形成更复杂组件的原子组合 (Button.jsx),例如 TodoItem组件。
// ✅ Molecules
// TodoItem.jsx
const TodoItem = ({ todo, onDelete, onComplete }) => {
return (
<li className="todo-item">
<span className={todo.completed ? 'completed' : ''}>{todo.text}</span>
<Button onClick={onDelete}>Delete</button>
<Button onClick={onComplete}>
{todo.completed ? 'Mark Incomplete' : 'Mark Complete'}
</Button>
</li>
);
};
它有自己的 JavaScript 文件 ( TodoItem.js ) 和 CSS 文件 ( TodoItem.css )。
生物体
有机体目录包含更大、功能更丰富的组件,例如TodoForm和TodoList组件。
// ✅ Organisms
// TodoForm.jsx
const TodoForm = ({ onAddTodo }) => {
const {inputChange, addTodo} = useTodoForm();
return (
<div className="todo-form">
<Input value={newTodo} onChange={inputChange} />
<Button onClick={addTodo}>Add Todo</Button>
</div>
);
};
// TodoList.jsx
const TodoList = ({ todos, onDeleteTodo, onCompleteTodo }) => {
return (
<ul className="todo-list">
{todos.map((todo, index) => (
<TodoItem
key={index}
todo={todo}
onDelete={() => onDeleteTodo(index)}
onComplete={() => onCompleteTodo(index)}
/>
))}
</ul>
);
};
它们由分子和/或原子组成,并有自己的 JSX(TodoForm.jsx、TodoList.jsx)、自定义 Hooks(useTodoForm.js)和CSS文件。
模板
模板包含提供页面或布局整体结构的组件。在这种情况下,Todo模板负责呈现TodoForm和TodoList组件。
// ✅ Templates
// Todo.jsx
const Todo = () => {
const {
todos,
addTodo,
deleteTodo,
completeTodo
} = useTodo();
return (
<div className="todo-app">
<h1>Todo App</h1>
<TodoForm onAddTodo={addTodo} />
<TodoList
todos={todos}
onDeleteTodo={deleteTodo}
onCompleteTodo={completeTodo}
/>
</div>
);
};
它有自己的 JSX 文件 ( Todo.jsx) 和自定义 Hook ( useTodo.js) 以及 CSS 文件 ( Todo.css)。
页数
代表应用程序中特定页面的页面目录组件。在此示例中,有一个HomePage组件充当 Todo 应用程序的主入口点。
// ✅ Pages
// HomePage.js
const HomePage = () => {
return (
<div className="home-page">
<TodoApp />
</div>
);
};
此示例演示了如何使用原子设计模式构建 Todo 应用程序代码库。每个组件负责一个关注点,并且可以轻松地重用和组合它们以构建完整的 Todo 应用程序。
最后的想法
设计 React 应用程序时,必须避免将多个职责分配给单个组件。以下是一些实用策略,可帮助您实现更清晰、更易于维护的代码库:
1. 明确职责:明确定义每个组件的目的。将复杂的功能分解为更小、更集中的组件,并具有明确的职责。
2. 关注点分离:根据应用程序的功能将应用程序划分为不同的组件来分离关注点。每个组件都应该具有特定的角色并处理单一的职责。
3. 组件组合:不要构建处理多个任务的大型组件,而是通过组合较小的、可重用的组件来组合您的 UI。这促进了可重用性和模块化。
4. 单任务功能:将复杂的逻辑从组件中提取到单独的功能或实用模块中。通过将特定功能封装在单独的函数中,您可以使组件专注于渲染和 UI 相关任务。
5. 遵循 SOLID 原则:遵守 SOLID 原则,例如单一职责原则 (SRP),该原则规定组件应该只有一个更改理由。这一原则可帮助您设计专注、可维护且更易于测试的组件。
6. 使用自定义钩子:将通用逻辑提取到可以跨组件共享的自定义钩子中。这允许您重用逻辑,而不会给各个组件带来不必要的复杂性。
7. 模块化架构:使用模块化架构组织代码库,例如基于功能的文件夹结构。这种方法促进了关注点的分离,并有助于使组件专注于其特定的职责。
通过有意识地根据这些实践来设计 React 应用程序,您可以避免向组件分配多重职责。这会带来更干净、更易于维护的代码,更容易理解、测试和扩展。
奖励 - 组件层次结构
通常建议遵循特定的组件层次结构,以保持代码库的一致性和可读性。
// ✅ Component Hierarchy
// External dependencies
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
// Internal dependencies
import { TodoItem } from './TodoItem';
import { TodoUtils } from '../utils';
import { useTodo } from '../hooks';
import { withTimer } from '../hoc';
import { TodoType } from '../enums';
// Stylesheets
import './Component.css';
import '../styles/common.css';
// Assets
import todoImage from '../assets/todoImage.png';
const Todo = () => {
// State logic
const [todos, setTodos] = useState([]);
// Ref
const inputRef = useRef(null);
// Variable
const title = 'Todo List';
// Custom hook
const {addTodo} = useTodo();
// Higher-order component
const timer =
withTimer(TodoItem);
// Component lifecycle methods (useEffect)
useEffect(() => {
//...
}, []);
// Component render
return (
<div>
{/* Component JSX */}
</div>
);
}
Todo.propTypes = {
// Prop types declaration
};
export { Todo };
通过以一致且有组织的方式构建组件层次结构,您可以提高 React 应用程序的可读性、可维护性和可扩展性。
定义良好的层次结构可帮助开发人员浏览代码库、理解组件关系并高效地进行修改。
请继续关注我未来的博客文章中有关构建高质量 React 应用程序的更多提示和技巧!
快乐编码!😊👩💻👨💻