繁体   English   中英

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

[英]How to pass props to {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}定义包装组件时,如何将某些属性传递给它的所有子组件?

用新道具克隆孩子

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

 const Child = ({ doSomething, value }) => ( <button onClick={() => doSomething(value)}>Click Me</button> ); function Parent({ children }) { function doSomething(value) { console.log("doSomething called by child with value:", value); } 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, { doSomething }); } return child; }); return <div>{childrenWithProps}</div> } function App() { return ( <Parent> <Child value={1} /> <Child value={2} /> </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>

将孩子作为函数调用

或者,您可以使用render props 将 props传递给孩子。 在这种方法中,孩子(可以是children或任何其他道具名称)是一个可以接受您想要传递的任何参数并返回孩子的函数:

 const Child = ({ doSomething, value }) => ( <button onClick={() => doSomething(value)}>Click Me</button> ); function Parent({ children }) { function doSomething(value) { console.log("doSomething called by child with value:", value); } // Note that children is called as a function and we can pass args to it. return <div>{children(doSomething)}</div> } function App() { // doSomething is the arg we passed in Parent, which // we now pass through to Child. return ( <Parent> {doSomething => ( <React.Fragment> <Child doSomething={doSomething} value={1} /> <Child doSomething={doSomething} value={2} /> </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>

如果您愿意,也可以返回一个数组,而不是<React.Fragment>或简单的<>

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

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

将道具传递给指导孩子。

查看所有其他答案

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

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

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

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

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

然后通过

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

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

每当 Provider 的 value 属性发生变化时,所有作为 Provider 后代的 Consumer 都会重新渲染。 从 Provider 到其后代 Consumer 的传播不受 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

将道具传递给嵌套的孩子

随着对 React Hooks 的更新,您现在可以使用React.createContextuseContext

import * as React from 'react';

// React.createContext accepts a defaultValue as the first param
const MyContext = React.createContext(); 

functional Parent(props) {
  const doSomething = (value) => {
    // Do something here with value
  };

  return (
     <MyContext.Provider value={{ doSomething }}>
       {this.props.children}
     </MyContext.Provider>
  );
}
 
function Child(props: { value: number }) {
  const myContext = React.useContext(MyContext);

  const onClick = () => {
    myContext.doSomething(props.value);
  }

  return (
    <div onClick={onClick}>{this.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.createContextReact.cloneElement案例无法处理嵌套组件的情况下大放异彩

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

允许您进行财产转移的最佳方法是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.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 不同,这个新函数没有任何用于合并 style 和 className 的神奇内置行为,原因与我们在 transferPropsTo 中没有该功能的原因相同。 没有人确切知道魔法事物的完整列表是什么,这使得当样式具有不同的签名时(例如在即将到来的 React Native 中)难以推理代码并且难以重用。

React.cloneElement 几乎等同于:

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

但是,与 JSX 和 cloneWithProps 不同的是,它还保留了 refs。 这意味着如果你得到一个带有 ref 的孩子,你不会不小心从你的祖先那里偷走它。 您将获得附加到新元素的相同 ref。

一种常见的模式是映射你的孩子并添加一个新的道具。 有很多关于 cloneWithProps 丢失 ref 的问题报告,这使得你的代码更难推理。 现在遵循与 cloneElement 相同的模式将按预期工作。 例如:

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

注意:React.cloneElement(child, { ref: 'newRef' }) 确实会覆盖 ref,因此两个父级仍然不可能拥有同一个子级的 ref,除非您使用回调引用。

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

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

更多在这里...

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

没有一个答案解决了拥有不是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>

}

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

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

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

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

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

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

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

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

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

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

作为子组件的功能

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

您可以让 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>);
  }

}

现在 fianl 结构将如下所示:

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

最巧妙的方法是:

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

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

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

根据cloneElement()的文档

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

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

React.cloneElement()几乎等同于:

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

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

所以 cloneElement 是你用来为孩子们提供自定义道具的东西。 但是,组件中可以有多个子组件,您需要对其进行循环。 其他答案建议您使用React.Children.map映射它们。 然而,与React.Children.map不同的React.cloneElement改变了 Element appending 的键和额外的.$作为前缀。 检查此问题以获取更多详细信息: React.Children.map 内的 React.cloneElement 导致元素键发生变化

如果你想避免它,你应该改为使用forEach函数,如

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

}

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

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

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

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

使用功能组件时,在尝试在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 newProps
        newProps.myNewProp = 'something'
        const preparedChild = { ...child, props: newProps }
        return preparedChild
      })}
    </div>
  )
}

这是你要求的吗?

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

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

我在反应文档高阶组件中找到了一篇文章

这是我的示例:

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

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

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

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

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

某些原因 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 您可以以类似的方式传递任何道具

渲染道具是解决此问题的最准确方法。 与其将子组件作为子 props 传递给父组件,不如让父组件手动渲染子组件。 Render是 react 内置的 props,它带有函数参数。 在此函数中,您可以让父组件使用自定义参数呈现您想要的任何内容。 基本上它与子道具做同样的事情,但它更可定制。

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 上的示例

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

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

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

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

示例 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/@justynazet/passing-props-to-props-children-using-react-cloneelement-and-render-props-pattern-896da70b24f6

这个答案是 wrt React v17.x ......

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM