如何将道具传递给{this.props.children}


887

我试图找到定义可以以一般方式使用的组件的正确方法:

<Parent>
  <Child value="1">
  <Child value="2">
</Parent>

当然,您可以想象<select><option>作为父本子组件之间的渲染逻辑。

对于这个问题,这是一个虚拟的实现:

var Parent = React.createClass({
  doSomething: function(value) {
  },
  render: function() {
    return (<div>{this.props.children}</div>);
  }
});

var Child = React.createClass({
  onClick: function() {
    this.props.doSomething(this.props.value); // doSomething is undefined
  },
  render: function() {
    return (<div onClick={this.onClick}></div>);
  }
});

问题是,每当您用于{this.props.children}定义包装器组件时,如何将某些属性传递给其所有子组件?


Answers:


955

用新道具克隆儿童

您可以使用React.Children遍历子级,然后使用React.cloneElement使用新的道具(浅合并)克隆每个元素,例如:

import React, { Children, isValidElement, cloneElement } from 'react';

const Child = ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    const childrenWithProps = Children.map(children, child => {
      // Checking isValidElement is the safe way and avoids a TS error too.
      if (isValidElement(child)) {
        return cloneElement(child, { doSomething })
      }

      return child;
    });

    return <div>{childrenWithProps}</div>
  }
};

ReactDOM.render(
  <Parent>
    <Child value="1" />
    <Child value="2" />
  </Parent>,
  document.getElementById('container')
);

小提琴:https : //jsfiddle.net/2q294y43/2/

称呼孩子为功能

您也可以将道具传递给带有渲染道具的孩子。在这种方法中,子代(可以是children或其他任何prop名称)是一个函数,可以接受您要传递的任何参数并返回子代:

const Child = ({ doSomething, value }) => (
  <div onClick={() =>  doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    // Note that children is called as a function and we can pass args to it
    return <div>{children(doSomething)}</div>
  }
};

ReactDOM.render(
  <Parent>
    {doSomething => (
      <React.Fragment>
        <Child doSomething={doSomething} value="1" />
        <Child doSomething={doSomething} value="2" />
      </React.Fragment>
    )}
  </Parent>,
  document.getElementById('container')
);

如果您愿意,也可以代替<React.Fragment>或简单地<>返回一个数组。

小提琴:https : //jsfiddle.net/ferahl/y5pcua68/7/


7
这对我不起作用。这未在React.cloneElement()中定义
Patrick

12
这个答案不起作用,value传递给doSomething的人丢失了。
戴夫

3
@DominicTobias Arg,对不起,我将console.log切换为警报,并忘记将两个参数合并为一个字符串。
戴夫

1
这个答案非常有帮助,但是我遇到了一个这里没有提到的问题,我想知道这是否是一些新的东西改变了,或者我觉得这很奇怪。克隆子元素时,将它的子元素设置为旧元素,直到将this.props.children.props.children添加到cloneElement的第三个参数中为止。
aphenine

7
如果孩子是通过从单独的路线页面加载的路线(v4)加载的,该怎么办?
blamb

393

对于更简洁的方法,请尝试:

