วิธีส่งอุปกรณ์ประกอบฉากไปยัง {this.props.children}


888

ฉันพยายามหาวิธีที่เหมาะสมในการกำหนดองค์ประกอบบางอย่างซึ่งสามารถใช้ในวิธีทั่วไป:

<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}ในการกำหนดองค์ประกอบ wrapper คุณจะส่งผ่านคุณสมบัติบางอย่างให้กับลูก ๆ ของมันได้อย่างไร


คำตอบ:


955

โคลนเด็กด้วยอุปกรณ์ประกอบฉากใหม่

คุณสามารถใช้React.Childrenเพื่อย้ำข้ามเด็ก ๆ แล้วโคลนแต่ละองค์ประกอบด้วยอุปกรณ์ประกอบฉากใหม่ (รวมเข้าด้วยกันตื้น) โดยใช้React.cloneElementเช่น:

import React, { Children, isValidElement, cloneElement } from 'react';

const Child = ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    const childrenWithProps = Children.map(children, child => {
      // Checking isValidElement is the safe way and avoids a TS error too.
      if (isValidElement(child)) {
        return cloneElement(child, { doSomething })
      }

      return child;
    });

    return <div>{childrenWithProps}</div>
  }
};

ReactDOM.render(
  <Parent>
    <Child value="1" />
    <Child value="2" />
  </Parent>,
  document.getElementById('container')
);

ซอ: https://jsfiddle.net/2q294y43/2/

เรียกเด็ก ๆ ว่าเป็นฟังก์ชั่น

นอกจากนี้คุณยังสามารถส่งผ่านอุปกรณ์ประกอบฉากให้กับเด็ก ๆ ด้วยทำให้อุปกรณ์ประกอบฉาก ในวิธีการนี้ children (ซึ่งอาจเป็นchildrenหรือชื่อ prop อื่น ๆ ) เป็นฟังก์ชั่นที่สามารถยอมรับข้อโต้แย้งใด ๆ ที่คุณต้องการผ่านและส่งกลับเด็ก ๆ :

