เหตุใดอุปกรณ์ประกอบฉาก JSX จึงไม่ควรใช้ฟังก์ชันลูกศรหรือการผูก


103

ฉันใช้ผ้าสำลีกับแอป React ของฉันและฉันได้รับข้อผิดพลาดนี้:

error    JSX props should not use arrow functions        react/jsx-no-bind

และนี่คือที่ที่ฉันเรียกใช้ฟังก์ชันลูกศร (ด้านในonClick):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

นี่คือการปฏิบัติที่ไม่ดีที่ควรหลีกเลี่ยงหรือไม่? แล้ววิธีไหนดีที่สุด?

คำตอบ:


170

เหตุใดคุณจึงไม่ควรใช้ฟังก์ชันลูกศรแบบอินไลน์ในอุปกรณ์ประกอบฉาก JSX

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

  1. เมื่อใดก็ตามที่มีการสร้างฟังก์ชันฟังก์ชันก่อนหน้านี้จะถูกเก็บรวบรวมขยะ การแสดงผลองค์ประกอบจำนวนมากอาจทำให้ภาพเคลื่อนไหวติดขัด

  2. การใช้ฟังก์ชันลูกศรอินไลน์จะทำให้เกิดPureComponents และส่วนประกอบที่ใช้shallowCompareในshouldComponentUpdateวิธีการแสดงผลต่อไป เนื่องจากฟังก์ชันลูกศรถูกสร้างขึ้นใหม่ทุกครั้งการเปรียบเทียบแบบตื้นจะระบุว่าเป็นการเปลี่ยนแปลงของเสาและส่วนประกอบจะแสดงผล

ดังที่คุณเห็นใน 2 ตัวอย่างต่อไปนี้ - เมื่อเราใช้ฟังก์ชันลูกศรอินไลน์<Button>คอมโพเนนต์จะแสดงผลทุกครั้ง (คอนโซลจะแสดงข้อความ "ปุ่มแสดงผล")

ตัวอย่างที่ 1 - PureComponent ที่ไม่มีตัวจัดการแบบอินไลน์

ตัวอย่างที่ 2 - PureComponent พร้อมตัวจัดการแบบอินไลน์

วิธีการเข้าเล่มthisโดยไม่มีฟังก์ชันลูกศร inlining

  1. การผูกวิธีด้วยตนเองในตัวสร้าง:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
  2. การผูกเมธอดโดยใช้ข้อเสนอคลาสฟิลด์ด้วยฟังก์ชันลูกศร เนื่องจากนี่เป็นข้อเสนอขั้นที่ 3 คุณจะต้องเพิ่มค่าที่ตั้งไว้ล่วงหน้าขั้นที่ 3หรือคุณสมบัติของคลาสจะเปลี่ยนเป็นการกำหนดค่า babel ของคุณ

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }

ส่วนประกอบของฟังก์ชันที่มีการเรียกกลับภายใน

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

ตัวอย่างที่ 1 - ส่วนประกอบของฟังก์ชันที่มีการเรียกกลับภายใน:

ในการแก้ปัญหานี้เราสามารถตัดการเรียกกลับด้วยuseCallback()hookและตั้งค่าการอ้างอิงเป็นอาร์เรย์ว่าง

หมายเหตุ:useStateฟังก์ชั่นที่สร้างยอมรับฟังก์ชั่นอัพเดทที่ให้สถานะปัจจุบัน useCallbackด้วยวิธีนี้เราไม่จำเป็นต้องตั้งค่าสถานะปัจจุบันของการพึ่งพา

ตัวอย่างที่ 2 - ส่วนประกอบของฟังก์ชันที่มีการเรียกกลับด้านในห่อด้วย useCallback:


3
คุณจะบรรลุสิ่งนี้ได้อย่างไรกับส่วนประกอบที่ไร้สัญชาติ
lux

4
ไม่มีส่วนประกอบ (ฟังก์ชัน) แบบไม่thisระบุสถานะจึงไม่มีอะไรผูกมัด โดยปกติแล้ววิธีการจะได้รับจากส่วนประกอบสมาร์ทของ Wrapper
Ori Drori

