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

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

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

825013 次浏览

这是你要求的吗?

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

用新道具克隆儿童

您可以使用#0遍历子元素,然后使用#1使用新道具(浅合并)克隆每个元素。

请参阅代码注释,为什么我不推荐这种方法。

const Child = ({ childName, sayHello }) => (<button onClick={() => sayHello(childName)}>{childName}</button>);
function Parent({ children }) {// We pass this `sayHello` function into the child elements.function sayHello(childName) {console.log(`Hello from ${childName} the child`);}
const childrenWithProps = React.Children.map(children, child => {// Checking isValidElement is the safe way and avoids a// typescript error too.if (React.isValidElement(child)) {return React.cloneElement(child, { sayHello });}return child;});
return <div>{childrenWithProps}</div>}
function App() {// This approach is less type-safe and Typescript friendly since it// looks like you're trying to render `Child` without `sayHello`.// It's also confusing to readers of this code.return (<Parent><Child childName="Billy" /><Child childName="Bob" /></Parent>);}
ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script><script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script><div id="container"></div>

调用子函数

或者,您可以通过道具渲染将道具传递给子级。在这种方法中,子级(可以是children或任何其他道具名称)是一个函数,它可以接受您想要传递的任何参数并返回实际的子级:

const Child = ({ childName, sayHello }) => (<button onClick={() => sayHello(childName)}>{childName}</button>);
function Parent({ children }) {function sayHello(childName) {console.log(`Hello from ${childName} the child`);}
// `children` of this component must be a function// which returns the actual children. We can pass// it args to then pass into them as props (in this// case we pass `sayHello`).return <div>{children(sayHello)}</div>}
function App() {// sayHello is the arg we passed in Parent, which// we now pass through to Child.return (<Parent>{(sayHello) => (<React.Fragment><Child childName="Billy" sayHello={sayHello} /><Child childName="Bob" sayHello={sayHello} /></React.Fragment>)}</Parent>);}
ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script><script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script><div id="container"></div>

我需要修复上面接受的答案,使其使用而不是这个指针。map函数范围内的这个没有定义做点什么函数。

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=这个

对于一个稍微干净的方法来做到这一点,尝试:

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

编辑:要与多个单独的子级一起使用(子级本身必须是一个组件),您可以这样做。在16.8.6中测试

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

试试这个

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

它使用react-15.1为我工作。

https://reactjs.org/docs/jsx-in-depth.html#spread-attributes中建议使用{...this.props}

最巧妙的方法做到这一点:

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

通过道具来指导孩子。

查看所有其他答案

通过背景通过组件树传递共享的全局数据

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传播到它的后代消费者不受制于showdComponentUpdate方法,因此即使祖先组件退出更新,消费者也会更新。 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>);}}

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

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

<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={() => (<Postsvalue1={1}value2={2}data={this.state.data}/>)} /><Route path="/about" component={About} /></div></BrowserRouter>

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

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

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

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

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

你可以使用React.cloneElement,在你开始在应用程序中使用它之前最好知道它是如何工作的。它在React v0.13中介绍,继续阅读以获取更多信息,所以一些东西与你的工作一起:

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

因此,请从React留档中获取线条,以了解它是如何工作的以及如何使用它们:

在React v0.13 RC2中,我们将引入一个新的API,类似于React.addons.clone与道具,用这个签名:

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

与cloneWith Props不同,这个新功能没有任何魔力出于同样的原因,合并样式和类名称的内置行为我们没有transferPropsTo的那个功能。没有人知道是什么魔法事物的完整列表,这使得它难以推理代码并且在样式时难以重用有不同的签名(例如在即将到来的React Native中)。

React.clone元素几乎等同于:

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

但是,与JSX和cloneWith Props不同,它还保留了引用。这这意味着如果你有个带裁判的孩子你就不会意外地从你的祖先那里偷来。你会得到同样的裁判你的新元素。

一种常见的模式是映射你的孩子并添加一个新道具。有很多关于克隆道具失去裁判的报道,使其更难推理你的代码。现在遵循相同的使用cloneElement的模式将按预期工作。例如:

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

注意:React.clone元素(child,{ref:'newRef'})会覆盖所以它仍然是不可能的两个父母有一个参考

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

我们计划最终弃用React.addons.clone但这是个开始思考的好机会你自己的用途,并考虑使用React.clone元素代替。我们将确保在我们实际发布之前发布一个带有弃用通知的版本删除它,所以不需要立即采取行动。

更多这里

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

// Render method of Parent componentrender(){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>
}

出于某种原因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.clone元素。您可以以类似的方式传递任何道具

留档cloneElement()

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

克隆并返回一个新的React元素,使用元素作为起始元素点。结果元素将具有原始元素的道具随着新道具的浅融合。新的孩子将取代现有的子元素。原始元素的key和ref将是保存。

React.cloneElement()几乎等于:

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

但是,它也保留了裁判。这意味着如果你得到一个孩子上面有裁判,你不会意外地从你的祖先那里偷走它。您将获得相同的ref附加到您的新元素。

因此,您将使用cloneElement为子级提供自定义道具。然而,组件中可以有多个子级,您需要循环遍历它。其他答案建议您使用React.Children.map映射它们。然而,React.Children.mapReact.cloneElement不同,更改了元素附加的键和额外的.$作为前缀。检查此问题以获取更多详细信息:React.Children.map中的React.clone元素导致元素键更改

如果你想避免它,你应该选择forEach函数,比如

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

也许您还可以找到有用的此功能,尽管许多人认为这是一种反模式,但如果您知道自己在做什么并设计好解决方案,它仍然可以使用。

作为子组件的函数

如果你有多个想要传球道具的孩子,你可以这样做,使用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>);}

