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

时间:2015-09-03 08:47:25

标签: javascript reactjs react-jsx

我正在尝试找到一种正确的方法来定义一些可以通用方式使用的组件:

<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}定义包装器组件,如何将一些属性传递给其所有子组件?

28 个答案:

答案 0 :(得分:806)

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

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

class Parent extends React.PureComponent {
  doSomething = (value) => {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    const childrenWithProps = React.Children.map(this.props.children, child =>
      React.cloneElement(child, { doSomething: this.doSomething })
    );

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

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

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

你应该使用渲染道具吗?

考虑render props是否可以更优雅地解决您的问题。渲染道具是一种通过具有回调的道具将数据传递给孩子的方法。 Parent组件不需要关心它的子节点(坏父节点!),但仍然可以呈现并传递它们信息。

在您的父组件中,您将使用prop:

渲染子项
// In your parent render:
{this.props.render({ son: 'Do you homework!', daughter: 'Go to bed!' })}

您现在可以接受孩子(或孩子)中的数据,但控制它的渲染方式:

<Parent render={data => (
  <>
    <Son messageFromMyParent={data.son} />
    <Daughter messageFromMyParent={data.daughter} />
  </>
)}/>

请注意,我在这里使用React.Fragment<>...</>)来渲染多个项目。只要你给孩子们一个key道具,你也可以使用一个数组。

如果您使用children道具,则可以使用更简洁的语法:

{this.props.children('Go to bed children!')}

<Parent>
  {message => (
    <Child messageFromMyParent={message} />
  )}
</Parent>

这是有效的,因为父内部的内容是children,它可以是回调函数。

深层嵌套的孩子怎么样?

在这种情况下你可以:

  1. 将道具传递给多个组件(仔细考虑这个,它会导致紧耦合)
  2. 使用reduxeasy-peasy等外部解决方案作为功能组件,将数据放入全局商店。
  3. 使用内置的Context API,允许任何下游组件消费&#34;来自最近的&#34;提供商&#34;。
  4. 的背景

答案 1 :(得分:334)

要想稍微更清洁一点,请尝试:

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

注意:这只有在有一个子节点时才有效,并且它是一个有效的React元素。

答案 2 :(得分:67)

试试这个

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

使用react-15.1对我有用。

答案 3 :(得分:54)

将道具传递给直接儿童。

查看所有其他答案

通过组件树通过context

传递共享的全局数据
  

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

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

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

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

然后使用via

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

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

作为提供商后代的所有消费者将在提供者的价值道具发生变化时重新呈现。 从Provider到其后代使用者的传播不受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

答案 4 :(得分:38)

将道具传递给嵌套儿童

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

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.createContext 闪耀 React.cloneElement 案例无法处理嵌套组件

class SomeComponent extends React.Component {

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

答案 5 :(得分:18)

您可以使用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不同,它还保留了refs。这个   意味着如果你的孩子有一个参考,你不会意外   从你的祖先那里偷走它。您将获得相同的参考   你的新元素。

     

一种常见的模式是映射您的孩子并添加新的道具。   关于cloneWithProps丢失ref的报道很多,   使你的代码更难以推理。现在跟着一样   cloneElement的模式将按预期工作。例如:

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

注意:React.cloneElement(child,{ref:'newRef'})DOES覆盖   参考,所以两个父母仍然不可能有一个参考   同一个孩子,除非你使用callback-refs。

     

这是进入React 0.13的一个关键功能,因为现在道具   不可改变的。升级路径通常是克隆元素,但是通过   这样做你可能会失去裁判。因此,我们需要更好的升级   路径在这里。当我们在Facebook上升级callites时,我们意识到了这一点   我们需要这种方法。我们从社区获得了相同的反馈。   因此,我们决定在最终发布前制作另一个RC   确保我们得到这个。

     

我们计划最终弃用React.addons.cloneWithProps。不是   这样做,但这是一个开始思考的好机会   你自己的用途,并考虑使用React.cloneElement。我们会的   我们确实在发布之前发布了弃用通知   删除它,所以不需要立即采取行动。

更多here ...

答案 6 :(得分:10)

允许进行属性转移的最佳方法是像函数一样children

示例:

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

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

答案 7 :(得分:5)

您不再需要{this.props.children}。现在,您可以使用render中的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>

答案 8 :(得分:4)

我需要修复上面接受的答案才能使用那个代替这个指针。地图功能范围内的未定义 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

答案 9 :(得分:4)

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

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

答案 10 :(得分:2)

根据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.cloneElement inside React.Children.map is causing element keys to change

如果你想避免它,你应该去forEach函数,如

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

}

答案 11 :(得分:2)

使用功能组件时,尝试在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>
  )
}

答案 12 :(得分:2)

如果你想要pass props到多个孩子,你可以这样做,使用React.Children.map:

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

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

如果您的组件只有一个孩子,那么就不需要映射,您可以立即克隆元素:

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

答案 13 :(得分:2)

也许你也可以找到有用的这个功能,虽然很多人都认为这是一个反模式,如果你知道自己在做什么并设计好你的解决方案,它仍然可以使用。

Function as Child Components

答案 14 :(得分:2)

没有一个答案解决了生成 NOT 的孩子的问题。反应组件,例如文本字符串。解决方法可能是这样的:

// 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>

}

答案 15 :(得分:1)

除了@and_rest回答之外,这是我克隆孩子并添加一个类的方法。

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

答案 16 :(得分:1)

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

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

答案 17 :(得分:1)

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

答案 18 :(得分:1)

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

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

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

  return children({ doSomething })
}

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

class Child extends React {

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

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

}

现在,fianl的结构将如下所示:

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

答案 19 :(得分:1)

方法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-使用可组合上下文

Context允许您将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>;
};

答案 20 :(得分:1)

受到以上所有答案的启发,这就是我所做的。我正在传递一些道具,例如一些数据和一些组件。

import React from "react";

const Parent = ({ children }) => {
  const { setCheckoutData } = actions.shop;
  const { Input, FieldError } = libraries.theme.components.forms;

  const onSubmit = (data) => {
    setCheckoutData(data);
  };

  const childrenWithProps = React.Children.map(
    children,
    (child) =>
      React.cloneElement(child, {
        Input: Input,
        FieldError: FieldError,
        onSubmit: onSubmit,
      })
  );

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

答案 21 :(得分: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>
  }
})

答案 22 :(得分: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。你可以以类似的方式传递任何道具

答案 23 :(得分:0)

Render props是解决此问题的最准确方法。与其将子组件作为子道具传递给父组件,不如让父组件手动渲染子组件。 Render 是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>;
  }
}

Example at codepen

答案 24 :(得分:0)

最简洁的方法:

var workbook = new XSSFWorkbook();
...
using (var fileData = new FileStream(@"path\filename.xlsx", FileMode.Create))
{
  workbook.Write(fileData);
}

答案 25 :(得分:0)

我是在研究类似需求时来到这篇文章的,但是我觉得克隆解决方案非常流行,过于原始,使我的注意力从功能上转移了。

我在React文档Higher Order Components

中找到了一篇文章

这是我的样品:

import React from 'react';

const withForm = (ViewComponent) => {
    return (props) => {

        const myParam = "Custom param";

        return (
            <>
                <div style={{border:"2px solid black", margin:"10px"}}>
                    <div>this is poc form</div>
                    <div>
                        <ViewComponent myParam={myParam} {...props}></ViewComponent>
                    </div>
                </div>
            </>
        )
    }
}

export default withForm;


const pocQuickView = (props) => {
    return (
        <div style={{border:"1px solid grey"}}>
            <div>this is poc quick view and it is meant to show when mouse hovers over a link</div>
        </div>
    )
}

export default withForm(pocQuickView);

对我来说,我发现了一种实现高阶组件模式的灵活解决方案。

当然,这取决于功能,但是如果其他人正在寻找类似的要求,则很好,这比依赖于原始级别的反应代码(如克隆)要好得多。

我积极使用的其他模式是容器模式。确实读过,那里有很多文章。

答案 26 :(得分:0)

这个答案是w.r.t. React v17.x...

children 用作函数并将 props 作为 render props 模式传递给它,如下所示:-

 <ParentComponent {...anyAdditionalProps}>
   {
     (actualPropsToPass) => <ChildComponent>{children(actualPropsToPass)}</ChildComponent>
   }
 </ParentComponent>

只要确保实际要投影的内容必须像渲染道具模式中的函数一样添加,以适应作为子函数中的 prop 传递的参数。

答案 27 :(得分:0)

有很多方法可以做到这一点。

您可以将孩子作为道具传递给父母。

示例 1

function Parent({ChildElement}){
   return <ChildElement propName={propValue} />
}

return <Parent ChildElement={ChildComponent}/>

将子项作为函数传递

示例 2

function Parent({children}){
   return children({className: "my_div"})
}

OR

function Parent({children}){
   let Child = children
   return <Child className='my_div' />
}

function Child(props){
  return <div {...props}></div>
}

export <Parent>{props => <Child {...props} />}</Parent>