componentDidMount เรียกว่า BEFORE ref callback


87

ปัญหา

ฉันกำลังตั้งค่าการตอบสนองrefโดยใช้นิยามฟังก์ชันแบบอินไลน์

render = () => {
    return (
        <div className="drawer" ref={drawer => this.drawerRef = drawer}>

ดังนั้นในcomponentDidMountการอ้างอิง DOM ไม่ได้ตั้งค่าไว้

componentDidMount = () => {
    // this.drawerRef is not defined

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

ตัวอย่างโค้ดอื่น ๆ ที่ฉันได้ดูตัวอย่างเช่นการสนทนาบน github นี้บ่งบอกถึงข้อสันนิษฐานเดียวกันcomponentDidMountควรถูกเรียกหลังจากการrefเรียกกลับใด ๆ ที่กำหนดไว้renderแม้จะระบุไว้ในการสนทนา

ดังนั้น componentDidMount จึงถูกไล่ออกหลังจากเรียกใช้ ref callback ทั้งหมดแล้ว?

ใช่.

ฉันใช้ react 15.4.1

อย่างอื่นฉันได้ลองแล้ว

เพื่อตรวจสอบว่าrefมีการเรียกใช้ฟังก์ชันนี้ฉันได้ลองกำหนดมันในคลาสเช่นนี้

setDrawerRef = (drawer) => {
  this.drawerRef = drawer;
}

จากนั้นใน render

<div className="drawer" ref={this.setDrawerRef}>

การล็อกคอนโซลในกรณีนี้แสดงให้เห็นว่ามีการเรียกกลับจริงหลังจากนั้น componentDidMount


6
ฉันอาจจะผิด แต่เมื่อคุณใช้ฟังก์ชั่นลูกศรสำหรับการแสดงผลเมทโธมันจะจับค่าthisจากขอบเขตศัพท์นอกคลาสของคุณ ลองกำจัดไวยากรณ์ฟังก์ชันลูกศรสำหรับเมธอดคลาสของคุณและดูว่าช่วยได้หรือไม่
โยชิ

3
@GProst นั่นคือลักษณะของคำถามของฉัน ฉันใส่ console.log ทั้งในฟังก์ชันและ componentDidMount กำลังทำงานก่อนการเรียกกลับอ้างอิงที่สอง
quickshiftin

3
เพียงแค่มี issue-- คล้ายกันสำหรับเราโดยทั่วไปเราพลาดมันในครั้งแรกrenderและจำเป็นจะต้องใช้ประโยชน์จึงcomponentDidUpdateเป็นcomponentDidMountไม่ได้เป็นส่วนหนึ่งของการปรับปรุงวงจร อาจไม่ใช่ปัญหาของคุณ แต่คิดว่าอาจคุ้มค่าที่จะยกเป็นแนวทางแก้ไข
Alexander Nied

4
เหมือนกันกับ React 16. เอกสารระบุชัดเจนref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.แต่ดูเหมือนจะไม่เป็นความจริง :(
Ryan H.

1
1. ref ประกาศลูกศรคือ: ref = {ref => { this.drawerRef = ref }}2. แม้ refs จะถูกเรียกก่อน componentDidMount; การอ้างอิงสามารถเข้าถึงได้หลังจากการเรนเดอร์ครั้งแรกเมื่อมีการแสดงผล div ในกรณีของคุณเท่านั้น ดังนั้นคุณต้องสามารถเข้าถึงการอ้างอิงในระดับถัดไปเช่นใน componentWillReceiveProps โดยใช้this.drawerRef3 หากคุณพยายามเข้าถึงก่อนการเมานต์เริ่มต้นคุณจะได้รับค่า ref ที่ไม่ได้กำหนดไว้เท่านั้น
bh4r4th

คำตอบ:


156

คำตอบสั้น ๆ :

React รับประกันว่า refs ถูกตั้งค่าก่อนcomponentDidMountหรือcomponentDidUpdatehooks แต่สำหรับเด็กที่แสดงผลจริงเท่านั้น

componentDidMount() {
  // can use any refs here
}

componentDidUpdate() {
  // can use any refs here
}

render() {
  // as long as those refs were rendered!
  return <div ref={/* ... */} />;
}

โปรดทราบว่านี่ไม่ได้หมายความว่า“ React จะตั้งค่าการอ้างอิงทั้งหมดก่อนที่ hooks เหล่านี้จะทำงานเสมอ”
ลองดูตัวอย่างบางส่วนที่ไม่ได้รับการอ้างอิง


Refs ไม่ได้รับการตั้งค่าสำหรับองค์ประกอบที่ไม่ได้แสดงผล

ตอบสนองจะโทรเรียกกลับโทษสำหรับองค์ประกอบจริงที่คุณกลับมาจากการแสดงผล

ซึ่งหมายความว่าหากรหัสของคุณมีลักษณะ

render() {
  if (this.state.isLoading) {
    return <h1>Loading</h1>;
  }

  return <div ref={this._setRef} />;
}

และต้นthis.state.isLoadingคือtrueคุณควรจะไม่ได้คาดหวังว่าจะได้รับการเรียกก่อนthis._setRefcomponentDidMount

สิ่งนี้น่าจะสมเหตุสมผล: หากการเรนเดอร์ครั้งแรกของคุณส่งคืน<h1>Loading</h1>ไม่มีทางเป็นไปได้ที่ React จะรู้ว่าภายใต้เงื่อนไขอื่น ๆ จะส่งคืนสิ่งอื่นที่จำเป็นต้องแนบการอ้างอิง นอกจากนี้ยังมีอะไรที่จะกำหนดโทษที่:<div>องค์ประกอบไม่ได้สร้างเพราะrender()วิธีการบอกว่ามันไม่ควรจะแสดงผล

ดังนั้นด้วยตัวอย่างนี้componentDidMountจะเริ่มทำงานเท่านั้น อย่างไรก็ตามเมื่อมีthis.state.loadingการเปลี่ยนแปลงfalseคุณจะเห็นไฟล์this._setRefแนบก่อนจากนั้นcomponentDidUpdateจะเริ่มทำงาน


ระวังส่วนประกอบอื่น ๆ

โปรดทราบว่าหากคุณส่งเด็กที่มีการอ้างอิงไปยังส่วนประกอบอื่น ๆมีโอกาสที่พวกเขาจะทำบางสิ่งที่ขัดขวางการแสดงผล (และทำให้เกิดปัญหา)

ตัวอย่างเช่นสิ่งนี้:

<MyPanel>
  <div ref={this.setRef} />
</MyPanel>

จะไม่ทำงานหากMyPanelไม่ได้รวมprops.childrenไว้ในผลลัพธ์:

function MyPanel(props) {
  // ignore props.children
  return <h1>Oops, no refs for you today!</h1>;
}

อีกครั้งก็ไม่ได้เป็นปัญหา: จะมีอะไรให้ตอบสนองกับตั้งเตะโทษเพราะองค์ประกอบ DOM ก็ไม่ได้สร้าง


Refs ไม่ได้รับการตั้งค่าก่อนวงจรชีวิตหากส่งผ่านไปยังที่ซ้อน ReactDOM.render()

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

ตัวอย่างเช่นบางทีมันอาจจะไม่ส่งเด็กกลับมาrender()และกำลังโทรหาReactDOM.render()lifecycle hook แทน คุณสามารถค้นหาตัวอย่างนี้ที่นี่ ในตัวอย่างนั้นเราแสดง:

<MyModal>
  <div ref={this.setRef} />
</MyModal>

แต่MyModalทำการReactDOM.render()โทรด้วย componentDidUpdateวิธีวงจรชีวิต:

componentDidUpdate() {
  ReactDOM.render(this.props.children, this.targetEl);
}

render() {
  return null;
}

ตั้งแต่ React 16 เช่นระดับบนสุดทำให้การโทรในช่วงวงจรชีวิตจะได้รับการเลื่อนออกไปจนกว่าวงจรชีวิตได้วิ่งไปที่ต้นไม้ทั้งหมด สิ่งนี้จะอธิบายว่าทำไมคุณไม่เห็นการอ้างอิงที่แนบมาในเวลา

วิธีแก้ปัญหานี้คือการใช้ พอร์ทัลแทนการReactDOM.renderเรียกซ้อน:

render() {
  return ReactDOM.createPortal(this.props.children, this.targetEl);
}

วิธีนี้ของเราที่<div>มีการอ้างอิงจะรวมอยู่ในผลลัพธ์การแสดงผล

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

อย่าใช้setStateเพื่อจัดเก็บการอ้างอิง

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


หากยังมีปัญหา

หากเคล็ดลับข้างต้นไม่มีความช่วยเหลือให้ยื่นปัญหาใน React แล้วเราจะตรวจสอบ


2
ฉันได้แก้ไขคำตอบของฉันเพื่ออธิบายสถานการณ์นี้ด้วย ดูส่วนแรก หวังว่านี่จะช่วยได้!
Dan Abramov

สวัสดี @DanAbramov ขอบคุณสำหรับสิ่งนี้! น่าเสียดายที่ฉันไม่สามารถพัฒนากรณีที่ทำซ้ำได้เมื่อฉันพบครั้งแรก น่าเศร้าที่ฉันไม่ได้ทำงานในโครงการนั้นอีกต่อไปและไม่สามารถทำซ้ำได้ตั้งแต่นั้นมา คำถามได้รับความนิยมมากพอที่ฉันเห็นด้วยการพยายามค้นหากรณีที่ทำซ้ำได้เป็นกุญแจสำคัญเนื่องจากหลายคนดูเหมือนจะประสบปัญหานี้
quickshiftin

ฉันคิดว่าในหลาย ๆ กรณีอาจเกิดจากความเข้าใจผิด ใน React 15 สิ่งนี้อาจเกิดขึ้นได้เนื่องจากข้อผิดพลาดที่กลืนเข้าไป (React 16 มีการจัดการข้อผิดพลาดที่ดีกว่าและป้องกันสิ่งนี้) ฉันยินดีที่จะตรวจสอบกรณีอื่น ๆ เมื่อเกิดเหตุการณ์นี้ขึ้นดังนั้นอย่าลังเลที่จะเพิ่มพวกเขาในความคิดเห็น
Dan Abramov

ช่วยด้วย! ฉันไม่ได้สังเกตจริงๆว่ามีพรีโหลด
Nazariy

1
คำตอบนี้ช่วยฉันได้จริงๆ ฉันกำลังดิ้นรนกับการอ้างอิง "อ้างอิง" ที่ว่างเปล่าและปรากฎว่า "องค์ประกอบ" ไม่ได้รับการแสดงผลเลย
MarkSkayff

1

การสังเกตปัญหาที่แตกต่างกัน

ฉันตระหนักว่าปัญหาเกิดขึ้นขณะอยู่ในโหมดการพัฒนาเท่านั้น หลังจากการตรวจสอบเพิ่มเติมฉันพบว่าการปิดใช้งานreact-hot-loaderในการกำหนดค่า Webpack ของฉันช่วยป้องกันปัญหานี้

ฉันใช้

  • "react-hot-loader": "3.1.3"
  • "webpack": "4.10.2",

และเป็นแอปอิเล็กตรอน

การกำหนดค่าการพัฒนา Webpack บางส่วนของฉัน

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')

module.exports = merge(baseConfig, {

  entry: [
    // REMOVED THIS -> 'react-hot-loader/patch',
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    '@babel/polyfill',
    './app/index'
  ],
  ...
})

มันน่าสงสัยเมื่อฉันเห็นว่าการใช้ฟังก์ชันอินไลน์ใน render () ใช้งานได้ แต่การใช้วิธีการผูกมัดนั้นขัดข้อง

ใช้งานได้ในทุกกรณี

class MyComponent {
  render () {
    return (
      <input ref={(el) => {this.inputField = el}}/>
    )
  }
}

ขัดข้องกับ react-hot-loader (ref ไม่ได้กำหนดใน componentDidMount)

class MyComponent {
  constructor (props) {
    super(props)
    this.inputRef = this.inputRef.bind(this)
  }

  inputRef (input) {
    this.inputField = input
  }

  render () {
    return (
      <input ref={this.inputRef}/>
    )
  }
}

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


นี่อาจอธิบายได้ว่าทำไมฉันถึงมีปัญหากับสิ่งนี้ใน CodePen แต่การใช้ฟังก์ชันอินไลน์ไม่ได้ช่วยในกรณีของฉัน
robartsd

0

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

componentDidMount(){
    interval_holder = setInterval(() => {
    this.myref = "something";//accessing ref of a component
    }, 2000);
  }

ช่วงเวลาที่ชัดเจนเสมอเช่น

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