简体   繁体   English

React.Children与非元素的孩子

[英]React.Children with non-element children

Given a component receives a function as a child ( callback as a child pattern, also known as render prop pattern): 给定一个组件接收一个函数作为子元素( 回调为子模式,也称为渲染道具模式):

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

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

The documentation doesn't seem to mention that React.Children accepts only valid React elements, so the fact that child function is ignored looks odd. 文档似乎没有提到React.Children只接受有效的React元素,因此忽略React.Children这一事实看起来很奇怪。

How does React.Children treat non-element children and why? React.Children如何对待非元素儿童?为什么?

References to official sources and/or source code are welcome. 欢迎参考官方来源和/或源代码。

As others have stated, the documentation states that React.Children.count(children) only returns the count of the number of children that are valid React Components . 正如其他人所说, 文档指出React.Children.count(children)只返回有效React Components的子节点数。

React.Children does not ignore other types of children, and if you need to get the count, you only need to determine the length of the array in the root child Object, just like you would in vanilla js. React.Children不会忽略其他类型的子React.Children ,如果需要获取计数,您只需要确定根子对象中数组的长度,就像在vanilla js中一样。 If you look at react-motion , you'll see that they specify that children must be type of func : 如果你看一下react-motion ,你会看到他们指定children必须是func类型:

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

And they further ensure that there's only one child with React.Children.only ( docs ): 并且他们进一步确保只有一个孩子使用React.Children.onlydocs ):

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

React does not handle different types of children on its own, instead, you have to handle them yourself. React不会自己处理不同类型的孩子,而是你必须自己处理它们。 I put together a code sandbox to show you why. 我整理了一个代码沙箱来向您展示原因。

Update: 更新:

Disclaimer: It's not a solution but just an eye where we could look at. 免责声明:这不是一个解决方案,而只是我们可以看到的眼睛。

I'm not sure but if it is indeed needed to be fixed in React itself, then I would suggest to change in the following function: 我不确定,但如果确实需要在React本身修复,那么我建议改变以下函数:

React Element 反应元素

export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

This code: 这段代码:

typeof object === 'object'

To something like: 对于这样的事情:

typeof Object.create(object) === 'object'

And also add a Symbol for such something like: 并为以下内容添加符号:

 Symbol.for('react.function')

in the React Symbol . React符号中

Current solution to count those children with: 目前解决这些儿童的解决方案:

this.props.children.length

This lets you count the function as child component as a children. 这使您可以将function as child componentfunction as child component作为function as child component this.props.children includes any type of element, expressions, or component whilst this.props.children inside React.Children function as child is being ignored as children. this.props.children包含任何类型的元素,表达式或组件,而作为子this.props.children React.Children函数中的React.Children被忽略为子React.Children Continue reading bellow to understand it better... 继续阅读以下内容,以便更好地了解它......

Here's a demo . 这是一个演示


React doesn't consider function as child component as CHILDREN. React不将CHILDREN视为子组件。

However, I have just submitted an issue and if you wish you can keep following there . 但是,我刚刚提交了一个问题,如果您希望可以继续关注那里

The docs specifies that the React.Children.count only counts the component in children. 文档指定React.Children.count仅计算子项中的组件。

You probably already have known what exactly is children in react . 你可能已经知道究竟是什么孩子在做出反应

React takes everything as children except the function as child. 除了作为孩子的功能外,React将所有内容都视为子项。

Here's the reference where it states: 以下是它所述的参考:

React components don't support functions as children. React组件不支持子功能。

If you wish you can look deeper here . 如果你希望你能在这里看得更深。

So, you have function as a child in <Foo /> component so it does return 0 as it's not being considered as children. 因此,您在<Foo />组件中具有作为子项的功能,因此它确实返回0,因为它不被视为子项。

You can optionally count those expressions as its children then you may convert them to array first and then count like: 您可以选择将这些表达式计为其子项,然后您可以先将它们转换为数组,然后计算如下:

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>

Here's a draft demo if you want to have a look. 如果你想看一下,这是一个草案演示

More on using children... 更多关于使用儿童......

Look at the following example how children is being counted: 请看以下示例如何计算子项:

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>

So, you can notice that React can accept any type of children regardless of array, a function, or an object, etc. 因此,您可以注意到React可以接受任何类型的子节点,而不管数组,函数或对象等。