将道具传递给嵌套的孩子

随着React Hooks的更新,您现在可以使用React.create背景用户信息

import * as React from 'react';
// React.createContext accepts a defaultValue as the first paramconst MyContext = React.createContext();
functional Parent(props) {const doSomething = React.useCallback((value) => {// Do something here with value}, []);
return (<MyContext.Provider value=\{\{ doSomething }}>{props.children}</MyContext.Provider>);} 
function Child(props: { value: number }) {const myContext = React.useContext(MyContext);
const onClick = React.useCallback(() => {myContext.doSomething(props.value);}, [props.value, myContext.doSomething]);
return (<div onClick={onClick}>{props.value}</div>);}

// Example of using Parent and Child
import * as React from 'react';
function SomeComponent() {return (<Parent><Child value={1} /><Child value={2} /></Parent>);}

React.create背景照亮了React.clone元素 case无法处理嵌套组件的地方

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

对于任何拥有单个子元素的人来说,这应该做到这一点。

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

允许您进行属性传输的最佳方法是children,就像函数模式一样https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9

代码段:https://stackblitz.com/edit/react-fcmubc

示例:

const Parent = ({ children }) => {const somePropsHere = {style: {color: "red"}// any other props here...}return children(somePropsHere)}
const ChildComponent = props => <h1 {...props}>Hello world!</h1>
const App = () => {return (<Parent>{props => (<ChildComponent {...props}>Bla-bla-bla</ChildComponent>)}</Parent>)}

渲染道具是解决这个问题最准确的方法。不是将子组件作为子道具传递给父组件,而是让父组件手动渲染子组件。渲染是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的例子

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

您可以让父级提供子组件中使用的必要道具,通过重构父级代码来查看如下内容:

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>

方法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>;};

当使用功能组件时,当尝试在props.children上设置新属性时,您经常会得到TypeError: Cannot add property myNewProp, object is not extensible错误。可以通过克隆道具然后使用新道具克隆子本身来解决此问题。

const MyParentComponent = (props) => {return (<div className='whatever'>{props.children.map((child) => {const newProps = { ...child.props }// set new props here on newPropsnewProps.myNewProp = 'something'const preparedChild = { ...child, props: newProps }return preparedChild})}</div>)}

我在研究类似需求时来到这篇文章,但我觉得克隆解决方案非常受欢迎,太原始了,把我的注意力从功能上移开了。

我在React Document高阶组件中找到了一篇文章

这是我的样本:

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

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

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

我积极使用的其他模式是容器模式。做阅读它,有很多文章在那里。

从上面的答案中得到了启发,这就是我所做的。我传递了一些道具,比如一些数据和一些组件。

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

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

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

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

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

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

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

例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 = childrenreturn <Child className='my_div' />}
function Child(props){return <div {...props}></div>}
export <Parent>{props => <Child {...props} />}</Parent>

我确实努力让列出的答案起作用,但失败了。最终,我发现问题在于正确设置了父子关系。仅仅将组件嵌套在其他组件中并不意味着存在父子关系。

例1.亲子关系;

function Wrapper() {return (<div><OuterComponent><InnerComponent /></OuterComponent></div>);}function OuterComponent(props) {return props.children;}function InnerComponent() {return <div>Hi! I'm in inner component!</div>;}export default Wrapper;

示例2.嵌套组件:

function Wrapper() {return (<div><OuterComponent /></div>);}function OuterComponent(props) {return <InnerComponent />}function InnerComponent() {return <div>Hi! I'm in inner component!</div>;}export default Wrapper;

如上所述,道具传递在示例1中起作用。

下面的文章解释了它https://medium.com//传递道具到道具儿童使用反应克隆元素和渲染道具模式896da70b24f6

如果有人想知道如何在有一个或多个子节点的TypeScript中正确执行此操作。我正在使用uuid库为子元素生成唯一的键属性,当然,如果您只克隆一个元素,则不需要这些属性。

export type TParentGroup = {value?: string;children: React.ReactElement[] | React.ReactElement;};
export const Parent = ({value = '',children,}: TParentGroup): React.ReactElement => (<div className={styles.ParentGroup}>{Array.isArray(children)? children.map((child) =>React.cloneElement(child, { key: uuidv4(), value })): React.cloneElement(children, { value })}</div>);

如您所见,此解决方案负责呈现一个数组或单个ReactElement,甚至允许您根据需要将属性传递给子组件。

这是我的版本,适用于单个,多个和无效的孩子。

const addPropsToChildren = (children, props) => {const addPropsToChild = (child, props) => {if (React.isValidElement(child)) {return React.cloneElement(child, props);} else {console.log("Invalid element: ", child);return child;}};if (Array.isArray(children)) {return children.map((child, ix) =>addPropsToChild(child, { key: ix, ...props }));} else {return addPropsToChild(children, props);}};

使用示例:

https://codesandbox.io/s/loving-mcclintock-59emq?file=/src/ChildVsChildren.jsx: 0-1069

在我的情况下React.clone元素()给我带来了许多问题,我在TypeScript中使用功能组件,所以我使用儿童(道具)作为将道具传递给我的子元素的一种方式。同样,我的情况非常独特,我必须向父组件传递一个属性,然后根据该属性将某些道具传递给子组件。这可以在这里的一个简单示例中看到代码沙盒

<强>App.tsx

import "./styles.css";import Parent from "./Parent";
export default function App() {return (<><Parent title={"With div wrapper"}>{({ title }) => <h1>{title}</h1>}</Parent>
<Parent>{({ title }) => <h1>{title === undefined && "this is undefined"}</h1>}</Parent></>);}

<强>Parent.tsx

export interface ChildrenProps {title?: string;}
interface ParentWrapperProps {children: (title: ChildrenProps) => JSX.Element;title?: string;}
const ParentWrapper: React.FC<ParentWrapperProps> = ({ children, title }) => {return title ? (<div>{children({ title: title })}</div>) : (<>{children({ title: undefined })}</>);};
export default ParentWrapper;