<div>
    {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>

编辑:可以与多个单独的子项(子项本身必须是组件)一起使用。在16.8.6中测试

<div>
    {React.cloneElement(props.children[0], { loggedIn: true, testingTwo: true })}
    {React.cloneElement(props.children[1], { loggedIn: true, testProp: false })}
</div>

10
我使用的是评分最高的答案,但是这个答案更直接了!此解决方案也是他们在react-router示例页面上使用的解决方案。
captDaylight

10
有人可以解释一下它是如何工作的(或实际上是什么)吗?阅读文档后,我看不到它将如何降入子级并将该道具添加到每个子级中-这是要做什么吗?如果是这样,我们如何知道这将是做什么?将不透明的数据结构(this.props.children)传递给cloneElement期望...元素的... 甚至不是很明显。
GreenAsJade

51
确实,这似乎不适用于多个孩子。
Danita,2016年

17
因此,您可以编写在某人仅将一个孩子传递到某个组件中时起作用的代码,但是当他们添加另一个孩子时,它崩溃了……听起来面值不是很好吗?对于OP来说,这似乎是一个陷阱,他们特别询问有关将道具传递给所有孩子的问题。
GreenAsJade's

10
只要您的组件要生一个孩子,@ GreenAsJade就可以了。您可以通过组件propTypes定义它期望一个孩子。React.Children.only函数将返回唯一的子项,如果有多个子项,则抛出异常(如果没有用例,则不存在)。
cchamberlain

80

尝试这个

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

它使用react-15.1为我工作。


3
是否可以React.cloneElement()直接返回而无需将其放在<div>标签中?因为如果孩子是一个<span>(或其他)孩子,而我们想保留其标记元素类型怎么办?
adrianmc '16

1
如果是一个孩子,则可以不用包装纸,此解决方案仅适用于一个孩子,所以可以。
ThaJay

1
为我工作。不包含<div>也可以。
Crash Override's

4
如果您需要明确强制只接收一个孩子,那么可以这样做React.cloneElement(React.Children.only(this.props.children), {...this.props}),如果传递了一个以上的孩子,则会引发错误。然后,您无需包装div。
itsananderson

1
此答案可能会产生TypeError:循环对象值。除非您想要孩子的道具之一本身,否则请使用let {children, ...acyclicalProps} = this.props然后React.cloneElement(React.Children.only(children), acyclicalProps)
Parabolord

68

将道具传递给直接的孩子。

查看所有其他答案

通过上下文通过组件树传递共享的全局数据

上下文旨在共享可被视为React组件树(例如当前经过身份验证的用户,主题或首选语言)的“全局”数据。1个

免责声明:这是一个更新的答案,上一个使用了旧的上下文API

它基于消费者/提供原则。首先,创建您的上下文

const { Provider, Consumer } = React.createContext(defaultValue);

然后通过

<Provider value={/* some value */}>
  {children} /* potential consumers */
<Provider />

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

只要提供商的价值支柱发生变化,作为提供商的后代的所有消费者都将重新渲染。从Provider到其后代的Consumer的传播不受shouldComponentUpdate方法的约束,因此即使祖先组件无法进行更新,也要对Consumer进行更新。 1个

完整示例,半伪代码。

import React from 'react';

const { Provider, Consumer } = React.createContext({ color: 'white' });

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: { color: 'black' },
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

class Toolbar extends React.Component {
  render() {
    return ( 
      <div>
        <p> Consumer can be arbitrary levels deep </p>
        <Consumer> 
          {value => <p> The toolbar will be in color {value.color} </p>}
        </Consumer>
      </div>
    );
  }
}

1 https://facebook.github.io/react/docs/context.html


6
与接受的答案不同,即使在“父项”下包含其他元素,此选项也可以正常工作。这绝对是最好的答案。
Zaptree

6
道具!=上下文
Petr Peller

您不能依靠通过上下文传播的更改。尽可能使用道具。
ThaJay

1
也许我听不懂,但是说“上下文使道具可用”是不是错误的?当我上次使用上下文时,它是另外一件事(即this.context),它并没有神奇地将上下文与props合并。您必须有意识地设置和使用上下文,这是另一回事。
乔什(Josh)

您完全理解,这是不正确的。我已经编辑了答案。
Lyubomir

48

将道具传递给嵌套儿童

随着React 16.6的更新,您现在可以使用React.createContextcontextType

import * as React from 'react';

// React.createContext accepts a defaultValue as the first param
const MyContext = React.createContext(); 

class Parent extends React.Component {
  doSomething = (value) => {
    // Do something here with value
  };

  render() {
    return (
       <MyContext.Provider value={{ doSomething: this.doSomething }}>
         {this.props.children}
       </MyContext.Provider>
    );
  }
}

class Child extends React.Component {
  static contextType = MyContext;

  onClick = () => {
    this.context.doSomething(this.props.value);
  };      

  render() {
    return (
      <div onClick={this.onClick}>{this.props.value}</div>
    );
  }
}


// Example of using Parent and Child

import * as React from 'react';

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <Child value={2} />
      </Parent>
    );
  }
}

