Re-re-render React component เมื่อ prop เปลี่ยนไป


95

ฉันกำลังพยายามแยกองค์ประกอบการนำเสนอออกจากส่วนประกอบคอนเทนเนอร์ ฉันมีSitesTableและSitesTableContainer. คอนเทนเนอร์มีหน้าที่ทริกเกอร์การกระทำซ้ำซ้อนเพื่อดึงข้อมูลไซต์ที่เหมาะสมตามผู้ใช้ปัจจุบัน

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

class SitesTableContainer extends React.Component {
    static get propTypes() {
      return {
        sites: React.PropTypes.object,
        user: React.PropTypes.object,
        isManager: React.PropTypes.boolean
      }
     }

    componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

    render() {
      return <SitesTable sites={this.props.sites}/>
    }
}

function mapStateToProps(state) {
  const user = userUtils.getCurrentUser(state)

  return {
    sites: state.get('sites'),
    user,
    isManager: userUtils.isManager(user)
  }
}

export default connect(mapStateToProps)(SitesTableContainer);

คุณมีฟังก์ชั่นอื่น ๆ ที่พร้อมใช้งานเช่น componentDidUpdate หรืออาจเป็นสิ่งที่คุณกำลังมองหา componentWillReceiveProps (nextProps) หากคุณต้องการยิงบางสิ่งบางอย่างเมื่ออุปกรณ์ประกอบฉากเปลี่ยนไป
2559

เหตุใดคุณจึงต้องแสดง SitesTable อีกครั้งหากไม่ได้เปลี่ยนอุปกรณ์ประกอบฉาก
QoP

@QoP การดำเนินการที่ส่งเข้ามาcomponentDidMountจะเปลี่ยนsitesโหนดในสถานะแอปพลิเคชันซึ่งส่งผ่านไปยังไฟล์SitesTable. sitesโหนดของ SitesStable จะเปลี่ยนไป
เดวิด

ฉันเข้าใจแล้วฉันจะเขียนคำตอบ
QoP

1
วิธีบรรลุสิ่งนี้ในองค์ประกอบที่ใช้งานได้
yaswanthkoneri

คำตอบ:


122

คุณต้องเพิ่มเงื่อนไขในcomponentDidUpdateวิธีการของคุณ

ตัวอย่างคือการใช้fast-deep-equalเพื่อเปรียบเทียบวัตถุ

import equal from 'fast-deep-equal'

...

constructor(){
  this.updateUser = this.updateUser.bind(this);
}  

componentDidMount() {
  this.updateUser();
}

componentDidUpdate(prevProps) {
  if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID  (this.props.user.id !== prevProps.user.id)
  {
    this.updateUser();
  }
} 

updateUser() {
  if (this.props.isManager) {
    this.props.dispatch(actions.fetchAllSites())
  } else {
    const currentUserId = this.props.user.get('id')
    this.props.dispatch(actions.fetchUsersSites(currentUserId))
  }  
}

การใช้ตะขอ (ตอบสนอง 16.8.0+)

import React, { useEffect } from 'react';

const SitesTableContainer = ({
  user,
  isManager,
  dispatch,
  sites,
}) => {
  useEffect(() => {
    if(isManager) {
      dispatch(actions.fetchAllSites())
    } else {
      const currentUserId = user.get('id')
      dispatch(actions.fetchUsersSites(currentUserId))
    }
  }, [user]); 

  return (
    return <SitesTable sites={sites}/>
  )

}

หากเสาที่คุณกำลังเปรียบเทียบเป็นวัตถุหรืออาร์เรย์คุณควรใช้useDeepCompareEffectแทนuseEffect.


โปรดทราบว่า JSON.stringify สามารถใช้สำหรับการเปรียบเทียบประเภทนี้เท่านั้นหากมีความเสถียร (ตามข้อกำหนดไม่ใช่) ดังนั้นจึงสร้างเอาต์พุตเดียวกันสำหรับอินพุตเดียวกัน ฉันแนะนำให้เปรียบเทียบคุณสมบัติ id ของอ็อบเจ็กต์ผู้ใช้หรือส่ง userId-s ในอุปกรณ์ประกอบฉากและเปรียบเทียบเพื่อหลีกเลี่ยงการโหลดซ้ำโดยไม่จำเป็น
LászlóKardinál

4
โปรดทราบว่าเมธอด componentWillReceiveProps นั้นเลิกใช้งานแล้วและมีแนวโน้มที่จะถูกลบออกใน React 17. การใช้การรวมกันของ componentDidUpdate และเมธอด getDerivedStateFromProps ใหม่เป็นกลยุทธ์ที่แนะนำจากทีม React dev เพิ่มเติมในโพสต์บล็อกของพวกเขา: reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak

