为了使系统方便维护,我们会在项目中引入 Typescript,通过使用 TypeScript,可以更好地定义对象和函数的类型,减少错误,提高代码的可读性和可维护性。然而大部分新手刚接触 Typescript 或者 React ,不知道如何声明 Props,类组件,函数组件等。接下来让我们通过例子来走进 React 和 Typescript 世界。

常见的 Props 类型声明

下面是常见的类型声明,我们通过 type 关键字来声明,当然你也可以通过 interface 来声明。

type Props = {
name: string; // 姓名
age: number; // 年龄
disabled: boolean; // 是否禁用
students: Array<{
name: string; // 姓名
age: number; // 年龄
}>; // 学生列表
people: {
name: string; // 姓名
age: number; // 年龄
};
obj2: object; // 对象类型
obj3: {}; // 对象类型
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; // 原生事件类型
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; // 原生事件类型
onSubmit: (val: string) => void; // 自定义事件
optional?: string; // 可选属性
};

在 TypeScript 中,object 作为非原始类型,可能会引起误解。实际上,object 并不是指“任何对象”,而是指“任何非原始类型”。也就是说,object 表示的并不是数字、字符串、布尔值、符号、null 或 undefined。通常在声明对象时,我们不会用 object,而是会使用具体的对象的属性,例如上述的 people ,如果需要声明无法确定的键值对,可以用 Record<string, any>

类组件声明

上述声明一些常用的组件属性,我们来声明一个类组件,通常需要声明组件的 PropsState

interface Props {
message: string;
}
interface State {
count: number;
}
class App extends React.Component<Props, State> {
state: State = {
count: 0,
};
render() {
const { message } = this.props;
const { count } = this.state;
return (
<div>
{message} {count}
</div>
);
}
}

函数组件声明

我们都知道函数组件只需要一个 Props ,所以声明函数组件跟类组件还是有区别的,最简单的函数组件声明如下:

type Props = {
name: string;
};
// 或者
// interface Props {
// name: string;
// }
const App = (props: Props) => {
return props.name;
};
// 或者
const App: React.FC<Props> = (props) => {
return props.name;
};
// 或者
const App: React.FC<Props> = ({ name }): string => {
return name;
};

组件 children 的类型

在 React 可以通过 children 来自定义子元素的渲染,我们可以这样声明:

interface Props {
children: React.ReactNode;
}
// 或者
interface Props {
children: JSX.Element;
}
// 或者
interface Props {
children: React.ReactElement;
}
// 或者
interface Props {
children: React.ReactNode | JSX.Element;
}

ReactNode 和 JSX.Element,ReactElement 的区别

// ============== ReactNode声明
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode =
| ReactChild
| ReactFragment
| ReactPortal
| boolean
| null
| undefined;
// ============== ReactElement声明
type Key = string | number;
interface ReactElement<
P = any,
T extends string | JSXElementConstructor<any> =
| string
| JSXElementConstructor<any>
> {
type: T;
props: P;
key: Key | null;
}
// ============== JSX.Element声明
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> {}
}
}

从上述定义看 JSX.Element 继承了 ReactElement ,只不过在 Props 和 State 声明指定为 any,更加通用,然而 ReactNode 更全面,通常我们 React 组件不一定是 JSX 元素,也可能是字符串,数字,空等类型,所以 children 用 ReactNode 来定义就对了。

hooks 声明

useState 声明 state 时,会接受一个泛型,如果我们不指定类型,通常会根据初始值自动推断出类型,下面的例子中,isDone 会被推断为 boolean 类型。

const [isDone, setDone] = useState(false);

如果 useState 参数是空,ts 会推断这个 state 是 undefined,接下来我们看下面的例子,我们手动设置 name 为 "",ts 会抛出警告**。「****Argument of type '""' is not assignable to parameter of type 'SetStateAction'.」**

function App() {
const [name, setName] = useState();
return (
<div>
<button
onClick={() => {
setName('');
}}
>
click
</button>
{name}
</div>
);
}

所以我们需要手动声明 state 的类型,即:

const [name, setName] = useState<string>(); // name = [string | undefined]

下面是一些常见的 useState 声明:

const [user, setUser] = useState<User | null>(null);
setUser(newUser); // newUser 可以是 User 或者 null
const [user, setUser] = useState<User>({}); // 报错 Argument of type '{}' is not assignable to parameter of type 'User | (() => User)'.
const [user, setUser] = useState<User>({} as User); // 正常