React模块与组件详解:从基础概念到实战复用,一篇吃透核心要点

77 阅读15分钟

React 模块与组件:从基础到实践​

欢迎来到 React 学习的第五个模块!在前面的学习中,我们已经对 React 有了初步的认识,今天我们将深入探讨 React 中非常重要的概念 —— 模块与组件。理解模块与组件,是构建高效、可维护的 React 应用的关键。​

一、模块的概念与作用​

(一)模块的概念​

在计算机编程中,模块是指将一个复杂的程序按照一定的规则(如功能、逻辑等)划分成多个独立的、可维护、可复用的单元。每个模块都专注于实现特定的功能,它们可以是一段代码、一个函数库、一个类或者一个文件等。在 React 中,模块通常以文件的形式存在,一个文件就是一个模块,文件中的函数、类、变量等都可以通过导出(export)的方式供其他模块使用。​

(二)模块的作用​

  1. 提高代码的可维护性:将复杂的代码拆分成多个模块,每个模块的功能相对单一,当出现问题时,更容易定位和修复。例如,一个处理用户登录的模块和一个处理数据展示的模块相互独立,修改登录模块的代码不会轻易影响到数据展示模块。​
  1. 促进代码的复用:如果多个地方需要使用相同的功能,只需编写一次模块,然后在需要的地方导入(import)即可。这样避免了重复编写代码,提高了开发效率。比如,我们可以编写一个通用的按钮模块,在多个页面中重复使用。​
  1. 实现代码的模块化管理:模块之间通过明确的接口(导出和导入)进行交互,使得代码结构更加清晰,便于团队协作开发。不同的开发者可以专注于不同的模块开发,最后将各个模块组合起来形成完整的应用。​

二、组件的定义与意义​

(一)组件的定义​

在 React 中,组件是构成用户界面(UI)的基本单元,它可以看作是一个独立的、可复用的 UI 模块。组件可以接收输入(称为 props)并返回需要渲染的 UI 元素(通常是 JSX 代码)。组件可以是简单的按钮、输入框,也可以是复杂的页面布局、数据表格等。​

(二)组件的意义​

  1. 实现 UI 的拆分与组合:将整个页面拆分成多个组件,每个组件负责渲染页面的一部分,然后通过组合这些组件来构建完整的页面。这种方式使得 UI 开发更加灵活和高效。例如,一个电商网站的页面可以拆分为头部导航组件、商品列表组件、底部信息组件等,每个组件独立开发,最后组合在一起。​
  1. 提高 UI 的复用性:和模块类似,组件也可以在多个地方重复使用。如果多个页面都需要使用相同的按钮样式和功能,只需创建一个按钮组件,然后在各个页面中引入该组件即可。这样不仅减少了代码量,还保证了 UI 的一致性。​
  1. 方便状态管理:组件可以拥有自己的状态(state),通过状态的变化来更新 UI。每个组件的状态只在自己内部管理,使得状态的变化更加可控,便于调试和维护。​

三、模块化与组件化的区别​

(一)概念层面​

  • 模块化:主要侧重于将代码按照功能、逻辑等划分成不同的模块,关注的是代码的组织和管理。模块化的目的是提高代码的可维护性、复用性和可扩展性,它不仅仅适用于 React,在其他编程语言和框架中也广泛应用。​
  • 组件化:则是针对 UI 开发而言,将 UI 拆分成多个独立的、可复用的组件,每个组件负责渲染特定的 UI 部分,并处理与该 UI 相关的逻辑和状态。组件化是 React 等前端框架的核心思想之一。​

(二)关注点不同​

  • 模块化更关注代码的结构和组织,例如如何将不同的功能函数、类等组织到不同的文件中,如何处理模块之间的依赖关系等。​
  • 组件化更关注 UI 的结构和交互,例如如何将 UI 拆分成合适的组件,组件之间如何通信,组件如何处理用户输入和状态变化等。​

(三)应用范围不同​

  • 模块化的应用范围更广,包括后端开发、前端开发等各个领域。​
  • 组件化主要应用于前端 UI 开发,尤其是在使用 React、Vue、Angular 等组件化框架时。​

四、组件的两种形式​

在 React 中,组件主要有两种形式:函数式组件和类式组件。​

