likes
comments
collection
share

React+TypeScript+Vite:深入探索React类组件的实践和入门技巧

作者站长头像
站长
· 阅读数 23

本文主要针对react初学者,具体为组件基本概念、类组件定义、生命周期以及状态管理这四个方面

1. 什么是组件

组件是构建用户界面的基本单元,它是将界面拆分为独立且可复用的部分。一个组件可以单独定义其结构、样式和行为。

组件的优点具体为以下几点:

  1. 模块化开发:组件化开发可以将复杂的应用程序拆分为小的、独立的模块。每个组件专注于特定的功能和任务,使代码更加清晰、易于理解和维护。组件化开发也促进团队协作,不同的开发者可以同时独立开发不同的组件,最后将它们组合成完整的应用程序。
  2. 可重用性:组件可以被多次使用,减少了重复编写相似代码的工作量。通过定义通用的组件,我们可以在不同的页面和应用中重复使用它们,提高开发效率。组件的重用性还可以保证应用的一致性,确保相同的功能和样式在不同的地方表现一致。
  3. 可维护性:组件化开发使代码更加结构化,易于维护。每个组件都有自己的功能和职责,使得修改和调试变得更加简单。当应用发生变化时,我们只需要关注特定的组件,而不需要修改整个应用的代码。
  4. 灵活性:组件化开发使得应用程序更加灵活和可扩展。我们可以轻松地添加、删除或替换组件,而不会对其他组件产生影响。这种灵活性使得我们可以快速响应需求的变化,使应用具有更好的适应性。

2. 类组件和函数组件

在 React 中,我们可以使用类组件和函数组件来创建组件,无论是类组件还是函数组件,它们都是 React 组件的基本构建块。在实际开发中,可以根据需求和个人偏好选择使用哪种方式来创建组件。通常来说,简单的展示型组件可以使用函数组件(有了hooks以后,也不一定了),而有复杂状态管理和生命周期需求的组件可以使用类组件。

2.1 类组件

import React, { Component } from 'react';

interface MyComponentProps {
  // 定义组件的属性
  name: string;
}

interface MyComponentState {
  // 定义组件的状态
  count: number;
}

class MyComponent extends Component<MyComponentProps, MyComponentState> {
  	//构造函数
    constructor(props: MyComponentProps) {
    super(props);
    // 初始化组件的状态
    this.state = {
      count: 0
    };
  }

  // 组件挂载后调用的生命周期方法
  componentDidMount() {
    console.log('Component mounted');
  }

  // 点击按钮触发的事件处理函数
  handleClick() {
    // 更新组件的状态
    this.setState({ count: this.state.count + 1 });
  }

  // 渲染组件
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <p>Hello, {this.props.name}</p>
        <button onClick={() => this.handleClick()}>Increment</button>
      </div>
    );
  }
}

export default MyComponent;

一个完整的类组件包含构造函数、生命周期、事件处理以及渲染组件(Render())

2.1.1 构造函数(constructor)

构造函数是类的一种特殊方法,在创建类的实例时被调用。主要作用是进行类的初始化操作,包括设置初始状态(state)、绑定方法的上下文、初始化实例变量等。

构造函数是可选的,不定义定义构造函数的话,React 会默认提供一个默认的构造函数。它会调用父类的构造函数,并且不做其他操作,组件的状态初始化可以在组件类的属性(后面会在组件实例的属性里介绍)中进行,like this:

state: MyComponentState = {
    count: 0,
  };

大多数情况下,我们可以不需要显式定义构造函数,除非需要进行一些特定的初始化操作,或者需要绑定事件处理方法的上下文。

2.1.2 生命周期

