如何将道具传递给{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}定义包装器组件时,如何将某些属性传递给其所有子级?


#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.mapReact.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.createContextcontextType

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.createContextReact.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>;
  }
}

Codepen示例


#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显式传递给中间的组件。

上下文具有缺点:

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值