TS实现组件react | 青训营笔记

106 阅读9分钟

1. UI 编程痛点

UI 编程的痛点通常集中在以下几个方面:

a. 组件之间的通信

  • 父子组件传值:React 通过 props 在父组件和子组件之间传递数据,但当组件层级较深时,父子组件传递数据变得冗长且不易管理。
  • 跨层级或兄弟组件通信:通过 props 无法直接跨越多个组件层级传递数据,因此需要使用 状态提升上下文(Context) 等技术,这可能导致组件间的耦合增加。

b. 状态管理

  • 全局状态和局部状态的平衡:当应用变得复杂时,状态管理变得困难。React 允许使用 useStateuseReducer 管理组件状态,但当多个组件需要共享同一状态时,需要使用 ReduxContext API。如何选择合适的状态管理工具,并合理拆分状态,避免过度使用全局状态或过度嵌套的组件结构,是一个常见的痛点。

c. 性能优化

  • 组件重渲染:当状态或 props 更新时,React 会触发组件的重新渲染,过度渲染会影响性能。尤其是在大型应用中,频繁的渲染可能导致页面卡顿。
    • 使用 React.memouseMemouseCallback 来避免不必要的渲染。
    • 使用 Lazy loadingSuspense 进行懒加载,避免初次加载时过多的组件渲染。

2. 响应式与转换式

在现代 UI 开发中,响应式编程转换式编程是两种常见的思想。

a. 响应式编程(Reactive Programming)

响应式编程是通过对数据的变化进行监听和反应来创建动态 UI,数据源和 UI 之间的依赖关系是自动维护的。在 React 中,组件的渲染依赖于状态(state)和属性(props),这就是响应式的体现。

React 与响应式编程

  • React 的数据流遵循 单向数据流:状态和 props 的变化会自动触发 UI 更新。React 会自动根据 state 的变化重新渲染组件。
  • 通过 Hooks(如 useStateuseEffect)和 Context API,React 更加便于管理响应式数据流。

b. 转换式编程(Declarative Programming)

转换式编程指的是通过声明如何做,而不是具体去做某件事情。在 UI 编程中,React 鼓励使用声明式编程,通过 JSX 语法描述 UI,而非操作 DOM 元素。

React 与声明式编程

  • 使用 JSX 语法描述 UI,React 会根据状态和 props 自动处理 DOM 更新,不需要开发者直接操作 DOM。
  • 例如,使用 useState 来声明一个计数器的状态,React 会自动处理当状态变化时如何更新 UI。

3. React 设计与实现 - 组件化

React 推崇 组件化的设计理念,即将 UI 分解成多个小的、可复用的组件。每个组件有自己的状态、生命周期和逻辑,可以独立开发、测试和维护。

a. 组件设计原则

  1. 单一职责原则:每个组件只做一件事,确保组件职责明确。
  2. 高内聚低耦合:组件之间的依赖尽量减少,使得组件更加独立。
  3. 复用性:组件应该设计为可以在不同场景下复用,避免重复代码。
  4. 可组合性:组件应该能够和其他组件组合,形成更复杂的界面。

b. 容器组件与展示组件

  • 容器组件(Container Components)负责管理应用的状态、处理业务逻辑、并将数据传递给子组件。它们通常不关注 UI 细节。
  • 展示组件(Presentational Components)专注于 UI 渲染,接收通过 props 传递的数据,负责展示界面,不包含业务逻辑。

这种分离的设计方式有助于提升组件的复用性和可维护性。

4. React 设计与实现 – 状态归属问题

在 React 中,状态(state)的管理是设计中的一个核心问题。状态决定了组件的 UI 和行为,合理的状态管理是构建高效应用的基础。

a. 状态归属的挑战

  1. 组件内部状态:有些状态是组件内部的,只有该组件需要使用的状态(例如,表单输入、UI 切换状态等)。这些状态应该保存在组件内部。
  2. 状态提升:当多个组件需要共享某个状态时,需要将状态提升到它们的最近共同父组件中。
  3. 全局状态管理:当应用中有多个组件跨越多个层级需要共享状态时,使用 ReduxContext APIRecoil 等工具管理全局状态。

