给定一个组件接收一个函数作为子组件(回调作为子模式,也称为渲染props模式):
<Foo>{() => <Bar/>}</Foo>
React.Children.count(props.children) === 0
在Foo
.
文档似乎没有提到React.Children
只接受有效的 React 元素,所以忽略子函数的事实看起来很奇怪。
如何React.Children
对待非元素儿童以及为什么?
欢迎参考官方来源和/或源代码。
给定一个组件接收一个函数作为子组件(回调作为子模式,也称为渲染props模式):
<Foo>{() => <Bar/>}</Foo>
React.Children.count(props.children) === 0
在Foo
.
文档似乎没有提到React.Children
只接受有效的 React 元素,所以忽略子函数的事实看起来很奇怪。
如何React.Children
对待非元素儿童以及为什么?
欢迎参考官方来源和/或源代码。
正如其他人所说,文档指出React.Children.count(children)
只返回有效的孩子数量的计数React
Components
。
React.Children
不会忽略其他类型的子对象,如果需要获取计数,只需在根子对象中确定数组的长度,就像在vanilla js中一样。如果您查看react-motion,您会看到它们指定children
必须是类型func
:
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
他们进一步确保只有一个孩子有React.Children.only
(docs):
render(): ReactElement {
const renderedChildren = this.props.children(this.state.currentStyle);
return renderedChildren && React.Children.only(renderedChildren);
}
React 不会自己处理不同类型的孩子,相反,您必须自己处理它们。我整理了一个代码沙箱来向您展示原因。
免责声明:这不是解决方案,而只是我们可以观察的眼睛。
我不确定,但如果确实需要在 React 本身中修复它,那么我建议更改以下函数:
export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
这段代码:
typeof object === 'object'
类似于:
typeof Object.create(object) === 'object'
并为这样的东西添加一个符号:
Symbol.for('react.function')
在react符号中。
this.props.children.length
这让您可以将其function as child component
视为孩子。this.props.children
包括任何类型的元素、表达式或组件,而this.props.children
内部React.Children
函数作为子项被忽略为子项。继续阅读下面的内容以更好地理解它......
这是一个演示。
但是,我刚刚提交了一个问题,如果您愿意,可以继续关注那里。
该文档指定React.Children.count
唯一计算子组件中的组件。
您可能已经知道react 中的 children到底是什么。
除了函数作为子项之外,React 将所有东西都当作子项。
这是它指出的参考:
React 组件不支持子函数。
如果您愿意,可以在这里深入了解。
因此,您在<Foo />
组件中具有作为子项的函数,因此它确实返回 0,因为它不被视为子项。
您可以选择将这些表达式计为其子项,然后您可以先将它们转换为数组,然后计算如下:
class CountExpression extends React.Component {
render() {
const children = React.Children.toArray(this.props.children)
return <p>{React.Children.count(children)}</p>
}
}
{ /* Counts 2 */ }
<CountExpression>
{'one'}
{'two'}
{ () => <p>Still, this will be ignored as child and is not included in array</p>}
</CountExpression>
如果你想看一看,这里有一个演示草案。
请看以下示例如何计算孩子:
class CountChildren extends React.Component {
render() {
return <p>{React.Children.count(this.props.children)}</p>
}
}
{ /* Renders 1 */ }
<CountChildren>
Simply a text!
</CountChildren>
{ /* Renders 2 */ }
<CountChildren>
<p>Html element</p>
<ChildComponent />
</CountChildren>
{ /* Renders 3 */ }
<CountChildren>
Simply a text!
<p>Html element</p>
<ChildComponent />
</CountChildren>
{ /* Renders 3 */ }
<CountChildren>
Simply a text!
<p>Html element</p>
<ChildComponent />
{ /* ignores it as it's not a component */ }
{ () => <div>Function as a child component</div> }
</CountChildren>
所以,你可以注意到 React 可以接受任何类型的子元素,无论是数组、函数还是对象等。
如果您愿意,您也可以忽略渲染子项检查它的条件。例如:
class SingleChildComponent extends React.Component {
render() {
return (
<div>
{
Array.isArray(this.props.children) ?
'Sorry, you can only pass single child!' :
this.props.children()
}
</div>
)
}
}
{ /* Renders 'Sorry, you can only pass single child!' */ }
<SingleChildComponent>
<p>First children</p>
<SecondChildren />
</SingleChildComponent>
{ /* Renders `<p>Single child</p>` */ }
<SingleChildComponent>
<p>Single child</p>
</SingleChildComponent>
如果您愿意,可以将子项转换为数组并按如下方式对其进行排序:
class SortComponent extends React.Component {
render() {
const children = React.Children.toArray(this.props.children)
return <>{ children.sort().join(', ') }</>
}
}
{ /* Renders 'Computer, Furniture, Machine' */ }
<SortComponent>
{'Machine'} { /* First child */ }
{'Computer'} { /* Second child */ }
{'Furniture'} { /* Third child */ }
</SortComponent>
强制独生子女:
坏的:
class OnlyChildComponent extends React.Component {
render() {
return this.props.children()
}
}
{ /* Enforcing it as a single child component */ }
OnlyChildComponent.propTypes = {
children: React.PropTypes.func.isRequired
}
如果有更多的孩子,那么它只是在控制台中显示警告并让程序接下来执行。
好的:
class OnlyChildComponent extends React.Component {
render() {
return React.Children.only(this.props.children)()
}
}
如果有多个孩子,那么它会抛出错误!它停止程序执行。避免弄乱我们的组件是完美的。
根据文档:
React.Children.count 返回子组件的总数,等于传递给 map 或 forEach 的回调将被调用的次数。
上述语句的意思是,如果传递给组件的子元素是可迭代的,那么只有计数会增加。
现在让我们看看 React 内部用来计算计数的代码片段。
React.children.count
使用以下代码
主要的代码是
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
所以如果children
是null
,它返回0。
函数 traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext, ) { ...
switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
if (invokeCallback) {
// Other code
return 1;
}
所以从上面的代码我们推断我们的孩子是String
,Number
它会返回 1;
继续下一部分
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
}
这意味着对于子数组中的每个元素,计数都会增加。
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
对于作为可迭代子项提供的函数,例如 Maps、Sequence,它将遍历元素并计算子树计数
对于一个对象,它将返回一个错误。
invariant(
false,
'Objects are not valid as a React child (found: %s).%s',
childrenString === '[object Object]'
? 'object with keys {' + Object.keys(children).join(', ') + '}'
: childrenString,
addendum,
);
来自官方文档:
React.Children
提供处理this.props.children
不透明数据结构的实用程序。
换句话说,React.Children
命名空间充满了实用函数,这些实用函数在组合模型中this.props.children
用于其主要预期目的时很有帮助。
所述组合物模型使用特殊的children
用于可被渲染的东西属性直接进入一个父组件的输出,使效用函数喜欢React.children.count
被编程为仅包括可直接呈现的东西。
因为函数不能直接渲染到父组件的输出中,所以它不包含在React.Children.count
.
children
props?该children
props是阵营用来传递一个组件的一个特殊的propschildren
。
我相信你知道,JSX 只是React.createElement
.
的调用签名React.createElement
如下所示:
React.createElement(type, props, ...children)
因此,当像这样在 JSX 中传递子组件时:
<Foo>
<div>child1</div>
<div>child2</div>
</Foo>
...它导致此代码:
React.createElement(Foo, null,
React.createElement("div", null, "child1"),
React.createElement("div", null, "child2")
);
...其中子项作为尾随参数传递,并作为特殊children
props在组件中可用。
children
用?所述children
支柱是用于特殊丙成分组成。
来自Composition vs Inheritance 文档:
React 有一个强大的组合模型,我们推荐使用组合而不是继承来重用组件之间的代码。
...然后:
我们建议此类组件使用特殊
children
props将子元素直接传递到它们的输出中
所以组合模型使用特殊的children
prop 来允许可重用的组件:
// Renders its children within a container div with class "foo":
const Foo = (props) => (<div className="foo">{props.children}</div>);
children
prop 中传递?嗯……什么都行。对children
prop 中可以传递的内容没有实际限制......
...但只有某些东西可以传递给children
用于组合模型的组件,并期望将子组件“直接渲染到他们的输出中”。
所以这是有效的:
<Foo>some text</Foo>
...这是有效的:
<Foo><div>something</div></Foo>
...甚至这是有效的:
<Foo>
<Foo>
some text
</Foo>
</Foo>
......但这不是有效的:
<Foo>{() => 'some text'}</Foo>
...因为Foo
不能将 afunction
直接渲染到其输出中。
正如您所指出的,这是渲染props模式的结果。
在描述 Render Props 模式的文档中,它首先将渲染函数作为render
prop传递。
然后它指出使用命名的propsrender
是完全任意的,props名称可以是任何东西......
...最后指出:
我们可以同样轻松地使用
children
props!
...并展示了如何将渲染函数作为children
props传入。
但随后它包含此警告:
由于这种技术有点不寻常,您可能希望在设计这样的 API 时明确声明它
children
应该是您的函数propTypes
。
换句话说,这是特殊props的非标准使用children
。
children
正如我之前提到的,任何东西都可以作为children
.
所以更进一步,这里有一个组件,它期望Object
包含三个函数header
,body
, 和footer
:
const ExpectsObject = (props) => (
<div>
{props.children.header()}
{props.children.body()}
{props.children.footer()}
</div>
);
这个非常不寻常的组件将像这样使用:
<ExpectsObject>
{{
header: () => (<div>header</div>),
body: () => (<div>body</div>),
footer: () => (<div>footer</div>)
}}
</ExpectsObject>
...这工作得很好。
但同样,这是对特殊属性的非常不标准的使用,children
需要仔细记录,而且由于这种方法不遵循组合模型,React.Children
命名空间中的实用程序函数不知道如何处理children
.
事实上,React.Children.count
使用这个特定对象 as调用children
会导致实用函数抛出错误。
React.Children.count
计一个函数?那么为什么函数不包含在返回的计数中React.Children.count
呢?
React.Children.count
被设计为组合模型和特殊children
属性的相关使用的实用方法。
因为函数不能直接渲染到遵循组合模型的组件的输出中,所以它不包含在count
返回的React.Children.count
.