React.Children 与非元素孩子

IT技术 javascript reactjs
2021-05-12 02:41:15

给定一个组件接收一个函数作为子组件(回调作为子模式,也称为渲染props模式):

<Foo>{() => <Bar/>}</Foo>

React.Children.count(props.children) === 0Foo.

文档似乎没有提到React.Children只接受有效的 React 元素,所以忽略子函数的事实看起来很奇怪。

如何React.Children对待非元素儿童以及为什么?

欢迎参考官方来源和/或源代码。

4个回答

正如其他人所说,文档指出React.Children.count(children)只返回有效的孩子数量的计数React Components

React.Children不会忽略其他类型的子对象,如果需要获取计数,只需在根子对象中确定数组的长度,就像在vanilla js中一样。如果您查看react-motion,您会看到它们指定children必须是类型func

Mouse.propTypes = {
  children: PropTypes.func.isRequired
};

他们进一步确保只有一个孩子有React.Children.onlydocs):

render(): ReactElement {
    const renderedChildren = this.props.children(this.state.currentStyle);
    return renderedChildren && React.Children.only(renderedChildren);
  }

React 不会自己处理不同类型的孩子,相反,您必须自己处理它们。我整理了一个代码沙箱来向您展示原因。

更新:

免责声明:这不是解决方案,而只是我们可以观察的眼睛。

我不确定,但如果确实需要在 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 那样将函数视为子组件。

但是,我刚刚提交了一个问题,如果您愿意,可以继续关注那里

文档指定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);
}

所以如果childrennull,它返回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;
  }

所以从上面的代码我们推断我们的孩子是StringNumber它会返回 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,
  );

上述信息的演示位于此处代码和框中

TLDR

来自官方文档

React.Children提供处理this.props.children不透明数据结构的实用程序

换句话说,React.Children命名空间充满了实用函数,这些实用函数组合模型中this.props.children用于其主要预期目的时很有帮助

所述组合物模型使用特殊的children用于可被渲染的东西属性直接进入一个父组件的输出,使效用函数喜欢React.children.count被编程为仅包括可直接呈现的东西

因为函数不能直接渲染到父组件的输出中,所以它不包含在React.Children.count.


什么是childrenprops?

childrenprops是阵营用来传递一个组件的一个特殊的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")
);

...其中子项作为尾随参数传递,并作为特殊childrenprops在组件中可用


props有什么children用?

所述children支柱是用于特殊丙成分组成

来自Composition vs Inheritance 文档

React 有一个强大的组合模型,我们推荐使用组合而不是继承来重用组件之间的代码。

...然后:

我们建议此类组件使用特殊childrenprops将子元素直接传递到它们的输出中

所以组合模型使用特殊的childrenprop 来允许可重用​​的组件:

// Renders its children within a container div with class "foo":
const Foo = (props) => (<div className="foo">{props.children}</div>);

什么可以在childrenprop 中传递

嗯……什么都行。childrenprop 中可以传递的内容没有实际限制......

...但只有某些东西可以传递给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 模式的文档中,它首先将渲染函数作为renderprop传递

然后它指出使用命名的propsrender是完全任意的,props名称可以是任何东西......

...最后指出:

我们可以同样轻松地使用childrenprops!

...并展示了如何将渲染函数作为childrenprops传入

但随后它包含此警告:

由于这种技术有点不寻常,您可能希望在设计这样的 API 时明确声明它children应该是您的函数propTypes

换句话说,这是特殊props的非标准使用children


任意传递一些东西 children

正如我之前提到的,任何东西都可以作为children.

所以更进一步,这里有一个组件,它期望Object包含三个函数headerbody, 和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.