具有非元素孩子的孩子

时间:2018-09-28 10:40:20

标签: javascript reactjs

鉴于组件接收的功能是子代的(作为子代的回调模式,也称为 render prop 模式):

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

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

文档似乎没有提到React.Children仅接受有效的React元素,因此忽略子功能这一事实看起来很奇怪。

React.Children如何对待没有孩子的孩子,为什么?

欢迎引用官方资源和/或源代码。

3 个答案:

答案 0 :(得分:13)

正如其他人所述,documentation指出React.Children.count(children)仅返回有效React Components的子代数的计数。

React.Children不会忽略其他类型的子级,并且,如果需要获取计数,则只需要确定根子级Object中数组的长度即可,就像在普通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不能自己处理不同类型的孩子,相反,您必须自己处理它们。我整理了code sandbox,向您展示原因。

答案 1 :(得分:4)

根据docs

  

React.Children.count返回子级中组件的总数,等于   回调传递给map或forEach的次数为   调用。

上面的语句的意思是,如果传递给组件的children元素是可迭代的,则只会增加计数。


现在让我们看一下React内部用于计算数量的代码段。

React.children.count使用following code

主要代码是

function traverseAllChildren(children, callback, traverseContext) {
  if (children == null) {
    return 0;
  }

  return traverseAllChildrenImpl(children, '', callback, traverseContext);
}

因此,如果childrennull,它将返回0。

function traverseAllChildrenImpl(   孩子们   名字SoFar,   打回来,   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,
  );

codesandbox here

中有上述信息的演示

答案 2 :(得分:0)

TLDR

来自official doc

  

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

换句话说,React.Children名称空间充满了实用程序功能,当在composition model内将this.props.children用于其主要预期目的时,这些功能很有用。 / p>

组成模型使用特殊的children属性来处理可以直接呈现到父组件输出中的内容,因此实用程序功能如{{ 1}}设置为仅包含可以直接渲染的内容

因为不能将函数直接呈现到父组件的输出中,所以它不包含在React.children.count的结果中。


React.Children.count道具是什么?

children道具是React用来传递其children组件的特殊道具。

我敢肯定,JSX is just syntactic sugar for React.createElement

React.createElement的呼叫签名如下:

children

因此,当组件通过JSX中的子代时,如下所示:

React.createElement(type, props, ...children)

...它产生以下代码:

<Foo>
  <div>child1</div>
  <div>child2</div>
</Foo>

......将子代作为尾随参数传递,并在组件中作为特殊的React.createElement(Foo, null, React.createElement("div", null, "child1"), React.createElement("div", null, "child2") ); 道具提供。


children道具有什么用?

children道具是用于组成成分的特殊道具。

来自Composition vs Inheritance doc

  

React具有强大的组合模型,我们建议使用组合而不是继承来在组件之间重用代码。

...以及更高版本:

  

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

因此,合成模型使用特殊的children属性来允许可重用​​的组件:

children

// Renders its children within a container div with class "foo": const Foo = (props) => (<div className="foo">{props.children}</div>); 道具可以传递什么?

好吧...任何事情。对于children道具中可以传递的内容没有实际限制。

...但是,只有使用children的合成模型可以将某些东西传递给组件,并期望将子代“直接呈现到其输出中”。

所以这是有效的:

children

...这是有效的:

<Foo>some text</Foo>

...甚至是有效的:

<Foo><div>something</div></Foo>

...但是这无效有效:

<Foo>
  <Foo>
    some text
  </Foo>
</Foo>

...由于<Foo>{() => 'some text'}</Foo> 无法将Foo直接渲染到其输出中。


为什么函数曾经作为子代传递?

正如您所指出的,这是Render Props模式的结果。

在描述“渲染道具”模式的文档中,它以passing a render function as a render prop开头。

然后指出,使用名为function的道具是完全任意的,而the prop name could be anything ...

...最后指出:

  

我们可以轻松使用render道具!

...并显示了如何将渲染函数作为children道具传入。

但是它包含以下警告:

  

由于这种技术有点不寻常,因此在设计这样的API时,您可能需要明确声明children应该是children中的一个函数。

换句话说,这是特殊propTypes道具的非标准用法。


children的形式传递任意内容

正如我之前提到的,任何都可以作为children传递。

更进一步,这是一个期望children包含三个功能Objectheaderbody的组件:

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> 属性的非常非标准使用,并且需要仔细的文档记录,并且由于此方法不遵循组成模型< / strong> children名称空间中的实用程序功能不知道如何处理React.Children中的自定义对象。

实际上,使用此特定对象作为children调用React.Children.count会导致实用程序函数抛出错误。


为什么children不算函数?

那么React.Children.count返回的计数中为什么不包含一个函数?

React.Children.count被设计为组成模型的实用方法,并且是特殊React.Children.count属性的关联使用。

由于无法按照合成模型直接将函数呈现到组件的输出中,因此该函数不包含在children返回的count中。