How to pass props to {this.props.children}
我正在尝试找到正确的方法来定义一些可以通用方式使用的组件:
1 2 3 4 | <Parent> <Child value="1"> <Child value="2"> </Parent> |
当然,在父组件和子组件之间进行渲染有一个逻辑,你可以想象
对于问题,这是一个虚拟实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var Parent = React.createClass({ doSomething: function(value) { }, render: function() { return ({this.props.children}); } }); var Child = React.createClass({ onClick: function() { this.props.doSomething(this.props.value); // doSomething is undefined }, render: function() { return (); } }); |
问题是每当你使用
用新道具克隆孩子
您可以使用React.Children迭代子项,然后使用React.cloneElement克隆每个元素和新的道具(浅合并),例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | const Child = ({ doSomething, value }) => ( doSomething(value)}>Click Me ); 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 {childrenWithProps} } }; ReactDOM.render( <Parent> <Child value="1" /> <Child value="2" /> </Parent>, document.getElementById('container') ); |
小提琴:https://jsfiddle.net/2q294y43/2/
称儿童为功能
您还可以使用渲染道具将道具传递给子项。在这种方法中,子项(可以是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | const Child = ({ doSomething, value }) => ( doSomething(value)}>Click Me ); class Parent extends React.PureComponent { doSomething = value => { console.log('doSomething called by child with value:', value); } render() { return {this.props.children(this.doSomething)} } }; ReactDOM.render( <Parent> {doSomething => ( <React.Fragment> <Child doSomething={doSomething} value="1" /> <Child doSomething={doSomething} value="2" /> </React.Fragment> )} </Parent>, document.getElementById('container') ); |
如果您愿意,也可以返回一个数组,而不是
小提琴:https://jsfiddle.net/ferahl/y5pcua68/7/
要获得更简洁的方法,请尝试:
1 | {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })} |
编辑:
要与多个孩子一起使用(孩子必须自己是一个组成部分),你可以这样做。测试结果为16.8.6
1 2 | {React.cloneElement(props.children[0], { loggedIn: true, testingTwo: true })} {React.cloneElement(props.children[1], { loggedIn: true, testProp: false })} |
试试这个
1 | {React.cloneElement(this.props.children, {...this.props})} |
使用react-15.1对我有用。
传递道具指导孩子。
查看所有其他答案
通过上下文通过组件树传递共享的全局数据
Context is designed to share data that can be considered"global" for a tree of React components, such as the current authenticated user, theme, or preferred language. 1
免责声明:这是一个更新的答案,前一个使用旧的上下文API
它基于消费者/提供原则。首先,创建您的上下文
1 | const { Provider, Consumer } = React.createContext(defaultValue); |
然后使用via
1 2 3 | <Provider value={/* some value */}> {children} /* potential consumers */ <Provider /> |
和
1 2 3 | <Consumer> {value => /* render something based on the context value */} </Consumer> |
All Consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant Consumers is not subject to the shouldComponentUpdate method, so the Consumer is updated even when an ancestor component bails out of the update. 1
完整的例子,半伪代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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 ( <p> Consumer can be arbitrary levels deep </p> <Consumer> {value => <p> The toolbar will be in color {value.color} </p>} </Consumer> ); } } |
1 https://facebook.github.io/react/docs/context.html
将道具传递给嵌套儿童
通过对React 16.6的更新,您现在可以使用React.createContext和contextType。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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 ( {this.props.value} ); } } // 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 shines where React.cloneElement case couldn't handle nested components
1 2 3 4 5 6 7 8 9 10 11 | class SomeComponent extends React.Component { render() { return ( <Parent> <Child value={1} /> <SomeOtherComp><Child value={2} /></SomeOtherComp> </Parent> ); } } |
您可以使用
1 | {React.cloneElement(this.props.children, {...this.props})} |
因此,请参阅React文档中的内容,以了解它是如何工作的,以及如何使用它们:
In React v0.13 RC2 we will introduce a new API, similar to
React.addons.cloneWithProps, with this signature:
1 | React.cloneElement(element, props, ...children); |
Unlike cloneWithProps, this new function does not have any magic
built-in behavior for merging style and className for the same reason
we don't have that feature from transferPropsTo. Nobody is sure what
exactly the complete list of magic things are, which makes it
difficult to reason about the code and difficult to reuse when style
has a different signature (e.g. in the upcoming React Native).React.cloneElement is almost equivalent to:
1 | <element.type {...element.props} {...props}>{children}</element.type> |
However, unlike JSX and cloneWithProps, it also preserves refs. This
means that if you get a child with a ref on it, you won't accidentally
steal it from your ancestor. You will get the same ref attached to
your new element.One common pattern is to map over your children and add a new prop.
There were many issues reported about cloneWithProps losing the ref,
making it harder to reason about your code. Now following the same
pattern with cloneElement will work as expected. For example:
1 2 3 | var newChildren = React.Children.map(this.props.children, function(child) { return React.cloneElement(child, { foo: true }) }); |
Note: React.cloneElement(child, { ref: 'newRef' }) DOES override the
ref so it is still not possible for two parents to have a ref to the
same child, unless you use callback-refs.This was a critical feature to get into React 0.13 since props are now
immutable. The upgrade path is often to clone the element, but by
doing so you might lose the ref. Therefore, we needed a nicer upgrade
path here. As we were upgrading callsites at Facebook we realized that
we needed this method. We got the same feedback from the community.
Therefore we decided to make another RC before the final release to
make sure we get this in.We plan to eventually deprecate React.addons.cloneWithProps. We're not
doing it yet, but this is a good opportunity to start thinking about
your own uses and consider using React.cloneElement instead. We'll be
sure to ship a release with deprecation notices before we actually
remove it so no immediate action is necessary.
更多......
允许您进行属性转移的最佳方法是
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | export const GrantParent = () => { return ( <Parent> {props => ( <ChildComponent {...props}> Bla-bla-bla </ChildComponent> )} </Parent> ) } export const Parent = ({ children }) => { const somePropsHere = { //...any } <> {children(somePropsHere)} </> } |
你不再需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <BrowserRouter> <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} /> </BrowserRouter> |
我需要修复上面接受的答案,使其工作,而不是使用此指针。这在map函数范围内没有定义doSomething函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 {childrenWithProps} }}) |
更新:此修复程序适用于ECMAScript 5,在ES6中不需要var = this
考虑一个或多个孩子的清洁方式
1 | { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))} |
这些答案都没有解决让孩子成为非React组件的问题,例如文本字符串。解决方法可能是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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 {childrenWithProps} } |
Parent.jsx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import React from 'react'; const doSomething = value => {}; const Parent = props => ( { !props || !props.children ? Loading... (required at least one child) : !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})) } ); |
Child.jsx:
1 2 3 4 5 6 7 | import React from 'react'; /* but better import doSomething right here, or use some flux store (for example redux library) */ export default ({ doSomething, value }) => ( doSomething(value)}/> ); |
和main.jsx:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
根据
1 2 3 4 5 | React.cloneElement( element, [props], [...children] ) |
Clone and return a new React element using element as the starting
point. The resulting element will have the original element’s props
with the new props merged in shallowly. New children will replace
existing children. key and ref from the original element will be
preserved.
React.cloneElement() is almost equivalent to:
1 <element.type {...element.props} {...props}>{children}</element.type>However, it also preserves refs. This means that if you get a child
with a ref on it, you won’t accidentally steal it from your ancestor.
You will get the same ref attached to your new element.
因此,cloneElement将用于为子项提供自定义道具。但是,组件中可能有多个子项,您需要循环它。其他答案建议您使用
如果你想避免它,你应该选择像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | render() { const newElements = []; React.Children.forEach(this.props.children, child => newElements.push( React.cloneElement( child, {...this.props, ...customProps} ) ) ) return ( {newElements} ) } |
也许你也可以找到有用的这个功能,虽然很多人都认为这是一种反模式,如果你知道自己在做什么并设计好你的解决方案,它仍然可以使用。
作为子组件的功能
如果你有多个孩子想要传递道具,你可以这样做,使用React.Children.map:
1 2 3 4 5 6 7 8 9 10 11 12 | render() { let updatedChildren = React.Children.map(this.props.children, (child) => { return React.cloneElement(child, { newProp: newProp }); }); return ( { updatedChildren } ); } |
如果你的组件只有一个孩子,则不需要映射,你可以立即克隆元素:
1 2 3 4 5 6 7 8 9 10 11 | render() { return ( { React.cloneElement(this.props.children, { newProp: newProp }) } ); } |
我认为渲染道具是处理这种情况的适当方式
您让Parent提供子组件中使用的必要道具,通过重构Parent代码来查找如下所示:
1 2 3 4 5 | const Parent = ({children}) => { const doSomething(value) => {} return children({ doSomething }) } |
然后在子组件中,您可以通过以下方式访问父级提供的功能:
1 2 3 4 5 6 7 8 9 | class Child extends React { onClick() => { this.props.doSomething } render() { return (); } } |
现在fianl结构将如下所示:
1 2 3 4 5 6 7 8 | <Parent> {(doSomething) => (<Fragment> <Child value="1" doSomething={doSomething}> <Child value="2" doSomething={doSomething}> <Fragment /> )} </Parent> |
继@and_rest回答之后,这就是我克隆孩子并添加一个类的方法。
1 | {React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))} |
方法1 - 克隆儿童
1 2 3 4 5 6 7 8 | const Parent = (props) => { const attributeToAddOrReplace="Some Value" const childrenWithAdjustedProps = React.Children.map(props.children, child => React.cloneElement(child, { attributeToAddOrReplace}) ); return {childrenWithAdjustedProps } } |
方法2 - 使用可组合的上下文
Context允许您将prop传递给深子组件,而不通过其间的组件将其作为prop显式传递。
上下文有缺点:
使用可组合的上下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | export const Context = createContext(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 {context.greeting}, {context.name}; }; |
最简洁的方法:
1 | {React.cloneElement(this.props.children, this.props)} |
对于任何一个拥有单个子元素的人来说,这应该是这样做的。
1 2 3 4 5 | {React.isValidElement(this.props.children) ? React.cloneElement(this.props.children, { ...prop_you_want_to_pass }) : null} |
这是你需要的吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | var Parent = React.createClass({ doSomething: function(value) { } render: function() { return <Child doSome={this.doSomething} /> } }) var Child = React.createClass({ onClick:function() { this.props.doSome(value); // doSomething is undefined }, render: function() { return } }) |
React.children没有为我工作的原因。这对我有用。
我想给孩子添加一个课程。类似于改变道具
1 2 3 4 5 6 | var newChildren = this.props.children.map((child) => { const className ="MenuTooltip-item" + child.props.className; return React.cloneElement(child, { className }); }); return {newChildren}; |
这里的技巧是React.cloneElement。你可以以类似的方式传递任何道具
渲染道具是解决这个问题的最准确方法。不要将子组件作为子组件传递给父组件,而是让父组件手动呈现子组件。 Render是react中的内置道具,它带有函数参数。在此函数中,您可以让父组件使用自定义参数呈现您想要的任何内容。基本上它与儿童道具的作用相同,但它更具可定制性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class Child extends React.Component { render() { return Child <p onClick={this.props.doSomething}>Click me </p> {this.props.a} ; } } class Parent extends React.Component { doSomething(){ alert("Parent talks"); } render() { return Parent {this.props.render({ anythingToPassChildren:1, doSomething: this.doSomething})} ; } } class Application extends React.Component { render() { return <Parent render={ props => <Child {...props} /> }/> ; } } |
codepen的例子