b. 如何决策状态的归属

  • 如果某个状态只对一个组件有意义,状态应该保存在该组件内部。
  • 如果多个子组件需要访问同一状态,将状态提升到它们的共同父组件。
  • 对于应用级别的共享状态,考虑使用 ReduxMobXContext API

c. React 中的状态管理模式

  • useStateuseReducer:适用于局部状态管理。useReducer 适用于复杂状态逻辑的场景。
  • Redux / Recoil / Zustand:适用于跨组件、跨页面的全局状态管理。

5. React 设计与实现 – 生命周期

React 的组件生命周期是指组件从创建到销毁的过程,生命周期方法允许你在不同的阶段插入自定义逻辑。

a. 类组件生命周期

在 React 的类组件中,生命周期方法分为三类:

  1. 挂载阶段(Component Mounting):
    • constructor():构造函数,用于初始化状态和绑定方法。
    • componentDidMount():组件挂载后调用,通常用于获取数据。
  2. 更新阶段(Component Updating):
    • shouldComponentUpdate():判断是否需要重新渲染组件。
    • getSnapshotBeforeUpdate():在更新之前获取 DOM 快照。
    • componentDidUpdate():组件更新后调用,用于处理副作用(如重新获取数据)。
  3. 卸载阶段(Component Unmounting):
    • componentWillUnmount():组件卸载前调用,用于清理副作用(如清除定时器、取消订阅等)。

b. 函数组件生命周期(通过 Hooks)

函数组件没有传统的生命周期方法,但通过 Hooks,React 提供了类似的功能:

  • useEffect:替代了类组件的 componentDidMountcomponentDidUpdatecomponentWillUnmount,通过控制 useEffect 的依赖项,可以模拟组件生命周期的各种阶段。

    useEffect(() => {
      // 组件挂载时执行
      return () => {
        // 组件卸载时清理副作用
      };
    }, []); // 依赖数组为空,表示仅在挂载和卸载时执行
    
  • useLayoutEffect:与 useEffect 类似,但它会在 DOM 更新后立即同步执行。适用于需要读取 DOM 布局或进行布局调整的情况。


在 TypeScript 中,组件通常指的是通过 React 或其他框架构建的可重用的 UI 单元。使用 TypeScript 来编写 React 组件能够提供类型安全,帮助开发者在编写代码时避免类型错误,增强代码的可维护性和可读性。

下面是一个基于 React 和 TypeScript 的组件实现示例,涵盖了函数式组件(Functional Component)和类组件(Class Component)的基本结构和类型声明。

1. React 函数组件 (Functional Component)

在 React 中,函数组件是最常见的组件形式。使用 TypeScript,可以为组件的 props(属性)提供类型注解。

示例:基本的函数组件

import React from 'react';

// 定义 props 的类型
interface MyComponentProps {
  title: string;
  isActive: boolean;
  onClick: () => void;
}

// 函数组件
const MyComponent: React.FC<MyComponentProps> = ({ title, isActive, onClick }) => {
  return (
    <div>
      <h1>{title}</h1>
      <p>{isActive ? 'Active' : 'Inactive'}</p>
      <button onClick={onClick}>Click Me</button>
    </div>
  );
};

export default MyComponent;

解释:

  • interface MyComponentProps:定义了该组件接收的 props 类型。它包含了 title(字符串类型)、isActive(布尔类型)和 onClick(一个函数类型,表示点击时的事件处理函数)。
  • React.FCReact.FC(Functional Component 的缩写)是 React 提供的一个泛型类型,它为组件类型提供了内置的类型推断支持,能够自动推断 children 的类型。
  • { title, isActive, onClick }:组件通过解构的方式接收 props,并在 JSX 中使用。

2. React 类组件 (Class Component)

