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

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

其他回答

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

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

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>

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

用新道具克隆孩子

你可以使用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 >

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

你可以在parent中传递子元素作为道具。

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

方法一——克隆儿童

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

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

完整的演示

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