# 组件的 props
props 是父子组件传值,最有用的功能了。如果项目中没有使用 TS 这种静态语言进行检查,我们为了组件的不易出现 bug,会使用 React 组件提供的静态属性 defaultProps 来约束。
# 还需要 defaultProps 吗?
但是现在,很明显是不需要的,因为 defaultProps 的出现就是为了,静态检查,如今项目出现了 TS 这么强大的静态语言,defaultProps就应该被束之高阁。
关于 defaultProps 的去留,你也可以看下这个讨论 You May Not Need defaultProps (opens new window)
TS 项目中:
函数组件:
type GreetProps = { age?: number };
const Greet = ({ age = 21 }: GreetProps) => // etc
1
2
3
2
3
类组件:
interface IProps = {
age?: number;
};
class Greet extends React.Component<IProps> {
render() {
const { age = 21 } = this.props;
/*...*/
}
}
let el = <Greet age={3} />;
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 最基本的 props
父组件传过来的 props 是个对象,但是这个对象的内容可是很千变万化的,我们来看看它常出现的结构:
type AppProps = {
/** 基本类型 */
message: string;
count: number;
disabled: boolean;
/** 数组 */
names: string[];
ages: Array<string>;
/** 字面量联合类型 */
status: "waiting" | "success";
/** 引用类型,不能访问类型的属性,(不推荐使用,但是作为占位很有用) */
obj: object;
/** 字面量对象类型表现和 object 几乎一样,也不能访问类型的属性 ,和 Object 类型完全一样 */
obj2: {};
/** 一个固定数量对象类型(常用!) */
obj3: {
id: string;
title: string;
};
/** 数组对象类型(常用!) */
objArr: {
id: string;
title: string;
}[];
/** 一个字典对象,对象包含的属性全部都属于同一种类型 */
dict1: {
[key: string]: MyTypeHere;
};
dict2: Record<string, MyTypeHere>; // 等同于 dict1
/** 定义一个函数,调用时返回的值的类型是 any (不推荐使用) */
onSomething: Function;
/** 定一个不返回值的函数(非常推荐) */
onClick: () => void;
/** 函数传了些形参 (非常推荐) */
onChange: (id: number) => void;
/** 携带事件的函数 (非常推荐) */
onClick(event: React.MouseEvent<HTMLButtonElement>): void;
/** 可选属性 (非常常用!) */
optional?: OptionalType;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
如果你足够细心,会发现上面代码的使用的多行注释,都多了个星号,这是用的 tsdoc (opens new window) 语法,推荐你使用。
# props 优雅接收 children
父组件在使用的过程中,children 可能是 JSX、字符串、数组等,那问题来了,哪种接受方法最好呢?
export declare interface AppProps {
/** 差, 只支持 JSX */
children1: JSX.Element;
/** 一般, 不支持字符串 */
children2: JSX.Element | JSX.Element[];
/** React.Children 是 React 暴露出来的顶层 API,包含一系列处理 props.children 的方法*/
children3: React.ReactChildren;
/** 很好 */
children4: React.ReactChild;
/** 最佳,支持所有类型 推荐使用 */
children: React.ReactNode;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
你一定好奇上面代码的好坏我是怎么知道的,可别是瞎掰的。凡事都讲究证据,我来带你看看 React 对上面 API 的具体实现:
JSX.Element
一目了然,JSX.Element 其实就是 ReactElement。
declare global {
namespace JSX {
// tslint:disable-next-line:no-empty-interface
interface Element extends React.ReactElement<any, any> { }
}
}
1
2
3
4
5
6
2
3
4
5
6
- ReactChildren
ReactChildren 是一系列方法的组合,使用可参考下面两个链接:
1. [reactchildren](https://reactjs.org/docs/react-api.html#reactchildren)
2. [对React children 的深入理解](https://www.jianshu.com/p/d1975493b5ea)
interface ReactChildren {
map<T, C>(children: C | C[], fn: (child: C, index: number) => T): C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
forEach<C>(children: C | C[], fn: (child: C, index: number) => void): void;
count(children: any): number;
only<C>(children: C): C extends any[] ? never : C;
toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- ReactChild
ReactChild 是就是在 JSX.element 的基础上加了 ReactText 类型。
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
1
2
2
- ReactNode
非常牛的类型,涵盖了 props.children 可能出现的所有情况:
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
1
2
3
2
3
functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop type
style?: React.CSSProperties // 传递style对象
onChange?: React.FormEventHandler<HTMLInputElement> // 表单事件, 泛型参数是event.target的类型
// more info: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring
props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
1
2
3
4
5
6
2
3
4
5
6
import React, { useState } from "react";
export default function App() {
const [value, setValue] = useState("哈哈哈");
const handleChange = (event: React.ChangeEvent<HTMLInputElement>):void => {
let value = event.target.value;
console.log(value, event.target)
setValue(event.target.value);
}
const handleClick = (event: React.MouseEvent<HTMLButtonElement>):void => {
event.persist()
console.log(event)
}
return (
<div className="App">
<input value={value} onChange={handleChange} />
<button onClick={handleClick}>按我</button>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21