React.createContextReact.cloneElement案例无法处理嵌套组件的地方闪耀

class SomeComponent extends React.Component {

  render() {
    return (
      <Parent>
        <Child value={1} />
        <SomeOtherComp><Child value={2} /></SomeOtherComp>
      </Parent>
    );
  }
}

3
您能解释为什么=>函数是一种不好的做法吗?=>函数有助于绑定事件处理程序以获取this上下文
Kenneth Truong

@KennethTruong,因为每次渲染时都会创建一个函数。
itdoesntwork

9
@itdoesntwork是不正确的。它仅在创建类时创建一个新函数。它没有被渲染功能期间创建..
肯尼斯·张庭选

@KennethTruong reactjs.org/docs/faq-functions.html#arrow-function-in-render我以为您在谈论渲染中的箭头功能。
itdoesntwork

24

您可以使用React.cloneElement,最好在应用程序中开始使用它之前先了解其工作方式。它已引入中React v0.13,请继续阅读以获取更多信息,因此还有一些适合您的工作:

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

因此,请阅读React文档中的内容,以了解它们如何工作以及如何使用它们:

在React v0.13 RC2中,我们将引入一个新的API,类似于React.addons.cloneWithProps,并带有以下签名:

React.cloneElement(element, props, ...children);

与cloneWithProps不同,此新函数没有用于合并样式和className的任何神奇的内置行为,其原因与我们在transferPropsTo中没有该功能的原因相同。没有人可以确定魔术物品的完整清单是什么,这使得很难推理代码,并且当样式具有不同的签名时(例如即将发布的React Native),也很难重用。

React.cloneElement几乎等同于:

<element.type {...element.props} {...props}>{children}</element.type>

但是,与JSX和cloneWithProps不同,它还保留引用。这意味着,如果您得到一个带有裁判的孩子,就不会意外地从祖先那里偷走它。您将获得与新元素相同的引用。

一种常见的模式是在孩子身上绘制地图并添加新道具。报告了很多有关cloneWithProps丢失引用的问题,这使得推理代码变得更加困难。现在,使用与cloneElement相同的模式将按预期工作。例如:

var newChildren = React.Children.map(this.props.children, function(child) {
  return React.cloneElement(child, { foo: true })
});

注意:React.cloneElement(child,{ref:'newRef'})确实会覆盖ref,因此,除非您使用callback-refs,否则两个父对象仍然不可能对同一个孩子拥有ref。

这是进入React 0.13的关键功能,因为道具现在是不可变的。升级路径通常是克隆元素,但是这样做可能会丢失引用。因此,我们需要一个更好的升级途径。在Facebook上升级呼叫站点时,我们意识到我们需要这种方法。我们从社区得到了同样的反馈。因此,我们决定在最终发行版之前再制作一个RC,以确保我们能收到它。

我们计划最终弃用React.addons.cloneWithProps。我们还没有这样做,但这是一个很好的机会,可以开始考虑您自己的用途并考虑使用React.cloneElement。在实际删除发布版本之前,我们一定会先发布其中包含弃用通知的发布版本,因此无需立即采取措施。

这里更多...


17

允许您进行属性转移的最佳方法children就像一个函数

例:

export const GrantParent = () => {
  return (
    <Parent>
      {props => (
        <ChildComponent {...props}>
          Bla-bla-bla
        </ChildComponent>
      )}
    </Parent>
  )
}

