![](/img/trans.png)
[英]How to pass props through this.props.children() not 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>
);
}
}
随着对 React Hooks 的更新,您现在可以使用React.createContext和useContext 。
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.createContext在React.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>
const Parent = (props) => {
const attributeToAddOrReplace= "Some Value"
const childrenWithAdjustedProps = React.Children.map(props.children, child =>
React.cloneElement(child, { attributeToAddOrReplace})
);
return <div>{childrenWithAdjustedProps }</div>
}
Context 允许您将 prop 传递给深层子组件,而无需将其作为 prop 显式传递给它们之间的组件。
上下文有缺点:
使用可组合的上下文
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>;
}
}
有很多方法可以做到这一点。
您可以将孩子作为道具传递给父母。
示例 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 的情况下有效。
这个答案是 wrt React v17.x ......
将children
用作函数并将道具作为render props
模式传递给它,如下所示: -
<ParentComponent {...anyAdditionalProps}>
{
(actualPropsToPass) => <ChildComponent>{children(actualPropsToPass)}</ChildComponent>
}
</ParentComponent>
只需确保必须像渲染道具模式中的函数一样添加实际要投影的内容,以适应子函数中作为prop
传递的参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.