(一)函数式组件​

函数式组件是最简单的组件形式,它本质上是一个 JavaScript 函数,接收一个名为 props 的对象作为参数,并返回一个 JSX 元素。​

  1. 基本写法​
function Welcome(props) {​
     return <h1>Hello, {props.name}!</h1>;​
}​

在这个例子中,Welcome 是一个函数式组件,它接收 props 中的 name 属性,并在 JSX 中显示出来。​

  1. 特点​
  • 无状态:函数式组件本身没有状态(state),它的 UI 完全由输入的 props 决定。如果组件需要管理自己的状态,就需要使用类式组件或者 React Hook(如 useState)。​
  • 简洁轻便:函数式组件的代码结构简单,没有复杂的生命周期方法和类的语法,易于编写和理解。​
  • 纯函数特性:如果函数式组件只依赖于输入的 props 而不产生任何副作用(如修改外部变量、发送网络请求等),那么它就是一个纯函数。纯函数具有良好的可测试性和确定性,相同的 props 输入一定会得到相同的 UI 输出。​

(二)类式组件​

类式组件是基于 ES6 的 class 语法创建的组件,它继承自 React.Component 类,需要定义一个 render 方法来返回要渲染的 JSX 元素。​

  1. 基本写法​
class Welcome extends React.Component {​
     render() {​
        return <h1>Hello, {this.props.name}!</h1>;​
     }​
}​

在这个例子中,Welcome 是一个类式组件,通过 this.props 来访问传入的 props。​

  1. 特点​
  • 有状态:类式组件可以通过定义 state 来管理组件内部的状态。例如,一个计数器组件可以使用 state 来保存当前的计数数值,当用户点击按钮时,更新 state 并重新渲染 UI。​
  • 生命周期方法:类式组件拥有完整的生命周期方法,如 componentDidMount(组件挂载后调用)、componentDidUpdate(组件更新后调用)、componentWillUnmount(组件卸载前调用)等。这些方法可以让我们在组件的不同阶段执行特定的操作,例如在组件挂载后发送网络请求获取数据。​
  • 功能更强大:类式组件适合处理复杂的逻辑和状态管理,尤其是在 React Hook 出现之前,类式组件是管理状态和生命周期的主要方式。​

五、函数式组件​

(一)接收 props​

函数式组件通过参数接收 props,props 是一个对象,包含了父组件传递给子组件的数据和回调函数等。我们可以通过解构的方式来获取 props 中的具体属性,使代码更加简洁。​

function UserInfo({ name, age, onButtonClick }) {​
      return (​
        <div>​
          <p>Name: {name}</p>
          <p>Age: {age}</p>​
          <button onClick={onButtonClick}>Click me</button>​
        </div>​
      );​
}​

(二)使用 React Hook 管理状态​

在 React 16.8 版本之后,引入了 Hook,使得函数式组件也可以管理状态和使用生命周期功能。最常用的 Hook 是 useState,它可以让函数式组件拥有自己的状态。​

import React, { useState } from 'react';​

// 定义计数器组件,使用函数式组件+useState实现状态管理
function Counter() {​
     // useState(0)初始化状态count为0,返回[状态值, 状态更新函数]
     // 使用数组解构获取count(当前计数)和setCount(更新计数的函数)
     const [count, setCount] = useState(0);​
     return (​
       <div>​
       <p>Count: {count}</p>
       {/* 箭头函数确保setCount中的this指向正确(函数式组件无需担心this绑定) */}
       <button onClick={() => setCount(count + 1)}>Increment</button>​
       </div>​
     );​
}​

在这个例子中,useState 函数返回一个数组,第一个元素是状态值 count,第二个元素是用于更新状态的函数 setCount。当点击按钮时,调用 setCount 函数更新状态,组件会重新渲染。​

(三)优点​

  • 代码简洁:函数式组件的语法简单,没有类的繁琐写法,适合编写简单的、无状态的组件或者只需要简单状态管理的组件。​
  • 性能优势:在某些情况下,函数式组件的性能可能略优于类式组件,因为它避免了类的实例化过程。​
  • 更灵活的组合:Hook 使得函数式组件可以更灵活地组合不同的功能,例如可以自定义 Hook 来复用状态管理逻辑,提高代码的复用性。​

