为了使系统方便维护,我们会在项目中引入 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>
类组件声明
上述声明一些常用的组件属性,我们来声明一个类组件,通常需要声明组件的 Props
和 State
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<undefined>'.」
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); // 正常