const Child = ({ doSomething, value }) => (
  <div onClick={() =>  doSomething(value)}>Click Me</div>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log('doSomething called by child with value:', value);
  }

  render() {
    // Note that children is called as a function and we can pass args to it
    return <div>{children(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/


7
มันไม่ได้ผลสำหรับฉัน สิ่งนี้ไม่ได้กำหนดไว้ใน React.cloneElement ()
Patrick

12
คำตอบนี้ใช้งานไม่ได้valueส่งผ่านไปdoSomethingจะหายไป
เดฟ

3
@DominicTobias Arg ขออภัยฉันเปลี่ยน console.log เพื่อแจ้งเตือนและลืมเชื่อมต่อสอง params กับสตริงเดียว
Dave

1
คำตอบนี้เป็นประโยชน์อย่างยิ่ง แต่ฉันพบปัญหาที่ไม่ได้กล่าวถึงที่นี่และฉันสงสัยว่ามันเป็นสิ่งใหม่ที่เปลี่ยนไปหรือไม่หรือว่าเป็นสิ่งที่แปลกในตอนท้ายของฉัน เมื่อฉันโคลนองค์ประกอบลูกของฉันมันเป็นลูกถูกตั้งค่าเป็นองค์ประกอบเก่าจนกว่าฉันจะเพิ่ม this.props.children.props.children ไปยังอาร์กิวเมนต์ที่สามของ cloneElement
aphenine

7
จะเกิดอะไรขึ้นถ้าเด็กถูกโหลดผ่านเส้นทาง (v4) ที่โหลดจากหน้าเส้นทางแยกต่างหาก
blamb

394

สำหรับวิธีทำความสะอาดเล็กน้อยให้ลอง:

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

10
ฉันใช้คำตอบที่ได้รับความนิยมมากที่สุด แต่อันนี้ตรงไปตรงมามากขึ้น! วิธีนี้เป็นสิ่งที่พวกเขาใช้ในหน้าตัวอย่างปฏิกิริยากับเราเตอร์
captDaylight

10
มีใครช่วยอธิบายวิธีการทำงานของมัน (หรือใช้งานได้จริง)? อ่านเอกสารฉันไม่สามารถเห็นว่าสิ่งนี้จะลงไปในเด็กและเพิ่มเสาที่ให้เด็กแต่ละคน - นั่นคือสิ่งที่มันตั้งใจจะทำอย่างไร ถ้าเป็นเช่นนั้นเราจะรู้ได้อย่างไรว่านี่คือสิ่งที่จะทำ? ไม่ชัดเจนเลยว่ามันใช้ได้แม้แต่กับการส่งโครงสร้างข้อมูลทึบ ( this.props.children) ถึงcloneElement... ซึ่งคาดว่าจะเป็นองค์ประกอบ ...
GreenAsJade

51
ตรงนี้ดูเหมือนจะไม่ทำงานกับเด็กมากกว่าหนึ่งคน
Danita

17
ดังนั้นคุณสามารถเขียนโค้ดที่ใช้งานได้ในขณะที่บางคนส่งลูกคนเดียวไปยังส่วนประกอบ แต่เมื่อพวกเขาเพิ่มอีกมันจะล้มเหลว ... ซึ่งฟังดูไม่คุ้มค่า ดูเหมือนว่าจะเป็นกับดักสำหรับกลุ่ม OP ซึ่งถามถึงการส่งอุปกรณ์ประกอบฉากให้กับเด็กทุกคนโดยเฉพาะ
GreenAsJade

10
@GreenAs แก้ไขได้ดีตราบใดที่องค์ประกอบของคุณคาดว่าจะมีลูกเดียว คุณสามารถกำหนดองค์ประกอบของคุณผ่าน propTypes ที่คาดว่าจะเป็นลูกคนเดียว React.Children.onlyฟังก์ชั่นส่งกลับเด็กคนเดียวหรือส่งข้อยกเว้นถ้ามีหลายคน (นี้จะไม่อยู่ถ้าไม่มีกรณีการใช้งาน)
cchamberlain

80

ลองสิ่งนี้

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

มันใช้งานได้สำหรับฉันโดยใช้ react-15.1


3
เป็นไปได้หรือไม่ที่จะส่งคืนReact.cloneElement()โดยตรงโดยไม่ต้องล้อมรอบด้วย<div>แท็ก? เพราะถ้าเด็กเป็น<span>(หรืออย่างอื่น) และเราต้องการรักษาประเภทองค์ประกอบแท็กของมัน?
adrianmc

1
หากเป็นลูกคนเดียวคุณสามารถแยกเสื้อคลุมออกได้และวิธีนี้ใช้ได้กับเด็กคนเดียวเท่านั้นใช่
ThaJay

1
ได้ผลสำหรับฉัน โดยไม่ต้องปิด <div> ก็โอเค
Crash Override

4
หากคุณต้องการบังคับใช้อย่างชัดเจนว่าคุณได้รับลูกเพียงคนเดียวคุณสามารถทำได้React.cloneElement(React.Children.only(this.props.children), {...this.props})ซึ่งจะเกิดข้อผิดพลาดหากมีการส่งลูกมากกว่าหนึ่งลูก จากนั้นคุณไม่จำเป็นต้องห่อเป็นกอง
itsananderson

1
คำตอบนี้อาจสร้างมูลค่าวัตถุ TypeError: cyclic ถ้าคุณต้องการเป็นหนึ่งในอุปกรณ์ประกอบฉากของเด็กที่จะเป็นตัวเองใช้แล้วlet {children, ...acyclicalProps} = this.props React.cloneElement(React.Children.only(children), acyclicalProps)
Parabolord

68

ผ่านอุปกรณ์ประกอบฉากเพื่อนำเด็ก

ดูคำตอบอื่น ๆ ทั้งหมด

ส่งผ่านข้อมูลส่วนกลางที่ใช้ร่วมกันผ่านโครงสร้างส่วนประกอบผ่านบริบท

บริบทได้รับการออกแบบมาเพื่อแบ่งปันข้อมูลที่ถือได้ว่าเป็น "ทั่วโลก" สำหรับโครงสร้างของส่วนประกอบการตอบสนองเช่นผู้ใช้ที่ผ่านการรับรองความถูกต้องของธีมหรือภาษาที่ต้องการ 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>

ผู้บริโภคทั้งหมดที่เป็นผู้สืบทอดของผู้ให้บริการจะแสดงผลซ้ำทุกครั้งที่การเปลี่ยนแปลงมูลค่าของผู้ให้บริการ การเผยแพร่จากผู้ให้บริการไปยังผู้สืบทอดที่เป็นผู้บริโภคไม่ได้อยู่ภายใต้เมธอด 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


6
แตกต่างจากคำตอบที่ยอมรับนี้จะทำงานได้อย่างถูกต้องแม้ว่าจะมีองค์ประกอบอื่น ๆ รวมอยู่ภายใต้ผู้ปกครอง นี่คือคำตอบที่ดีที่สุดแน่นอน
Zaptree

6
อุปกรณ์ประกอบฉาก! = บริบท
Petr Peller

คุณไม่สามารถพึ่งพาการเปลี่ยนแปลงที่เผยแพร่ผ่านบริบท ใช้อุปกรณ์ประกอบฉากเมื่อเป็นไปได้ที่พวกเขาเปลี่ยน
ThaJay

1
บางทีฉันอาจไม่เข้าใจ แต่มันไม่ถูกต้องหรือที่จะพูดว่า "บริบททำให้อุปกรณ์ประกอบฉากพร้อมใช้งาน" เมื่อฉันใช้บริบทครั้งล่าสุดมันเป็นสิ่งที่แยกต่างหาก (เช่นthis.context) - มันไม่ได้รวมบริบทกับอุปกรณ์ประกอบฉากอย่างน่าอัศจรรย์ คุณต้องตั้งใจและใช้บริบทซึ่งเป็นสิ่งอื่นทั้งหมด
Josh

คุณเข้าใจอย่างสมบูรณ์มันไม่ถูกต้อง ฉันแก้ไขคำตอบของฉันแล้ว
Lyubomir

48

ส่งอุปกรณ์ประกอบฉากให้กับเด็กซ้อน

ด้วยการอัพเดตเป็น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>
    );
  }
}

3
คุณช่วยอธิบายได้ไหมว่าทำไม => ฟังก์ชั่นเป็นการปฏิบัติที่ไม่ดี? ฟังก์ชัน => ช่วยเชื่อมโยงตัวจัดการเหตุการณ์เพื่อรับthisบริบท
Kenneth Truong

@ KennethTruong เพราะทุกครั้งที่มันแสดงผลมันจะสร้างฟังก์ชั่น
itdoesntwork

9
@itdoesntwork ที่ไม่เป็นความจริง มันสร้างฟังก์ชั่นใหม่เมื่อสร้างคลาสเท่านั้น มันไม่ได้ถูกสร้างขึ้นระหว่างฟังก์ชั่นการเรนเดอร์ ..
Kenneth Truong

@KennethTruong reactjs.org/docs/faq-functions.html#arrow-function-in-renderฉันคิดว่าคุณพูดถึงฟังก์ชั่นลูกศรในการแสดงผล
itdoesntwork

24

คุณสามารถใช้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 ฟังก์ชั่นใหม่นี้ไม่มีพฤติกรรมเวทย์มนตร์ในตัวสำหรับการผสานสไตล์และ className ด้วยเหตุผลเดียวกันกับที่เราไม่มีคุณสมบัติดังกล่าวจาก transferPropsTo ไม่มีใครแน่ใจว่ารายชื่อของสิ่งมหัศจรรย์ทั้งหมดคืออะไรซึ่งทำให้มันยากที่จะให้เหตุผลเกี่ยวกับรหัสและยากที่จะนำมาใช้ใหม่เมื่อสไตล์มีลายเซ็นที่แตกต่างกัน (เช่นใน React Native)

React.cloneElement เกือบเทียบเท่ากับ:

<element.type {...element.props} {...props}>{children}</element.type>