If you wish you can also ignore rendering the children checking it with a condition. 如果您愿意,您也可以忽略渲染孩子用条件检查它。 For eg.: 例如:

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>

If you wish, you can convert the children to array and sort it out like below: 如果您愿意,可以将子项转换为数组并将其排序如下:

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>

Enforcing a single child: 强迫单个孩子:

Bad: 坏:

class OnlyChildComponent extends React.Component {
  render() {
    return this.props.children()
  }
}

{ /* Enforcing it as a single child component */ }
OnlyChildComponent.propTypes = {
  children: React.PropTypes.func.isRequired
}

If there are more children, then it just shows warning in the console and let the program execute next. 如果有更多的孩子,那么它只是在控制台中显示警告并让程序接下来执行。

Good: 好:

class OnlyChildComponent extends React.Component {
  render() {
    return React.Children.only(this.props.children)()
  }
}

If there are more than one child, then it will throw an error! 如果有多个孩子,那么就会抛出错误! And it halts the program execution. 它会暂停程序执行。 It's perfect to avoid mess with our component. 这是完美的避免混乱我们的组件。

According to the docs : 根据文件

React.Children.count returns the total number of components in children, equal to the number of times that a callback passed to map or forEach would be invoked. React.Children.count返回子节点中的组件总数,等于调用传递给map或forEach的回调的次数。

What the above statement means is that if the children element passed to a component is iteratable then only the count will be incremented. 上述语句的含义是,如果传递给组件的子元素是可迭代的,那么只有计数才会递增。


Now lets look at the code snippet that React internally used to calculate count. 现在让我们看一下React内部用来计算count的代码片段。

React.children.count uses the following code React.children.count使用以下代码

The major piece of code is 主要代码是

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

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

So if children is null , it returns 0. 因此,如果childrennull ,则返回0。

function traverseAllChildrenImpl( children, nameSoFar, callback, traverseContext, ) { ... function 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;
  }

So from the above code we infer that we children is String , Number it will return 1 ; 所以从上面的代码我们可以推断,我们的孩子是StringNumber返回1;

Moving on to the next part 继续下一部分

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,
      );
    }
  }

It implies that for each element within the children array, the count is incremented. 这意味着对于children数组中的每个元素,计数都会递增。

const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {

For functions provided as children which are iteratable such as Maps, Sequence it will iterate though the elements and calculate the subtree count 对于作为可迭代的子项提供的函数,例如Maps,Sequence,它将迭代元素并计算子树计数

For an object it will return an error. 对于对象,它将返回错误。

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,
  );

A demo for the above piece of information is in the codesandbox here 上面的信息的演示在这里代码框中

TLDR TLDR

From the official doc : 来自官方文件

React.Children provides utilities for dealing with the this.props.children opaque data structure. React.Children提供了处理this.props.children不透明数据结构的实用程序。

In other words, the React.Children namespace is full of utility functions that are helpful when using this.props.children for its primary intended purpose within the composition model . 换句话说, React.Children命名空间中充满了实用函数,当将this.props.children用作组合模型中的 主要用途时,这些函数this.props.children用。

The composition model uses the special children property for things that can be rendered directly into the output of a parent component , so utility functions like React.children.count are programmed to only include things that can be directly rendered . 组合模型使用特殊的children属性来处理可以直接呈现到父组件的输出中的东西,因此像React.children.count这样的实用程序函数被编程为仅包含可以直接呈现的内容

Because a function cannot be directly rendered into the output of a parent component, it is not included in the results from React.Children.count . 因为函数不能直接呈现到父组件的输出中,所以它不包含在React.Children.count的结果中。


What is the children prop? 什么是children道具?

The children prop is a special prop that React uses to pass a component its children . children道具是React用来传递children成分的特殊道具。

As I'm sure you know, JSX is just syntactic sugar for React.createElement . 我相信你知道, JSX只是React.createElement语法糖

The call signature for React.createElement looks like this: React.createElement的调用签名如下所示:

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

So when a component is passed children in JSX like this: 所以当一个组件在JSX中传递子代像这样:

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

...it results in this code: ...导致此代码:

React.createElement(Foo, null,
  React.createElement("div", null, "child1"),
  React.createElement("div", null, "child2")
);

...where the children are passed as the trailing arguments and are available in the component as the special children prop. ...将子项作为尾随参数传递,并在组件中作为特殊children


What is the children prop for? children道具是什么?

