React.Children 的用法及使用场景
本文主要介绍 React.Children 用法及使用场景,只因为偶然间看到了React.Children.only的使用,但不知道他是干啥的,仅此记录一下。
先来看简单的个例子:
import React, { PropsWithChildren } from 'react';
const Parent = () => {
return <Child>
<p>这是内容111</p>
<p>这是内容222</p>
</Child>
}
const Child = (props: PropsWithChildren) => {
return <>
<h6>一共有{props.children.length}个子元素</h6>
{props.children}
</>
}
props.children 是我们在编写时常用的一个属性,children 可以是任何类型的值,包括:文本,JSX,DOM元素,函数等等。
我们可以从 ts 的定义看到 children 的类型:
type PropsWithChildren<P = unknown> = P & { children?: ReactNode | undefined };
type ReactNode = ReactElement | string | number | ReactFragment | ReactPortal | boolean | null | undefined;
props.children的类型判断
通常情况下,我们通过children传入到子组件的数据都是单一类型的,我们也明确知道是什么类型,比如上面的例子中,传入的就是DOM元素数组,我们展示了数组的个数。
但是如果我们传入的是字符传又是该怎样展示呢?
const Parent = () => {
return <Child>
React.Children 的用武之地
</Child>
}
此时我们期望展示“一共有1个子元素”,但是展示出来的真实效果是“一共有20个子元素”,props.children.length 读取到的是字符串的长度,这显然是不符合期望的。
通常我们需要判断children的类型,然后再根据不同类型去写逻辑代码,这显然就增加了我们的工作量,此时 React.Children 就可以发挥作用了。
我们想正确展示children包含的元素个数,我们可以这样写 Child 组件:
const Child = (props: PropsWithChildren) => {
return <>
<h6>一共有{React.Children.count(props.children)}个字元素</h6>
{props.children}
</>
}
这只是其中一个例子,React.Children还提供了很多其他工具函数给开发者使用。
React.Children 的使用
我们从 ts 类型定义中可以看到 React.Chilren 包含以下方法:
const Children: {
map<T, C>(children: C | ReadonlyArray<C>, fn: (child: C, index: number) => T):
C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
forEach<C>(children: C | ReadonlyArray<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. React.Children.count
React.Children.count 可以统计子组件个数。
由于 props.children 类型的不确定,我们要判断有多少个子组件就比较困难了。如果幼稚的使用 props.children.length 就很容易报错了,比如传来一个子组件"Hello World!",.length会返回12!
2. React.Children.map
在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。
3.React.Children.only
验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。
4.React.Children.toArray
当你需要把子组件转化成数组时,可以使用toArray这个函数
5.React.Children.forEach
遍历子组件,与 React.Children.map 类似,但它不会返回一个数组。
应用场景
1.组件限制子元素类型以及数量
// 限制子元素类型
const limitChildType = (props: PropsWithChildren) => {
return React.Children.map(this.props.children, child => {
if (child.type.name !=='Col' || child.type.dispalyName !== 'Col') {
throw new Error('the children of component Row muse be component Col')
}
})
}
// 限制子元素数量
const limitChildCount = (props: PropsWithChildren) => {
return React.Children.only(props.children);
}
2.修改 children 的属性
const renderCustomElement = () => {
return React.children.map(child => {
// do something, for example, add event to props or style etc.
return React.cloneElement(child, config)
})
}
3.组件提供自定义触发事件元素功能
举个例子,比如 Form 组件,支持触发提交的 Button 自定义,思路是遍历 Form 的 children,找到类型为 Button 的子节点,为此子节点添加 onClick 事件。
const renderSubmitButton: (submitButton: any) => any = submitButton => {
return Children.map(child => {
if (['boolean', 'undefined', 'string', 'number'].includes(typeof child) || child === null) {
return child
}
if (child.type.name === 'Button') {
return React.cloneElement(child, {
onClick: () => {
if (child.props.onClick === undefined
|| typeof child.props.onClick === 'function'
&& child.props.onClick() !== false
) {
triggerSubmit()
}
}
})
}
if (child.props.children) {
return React.cloneElement(
child,
{},
renderSubmitButton(child.props.children)
)
}
return child
})
}
小结
熟悉了 React.Children 的用法,相信会更有利于我们的组件开发,简化我们的代码逻辑。