第三部分:组件结构 - 在 React 中构建可重用和可维护的组件!
欢迎来到“2023 年 React 最佳实践”系列的第三部分!在这一部分中,我们将探讨组件结构的重要性,以及它如何帮助创建高度可重用、模块化和易于维护的组件。
在 React 中构建可重用和可维护的组件不仅仅是编写代码;它还关乎采用最佳实践和遵循合理的架构原则。
通过精心构建组件,遵循单一职责原则,并采用原子设计和组件组合等概念,我们可以创建更模块化、更易于测试和更易于维护的代码。
这种方法可以提高开发效率,最终产出高质量、可扩展的 React 应用程序。
让我们来看一个例子,假设我们有一个用 React 实现的待办事项应用程序。
// ❌ 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>
);
};
上述代码库包含一个单一组件,该组件负责处理从渲染用户界面到数据和状态管理的所有操作。这种单体式设计导致缺乏关注点分离,违反了单一职责原则(SRP)和原子设计原则。
为了改进代码,我们可以遵循单一职责原则(SRP)和原子设计原则:
单一职责原则(SRP)
这条原则指出,一个类或组件应该只负责一项职责,或者只有一个变更原因。通过让组件专注于特定任务,可以提高代码的可读性、可维护性和可重用性。
它提倡将复杂的功能分解成更小、更专注的部分,这些部分更容易理解、测试和维护。
它鼓励组件拥有清晰明确的职责,从而提高组件的可重用性和可维护性。
它有助于避免组件之间紧密耦合,使它们专注于特定任务。
让我们来拆解这个庞然大物,
- TodoInput:将输入处理逻辑提取到一个单独的
useTodoInput自定义钩子和组件中TodoInput。
负责处理用户输入并添加新的待办事项。
- 待办事项列表:将待办事项列表处理逻辑提取到一个单独的
useTodoList自定义钩子和组件中TodoList。
负责渲染待办事项列表。
- TodoItem:将单个待办事项的渲染逻辑移到一个单独的
TodoItem组件中。
负责渲染单个待办事项。
通过将状态和事件处理逻辑分离到自定义钩子或组件中,我们确保每个组件都具有以下单一职责。
待办事项输入
useTodoInput自定义钩子可以使用 useState 钩子管理输入状态并处理输入更改事件。
useTodoInput.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 中查看整个代码库。
我们可以使用原子设计原则进一步重构这段代码库。
原子设计原理
原子设计是一种根据组件的抽象程度和复杂性,以分层方式设计和组织组件的方法。
它将组件分为五个级别:原子、分子、有机体、模板和页面,每个级别都有其特定的职责。
- 原子:在最低级别,原子代表最小、最基本的用户界面元素,例如按钮、输入框或图标。
它们只有一个职责,那就是专注于外观和基本功能。
- 分子:分子是由原子组成的组合,它们共同作用以创建更复杂的用户界面元素。
它们承担着稍高的责任,代表着一组相关的原子。
- 有机体:有机体由分子和原子组成,代表用户界面中更大、更独立的组成部分。
它们具有更复杂的行为,可能包括状态管理和交互逻辑。
- 模板:模板是为页面或章节提供基本结构的生物体的特定排列方式。
它们定义了用户界面的整体布局和组成。
- 页面:页面是模板填充真实数据并创建实际内容供用户交互的实例。
我们以同一个待办事项应用为例。我将使用原子设计模式给出一个高层次的代码设计:
原子
Atoms 包含小型、可重用的 UI 组件,例如按钮和输入框。
// ✅ 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)。
分子
molecules 目录包含原子(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 )。
生物
organisms 目录包含更大、功能更丰富的组件,例如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)、自定义钩子(useTodoForm.js)和CSS文件。
模板
模板包含各种组件,这些组件构成页面或布局的整体结构。在本例中,待办事项模板负责渲染待办表单和待办事项列表组件。
// ✅ 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)、自定义钩子(useTodo.js)和 CSS 文件(Todo.css)。
页
页面目录组件代表应用程序中的特定页面。在本例中,有一个HomePage组件,它作为待办事项应用程序的主要入口点。
// ✅ Pages
// HomePage.js
const HomePage = () => {
return (
<div className="home-page">
<TodoApp />
</div>
);
};
本示例演示了如何使用原子设计模式构建待办事项应用程序的代码库。每个组件负责一个单一的功能,并且可以轻松地重用和组合,从而构建完整的待办事项应用程序。
最后想说的话
在设计 React 应用时,务必避免将多个职责分配给单个组件。以下是一些实用策略,可帮助您实现更简洁、更易于维护的代码库:
1. 明确职责:清楚地定义每个组件的用途。将复杂的功能分解成更小、更专注的组件,并赋予它们明确的职责。
2. 关注点分离:将应用程序根据其功能划分为不同的组件,从而实现关注点分离。每个组件都应该有特定的角色,只负责一项职责。
3. 组件组合:与其构建处理多个任务的大型组件,不如通过组合更小、可重用的组件来构建用户界面。这有助于提高可重用性和模块化程度。
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 应用程序的技巧和窍门!
祝你编程愉快!😊👩💻👨💻
文章来源:https://dev.to/sathishskdev/part-3-component-struct-building-reusable-and-maintainable-components-in-react-54n6