组件状态属性声明
函数组件
属性声明
简单组件声明
类组件通过 React.FC<Props>
声明组件的属性
// test.tsx
import { FC } from 'react';
interface TestProps {
val: string;
onClick?: () => void;
}
const Test: FC<TestProps> = ({ val, onClick, children }) => {
return <div></div>;
};
export default Test;
使用组件
import Test from './test';
// 以下用法是正常的,不会报错
<Test val="test" />
<Test val="test" onClick={() => {}} />
<Test val="test" onClick={() => {}}>
<div className="text"></div>
</Test>
// 以下是错误的,会报错
<Test /> 缺失val
<Test val={1} /> val类型不对
<Test val="test" onClick={(val: string) => {}} /> onClick 属性不对
<Test val="test" onClick={(val: string) => {}} extra={'test'} /> 多了额外属性
含子组件声明
// 例子举得不好,可以看下 Ant Design 的 Select 和 Select.Option
import { FC } from 'react';
interface TestItemProps {
val: string;
onClick: () => void;
}
const TestItem: FC<TestItemProps> = ({ onClick, val, children }) => {
return <div></div>;
};
interface TestProps {
list: string[];
onItemClick: (idx: number) => void;
}
interface TestComponentProps extends FC<TestProps> {
Item: typeof TestItem;
}
const Test: TestComponentProps = ({ list, onItemClick, children }) => {
return <div></div>;
};
Test.Item = TestItem;
export default Test;
含子组件使用
import Test from './test';
const UseTest = () => {
const list = ['a', 'b', 'c'];
return (
<Test onItemClick={() => {}} list={list}>
{list.map((i, idx) => (
<Test.Item onClick={() => {}} val={i}>
{idx}
</Test.Item>
))}
</Test>
);
};
状态声明
状态声明的时候,建议用范型显式标明属性的类型
const Test: TestComponentProps = ({ list, onItemClick, children }) => {
const item = useState<string>('');
const itemRef = useRef<string>(null);
const handledList = useMemo<number[]>(() => list.map((i, idx) => idx), [list]);
return <div></div>;
};
类组件
类组件通过 React.Component<Props, State> | React.PureComponent<Props, State>
声明组件的属性和状态
类组件声明状态和属性
import { Component } from 'react';
interface State {
count: number;
}
interface Props {
list: string[];
}
export default class ClassComponent extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { count: 0 };
}
}
// 使用
<ClassComponet list={['1']}></ClassComponet>;
常见的一些问题
Window 上面挂属性的方法
这个问题是 TypeScript
本身的问题,和 React
无关,不过因为经常遇到,直接在这里提一下
// 因为 __test_data__ 不是原生的 Window 上的属性,所以这样用是会报错的
window.__test_data__(
// 大部分时候的解法
window as any,
).__test_data__;
// 这样写没有任何问题,大部分时候也推荐这么写,可能数据结构比较简单,但是如果属性比较复杂,建议还是做一下声明,方便后续使用
// 假设
enum Enviroment {
local,
pre,
prod,
}
interface TestData {
index: number;
item: string;
}
// 在项目根目录随便新建一个 index.d.ts 文件,
// 注意不能写 export interface Window
interface Window {
env: Enviroment;
__test_data__: TestData;
}
// 同样的,某些格式导出报错的问题,也可以用这个方式,index.d.ts 文件声明这些模块
declare module '*.css';
declare module '*.less';
declare module '*.scss';
declare module '*.sass';
declare module '*.styl';
declare module '*.stylus';
declare module '*.png';
declare module '*.jpeg';
declare module '*.jpg';
declare module '*.svg' {
export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElement;
const url: string;
export default url;
}
JSDoc 生成属性说明
TypeScript
完整支持 JSDoc
,在 React
中可以使用 JSDoc
生成组件的属性说明,这样在提供公共组件的时候,用户在引用你的组件的时候,通过编辑器就能知道属性的注释是什么
// test.tsx
import { FC } from 'react';
// 注意不能是 //
interface TestProps {
/** 组件的值 */
val: string;
/** 组件点击回调 */
onClick?: () => void;
}
interface TestProps {
/**
* 组件的值
*/
val: string;
/** 组件点击回调 */
onClick?: () => void;
}
const Test: FC<TestProps> = ({ val, onClick, children }) => {
return <div></div>;
};
export default Test;
// 使用组件
<Test val={1} onClick={() => {}} />
当鼠标移到 val 上
(property) TestProps.val: string
组件的值
当鼠标移到 onClick 上
(property) TestProps.onClick?: () => void
组件点击回调
关于原生组件一些方法的声明
对于原生的 html
标签,触发 onClick
或者其他事件的时候,有时候需要禁止事件的一些冒泡,或者禁止默认行为,这时候需要使用到类似于原生事件的 stopPropagation
的方法,但是如果不熟悉这里怎么定义,只能把组件回调参数 event
定义为 any
const onClick = (e: any) => {
e.stopPropagation();
};
这样写没有任何问题,不过没有任何语法提示就是了
其实 React
对各个 HTML
标签属性的类型声明做的非常好,当你在 div
声明一个 onClick
的时候,把鼠标移到上面,或者跳转到属性定义,就能看到 onClick
的函数声明
(property) React.DOMAttributes<HTMLDivElement>.onClick?: React.MouseEventHandler<HTMLDivElement> | undefined
<div onClick={onClick}></div>
因而,我们的函数声明可以直接用 React.MouseEventHandler<HTMLDivElement>
,同时,这时候的回调参数 e
会被识别为 React.MouseEvent<HTMLDivElement, MouseEvent>
,你可以随意使用 React
合成事件里面的方法
(parameter) e: React.MouseEvent<HTMLDivElement, MouseEvent>
const onClick: React.MouseEventHandler<HTMLDivElement> = e => {
e.stopPropagation();
e.preventDefault();
};
当然,知道 e
的类型,也可以自己写,这样写也完全没有问题,看自己习惯了。
(parameter) e: React.MouseEvent<HTMLDivElement, MouseEvent>
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
e.preventDefault();
};
以上三种都能解决原生方法类型不对的问题,更加推荐 React.MouseEventHandler<HTMLDivElement>
的写法,因为这个有比较完整的提示
React.memo 和 React.FC
这两个结合起来本身是没什么问题的,可以看下面的例子
interface DrawerProps {
visible: boolean;
onClose: () => void;
closeWhenClickMask?: boolean;
zIndex?: number;
}
const Drawer: React.FC<DrawerProps> = ({ visible, onClose, children }) => {};
export default React.memo(Drawer);
// 在别的组件使用
// 正常
<Drawer visible={visible} onClose={() => {}}></Drawer>
// 异常
<Drawer visible={visible} onClose={() => {}}>
<div>121</div>
</Drawer>
Type '{ children: Element[]; visible: boolean; onClose: () => void; }' is not assignable to type 'IntrinsicAttributes & DrawerProps'.
Property 'children' does not exist on type 'IntrinsicAttributes & DrawerProps'.ts(2322)
这里的异常表明 children
不是 Drawer
的属性,但是正常情况下,React.FC
会注入 & { children?: React.ReactNode }
这个属性,但是这里好像凭空消失了一样,尝试了很多方法,最后总结出几种写法
// 手动把 children 属性补上
interface DrawerProps {
visible: boolean;
onClose: () => void;
closeWhenClickMask?: boolean;
zIndex?: number;
children?: React.ReactNode;
}
// 其他地方写法依旧
// 手动更改导出的组件类型
export default React.memo(Drawer) 改成
export default React.memo(Drawer) as React.FC<DrawerProps>;
// 第三种,和第二种写法其实没有差别,不过将 React.memo 换在上面包裹了,改变了导出组件的类型
const Drawer: React.FC<DrawerProps> = React.memo(({ visible, onClose, children }) => {});
export default Drawer;