export const Parent = ({ children }) => {
    const somePropsHere = { //...any }
    <>
        {children(somePropsHere)}
    </>
}

1
对我来说,这似乎比公认的答案更直接(并且对性能更好?)。
Shikyo

2
这要求子项具有功能,并且不适用于深度嵌套的组件
数字幻觉

@digitalillusion,我不明白这是什么意思nested components。React没有嵌套模式,只有组合。是的,子代必须是一个函数,没有任何冲突,因为这是有效的JSX子代。你能举个例子nesting components吗?
Nick Ovchinnikov,

1
您是对的,也可以处理深度嵌套的孩子<Parent>{props => <Nest><ChildComponent /></Nest>}</Parent>而不是(不工作)的情况,<Parent><Nest>{props => <ChildComponent />}</Nest></Parent>所以我同意这是最好的答案
数字错觉

尝试时,我收到以下消息:TypeError: children is not a function
Ryan Prentiss

6

我需要修复上面接受的答案,以使其使用答案而不是指针。在map函数范围内,函数未定义doSomething函数。

var Parent = React.createClass({
doSomething: function() {
    console.log('doSomething!');
},

render: function() {
    var that = this;
    var childrenWithProps = React.Children.map(this.props.children, function(child) {
        return React.cloneElement(child, { doSomething: that.doSomething });
    });

    return <div>{childrenWithProps}</div>
}})

更新:此修补程序适用于ECMAScript 5,在ES6中,不需要var that = this


13
或者只是使用bind()
加锁存

1
或使用绑定到词法范围的箭头函数,我更新了答案
Dominic'1

如果doSomething拿了一个物体,例如,doSomething: function(obj) { console.log(obj) }然后在孩子中您要this.props.doSomething(obj)登出该"obj"
怎么办

4
@ plus-我知道这很旧,但是在这里使用bind是一个糟糕的主意,bind创建了一个将上下文绑定到新函数的新函数。基本上是一个调用apply方法的函数。使用bind()在渲染功能将创建一个新的功能,每个渲染方法被调用的时间。
巴米耶

6

考虑一个或多个孩子的更清洁方式

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>

这对我不起作用,它给出了一个错误:未定义子级。
Deelux

@Deelux this.props.children代替孩子
Martin Dawson

这使孩子成为自己的孩子this.props。通常,我只建议使用特定的道具进行克隆,而不是整个shebang。
安迪

通过{...this.props}对我没有用,方法{...child.props}正确吗?
费利佩·奥古斯托

对于功能组件:React.Children.map(children, child => React.cloneElement(child, props))
vsync

5

没有一个答案解决拥有不是 React组件的子代的问题,例如文本字符串。解决方法可能是这样的:

// Render method of Parent component
render(){
    let props = {
        setAlert : () => {alert("It works")}
    };
    let childrenWithProps = React.Children.map( this.props.children, function(child) {
        if (React.isValidElement(child)){
            return React.cloneElement(child, props);
        }
          return child;
      });
    return <div>{childrenWithProps}</div>

}

5

您不再需要{this.props.children}。现在,您可以使用renderin 包装您的子组件,Route并像往常一样传递道具:

<BrowserRouter>
  <div>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/posts">Posts</Link></li>
      <li><Link to="/about">About</Link></li>
    </ul>

    <hr/>

    <Route path="/" exact component={Home} />
    <Route path="/posts" render={() => (
      <Posts
        value1={1}
        value2={2}
        data={this.state.data}
      />
    )} />
    <Route path="/about" component={About} />
  </div>
</BrowserRouter>

2
渲染道具现在已成为React(Reactjs.org/docs/render-props.html)的标准配置,值得考虑作为此问题的新接受答案。
伊恩·丹佛斯

19
这如何回答问题?
Maximo Dominguez

4

Parent.jsx:

import React from 'react';

const doSomething = value => {};

const Parent = props => (
  <div>
    {
      !props || !props.children 
        ? <div>Loading... (required at least one child)</div>
        : !props.children.length 
            ? <props.children.type {...props.children.props} doSomething={doSomething} {...props}>{props.children}</props.children.type>
            : props.children.map((child, key) => 
              React.cloneElement(child, {...props, key, doSomething}))
    }
  </div>
);

Child.jsx:

import React from 'react';

/* but better import doSomething right here,
   or use some flux store (for example redux library) */
export default ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}/>
);