The children prop is a special prop used for component composition . children道具是用于组成部分的特殊道具。

From the Composition vs Inheritance doc : Composition vs Inheritance doc

React has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components. React有一个强大的组合模型,我们建议使用组合而不是继承来重用组件之间的代码。

...and later: ...然后:

We recommend that such components use the special children prop to pass children elements directly into their output 我们建议这些组件使用特殊的children prop将子元素直接传递给它们的输出

So the composition model uses the special children prop to allow for reusable components: 因此组合模型使用特殊的children prop来允许可重用​​的组件:

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

What can be passed in the children prop? 什么可以通过children道具?

Well...anything. 嗯...什么。 There are no actual restrictions on what can be passed in the children prop... children道具中可以传递的内容没有实际限制......

...but only certain things can be passed to a component using children for the composition model and expecting to render the child "directly into their output". ...但是只有某些东西可以传递给使用children组件组件模型的组件,并期望将子项“直接呈现给他们的输出”。

So this is valid: 所以这是有效的:

<Foo>some text</Foo>

...and this is valid: ......这是有效的:

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

...and even this is valid: ......甚至这是有效的:

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

...but this is not valid: ......但这不是有效的:

<Foo>{() => 'some text'}</Foo>

...since Foo cannot render a function directly into its output. ...因为Foo无法将function直接渲染到其输出中。


Why are functions ever passed as children? 为什么功能会像孩子一样传递?

As you pointed out, this is the result of the Render Props pattern. 正如您所指出的,这是Render Props模式的结果。

In the doc describing the Render Props pattern it starts with passing a render function as a render prop . 在描述Render Props模式的doc中,它首先将渲染函数作为render道具传递

Then it points out that using a prop named render is completely arbitrary and the prop name could be anything ... 然后它指出使用名为render的道具完全是任意的, 道具名称可以是任何东西 ......

...and it ends by pointing out that: ......最后指出:

we could just as easily use the children prop! 我们可以轻松地使用children道具!

...and showing how a render function could be passed in as the children prop. ...并展示如何渲染功能,可以通过在为children撑起。

But then it includes this warning: 但是它包含了这个警告:

Since this technique is a little unusual, you'll probably want to explicitly state that children should be a function in your propTypes when designing an API like this. 由于这种技术有点不寻常, propTypes在设计像这样的API时,您可能希望明确说明children应该是propTypes的函数。

In other words, this is a non-standard use of the special children prop. 换句话说,这是特殊children道具的非标准使用。


Passing something arbitrary as children 作为children传递任意的东西

As I noted earlier, anything can be passed as children . 正如我前面提到的, 任何东西都可以作为children传递。

So taking it a step further, here is a component that expects an Object containing three functions, header , body , and footer : 所以更进一步,这里是一个组件,它需要一个包含三个函数的Objectheaderbodyfooter

const ExpectsObject = (props) => (
  <div>
    {props.children.header()}
    {props.children.body()}
    {props.children.footer()}
  </div>
);

This highly unusual component would be used like this: 这个非常不寻常的组件将使用如下:

<ExpectsObject>
  {{
    header: () => (<div>header</div>),
    body: () => (<div>body</div>),
    footer: () => (<div>footer</div>)
  }}
</ExpectsObject>

...and this works just fine. ......这很好用。

But again, this is a very non-standard use of the special children property and would require careful documentation, and since this approach does not follow the composition model the utility functions in the React.Children namespace don't know how to handle the custom object in children . 但同样,这是一个非常标准的特殊children属性使用,需要仔细的文档,并且由于这种方法不遵循组合模型React.Children命名空间中的实用程序函数不知道如何处理自定义children对象。

In fact, calling React.Children.count with this particular object as children causes the utility function to throw an error. 实际上,使用此特定对象作为children对象调用React.Children.count会导致实用程序函数抛出错误。


Why does React.Children.count not count a function? 为什么React.Children.count不计算函数?

So why is a function not included in the count returned by React.Children.count ? 那么为什么函数不包含在React.Children.count返回的计数中呢?

React.Children.count is designed to be a utility method for the composition model and associated use of the special children property. React.Children.count旨在作为组合模型的实用方法以及特殊children属性的关联使用。

Because a function cannot be rendered directly into the output of a component following the composition model, it is not included in the count returned by React.Children.count . 由于函数无法直接呈现在组合模型之后的组件输出中,因此它不包含在React.Children.count返回的count

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM