我试图找到正确的方法来定义一些可以以通用方式使用的组件:
<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.map()
迭代孩子。在 How To Pass Props to {react.children} 中查看更多信息
用新道具克隆孩子
您可以使用 React.Children
来迭代子元素,然后使用 React.cloneElement
克隆每个带有新道具(浅合并)的元素。例如:
const Child = ({ doSomething, value }) => ( ); function Parent({ children }) { function doSomething(value) { console.log("doSomething 由 child 调用,具有 value:", value); } const childrenWithProps = React.Children.map(children, child => { // 检查 isValidElement 是安全的方法,并且也避免了打字稿 // 错误。 if (React.isValidElement(child)) { return React.cloneElement(child, { doSomething }); } 返回孩子; }); return
将孩子作为函数调用
或者,您可以使用 render props 将道具传递给孩子。在这种方法中,孩子(可以是 children
或任何其他道具名称)是一个可以接受您想要传递的任何参数并返回孩子的函数:
const Child = ({ doSomething, value }) => ( ); function Parent({ children }) { function doSomething(value) { console.log("doSomething 由 child 调用,具有 value:", value); } // 请注意,children 作为函数调用,我们可以将 args 传递给它。 return
如果您愿意,您也可以返回一个数组,而不是 <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>
this.props.children
) 传递给 cloneElement
... 这一点也不明显,这需要一个 ... 元素。
React.Children.only
函数返回唯一的孩子,如果有多个则抛出异常(如果没有用例,则不会存在)。
尝试这个
<div>{React.cloneElement(this.props.children, {...this.props})}</div>
使用 react-15.1 对我有用。
https://reactjs.org/docs/jsx-in-depth.html#spread-attributes 中建议使用 {...this.props}
React.cloneElement()
而无需将其括在 <div>
标记中?因为如果孩子是 <span>
(或其他)并且我们想要保留其标签元素类型怎么办?
React.cloneElement(React.Children.only(this.props.children), {...this.props})
,如果传递多个孩子,则会引发错误。然后你不需要包装在一个div中。
let {children, ...acyclicalProps} = this.props
,然后使用 React.cloneElement(React.Children.only(children), acyclicalProps)
。
将道具传递给指导孩子。
查看所有其他答案
通过上下文通过组件树传递共享的全局数据
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
this.context
)——它并没有神奇地将上下文与道具合并。你必须有意识地设置和使用上下文,这是另一回事。
将道具传递给嵌套的孩子
随着对 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>
);
}
this
上下文
value=myContext
。原因:1)不需要解释上下文。 2)它分散了代码的实际关键点,即myContext
的使用。 :)
允许您进行属性转移的最佳方法是 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>
)
}
nested components
是什么意思。 React 没有嵌套模式,只有组合。是的,children 必须是一个函数,没有任何冲突,因为这是有效的 JSX 孩子。你能举个 nesting components
的例子吗?
<Parent>{props => <Nest><ChildComponent /></Nest>}</Parent>
而不是(不工作)<Parent><Nest>{props => <ChildComponent />}</Nest></Parent>
所以我同意这是最好的答案
TypeError: children is not a function
您可以使用 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 的好机会。在我们实际删除它之前,我们一定会发布带有弃用通知的版本,因此无需立即采取行动。
更多here...
我需要修复上面接受的答案,以使其使用它而不是这个指针来工作。这在 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
bind()
doSomething
获取一个对象,例如 doSomething: function(obj) { console.log(obj) }
,并且在 Child 中调用 this.props.doSomething(obj)
以注销 "obj"
,会怎样?
apply
方法的函数。在渲染函数中使用 bind()
将在每次调用渲染方法时创建一个新函数。
没有一个答案解决了拥有不是 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>
this.props
中传递。一般来说,我只建议使用特定道具进行克隆,而不是整个 shebang。
{...this.props}
对我不起作用,{...child.props}
的方式是否正确?
React.Children.map(children, child => React.cloneElement(child, props))
方法 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 显式传递给它们之间的组件。
上下文有缺点:
数据不会以常规方式流动——通过道具。使用上下文创建消费者和提供者之间的合同。理解和复制重用组件所需的需求可能会更加困难。
使用可组合的上下文
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>;
};
{children}:{children:ReactNode}
中的符号吗?
"children"
的值是 ReactNode
类型
attributeToAddOrReplace
访问到子文件中?
父.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
如果您有多个想要pass props的孩子,您可以使用 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}</>;
};
<>{childrenWithProps}</>
是没有用的,因为 childrenWithProps
已经是一个数组。返回 childrenWithProps
就足够了。顺便说一句,这个答案与 6 年前选择的答案相同 - 都使用 React.Children.map
然后 cloneElement
除了@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)}
根据 cloneElement()
的文档
React.cloneElement(
element,
[props],
[...children]
)
使用 element 作为起点克隆并返回一个新的 React 元素。结果元素将具有原始元素的道具和新道具的浅层合并。新的孩子将取代现有的孩子。原始元素的 key 和 ref 将被保留。 React.cloneElement() 几乎等同于:
所以 cloneElement 是你用来为孩子们提供自定义道具的东西。但是,组件中可以有多个子组件,您需要对其进行循环。其他答案建议您使用 React.Children.map
映射它们。然而,React.Children.map
与 React.cloneElement
不同,它改变了 Element appending 和额外的 .$
作为前缀的键。检查此问题以获取更多详细信息:React.cloneElement inside React.Children.map is causing element keys to change
如果您想避免它,您应该转而使用 forEach
函数,例如
render() {
const newElements = [];
React.Children.forEach(this.props.children,
child => newElements.push(
React.cloneElement(
child,
{...this.props, ...customProps}
)
)
)
return (
<div>{newElements}</div>
)
}
您不再需要 {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>
对于任何拥有单个子元素的人来说,都应该这样做。
{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>
}
})
我在研究类似需求时来到这篇文章,但我觉得克隆解决方案非常流行,太原始并且让我的注意力从功能上移开。
我在 React 文档中找到了一篇文章Higher Order Components
这是我的示例:
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
数组或单个 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。您可以以类似的方式传递任何道具
Render props 是解决此问题的最准确方法。与其将子组件作为子 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 的情况下有效。
不定期副业成功案例分享
doSomething
的value
丢失了。