和main.jsx:

import React from 'react';
import { render } from 'react-dom';
import Parent from './Parent';
import Child from './Child';

render(
  <Parent>
    <Child/>
    <Child value='1'/>
    <Child value='2'/>
  </Parent>,
  document.getElementById('...')
);

在此处查看示例:https//plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p = preview



4

如果您有多个孩子想要传递道具,则可以使用React.Children.map这样实现:

render() {
    let updatedChildren = React.Children.map(this.props.children,
        (child) => {
            return React.cloneElement(child, { newProp: newProp });
        });

    return (
        <div>
            { updatedChildren }
        </div>
    );
}

如果您的组件只有一个孩子,则不需要映射,您可以直接克隆cloneElement:

render() {
    return (
        <div>
            {
                React.cloneElement(this.props.children, {
                    newProp: newProp
                })
            }
        </div>
    );
}

3

根据文档 cloneElement()

React.cloneElement(
  element,
  [props],
  [...children]
)

克隆并使用element作为起点返回一个新的React元素。生成的元素将具有原始元素的道具,而新的道具将被浅层合并。新的孩子将替换现有的孩子。原始元素的key和ref将被保留。

React.cloneElement() 几乎等同于:

<element.type {...element.props} {...props}>{children}</element.type>

但是,它也保留引用。这意味着,如果您得到一个带有裁判的孩子,就不会意外地从祖先那里偷走它。您将获得与新元素相同的引用。

因此,cloneElement是用来为子项提供自定义道具的东西。但是,组件中可以有多个子代,您需要对其进行循环。其他答案建议您使用来映射它们React.Children.map。但是,React.Children.mapReact.cloneElement更改元素的键不同,附加键和附加键.$作为前缀。检查此问题以获取更多详细信息:React.Children.map中的React.cloneElement导致元素键更改

如果您希望避免这种情况,则应改用forEach类似

render() {
    const newElements = [];
    React.Children.forEach(this.props.children, 
              child => newElements.push(
                 React.cloneElement(
                   child, 
                   {...this.props, ...customProps}
                )
              )
    )
    return (
        <div>{newElements}</div>
    )

}

2

除了@and_rest答案,这是我克隆孩子并添加类的方式。