อย่างไรก็ตามแตกต่างจาก JSX และ cloneWithProps ก็ยังรักษา refs ซึ่งหมายความว่าหากคุณมีลูกด้วยการอ้างอิงคุณจะไม่ขโมยมันจากบรรพบุรุษของคุณ คุณจะได้รับการอ้างอิงเดียวกันกับองค์ประกอบใหม่ของคุณ

รูปแบบทั่วไปอย่างหนึ่งคือการแมปกับลูก ๆ ของคุณและเพิ่มเสาใหม่ มีปัญหามากมายที่รายงานเกี่ยวกับ cloneWithProps ที่สูญเสียการอ้างอิงทำให้ยากที่จะให้เหตุผลเกี่ยวกับรหัสของคุณ ตอนนี้ตามรูปแบบเดียวกันกับ cloneElement จะทำงานตามที่คาดไว้ ตัวอย่างเช่น:

var newChildren = React.Children.map(this.props.children, function(child) {
  return React.cloneElement(child, { foo: true })
});

หมายเหตุ: React.cloneElement (child, {ref: 'newRef'}) จะแทนที่การอ้างอิงดังนั้นจึงเป็นไปไม่ได้ที่ผู้ปกครองสองคนจะมีการอ้างอิงถึงเด็กคนเดียวกันยกเว้นว่าคุณใช้ call-refs

นี่เป็นคุณสมบัติที่สำคัญในการเข้าทำปฏิกิริยา 0.13 เนื่องจากอุปกรณ์ประกอบฉากไม่เปลี่ยนรูป เส้นทางการอัพเกรดมักจะโคลนองค์ประกอบ แต่โดยการทำเช่นนั้นคุณอาจสูญเสียการอ้างอิง ดังนั้นเราต้องการเส้นทางการอัพเกรดที่ดีกว่าที่นี่ เมื่อเราอัพเกรดเว็บไซต์ที่ Facebook เรารู้ว่าเราต้องการวิธีนี้ เราได้รับการตอบรับที่เหมือนกันจากชุมชน ดังนั้นเราจึงตัดสินใจสร้าง RC อีกครั้งก่อนการเปิดตัวขั้นสุดท้ายเพื่อให้แน่ใจว่าเราได้รับสิ่งนี้

เราวางแผนที่จะคัดค้าน React.addons.cloneWithProps ในที่สุด เรายังไม่ได้ทำ แต่นี่เป็นโอกาสที่ดีในการเริ่มคิดเกี่ยวกับการใช้ของคุณเองและลองใช้ React.cloneElement แทน เราจะแน่ใจว่าได้จัดส่งการแจ้งเตือนด้วยการคัดค้านก่อนที่เราจะลบออกจริง ๆ ดังนั้นจึงไม่จำเป็นต้องดำเนินการ

เพิ่มเติมที่นี่ ...


18

วิธีที่ดีที่สุดที่ช่วยให้คุณสามารถโอนทรัพย์สินได้childrenเหมือนฟังก์ชั่น

ตัวอย่าง:

export const GrantParent = () => {
  return (
    <Parent>
      {props => (
        <ChildComponent {...props}>
          Bla-bla-bla
        </ChildComponent>
      )}
    </Parent>
  )
}

export const Parent = ({ children }) => {
    const somePropsHere = { //...any }
    <>
        {children(somePropsHere)}
    </>
}

1
ดูเหมือนว่าวิธีนี้จะตรงไปตรงมา (และดีกว่าสำหรับการแสดง?) สำหรับฉันมากกว่าคำตอบที่ยอมรับ
Shikyo

2
สิ่งนี้ต้องการให้เด็กเป็นฟังก์ชั่นและไม่สามารถใช้ได้กับส่วนประกอบที่ซ้อนกันอย่างลึกล้ำ
ภาพลวงตาดิจิทัล

@digitalillusion nested componentsผมไม่เข้าใจว่ามันหมายความว่าอะไร ปฏิกิริยาไม่ได้มีรูปแบบซ้อนกันมีเพียงการแต่งเพลงเท่านั้น ใช่ลูกต้องเป็นฟังก์ชันไม่มีข้อขัดแย้งใด ๆ เนื่องจากลูก JSX ที่ถูกต้องนี้ คุณสามารถยกตัวอย่างด้วยnesting components?
Nick Ovchinnikov

1
คุณถูกต้องกรณีของเด็กที่ซ้อนกันอย่างล้ำลึกสามารถจัดการได้เช่นกัน<Parent>{props => <Nest><ChildComponent /></Nest>}</Parent>แทนที่จะเป็น (ไม่ทำงาน) <Parent><Nest>{props => <ChildComponent />}</Nest></Parent>ดังนั้นฉันจึงเห็นด้วยว่านี่เป็นคำตอบที่ดีที่สุด
ภาพลวงตาดิจิตอล

เมื่อพยายามฉันได้รับสิ่งต่อไปนี้:TypeError: children is not a function
Ryan Prentiss

6

ฉันต้องการที่จะแก้ไขคำตอบที่ได้รับการยอมรับดังกล่าวข้างต้นจะทำให้การทำงานโดยใช้ที่แทนการนี้ชี้ สิ่งนี้อยู่ในขอบเขตของฟังก์ชั่นแผนที่ไม่ได้กำหนดฟังก์ชั่น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


13
หรือเพียงแค่ใช้bind()
บวก -

1
หรือใช้ฟังก์ชั่นลูกศรซึ่งผูกขอบเขตศัพท์ฉันปรับปรุงคำตอบของฉัน
โดมินิก

เกิดอะไรขึ้นถ้าdoSomethingเอาวัตถุเหมือนdoSomething: function(obj) { console.log(obj) }และในเด็กที่คุณต้องการโทรthis.props.doSomething(obj)ออกจากระบบ"obj"
conor909

4
@ plus- ฉันรู้ว่ามันเก่า แต่การใช้การผูกที่นี่เป็นความคิดที่น่ากลัวการผูกสร้างฟังก์ชันใหม่ที่ผูกบริบทกับใหม่ ฟังก์ชั่นพื้นเรียกapplyวิธีการ การใช้bind()ในฟังก์ชั่นการเรนเดอร์จะสร้างฟังก์ชั่นใหม่ทุกครั้งที่เมธอดการเรนเดอร์เรียก
Bamieh

6

วิธีที่สะอาดกว่าเมื่อพิจารณาเด็กหนึ่งคนขึ้นไป

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>

อันนี้ใช้งานไม่ได้สำหรับฉันมันให้ข้อผิดพลาด: เด็กไม่ได้กำหนดไว้
Deelux

@Deelux this.props.children แทนที่จะเป็นเด็ก
Martin Dawson

this.propsนี้ผ่านเด็กที่เป็นเด็กของตัวเองใน โดยทั่วไปฉันขอแนะนำให้โคลนด้วยอุปกรณ์ประกอบฉากที่เฉพาะเจาะจงเท่านั้น
Andy

ผ่าน{...this.props}ไม่ได้สำหรับฉันเป็นวิธีที่{...child.props}ถูกต้อง?
เฟลิเป้ออกัสโต

สำหรับส่วนประกอบที่ใช้งานได้:React.Children.map(children, child => React.cloneElement(child, props))
vsync

5

ไม่มีคำตอบใดที่แก้ไขปัญหาของการมีลูกที่ไม่ใช่องค์ประกอบที่ตอบสนองเช่นสตริงข้อความ วิธีแก้ปัญหาอาจเป็นดังนี้:

// 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>

}

5

{this.props.children}คุณไม่จำเป็นต้อง ตอนนี้คุณสามารถห่อองค์ประกอบเด็กของคุณโดยใช้renderในRouteและผ่านอุปกรณ์ประกอบฉากของคุณตามปกติ:

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

2
Render props เป็นมาตรฐานใน React ( reactjs.org/docs/render-props.html ) และควรพิจารณาว่าเป็นคำตอบที่ยอมรับใหม่สำหรับคำถามนี้
Ian Danforth

19
นี่เป็นคำตอบของคำถามได้อย่างไร
Maximo Dominguez

4

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


4

บางทีคุณอาจพบว่าคุณสมบัตินี้มีประโยชน์แม้ว่าหลาย ๆ คนจะคิดว่านี่เป็นรูปแบบการต่อต้าน แต่ก็ยังสามารถใช้งานได้หากคุณรู้ว่าคุณกำลังทำอะไรและออกแบบโซลูชันของคุณได้ดี

ทำหน้าที่เป็นส่วนประกอบลูก


นี่ไม่ใช่รูปแบบต่อต้าน: youtube.com/watch?v=BcVAq3YFiuc
Pietro Coelho

4

หากคุณมีลูกหลายคนที่คุณต้องการส่งอุปกรณ์ประกอบฉากไปที่คุณสามารถทำได้โดยใช้ React.Children.map:

render() {
    let updatedChildren = React.Children.map(this.props.children,
        (child) => {
            return React.cloneElement(child, { newProp: newProp });
        });

    return (
        <div>
            { updatedChildren }
        </div>
    );
}

หากองค์ประกอบของคุณมีลูกเพียงคนเดียวไม่จำเป็นต้องทำแผนที่คุณสามารถโคลนได้ทันที:

render() {
    return (
        <div>
            {
                React.cloneElement(this.props.children, {
                    newProp: newProp
                })
            }
        </div>
    );
}

3

ตามเอกสารของ cloneElement()

React.cloneElement(
  element,
  [props],
  [...children]
)

โคลนและส่งคืนองค์ประกอบปฏิกิริยาใหม่โดยใช้องค์ประกอบเป็นจุดเริ่มต้น องค์ประกอบที่เกิดขึ้นจะมีอุปกรณ์ประกอบฉากขององค์ประกอบดั้งเดิมพร้อมกับอุปกรณ์ประกอบฉากใหม่ที่ผสานกันอย่างตื้นเขิน เด็กใหม่จะเข้ามาแทนที่เด็กที่มีอยู่ คีย์และการอ้างอิงจากองค์ประกอบดั้งเดิมจะถูกเก็บไว้

React.cloneElement() เกือบเทียบเท่ากับ:

<element.type {...element.props} {...props}>{children}</element.type>

อย่างไรก็ตามมันยังเก็บรักษา refs ซึ่งหมายความว่าหากคุณมีลูกด้วยการอ้างอิงคุณจะไม่ขโมยมันจากบรรพบุรุษของคุณ คุณจะได้รับการอ้างอิงเดียวกันกับองค์ประกอบใหม่ของคุณ

ดังนั้น cloneElement คือสิ่งที่คุณจะใช้เพื่อจัดทำอุปกรณ์ประกอบฉากที่กำหนดเองให้กับเด็ก ๆ อย่างไรก็ตามอาจมีหลายลูกในองค์ประกอบและคุณจะต้องวนซ้ำมัน อะไรคำตอบอื่น ๆ แนะนำคือให้คุณ map React.Children.mapเหนือพวกเขาใช้ อย่างไรก็ตามReact.Children.mapไม่เหมือนกับReact.cloneElementการเปลี่ยนแปลงคีย์ขององค์ประกอบต่อท้ายและส่วนเสริม.$เป็นส่วนนำหน้า ตรวจสอบคำถามนี้เพื่อดูรายละเอียดเพิ่มเติม: React.cloneElement ภายใน React.Children.map ทำให้คีย์องค์ประกอบเปลี่ยนแปลง

หากคุณต้องการหลีกเลี่ยงคุณควรไปforEachฟังก์ชั่นดังนี้

render() {
    const newElements = [];
    React.Children.forEach(this.props.children, 
              child => newElements.push(
                 React.cloneElement(
                   child, 
                   {...this.props, ...customProps}
                )
              )
    )
    return (
        <div>{newElements}</div>
    )

}

2

ต่อจากคำตอบ @and_rest นี่คือวิธีที่ฉันลอกแบบเด็ก ๆ และเพิ่มชั้นเรียน