六、类式组件​

(一)初始化 state​

类式组件通过在 constructor 方法中初始化 state。​

class Counter extends React.Component {
  // 构造函数:组件实例化时调用,用于初始化state和绑定方法
  constructor(props) {​
    super(props);​// 必需调用,确保正确继承React.Component的特性
    this.state = {​count: 0​};​
  }​
​
  render() {​
    return (​
      <div>​
        <p>Count: {this.state.count}</p>​
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>​
          Increment​ {/*递增按钮,相当于i++*/}
        </button>​
      </div>​
    );​
  }​
}

在 constructor 中,必须调用 super(props) 来调用父类(React.Component)的构造函数,以便正确初始化组件的 props 和 state。​

(二)更新 state​

通过调用 this.setState 方法来更新 state,setState 方法会触发组件的重新渲染。需要注意的是,setState 是异步的,不能直接通过修改 this.state 来更新状态,否则不会触发重新渲染。​

(三)生命周期方法示例​

class DataFetchComponent extends React.Component {​
  // 类属性初始化state(ES7语法),等价于constructor中初始化​// 生命周期方法:组件挂载到DOM后立即调用(仅客户端渲染时触发)​
  // 常用于发起网络请求、添加事件监听等副作用操作​
  componentDidMount() {​
    // 发送GET请求获取数据(示例API)​
    fetch('https://api.example.com/data')​
      .then(response => response.json()) // 解析响应为JSON格式​
      // 使用setState更新data状态,触发组件重新渲染​
      .then(data => this.setState({ data }));​
  }​
​
  render() {​
    return (​
      <div>​
        <h2>Data List</h2>​
        <ul>​
          {/* 使用map遍历state中的data数组,渲染列表项 */}​
          {/* key属性用于React Diff算法识别列表项,需使用唯一标识 */}​
          {this.state.data.map(item => (​
            <li key={item.id}>{item.name}</li> // 显示每个item的name属性​
          ))}​
        </ul>​
      </div>​
    );​
  }​
}

在这个例子中,componentDidMount 方法在组件挂载到 DOM 后调用,用于发送网络请求获取数据并更新 state,从而渲染数据列表。​

(四)缺点​

  • 代码相对繁琐:类式组件需要编写 class、constructor、render 等方法,代码量比函数式组件多,尤其是在处理简单组件时,显得不够简洁。​
  • this 指向问题:在类式组件中,使用自定义方法时需要注意 this 的指向问题,通常需要使用箭头函数或者绑定 this,否则可能会导致方法中的 this 不是指向组件实例。​

七、组件的封装与复用​

(一)组件的封装​

组件的封装是指将组件的逻辑(包括处理 props、管理 state、生命周期方法等)和 UI 实现封装在一个独立的文件中,使其成为一个可复用的单元。封装组件时,需要注意以下几点:​

  1. 单一职责原则:每个组件应该只负责完成一个相对独立的功能,不要将多个功能混合在一个组件中。例如,一个按钮组件只负责按钮的样式和点击事件处理,而不应该同时处理数据请求等逻辑。​
  1. 合理处理 props 和 state:明确组件的输入(props)和内部状态(state),props 用于接收父组件传递的数据和回调函数,state 用于管理组件内部的状态变化。​
  1. 分离样式:可以将组件的样式单独编写在一个 CSS 文件中,或者使用内联样式、CSS-in-JS 等方式,使组件的样式和逻辑分离,提高组件的可维护性。​

(二)组件的复用​

组件的复用是指在不同的地方(如不同的页面、不同的组件中)使用已经封装好的组件。复用组件的步骤如下:​

  1. 导出组件:在组件文件中使用 export 语句导出组件,例如 export default function MyComponent(props) { ... } 或者 export default class MyComponent extends React.Component { ... }。​
  1. 导入组件:在需要使用该组件的文件中,使用 import 语句导入组件,例如 import MyComponent from './MyComponent';。​
  1. 使用组件:在 JSX 中像使用普通 HTML 标签一样使用组件,例如 ,传递相应的 props。​

(三)示例:封装一个可复用的按钮组件​

Button.js- 可复用的按钮组件​