39
@OriDrori: มันทำงานอย่างไรเมื่อคุณต้องการส่งผ่านข้อมูลในการโทรกลับ? onClick={() => { onTodoClick(todo.id) }
adam-beck

4
@ ADAM-เบ็ค - cb() { onTodoClick(this.props.todo.id); }เพิ่มไว้ในความหมายวิธีการโทรกลับในชั้นเรียน
Ori Drori

2
@ adam-beck ฉันคิดว่านี่คือวิธีใช้useCallbackกับค่าไดนามิก stackoverflow.com/questions/55006061/…
Shota Tamura

9

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

คุณสามารถดูคำอธิบายทั้งหมดและข้อมูลเพิ่มเติมได้ที่https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md


ไม่เพียงแค่นั้น. การสร้างอินสแตนซ์ฟังก์ชันใหม่ทุกครั้งหมายถึงสถานะถูกแก้ไขและเมื่อมีการแก้ไขสถานะของคอมโพเนนต์ระบบจะแสดงผลอีกครั้ง เนื่องจากเหตุผลหลักประการหนึ่งในการใช้ React คือการแสดงผลองค์ประกอบที่เปลี่ยนแปลงเท่านั้นการใช้bindหรือฟังก์ชั่นลูกศรในที่นี้คือการยิงตัวเองที่เท้า แม้ว่าจะไม่ได้รับการบันทึกไว้เป็นอย่างดีโดยเฉพาะอย่างยิ่งในกรณีของการทำงานกับmapอาร์เรย์ ping ภายใน Lists เป็นต้น
hippietrail

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

4

เพื่อหลีกเลี่ยงการสร้างฟังก์ชั่นใหม่ด้วยอาร์กิวเมนต์เดียวกันคุณสามารถจดจำผลลัพธ์การผูกฟังก์ชันได้นี่คือยูทิลิตี้ง่ายๆที่มีชื่อmemobindให้ทำ: https://github.com/supnate/memobind


4

การใช้ฟังก์ชันอินไลน์เช่นนี้ทำได้ดีอย่างสมบูรณ์ กฎการขุยล้าสมัย

กฎนี้มาจากช่วงเวลาที่ฟังก์ชันลูกศรไม่เหมือนกันและผู้คนใช้ .bind (this) ซึ่งเคยช้า ปัญหาด้านประสิทธิภาพได้รับการแก้ไขแล้วใน Chrome 49

โปรดสังเกตว่าคุณไม่ได้ส่งผ่านฟังก์ชันอินไลน์เป็นอุปกรณ์ประกอบฉากไปยังส่วนประกอบย่อย

Ryan Florence ผู้เขียน React Router ได้เขียนบทความที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578


คุณช่วยแสดงวิธีเขียนหน่วยทดสอบส่วนประกอบที่มีฟังก์ชันลูกศรอินไลน์ได้ไหม
krankuba

1
@krankuba นี่ไม่ใช่คำถามเกี่ยวกับเรื่องนี้ คุณยังคงสามารถส่งผ่านในฟังก์ชันที่ไม่ระบุชื่อที่ไม่ได้กำหนดไว้ในบรรทัด แต่ยังไม่สามารถทดสอบได้
sbaechler

-1

คุณสามารถใช้ฟังก์ชั่นลูกศรโดยใช้ไลบรารีตัวจัดการแคชปฏิกิริยาโดยไม่ต้องกังวลเกี่ยวกับประสิทธิภาพการแสดงผล:

หมายเหตุ: ภายในจะแคชฟังก์ชันลูกศรของคุณตามคีย์ที่ระบุไม่ต้องกังวลเกี่ยวกับการแสดงผลอีกต่อไป!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

คุณสมบัติอื่น ๆ :

  • ตัวจัดการที่มีชื่อ
  • จัดการเหตุการณ์ด้วยฟังก์ชันลูกศร
  • เข้าถึงคีย์อาร์กิวเมนต์ที่กำหนดเองและเหตุการณ์เดิม
  • ประสิทธิภาพการแสดงผลคอมโพเนนต์
  • บริบทที่กำหนดเองสำหรับเครื่องจัดการ

คำถามคือทำไมเราใช้ไม่ได้ ไม่ใช่วิธีใช้กับแฮ็คอื่น ๆ
kapil

-1

เหตุใดอุปกรณ์ประกอบฉาก JSX จึงไม่ควรใช้ฟังก์ชันลูกศรหรือการผูก

ส่วนใหญ่เนื่องจากฟังก์ชันแบบอินไลน์สามารถทำลายการบันทึกส่วนประกอบที่ปรับให้เหมาะสมได้:

ตามเนื้อผ้าความกังวลด้านประสิทธิภาพเกี่ยวกับฟังก์ชันอินไลน์ใน React เกี่ยวข้องกับวิธีการส่งการเรียกกลับใหม่ในการแสดงผลแต่ละครั้งจะแบ่งshouldComponentUpdateการเพิ่มประสิทธิภาพในองค์ประกอบย่อย ( เอกสาร )

ค่าใช้จ่ายในการสร้างฟังก์ชันเพิ่มเติมน้อยกว่า:

ปัญหาเกี่ยวกับประสิทธิภาพFunction.prototype.bind ได้รับการแก้ไขที่นี่และฟังก์ชันลูกศรอาจเป็นสิ่งที่เกิดขึ้นเองหรือถูกถ่ายทอดโดย Babel เป็นฟังก์ชันธรรมดา ในทั้งสองกรณีเราถือว่าไม่ช้า ( การฝึกปฏิกิริยา )

ฉันเชื่อว่าคนที่อ้างว่าการสร้างฟังก์ชันมีราคาแพงมักได้รับข้อมูลที่ผิด (ทีม React ไม่เคยพูดแบบนี้) ( ทวีต )

เมื่อไหร่จะมีreact/jsx-no-bindกฎมีประโยชน์หรือไม่

คุณต้องการให้แน่ใจว่าส่วนประกอบที่บันทึกไว้นั้นทำงานได้ตามที่ตั้งใจไว้:

  • React.memo (สำหรับส่วนประกอบของฟังก์ชัน)
  • PureComponentหรือกำหนดเองshouldComponentUpdate(สำหรับส่วนประกอบของคลาส)

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

วิธีแก้ข้อผิดพลาด ESLint

คลาส: กำหนดตัวจัดการเป็นวิธีการหรือคุณสมบัติคลาสสำหรับการthisเชื่อมโยง
ตะขอ: ใช้useCallback.

Middleground

ในหลาย ๆ กรณีฟังก์ชั่นแบบอินไลน์นั้นสะดวกในการใช้งานและดีมากในแง่ของความต้องการด้านประสิทธิภาพ น่าเสียดายที่กฎนี้ไม่สามารถ จำกัด ได้เฉพาะประเภทส่วนประกอบที่บันทึกไว้เท่านั้น หากคุณยังต้องการใช้งานทั่วทั้งบอร์ดคุณสามารถปิดการใช้งานได้สำหรับโหนด DOM แบบธรรมดา:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.