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

问题描述 投票:740回答:23

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

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

javascript reactjs react-jsx
23个回答
817
投票

您可以使用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将道具传递给儿童。


4
投票

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

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

3
投票

这些答案都没有解决让孩子成为非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>

}

2
投票

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


2
投票

根据cloneElement()的文件

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

使用element作为起点克隆并返回一个新的React元素。结果元素将具有原始元素的道具,新道具以浅层方式合并。新的孩子将取代现有的孩子。 key和ref将保留原始元素。

React.cloneElement()几乎相当于:

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

但是,它也保留了refs。这意味着,如果你的孩子有一个参考,你不会意外地从你的祖先窃取它。您将获得与新元素相同的参考。

因此,cloneElement将用于为子项提供自定义道具。但是,组件中可能有多个子项,您需要循环它。其他答案建议您使用React.Children.map映射它们。然而,与React.Children.map不同的React.cloneElement更改了Element附加的键和额外的.$作为前缀。查看此问题以获取更多详细信息: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>
    )

}

2
投票

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

Function as Child Components


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

1
投票

最简洁的方法:

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

1
投票

继@and_rest回答之后,这就是我克隆孩子并添加一个类的方法。

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

1
投票

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

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

1
投票

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

您让Parent提供子组件中使用的必要道具,通过重构Parent代码来查找如下所示:

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>

337
投票

要获得更简洁的方法,请尝试:

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

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


1
投票

Method 1 - clone children

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

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

Method 2 - use a mapChildrenWithProps function

这是一种常见的模式,因此我们可以使用提供稍微简单的API的函数。

const mapChildrenWithProps = (children, fromOldPropsToNewPropsFunction) => 
   React.Children.map(children, child =>
      React.cloneElement(child, fromOldPropsToNewPropsFunction(child.props))
   )

使用变得稍微简单一些

const Parent = (props) => {
   const attributeToAddOrReplace= "Some Value"
   const childrenWithAdjustedProps = mapChildrenWithProps( 
      props.children, (oldProps) => ({...oldProps, attributeToAddOrReplace})
   )

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

这种模式是

Method 3 - use composable context

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


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

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


68
投票

试试这个

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

使用react-15.1对我有用。


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


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

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丢失引用的问题,因此很难推断出你的代码。现在遵循与cloneElement相同的模式将按预期工作。例如:

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

注意:React.cloneElement(child,{ref:'newRef'})DOES会覆盖ref,因此除非使用callback-refs,否则两个父级仍然无法对同一个子进行引用。

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

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

更多here ...


11
投票

允许您进行财产转移的最佳方式是children就像一个功能

例:

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

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

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>

4
投票

我需要修复上面接受的答案,使其工作,而不是使用此指针。这在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 = this

© www.soinside.com 2019 - 2024. All rights reserved.