类组件是 React 中较早的组件定义方式,虽然函数组件(结合 hooks)已成为主流,但类组件仍然在一些代码库中广泛使用。

示例:基本的类组件

import React, { Component } from 'react';

// 定义 props 和 state 的类型
interface MyComponentProps {
  title: string;
  isActive: boolean;
}

interface MyComponentState {
  count: number;
}

class MyComponent extends Component<MyComponentProps, MyComponentState> {
  constructor(props: MyComponentProps) {
    super(props);
    this.state = {
      count: 0
    };
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    const { title, isActive } = this.props;
    const { count } = this.state;

    return (
      <div>
        <h1>{title}</h1>
        <p>{isActive ? 'Active' : 'Inactive'}</p>
        <button onClick={this.handleClick}>Increment</button>
        <p>Count: {count}</p>
      </div>
    );
  }
}

export default MyComponent;

解释:

  • MyComponentPropsMyComponentState:分别定义了组件的 propsstate 类型。MyComponentProps 包含组件所需的属性(titleisActive),MyComponentState 定义了组件的状态(count)。
  • constructor(props: MyComponentProps):类组件的构造函数,用于初始化组件的状态。通过 super(props) 调用父类的构造函数。
  • render()render() 方法是类组件中必须实现的,用于渲染组件的 UI。

3. 使用 TypeScript 和 React Hooks

在现代 React 开发中,函数组件结合 hooks(如 useState, useEffect)已经成为常用的开发模式。下面是一个使用 useStateuseEffect hooks 的 TypeScript 组件示例。

示例:带有 Hooks 的函数组件

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

// 定义 props 类型
interface TimerProps {
  initialTime: number;
}

const Timer: React.FC<TimerProps> = ({ initialTime }) => {
  const [time, setTime] = useState<number>(initialTime);

  useEffect(() => {
    const timer = setInterval(() => {
      setTime((prevTime) => prevTime - 1);
    }, 1000);

    return () => clearInterval(timer); // 清除定时器
  }, []);

  return (
    <div>
      <h1>Time Remaining: {time}s</h1>
      {time <= 0 && <p>Time's up!</p>}
    </div>
  );
};

export default Timer;

解释:

  • useState<number>(initialTime):使用 useState hook 来创建组件的状态变量 time,并设置初始值为 initialTimeuseState 的泛型参数指定了状态的类型,这里是 number 类型。
  • useEffectuseEffect hook 用于执行副作用操作(如启动定时器)。在这里,我们使用 setInterval 每秒钟更新一次时间。当组件卸载时,清除定时器以避免内存泄漏。

4. 类型推导与默认值

TypeScript 在 React 中还支持 类型推导默认值,让组件的使用更加灵活。例如,props 可以为可选项或提供默认值:

interface ButtonProps {
  label: string;
  color?: string; // 可选属性
}

const Button: React.FC<ButtonProps> = ({ label, color = 'blue' }) => {
  return <button style={{ backgroundColor: color }}>{label}</button>;
};

解释:

  • color?: stringcolor 属性是可选的(使用 ? 标记),表示如果没有传入 color,则会使用默认的 blue
  • color = 'blue':在函数组件的参数解构中,我们给 color 设置了默认值 'blue'

5. Props 和 State 的复杂类型

有时组件需要接收更复杂的 props,比如数组、对象或函数等类型。TypeScript 可以帮助我们精确地定义这些类型。

示例:接收数组和函数作为 Props

interface ListProps {
  items: string[];
  renderItem: (item: string) => JSX.Element;
}

const List: React.FC<ListProps> = ({ items, renderItem }) => {
  return (
    <div>
      {items.map((item, index) => (
        <div key={index}>{renderItem(item)}</div>
      ))}
    </div>
  );
};

export default List;

解释:

  • items: string[]items 是一个字符串数组,代表要渲染的列表项。
  • renderItem: (item: string) => JSX.ElementrenderItem 是一个函数,接收一个字符串作为参数,返回一个 JSX 元素。