<div className="parent">
    {React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>

2

我认为渲染道具是处理这种情况的合适方法

通过重构父代码看起来像这样,可以让父对象提供子组件中使用的必要道具:

const Parent = ({children}) => {
  const doSomething(value) => {}

  return children({ doSomething })
}

然后,在子组件中,您可以通过以下方式访问父组件提供的功能:

class Child extends React {

  onClick() => { this.props.doSomething }

  render() { 
    return (<div onClick={this.onClick}></div>);
  }

}

现在,财务结构将如下所示:

<Parent>
  {(doSomething) =>
   (<Fragment>
     <Child value="1" doSomething={doSomething}>
     <Child value="2" doSomething={doSomething}>
    <Fragment />
   )}
</Parent>

2

方法1-克隆孩子

const Parent = (props) => {
   const attributeToAddOrReplace= "Some Value"
   const childrenWithAdjustedProps = React.Children.map(props.children, child =>
      React.cloneElement(child, { attributeToAddOrReplace})
   );

   return <div>{childrenWithAdjustedProps }</div>
}

方法2-使用可组合上下文

上下文允许您将prop传递给深子组件,而无需将其作为prop显式传递给中间的组件。

上下文具有缺点:

  1. 数据不是以常规方式流动-通过道具。
  2. 使用上下文会在消费者和提供者之间建立契约。理解和复制重用组件所需的需求可能会更加困难。

使用可组合的上下文

export const Context = createContext<any>(null);

export const ComposableContext = ({ children, ...otherProps }:{children:ReactNode, [x:string]:any}) => {
    const context = useContext(Context)
    return(
      <Context.Provider {...context} value={{...context, ...otherProps}}>{children}</Context.Provider>
    );
}

function App() {
  return (
      <Provider1>
            <Provider2> 
                <Displayer />
            </Provider2>
      </Provider1>
  );
}

const Provider1 =({children}:{children:ReactNode}) => (
    <ComposableContext greeting="Hello">{children}</ComposableContext>
)

const Provider2 =({children}:{children:ReactNode}) => (
    <ComposableContext name="world">{children}</ComposableContext>
)

const Displayer = () => {
  const context = useContext(Context);
  return <div>{context.greeting}, {context.name}</div>;
};

有点晚了,但是您可以在中解释这个符号{children}:{children:ReactNode}吗?
卡米尔

@camille,这是打字稿的东西。现在看,我只会用Javascript回答,即使我写Typescript,我也会做不同的事情。以后可能会对其进行编辑。
Ben Carp

1
@camille,基本上这意味着具有键的"children"值的类型为ReactNode
Ben Carp

1

最巧妙的方法是:

    {React.cloneElement(this.props.children, this.props)}

5
这是否不将this.props.children复制到孩子的this.props.children中?并实际上将孩子复制到自己身上?
Arshabh Agarwal

1

对于任何具有单个子元素的人,都应该这样做。

{React.isValidElement(this.props.children)
                  ? React.cloneElement(this.props.children, {
                      ...prop_you_want_to_pass
                    })
                  : null}

0

这是您所需要的吗?

var Parent = React.createClass({
  doSomething: function(value) {
  }
  render: function() {
    return  <div>
              <Child doSome={this.doSomething} />
            </div>
  }
})

var Child = React.createClass({
  onClick:function() {
    this.props.doSome(value); // doSomething is undefined
  },  
  render: function() {
    return  <div onClick={this.onClick}></div>
  }
})

4
不,我不想将包装器的内容限制为某些特定的内容。
加锁存

0

原因React.children不为我工作。这对我有用。

我只想给孩子增加一堂课。类似于更换道具

 var newChildren = this.props.children.map((child) => {
 const className = "MenuTooltip-item " + child.props.className;
    return React.cloneElement(child, { className });
 });

 return <div>{newChildren}</div>;

这里的窍门是React.cloneElement。您可以通过类似的方式传递任何道具


0

渲染道具是解决此问题的最准确方法。与其将子组件作为子道具传递给父组件,不如让父组件手动渲染子组件。Render是内置的props在react中,它带有功能参数。在此函数中,您可以让父组件使用自定义参数呈现任何内容。基本上,它的功能与儿童道具相同,但更可定制。

class Child extends React.Component {
  render() {
    return <div className="Child">
      Child
      <p onClick={this.props.doSomething}>Click me</p>
           {this.props.a}
    </div>;
  }
}

class Parent extends React.Component {
  doSomething(){
   alert("Parent talks"); 
  }

  render() {
    return <div className="Parent">
      Parent
      {this.props.render({
        anythingToPassChildren:1, 
        doSomething: this.doSomething})}
    </div>;
  }
}

class Application extends React.Component {
  render() {
    return <div>
      <Parent render={
          props => <Child {...props} />
        }/>
    </div>;
  }
}

Codepen示例


0

使用功能组件TypeError: Cannot add property myNewProp, object is not extensible时,尝试在上设置新属性时经常会出现错误props.children。有一种解决方法,就是克隆道具,然后用新的道具克隆孩子本身。

const MyParentComponent = (props) => {
  return (
    <div className='whatever'>
      {props.children.map((child) => {
        const newProps = { ...child.props }
        // set new props here on newProps
        newProps.myNewProp = 'something'
        const preparedChild = { ...child, props: newProps }
        return preparedChild
      })}
    </div>
  )
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.