类组件的生命周期(React 16.3+)包括以下几个阶段:

  1. 挂载阶段(Mounting)
    • constructor():组件初始化构造函数,在组件实例化时调用。
    • static getDerivedStateFromProps():在组件实例化和更新时调用,用于根据新的 props 计算并返回新的 state,替代了老版本的componentWillReceiveProps
    • render():渲染组件的 UI,并返回 React 元素。
    • componentDidMount():组件挂载到 DOM 后立即调用,通常用于执行一些异步操作、添加事件监听器等。
  2. 更新阶段(Updating)
    • static getDerivedStateFromProps():和Mounting阶段是一样的。
    • shouldComponentUpdate():在组件更新之前调用,用于判断是否需要进行更新,默认返回 true,除非特殊需求,一般不设置。
    • render():重新渲染组件的 UI,并返回 React 元素。
    • getSnapshotBeforeUpdate():在 render 之后、更新 DOM 之前调用,可以获取更新前的 DOM 状态,“sanpshot”顾名思义了,就是在更新之前咔擦一下,替代了老版本的componentWillUpdate
    • componentDidUpdate():组件更新后调用,通常用于执行一些额外的操作,如更新后的 DOM 操作、网络请求等。
  3. 卸载阶段(Unmounting)
    • componentWillUnmount():在组件卸载之前调用,用于执行一些清理操作,如取消订阅、清除计时器等。

以上是16.3之后的生命周期,之前的一些旧的被替代或废弃的生命周期方法,以及新增的生命周期方法,我整理了一下:

  1. 废弃的生命周期方法:

  • componentWillMount:组件被挂载到DOM前调用,废弃了❌。可以将逻辑移动到constructorcomponentDidMount中。

  • componentWillReceiveProps:在组件接收到新的props时调用。废弃了❌。可以使用新的static getDerivedStateFromPropscomponentDidUpdate来替代。

  • componentWillUpdate:在组件即将重新渲染前调用。废弃了❌。可以使用新的getSnapshotBeforeUpdatecomponentDidUpdate来替代。

  1. 新增的生命周期方法:

  • static getDerivedStateFromProps:在组件接收到新的props时调用,用于计算并返回新的state。取代了componentWillReceiveProps,应该用于更新状态。
  • getSnapshotBeforeUpdate:在组件即将重新渲染前调用,用于获取DOM更新前的快照信息。可以返回一个值,作为后续componentDidUpdate方法的第三个参数。
  • componentDidCatch:在子组件抛出错误后调用,用于捕获和处理错误。用于错误边界的处理。(很少用)

还想深入学习的伙伴可以看这篇,深入详解React生命周期

2.1.3 事件处理

React提供的事件处理函数:

  • onClick:处理鼠标点击事件的事件处理函数。它用于在用户点击元素时执行相应的逻辑。
  • onChange:处理表单元素的值变化事件,如<input><textarea><select>等。
  • onSubmit:处理表单提交事件,通常用于包装在<form>元素上。
  • onKeyDownonKeyUponKeyPress:处理键盘按键事件。
  • onFocusonBlur:处理元素获得焦点和失去焦点事件。
  • onMouseOveronMouseOut:处理鼠标悬停和离开事件。
  • onMouseMove:处理鼠标移动事件。
  • onContextMenu:处理右键菜单事件。
  • onScroll:处理滚动事件。
  • onTouchStartonTouchMoveonTouchEnd:处理触摸事件。
  • onDragStartonDragonDragEnd:处理拖拽事件。
  • onResize:处理窗口大小调整事件。

绑定事件处理函数(确保在事件处理函数内部,this 关键字指向组件实例)的方法:

  1. 箭头函数绑定:

    class MyComponent extends React.Component {
      handleClick = () => {
        // 处理点击事件的逻辑
      };
    
      render() {
        return (
          <button onClick={this.handleClick}>点击我</button>
        );
      }
    }
    
  2. bind方法绑定

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        // 处理点击事件的逻辑
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>点击我</button>
        );
      }
    }
    

    这是在类的构造函数中使用bind方法将事件处理函数handleClick绑定到组件实例,在这种情况下,事件处理函数handleClick只会在组件实例化时绑定一次,而不会在每次渲染时创建新的函数实例,当然还可以直接在事件处理函数里用:

    class MyComponent extends React.Component {
      handleClick() {
        // 处理点击事件的逻辑
      }
    
      render() {
        return (
          <button onClick={this.handleClick.bind(this)}>点击我</button>
        );
      }
    }
    

    和第一种用→函数有细微区别。

  3. 匿名箭头函数绑定

    class MyComponent extends React.Component {
      handleClick() {
        // 处理点击事件的逻辑
      }
      
      render() {
        return (
          <button onClick={() => this.handleClick()}>点击我</button>
        );
      }
    

    需要注意的是,在以上方法中,除了在构造函数里用bind绑定事件处理函数的方法,其他方法都会在每次渲染时创建新的函数实例。如果在大型应用程序中使用大量事件处理函数,可能会对性能产生影响。所以如果有性能需求,还是在构造函数里绑定比较好。

  4. 还有一种方法就是通过ref来获取DOM元素,然后手动绑定事件处理函数

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.buttonRef = React.createRef();
      }
    
      componentDidMount() {
        // 在组件挂载后,手动绑定点击事件处理函数到DOM元素
        this.buttonRef.current.addEventListener('click', this.handleClick);
      }
    
      componentWillUnmount() {
        // 在组件卸载前,移除点击事件处理函数的绑定
        this.buttonRef.current.removeEventListener('click', this.handleClick);
      }
    
      handleClick = () => {
        // 处理点击事件的逻辑
      };
    
      render() {
        return (
          <button ref={this.buttonRef}>点击我</button>
        );
      }
    }
    