@QoP ตัวอย่างที่สองด้วย React Hooks จะยกเลิกการต่อเชื่อมและต่อเชื่อมใหม่ทุกuserครั้งที่มีการเปลี่ยนแปลง แพงขนาดนี้?
Robotron

32

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

ComponentDidUpdate()ถูกเรียกเมื่อใดก็ตามที่คอมโพเนนต์อัปเดตและหากShouldComponentUpdate()ส่งคืนค่าจริง (หากShouldComponentUpdate()ไม่ได้กำหนดไว้จะส่งคืนtrueตามค่าเริ่มต้น)

shouldComponentUpdate(nextProps){
    return nextProps.changedProp !== this.state.changedProp;
}

componentDidUpdate(props){
    // Desired operations: ex setting state
}

พฤติกรรมเดียวกันนี้สามารถทำได้โดยใช้ComponentDidUpdate()วิธีการเท่านั้นโดยรวมคำสั่งเงื่อนไขไว้ในนั้น

componentDidUpdate(prevProps){
    if(prevProps.changedProp !== this.props.changedProp){
        this.setState({          
            changedProp: this.props.changedProp
        });
    }
}

หากพยายามตั้งค่าสถานะโดยไม่มีเงื่อนไขหรือไม่มีการกำหนดShouldComponentUpdate()องค์ประกอบจะแสดงผลซ้ำได้ไม่สิ้นสุด


2
คำตอบนี้ต้องได้รับการโหวต (อย่างน้อยตอนนี้) เนื่องจากcomponentWillReceivePropsกำลังจะเลิกใช้งานและแนะนำให้ใช้
AnBisw

รูปแบบที่สอง (คำสั่งเงื่อนไขภายใน componentDidUpdate) ใช้ได้กับฉันเพราะฉันต้องการให้การเปลี่ยนแปลงสถานะอื่น ๆ ยังคงเกิดขึ้นเช่นการปิดข้อความแฟลช
Little Brain

มีคนใช้ shouldComponentUpdate ในรหัสของฉันและฉันไม่เข้าใจว่าอะไรเป็นสาเหตุของปัญหานี้ทำให้ชัดเจน
สตีฟมอเรตซ์

14

คุณสามารถใช้KEYคีย์ที่ไม่ซ้ำกัน (การรวมกันของข้อมูล) ที่เปลี่ยนไปพร้อมกับอุปกรณ์ประกอบฉากและส่วนประกอบนั้นจะแสดงผลด้วยอุปกรณ์ประกอบฉากที่อัปเดต


4
componentWillReceiveProps(nextProps) { // your code here}

ฉันคิดว่านั่นคือเหตุการณ์ที่คุณต้องการ componentWillReceivePropsทริกเกอร์เมื่อใดก็ตามที่ส่วนประกอบของคุณได้รับบางสิ่งผ่านอุปกรณ์ประกอบฉาก จากนั้นคุณสามารถตรวจสอบได้จากนั้นทำสิ่งที่คุณต้องการทำ


12
componentWillReceivePropsเลิกใช้แล้ว *
Maihan Nijat

3

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

ส่วนนี้ของรหัสของคุณ:

componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

ไม่ควรถูกทริกเกอร์ในส่วนประกอบควรจัดการหลังจากดำเนินการตามคำขอแรกของคุณ

ดูตัวอย่างนี้จากredux-thunk :

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

คุณไม่จำเป็นต้องใช้ redux-thunk แต่จะช่วยให้คุณหาเหตุผลเกี่ยวกับสถานการณ์เช่นนี้และเขียนโค้ดให้ตรงกัน


ใช่ฉันเข้าใจแล้ว แต่คุณส่งmakeASandwichWithSecretSauce ส่วนประกอบของคุณไปที่ไหนกันแน่?
เดวิด

ฉันจะเชื่อมโยงคุณไปยัง repo พร้อมตัวอย่างที่เกี่ยวข้องคุณใช้ react-router กับแอปของคุณหรือไม่?
TameBadger

@David ขอขอบคุณลิงก์ไปยังตัวอย่างนั้นโดยพื้นฐานแล้วฉันมีปัญหาเดียวกัน
SamYoungNY

0

วิธีการใช้งานที่เป็นมิตรมีดังต่อไปนี้เมื่ออัปเดต prop แล้วจะแสดงผลองค์ประกอบโดยอัตโนมัติ:

render {

let textWhenComponentUpdate = this.props.text 

return (
<View>
  <Text>{textWhenComponentUpdate}</Text>
</View>
)

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