<div className="parent">
    {React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>

2

ฉันคิดว่า prop แสดงผลเป็นวิธีที่เหมาะสมในการจัดการสถานการณ์นี้

คุณปล่อยให้ผู้ปกครองจัดเตรียมอุปกรณ์ประกอบฉากที่จำเป็นที่ใช้ในองค์ประกอบลูกโดยปรับโครงสร้างรหัสผู้ปกครองให้มีลักษณะดังนี้:

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>

เกิดอะไรขึ้นถ้าผู้ปกครองเป็นเพียงเสื้อคลุมสำหรับ {children} ที่เป็น textarea ในองค์ประกอบระดับอื่น?
Adebayo

2

วิธีที่ 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 - ใช้บริบทที่รวมกันได้

บริบทช่วยให้คุณสามารถส่งเสาไปยังองค์ประกอบลูกที่ลึกโดยไม่ต้องผ่านมันอย่างชัดเจนเป็นเสาผ่านองค์ประกอบในระหว่าง

บริบทมาพร้อมกับข้อเสีย:

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

สายไปหน่อย แต่คุณสามารถอธิบายสัญลักษณ์ได้{children}:{children:ReactNode}ไหม?
คามิลล์

@ คามิลล์มันเป็นสิ่งที่เรียงพิมพ์ ดูที่ตอนนี้ฉันจะตอบด้วย Javascript และแม้ว่าฉันจะเขียน typescript ฉันจะทำมันแตกต่างกัน อาจแก้ไขได้ในอนาคต
Ben Carp

1
@ คามิลล์โดยทั่วไปมันหมายความว่าค่าที่มีคีย์"children"เป็นของประเภทReactNode
Ben Carp

1

วิธีที่ลื่นที่สุดในการทำเช่นนี้:

    {React.cloneElement(this.props.children, this.props)}

5
สิ่งนี้ไม่ได้คัดลอก this.props.children ไปยัง this.props.children ของเด็กหรือไม่? และมีผลในการคัดลอกเด็กเป็นของตัวเอง?
Arshabh Agarwal

1

สำหรับผู้ใดก็ตามที่มีองค์ประกอบลูกเดียวควรทำเช่นนี้

{React.isValidElement(this.props.children)
                  ? React.cloneElement(this.props.children, {
                      ...prop_you_want_to_pass
                    })
                  : null}

0

นี่คือสิ่งที่คุณต้องการหรือไม่

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

4
ไม่ฉันไม่ต้องการ จำกัด เนื้อหาของ wrapper ของฉันไปยังเนื้อหาที่เฉพาะเจาะจง
บวก -

0

เหตุผลบางอย่าง 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 คุณสามารถผ่านเสาในลักษณะที่คล้ายกัน


0

Render propsเป็นวิธีที่แม่นยำที่สุดในการแก้ไขปัญหานี้ แทนที่จะส่งองค์ประกอบลูกไปยังองค์ประกอบหลักเป็นอุปกรณ์ประกอบฉากสำหรับเด็กให้ผู้ปกครองสร้างองค์ประกอบเด็กด้วยตนเอง Renderเป็นอุปกรณ์ประกอบฉากในตัวในการตอบสนองซึ่งใช้พารามิเตอร์ฟังก์ชั่น ในฟังก์ชั่นนี้คุณสามารถให้ผู้ปกครององค์ประกอบแสดงสิ่งที่คุณต้องการด้วยพารามิเตอร์ที่กำหนดเอง โดยพื้นฐานแล้วมันจะเหมือนกับอุปกรณ์สำหรับเด็ก แต่สามารถปรับแต่งได้มากกว่า

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


0

เมื่อมีการใช้ชิ้นส่วนการทำงานคุณมักจะได้รับข้อผิดพลาดเมื่อพยายามที่จะตั้งค่าคุณสมบัติใหม่บนTypeError: Cannot add property myNewProp, object is not extensible props.childrenมีการทำงานเกี่ยวกับเรื่องนี้โดยการโคลนอุปกรณ์ประกอบฉากแล้วโคลนเด็กด้วยอุปกรณ์ใหม่

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>
  )
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.