总结一下(需要注意):

React事件并不是直接绑定在 DOM 元素上的。而是通过 React 的事件系统,在最外层的 DOM 元素上添加统一的事件监听器,并根据实际触发的事件来调用相应的事件处理函数(事件委托(event delegation))。

就比如:当你将 handleClick 方法作为 onClick 的值传递给按钮元素时,React 会创建一个点击事件的监听器,并在用户点击按钮时触发该监听器。然后,React 会调用 handleClick 方法来处理该事件。

2.2 函数组件

import React from 'react';

interface Props {
  name: string;
}

const MyComponent: React.FC<Props> = ({ name }) => {
  return (
    <div>
      <h1>Hello, {name}!</h1>
    </div>
  );
};

export default MyComponent;

3. 组件实例的属性

3.1 State

State用于存储和管理组件的内部状态,可以理解为“状态机”,页面的显示都需要根据state属性里的数据来显示(感觉和vue的数据劫持很像,vue不做数据劫持,页面就不会根据数据变化了)

import React, { Component } from 'react';

interface State {
  count: number;
  message: string;
}

class MyComponent extends Component<{}, State> {
  constructor(props: {}) {
    super(props);

    // 初始化组件的状态
    this.state = {
      count: 0, // 计数器
      message: 'Hello', // 消息
    };
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p> {/* 展示计数器 */}
        <p>Message: {this.state.message}</p> {/* 展示消息 */}
      </div>
    );
  }
}

export default MyComponent;

3.2 Props

Props用于接收从父组件传递的数据和配置信息(组件间传值的方法还有其他的,后续会更新)。Props 是只读的,子组件无法直接修改自己的 props。在类组件中,可以通过 this.props 来访问 props 的值。在函数组件中,可以通过函数参数来访问 props 的值。

import React, { Component } from 'react';

interface Props {
  name: string; // 名称属性
}

class ChildComponent extends Component<Props> {
  render() {
    return <p>Hello, {this.props.name}!</p>; {/* 使用传入的名称属性 */}
  }
}

class ParentComponent extends Component {
  render() {
    return <ChildComponent name="Alice" />; {/* 传递名称属性给子组件 */}
  }
}

export default ParentComponent;

3.3 Refs

用于获取对组件或 DOM 元素的引用。它提供了一种直接访问组件实例或 DOM 元素的方式。在类组件中,可以通过 React.createRef() 创建 Ref 对象,并将其分配给类的实例属性。在函数组件中,可以使用 useRef 钩子来创建 Ref 对象。

import React, { Component, RefObject } from 'react';

class MyComponent extends Component {
  private myRef: RefObject<HTMLInputElement>; // 创建一个用于引用输入框的 Ref 对象

  constructor(props: {}) {
    super(props);

    this.myRef = React.createRef<HTMLInputElement>(); // 创建 Ref 对象
  }

  componentDidMount() {
    if (this.myRef.current) {
      this.myRef.current.focus(); // 设置焦点到输入框
    }
  }

  render() {
    return <input type="text" ref={this.myRef} />; {/* 将 Ref 对象赋值给输入框的 ref 属性 */}
  }
}

export default MyComponent;