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

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


当前回答

用新道具克隆孩子

你可以使用React。子元素遍历子元素,然后使用React.cloneElement用新道具(浅合并)克隆每个元素。

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

const Child = ({childName, sayHello}) => ( <button onClick={() => sayHello(childName)}>{childName}</button> ); 函数父({children}) { //我们将sayHello函数传递给子元素。 函数sayHello(childName) { console.log(' Hello from ${childName} the child '); } const childrenWithProps = React.Children。映射(children, child => { //检查isValidElement是安全的方法,可以避免 // typescript错误。 if (React.isValidElement(child)) { 返回的反应。cloneElement(child, {sayHello}); } 返回的孩子; }); 返回< div > {childrenWithProps} < / div > } 函数App() { //这种方法不太类型安全,Typescript不友好 //看起来像你试图渲染' Child '没有' sayHello '。 //这也会让代码的读者感到困惑。 回报( <父> <Child childName="Billy" /> <Child childName="Bob" /> 父> < / ); } ReactDOM。render(<App />, document.getElementById("container")); < script src = " https://unpkg.com/react@17 umd格式/ react.production.min.js " > < /脚本> < script src = " https://unpkg.com/react-dom@17 umd格式/ react-dom.production.min.js " > < /脚本> < div id = "容器" > < / div >

将子函数作为函数调用

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

const Child = ({childName, sayHello}) => ( <button onClick={() => sayHello(childName)}>{childName}</button> ); 函数父({children}) { 函数sayHello(childName) { console.log(' Hello from ${childName} the child '); } //这个组件的children必须是一个函数 //返回实际的子节点。我们可以通过 //它的参数然后传递给他们作为道具(在这个 //如果我们传递' sayHello ')。 返回< div >{孩子(sayHello)} < / div > } 函数App() { // sayHello是我们在Parent中传递的参数 //我们现在传递到Child。 回报( <父> {(sayHello) => ( <反应。片段> <Child childName="Billy" sayHello={sayHello} /> <Child childName="Bob" sayHello={sayHello} /> < /反应。片段> )} 父> < / ); } ReactDOM。render(<App />, document.getElementById("container")); < script src = " https://unpkg.com/react@17 umd格式/ react.production.min.js " > < /脚本> < script src = " https://unpkg.com/react-dom@17 umd格式/ react-dom.production.min.js " > < /脚本> < div id = "容器" > < / div >

其他回答

用新道具克隆孩子

你可以使用React。子元素遍历子元素,然后使用React.cloneElement用新道具(浅合并)克隆每个元素。

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

const Child = ({childName, sayHello}) => ( <button onClick={() => sayHello(childName)}>{childName}</button> ); 函数父({children}) { //我们将sayHello函数传递给子元素。 函数sayHello(childName) { console.log(' Hello from ${childName} the child '); } const childrenWithProps = React.Children。映射(children, child => { //检查isValidElement是安全的方法,可以避免 // typescript错误。 if (React.isValidElement(child)) { 返回的反应。cloneElement(child, {sayHello}); } 返回的孩子; }); 返回< div > {childrenWithProps} < / div > } 函数App() { //这种方法不太类型安全,Typescript不友好 //看起来像你试图渲染' Child '没有' sayHello '。 //这也会让代码的读者感到困惑。 回报( <父> <Child childName="Billy" /> <Child childName="Bob" /> 父> < / ); } ReactDOM。render(<App />, document.getElementById("container")); < script src = " https://unpkg.com/react@17 umd格式/ react.production.min.js " > < /脚本> < script src = " https://unpkg.com/react-dom@17 umd格式/ react-dom.production.min.js " > < /脚本> < div id = "容器" > < / div >

将子函数作为函数调用

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

const Child = ({childName, sayHello}) => ( <button onClick={() => sayHello(childName)}>{childName}</button> ); 函数父({children}) { 函数sayHello(childName) { console.log(' Hello from ${childName} the child '); } //这个组件的children必须是一个函数 //返回实际的子节点。我们可以通过 //它的参数然后传递给他们作为道具(在这个 //如果我们传递' sayHello ')。 返回< div >{孩子(sayHello)} < / div > } 函数App() { // sayHello是我们在Parent中传递的参数 //我们现在传递到Child。 回报( <父> {(sayHello) => ( <反应。片段> <Child childName="Billy" sayHello={sayHello} /> <Child childName="Bob" sayHello={sayHello} /> < /反应。片段> )} 父> < / ); } ReactDOM。render(<App />, document.getElementById("container")); < script src = " https://unpkg.com/react@17 umd格式/ react.production.min.js " > < /脚本> < script src = " https://unpkg.com/react-dom@17 umd格式/ react-dom.production.min.js " > < /脚本> < div id = "容器" > < / div >

如果有人想知道如何在有一个或多个子节点的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的数组或单个ReactElement,甚至允许您根据需要将属性传递给子组件。

给孩子们传递道具。

查看所有其他答案

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

Context被设计用来共享React组件树的“全局”数据,比如当前认证的用户、主题或首选语言。1

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

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

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

然后使用via

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

and

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

当提供者的值道具发生变化时,提供者的所有后代消费者都将重新呈现。从提供者到其后代消费者的传播不受shouldComponentUpdate方法的约束,因此即使在祖先组件退出更新时,消费者也会被更新。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

下面是我的版本,适用于单个、多个和无效的子节点。

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