React & Typescript:组件的入门实例

本文介绍了React组件从普通函数组件到类组件,再到高阶组件的演化过程,并探讨了在使用TypeScript时如何定义组件类型。文章通过实例详细讲解了React.FC的用法,类组件的实现,以及在高阶组件中遇到的类型问题和解决方案。最后,文章提到了Hooks的使用,并解释了为什么在TypeScript中定义组件时应使用泛型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React 组件的演化

组件复用方式优势劣势状态
类组件(Class发展时间长,接受度广泛只能继承父类作为一种传统开发模式,会长期存在
Mixin可以复制任意对象的任意多个方法,实现组件间的复用组件间相互依赖、耦合,可能产生冲突,不利于维护被官方抛弃
高阶组件(HOC)利用装饰器模式,在不改变组件的基础上,动态地为其添加新的能力嵌套过多调试困难,需要遵循某些约定(不改变原始组件,透传 props 等)能力强大,广泛引用
Hooks替代类组件,多个 Hooks 间互不影响,避免嵌套地狱,开发效率高切换新思维需要成本React 的未来(官方主推)

函数组件

普通函数组件

上一篇 中,我们创建了一个无状态组件(没有状态影响,作为纯静态展示) <Hello />,同时它也是一个函数组件。

import React from "react";
import { Button } from "antd";

interface Greeting {
  name: string;
  firstName: string;
  lastName: string;
}

const Hello = (props: Greeting) => <Button>hello {props.name}</Button>;

Hello.defaultProps = {
  firstName: "",
  lastName: "",
};

export default Hello;

React.FC

在 react 的声明文件中,对函数组件单独定义了一个类型 – React.FC:

type React.FC<P = {}> = React.FunctionComponent<P>

现在使用 React.FC 重新定义一下 <Hello />

const Hello: React.FC<Greeting> = ({ name, firstName, lastName }) => (
  <Button>hello {name}</Button>
);

使用 React.FC 后的区别

FCFunctionComponent 的简写,这个类型定义了默认的 props (如 children)。

const Hello: React.FC<Greeting> = ({ name, firstName, lastName, children }) => (
  <Button>hello {name}</Button>
);

在使用 React.FC后定义 defaultProps 时,默认属性必须是可选的(这和普通函数组件不同):

interface Greeting {
  name: string;
  firstName?: string;
  lastName?: string;
}

const Hello: React.FC<Greeting> = ({ name, firstName, lastName, children }) => (
  <div>
    <Button>hello {name}</Button>
  </div>
);

Hello.defaultProps = {
  firstName: "",
  lastName: "",
};

小结

TypeScript 中,函数组件需要为 props 定义类型。

类组件

Component

类组件需要继承 Components 组件,在 react 的声明文件中,Component 被定义为泛型类:

// P: 属性的类型,默认{}
// S: 状态的类型,默认{}
// SS: snapshot
(alias) class Component<P = {}, S = {}, SS = any>

实现

将上面的函数组件改造成类组件:

// src/componets/demo/HellpClass.tsx
import React, { Component } from "react";
import { Button } from "antd";

interface Greeting {
  name: string;
  firstName: string;
  lastName: string;
}

interface State {
  count: number;
}

class HelloClass extends Component<Greeting, State> {
  // 初始化 state
  state: State = { count: 0 };
  // 默认属性值
  static defaultProps = {
    firstName: "",
    lastName: "",
  };
  render() {
    return <Button>hello {this.props.name}</Button>;
  }
}

export default HelloClass;

通过 setState<HelloClass /> 添加一个点击计数功能。

class HelloClass extends Component<Greeting, State> {
  state: State = { count: 0 };
  static defaultProps = {
    firstName: "",
    lastName: "",
  };
  render() {
    return (
      <>
        <div>您点击了 {this.state.count} 次</div>
        <Button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        >
          hello {this.props.name}
        </Button>
      </>
    );
  }
}

小结

TypeScript 中,类组件需要为 propsstate 定义类型。

高阶组件

我们现在要利用高阶组件包装一下 <HelloClass />,包装后的组件有一个新属性 loading,通过该属性控制被包装组件的 显示/隐藏。

React.ComponentType

指定被包装组件的类型为 React.ComponentType(一种 React 预定义类型),既可以是类组件,也可以是函数组件:

type React.ComponentType<P = {}> = React.ComponentClass<P, any> | React.FunctionComponent<P>

实现

添加 <HelloHOC /> 组件:

import React, { Component } from "react";
import HelloClass from "./HelloClass";

interface Loading {
  loading: boolean;
}

/*
 ** WrapperComponetn: 需要被包装的组件
 */
function HelloHOC<P>(WrapperComponetn: React.ComponentType<P>) {
  // 定义 props 为 P 和 Loading 的交叉类型
  return class extends Component<P & Loading> {
    render() {
      // 解构 props,拆分出 loading
      const { loading, ...props } = this.props;
      // {...props}:属性透传
      return loading ? (
        <div>Loading...</div>
      ) : (
        <WrapperComponetn {...(props as P)} />
      );
    }
  };
}

// 导出经过高阶组件包装后的组件
export default HelloHOC(HelloClass);

有个报错

我们在 index.tsx 中引入这个组件,这时会有一个报错:

import React from "react";
import ReactDOM from "react-dom";
import HelloHOC from "./components/demo/HelloHOC";

ReactDOM.render(
  <HelloHOC name="typescript" loading={true} />,
  document.querySelectorAll(".app")[0]
);

// ERROR! 因为 HelloClass 的静态属性 defaultProps 传不出来。

解决方案:将 defaultProps 设置为可选属性。

interface Greeting {
  name: string;
  firstName?: string;
  lastName?: string;
}

小结

TypeScript 中,高阶组件的使用会遇到很多类型问题,还有可能遇到一些已知的 bug,但这并不是高阶组件本身的问题,而是因为 react 声明文件没有很好的兼容。其实官方最推荐的是使用 Hooks,下面就再用 Hooks 实现一下吧

hooks

hooks 也是一种函数组件,对比类组件,明显简化了许多:

import React, { useEffect, useState } from "react";
import { Button } from "antd";

interface Greeting {
  name: string;
  firstName: string;
  lastName: string;
}

const HelloHooks = (props: Greeting) => {
  // 定义 [组件的状态,设置状态的方法],给定状态的初始值:不需要再定义类型
  const [count, setCount] = useState(0);
  const [text, setText] = useState<string | null>(null);
  return (
    <>
      <div>您点击了 {count} 次</div>
      <Button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        hello {props.name}
      </Button>
    </>
  );
};

HelloHooks.defaultProps = {
  firstName: "",
  lastName: "",
};

export default HelloHooks;

利用 useEffect 新增一个功能: 点击超过 5 次给出提示。

const HelloHooks = (props: Greeting) => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState<string | null>(null);

  // 只有当 count 改变时,渲染逻辑才会执行。
  useEffect(() => {
    if (count > 5) {
      setText("休息一下");
    }
  }, [count]);

  return (
    <>
      <div>
        您点击了 {count} 次,{text}
      </div>
      <Button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        hello {props.name}
      </Button>
    </>
  );
};

为什么要定义为泛型?

不用泛型变量,this.props 的类型无法确定,在内部只能使用类型断言来访问属性:

(this.props as Greeting).name;

这样很麻烦,而 React 声明文件把这些约束关系都用泛型定义好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值