第3部分:组件结构 - 构建可重复使用和可维护的组件在React中!
#javascript #react #bestpractice #componentstructure

欢迎来到我们的第3部分“ 2023年的最佳实践” 系列!在这一部分中,我们将探讨组件结构的重要性及其对创建高度重复使用,模块化且易于维护的组件的贡献。

React中的可重复使用和可维护的组件不仅与编写代码有关;这是关于采用最佳实践并遵循合理的建筑原则。

通过仔细构造我们的组件,遵守单一责任原则,并拥抱 Atomic Design等概念代码更模块化易于测试更简单地维护

这种方法导致更有效的开发过程,并最终导致高质量,可扩展的反应应用。

让我们考虑一个示例,其中我们在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)

该原则指出,类或组件应具有单一责任更改的单一理由。通过将组件集中在特定任务上,您可以改善代码可读性可维护性可重复使用性

它将复杂的功能分解为较小集中的零件易于理解,测试和维护。

它鼓励组件具有 clear 特定责任,增强了其可重复性和可维护性。

它有助于避免紧密耦合组件,使它们专注于特定任务。

让我们分解整体,

  • todoInput :将输入处理逻辑提取到单独的useTodoInput自定义钩和组件TodoInput中。

负责处理用户输入并添加新的Todos。

  • todolist :将待办事项清单处理逻辑提取到单独的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代码,“添加todo”按钮,然后将todo列表到单独的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

这种重构方法通过向每个组件分配单个责任,将自定义挂钩用于状态和事件处理,并将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元素,例如按钮,输入或图标。

他们有一个责任,专注于视觉外观和基本功能。

  • 分子:分子是原子的组合,共同创建更复杂的UI元素。

他们的责任水平略高,代表一组相关的原子。

  • 生物:生物由分子和原子组成,代表用户界面的更大且更具独立的部分。

它们具有更复杂的行为,可能包括状态管理和互动逻辑。

  • 模板:模板是有机体的特定布置,可为页面或部分提供基本结构。

他们定义了UI的整体布局和组成。

  • 页面:页面是模板中填充真实数据的实例,创建了实际内容供用户与之互动。

让我们以同一todo应用程序为例。我将使用原子设计模式进行高级代码设计:

原子

原子包含按钮输入

// ✅ 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.jsxInput.jsx)和CSS文件(Button.cssInput.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>
  );
};

它们由分子和/或 Atoms 组成,并具有自己的JSX(TodoForm.jsx,Todolist.jsx),自定义钩(USETODODOFORM.JS)和<<强> CSS 文件。

模板

这些模板包含提供页面或布局整体结构的组件。在这种情况下, todo 模板负责渲染 todoform todolist components。

// ✅ 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)和Custom Hook(useTodo.js)和CSS文件(Todo.css)。

页面

代表应用程序中特定页面的页面目录组件。在此示例中,有一个主页组件,它是TODO应用的主要入口。

// ✅ Pages

// HomePage.js
const HomePage = () => {
  return (
    <div className="home-page">
      <TodoApp />
    </div>
  );
};

此示例演示了如何使用原子设计模式来构建TODO App Codebase。每个组件都负责一个问题,并且可以轻松地重复使用并组成以构建完整的todo应用程序。

最后的想法

设计您的React应用程序时,必须避免将多个职责分配给单个组件。以下是一些实用策略,可以帮助您实现更清洁,更可维护的代码库:

1。确定明确的责任:清楚地定义了每个组件的目的。将复杂的功能分解为具有明确责任的较小,集中的组件。

2。关注点的分离:通过根据其功能将应用程序分为不同的组件,将其分开。每个组件应具有特定的角色并承担单一责任。

3。组成部分:与其构建处理多个任务的大型组件,不如结合较小的可重复使用的组件来组成您的UI。这促进了可重复性和模块化。

4。单任务函数:将组件中的复杂逻辑提取到单独的功能或实用程序模块中。通过将特定功能封装在单独的功能中,您可以将组件集中在渲染和与UI相关的任务上。

5。遵循扎实的原则:遵守可靠的原则,例如单一责任原则(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应用程序的可读性,可维护性和可扩展性。

一个定义明确的层次结构可帮助开发人员浏览代码库,了解组件关系并有效地修改。

请继续关注我未来的博客文章中有关构建高质量反应应用程序的更多提示和技巧!

快乐的编码!