import React from 'react'; // 引入React,函数式组件需显式引入​// 定义函数式组件Button,接收三个props:​
// text - 按钮显示的文本​
// onClick - 点击按钮时触发的回调函数​
// color - 按钮的背景颜色(字符串格式,如'#4a90e2')​
function Button({ text, onClick, color }) {​
  return (​
    <button​
      onClick={onClick} // 绑定点击事件回调​
      // 使用内联样式设置背景颜色和内边距,color来自props​
      style={{ backgroundColor: color, padding: '8px 16px' }}​
    >​
      {text} {/* 渲染按钮文本 */}​
    </button>​
  );​
}​
​
export default Button; // 导出组件,供其他模块导入使用

App.js-使用Button组件的父组件

import React from 'react'; // 引入React​
import Button from './Button'; // 导入自定义的Button组件​function App() {​
  // 定义按钮点击时的处理函数,弹出提示框​
  const handleClick = () => {​
    alert('Button clicked!'); // 简单的事件处理逻辑​
  };​
​
  return (​
    <div>​
      {/* 使用Button组件,传递不同的props生成不同样式的按钮 */}​
      {/* 第一个按钮:主按钮样式,蓝色背景 */}​
      <Button text="Primary Button" onClick={handleClick} color="#4a90e2" />​
      {/* 第二个按钮:次按钮样式,深灰色背景 */}​
      <Button text="Secondary Button" onClick={handleClick} color="#555" />​
    </div>​
  );​
}​
​
export default App; // 导出根组件App

在这个例子中,Button 组件被封装成一个独立的文件,通过 props 接收按钮的文本、点击事件处理函数和颜色,然后在 App 组件中多次复用该组件,传递不同的 props 来显示不同样式和功能的按钮。​

八、组件设计原则​

(一)单一职责原则(Single Responsibility Principle)​

每个组件应该只负责一项职责,即完成一个特定的功能。如果一个组件承担了过多的职责,会导致组件变得复杂,难以维护和复用。例如,一个组件不应该同时负责数据获取、数据处理和 UI 渲染,而应该将数据获取和处理的逻辑提取到单独的模块或自定义 Hook 中,组件只专注于 UI 渲染。​

(二)高内聚低耦合(High Cohesion, Low Coupling)​

  • 高内聚:组件内部的各个部分应该紧密围绕组件的核心功能,相关的逻辑和代码应该尽量放在同一个组件中,避免将不相关的功能混杂在一起。例如,一个表单组件应该将表单的输入处理、验证逻辑等封装在组件内部,而不是分散到多个地方。​
  • 低耦合:组件之间的依赖关系应该尽可能简单,组件之间通过 props 进行通信,避免直接操作其他组件的内部状态或 DOM 元素。这样可以使组件更加独立,修改一个组件不会轻易影响到其他组件,提高系统的可扩展性和稳定性。​

(三)可维护性(Maintainability)​

组件的代码应该清晰易懂,命名规范,结构合理,便于后续的修改和维护。可以通过添加注释、合理划分代码块、使用有意义的变量和函数名等方式来提高组件的可维护性。​

(四)可扩展性(Extensibility)​

组件应该设计成可以容易地进行扩展和修改,以适应不同的需求变化。例如,通过使用 props 来接收不同的配置参数,使组件可以在不同的场景下灵活使用;或者通过继承、组合等方式来创建组件的变体。​

(五)可测试性(Testability)​

组件应该易于编写单元测试和集成测试,以确保组件的功能正确性。为了提高组件的可测试性,应该尽量将组件的逻辑和 UI 分离,避免在组件中包含复杂的副作用逻辑,或者将副作用逻辑提取到可单独测试的模块中。​

通过遵循以上组件设计原则,我们可以创建出高质量、可复用、易维护的 React 组件,从而提高整个应用的开发效率和质量。​

经典面试题

请说说 “==” 操作符的强制类型转换规则是什么?

    1. 判断是不是相同类型,如果不是,进行类型转换
    1. 转换规则

    • 如果是 nullundefined 比较,return true
    • 如果是 stringnumber 比较,string ----> number 再比较
    • 如果有一方是 boolean,会把 boolean ---> number 再比较
    • 如果一方是 object,一方是 string number symbol,会把 object 转 原始类型"[object Object]"再比较