我试图找到定义可以以一般方式使用的某些组件的正确方法:
<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}
定义包装器组件时,如何将某些属性传递给其所有子级?
#1楼
这是您所需要的吗?
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>
}
})
#2楼
用新道具克隆儿童
您可以使用React.Children遍历子级,然后使用React.cloneElement使用新的道具(浅合并)克隆每个元素,例如:
const Child = ({ doSomething, value }) => (
<div onClick={() => doSomething(value)}>Click Me</div>
);
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 <div>{childrenWithProps}</div>
}
};
ReactDOM.render(
<Parent>
<Child value="1" />
<Child value="2" />
</Parent>,
document.getElementById('container')
);
小提琴: https : //jsfiddle.net/2q294y43/2/
称呼孩子为功能
您也可以将道具传递给带有渲染道具的孩子。 在这种方法中,子代(可以是children
或任何其他道具名称)是一个函数,可以接受您要传递的任何参数并返回子代:
const Child = ({ doSomething, value }) => (
<div onClick={() => doSomething(value)}>Click Me</div>
);
class Parent extends React.PureComponent {
doSomething = value => {
console.log('doSomething called by child with value:', value);
}
render() {
return <div>{this.props.children(this.doSomething)}</div>
}
};
ReactDOM.render(
<Parent>
{doSomething => (
<React.Fragment>
<Child doSomething={doSomething} value="1" />
<Child doSomething={doSomething} value="2" />
</React.Fragment>
)}
</Parent>,
document.getElementById('container')
);
除了<React.Fragment>
或简单地<>
您还可以根据需要返回一个数组。
小提琴: https : //jsfiddle.net/ferahl/y5pcua68/7/
#3楼
我需要修复上面接受的答案,以使其使用该答案而不是此指针。 在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
#4楼
对于更简洁的方法,请尝试:
<div>
{React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>
编辑:可以与多个单独的子代(子代本身必须是组件)一起使用。 在16.8.6中测试
<div>
{React.cloneElement(props.children[0], { loggedIn: true, testingTwo: true })}
{React.cloneElement(props.children[1], { loggedIn: true, testProp: false })}
</div>
#5楼
尝试这个
<div>{React.cloneElement(this.props.children, {...this.props})}</div>
它使用react-15.1为我工作。
#6楼
最巧妙的方法是:
{React.cloneElement(this.props.children, this.props)}
#7楼
将道具传递给直接的孩子。
查看所有其他答案
通过上下文通过组件树传递共享的全局数据
上下文旨在共享可被视为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到其后代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>
);
}
}
1https://facebook.github.io/react/docs/context.html
#8楼
您不再需要{this.props.children}
。 现在,您可以在Route
使用render
包装您的子组件,并像往常一样传递道具:
<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>
#9楼
Parent.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
#10楼
考虑一个或多个孩子的更清洁方式
<div>
{ React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>
#11楼
除了@and_rest答案,这是我克隆孩子并添加类的方式。
<div className="parent">
{React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>
#12楼
您可以使用React.cloneElement
,最好在开始在应用程序中使用它之前先了解它的工作方式。 它是在React v0.13
引入的,请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不同,此新函数没有合并样式和className的任何神奇的内置行为,其原因与我们没有transferPropsTo的功能相同。 没有人能确定魔术物品的完整清单是什么,这使得很难推理代码,并且当样式具有不同的签名时(例如即将发布的React Native),也很难重用。
React.cloneElement几乎等同于:
<element.type {...element.props} {...props}>{children}</element.type>
但是,与JSX和cloneWithProps不同,它还保留引用。 这意味着,如果您得到一个带有裁判的孩子,则不会意外地从祖先那里偷走它。 您将获得与新元素相同的引用。
一种常见的模式是在孩子身上绘制地图并添加新道具。 报告了很多有关cloneWithProps丢失引用的问题,这使得推理代码变得更加困难。 现在,使用与cloneElement相同的模式将按预期工作。 例如:
var newChildren = React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, { foo: true })
});
注意:React.cloneElement(child,{ref:'newRef'})确实会覆盖ref,因此,除非您使用callback-refs,否则两个父对象仍然无法对同一个孩子拥有ref。
这是进入React 0.13的关键功能,因为道具现在是不可变的。 升级路径通常是克隆元素,但是这样做可能会丢失引用。 因此,我们需要一个更好的升级途径。 在Facebook上升级呼叫站点时,我们意识到我们需要这种方法。 我们从社区得到了同样的反馈。 因此,我们决定在最终发行版之前再制作一个RC,以确保我们能收到它。
我们计划最终弃用React.addons.cloneWithProps。 我们还没有这样做,但这是一个很好的机会,可以开始考虑您自己的用途并考虑使用React.cloneElement。 在实际删除发布版本之前,我们一定会先发布其中包含弃用通知的发布版本,因此无需立即采取措施。
更在这里 ...
#13楼
没有一个答案解决拥有非 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>
}
#14楼
原因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 。 您可以通过类似的方式传递任何道具
#15楼
根据cloneElement()
的文档
React.cloneElement(
element,
[props],
[...children]
)
克隆并使用element作为起点返回一个新的React元素。 生成的元素将具有原始元素的道具,而新的道具将被浅层合并。 新的孩子将替换现有的孩子。 原始元素的key和ref将被保留。
React.cloneElement()
几乎等同于:<element.type {...element.props} {...props}>{children}</element.type>
但是,它也保留引用。 这意味着,如果您得到一个带有裁判的孩子,则不会意外地从祖先那里偷走它。 您将获得与新元素相同的引用。
因此,cloneElement是用来为子项提供自定义道具的东西。 但是,组件中可以有多个子代,您需要对其进行循环。 其他答案建议您使用React.Children.map
在其上进行React.Children.map
。 但是React.Children.map
与React.Children.map
不同, React.cloneElement
更改了元素附加键,并以.$
作为前缀。 检查此问题以获取更多详细信息: 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>
)
}
#16楼
也许您也可以找到有用的功能,尽管许多人认为这是一种反模式,但是如果您知道自己在做什么并且设计得很好的解决方案,它仍然可以使用。
#17楼
如果您有多个孩子想要传递道具 ,则可以使用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>
);
}
#18楼
将道具传递给嵌套儿童
随着React 16.6的更新,您现在可以使用React.createContext和contextType 。
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 (
<div onClick={this.onClick}>{this.props.value}</div>
);
}
}
// 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在React.cloneElement案例无法处理嵌套组件的地方闪耀
class SomeComponent extends React.Component {
render() {
return (
<Parent>
<Child value={1} />
<SomeOtherComp><Child value={2} /></SomeOtherComp>
</Parent>
);
}
}
#19楼
对于任何具有单个子元素的人,都应该这样做。
{React.isValidElement(this.props.children)
? React.cloneElement(this.props.children, {
...prop_you_want_to_pass
})
: null}
#20楼
允许您进行财产转移的最佳方法是将children
当作函数
例:
export const GrantParent = () => {
return (
<Parent>
{props => (
<ChildComponent {...props}>
Bla-bla-bla
</ChildComponent>
)}
</Parent>
)
}
export const Parent = ({ children }) => {
const somePropsHere = { //...any }
<>
{children(somePropsHere)}
</>
}
#21楼
渲染道具是解决此问题的最准确方法。 与其将子组件作为子道具传递给父组件,不如让父组件手动渲染子组件。 Render是内置的props在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>;
}
}
#22楼
我认为渲染道具是处理这种情况的合适方法
通过重构父代码看起来像这样,可以让父对象提供子组件中使用的必要道具:
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>
#23楼
方法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-使用可组合上下文
上下文允许您将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>;
};