คอมโพเนนต์ไม่ติดตั้งใหม่เมื่อพารามิเตอร์เส้นทางเปลี่ยนไป


104

ฉันกำลังทำงานกับแอปพลิเคชันตอบกลับโดยใช้ react-router ฉันมีหน้าโครงการที่มี url ดังนี้:

myapplication.com/project/unique-project-id

เมื่อคอมโพเนนต์ของโปรเจ็กต์โหลดฉันจะทริกเกอร์การร้องขอข้อมูลสำหรับโปรเจ็กต์นั้นจากเหตุการณ์ componentDidMount ตอนนี้ฉันพบปัญหาที่ถ้าฉันสลับระหว่างสองโครงการโดยตรงดังนั้น id เท่านั้นที่เปลี่ยนไปเช่นนี้ ...

myapplication.com/project/982378632
myapplication.com/project/782387223
myapplication.com/project/198731289

componentDidMount ไม่ถูกทริกเกอร์อีกดังนั้นข้อมูลจึงไม่ถูกรีเฟรช มีเหตุการณ์วงจรชีวิตอื่นที่ฉันควรใช้เพื่อเรียกใช้คำขอข้อมูลของฉันหรือใช้กลยุทธ์อื่นในการแก้ไขปัญหานี้หรือไม่


1
คุณช่วยโพสต์รหัสส่วนประกอบของคุณได้ไหม
Pierre Criulanscy

คำตอบ:


83

หากลิงก์นำไปยังเส้นทางเดียวกันโดยใช้เพียงพารามิเตอร์ที่แตกต่างกันจะไม่ทำการต่อเชื่อมใหม่ แต่จะได้รับอุปกรณ์ประกอบฉากใหม่แทน ดังนั้นคุณสามารถใช้ฟังก์ชั่นและมองหาcomponentWillReceiveProps(newProps)newProps.params.projectId

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


2
ขอขอบคุณ. ฉันสามารถทำงานนี้ได้โดยใช้ componentWillReceiveProps ฉันยังไม่เข้าใจโฟลว์ข้อมูล React Router Mega Demo จริงๆ ฉันเห็น fetchData แบบคงที่ที่กำหนดไว้ในส่วนประกอบบางอย่าง สถิติเหล่านี้ถูกเรียกจากฝั่งไคลเอ็นต์ของเราเตอร์อย่างไร
Constellates

1
คำถามที่ดี ตรวจสอบไฟล์ client.js และไฟล์ server.js ในระหว่างการรันของเราเตอร์พวกเขาเรียกไฟล์ fetchData.js และส่งต่อstateไปยังไฟล์นั้น ตอนแรกสับสนเล็กน้อย แต่ก็ชัดเจนขึ้นเล็กน้อย
Brad B

13
FYI: "componentWillReceiveProps" ถือเป็นมรดก
Idan Dagan

5
คุณควรใช้ ComponentDidUpdate กับ React 16 เนื่องจาก componentWillReceiveProps เลิกใช้งาน
Pato Loco

1
วิธีนี้ใช้งานได้ แต่มีปัญหาที่คุณต้องเพิ่ม componentWillReceiveProps () หรือ componentDidUpdate () สำหรับจัดการ re-renders สำหรับทุกส่วนประกอบที่กำลังโหลดข้อมูล ตัวอย่างเช่นลองนึกถึงหน้าที่มีส่วนประกอบหลายอย่างที่กำลังโหลดข้อมูลตามพารามิเตอร์พา ธ เดียวกัน
Juha Syrjälä

104

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


3
ยอดเยี่ยม! ขอบคุณสำหรับคำตอบที่เรียบง่ายและสง่างาม
Z_z_Z

แค่ถามว่าสิ่งนี้จะไม่ส่งผลต่อประสิทธิภาพของแอปหรือไม่?
Alpit Anand

1
@AlpitAnand นี่เป็นคำถามกว้าง ๆ การติดตั้งใหม่นั้นช้ากว่าการเรนเดอร์อย่างแน่นอนเนื่องจากจะทำให้เกิดอายุการใช้งานมากขึ้น ที่นี่ฉันจะตอบว่าจะให้รีเมาท์ได้อย่างไรเมื่อมีการเปลี่ยนแปลงเส้นทาง
wei

แต่มันไม่ได้ส่งผลกระทบมาก? คำแนะนำของคุณคืออะไรเราสามารถใช้ได้อย่างปลอดภัยโดยไม่มีผลกระทบมาก?
Alpit Anand

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

88

นี่คือคำตอบของฉันคล้ายกับบางส่วนข้างต้น แต่มีรหัส

<Route path="/page/:pageid" render={(props) => (
  <Page key={props.match.params.pageid} {...props} />)
} />

4
กุญแจสำคัญที่นี่มีบทบาทสำคัญที่สุดเนื่องจากรูปภาพที่คุณมีลิงก์ไปยังหน้าโปรไฟล์เมื่อคลิกมันก็จะเปลี่ยนเส้นทางไปยังเส้นทางเดียวกัน แต่ด้วยพารามิเตอร์ที่แตกต่างกัน แต่คอมโพเนนต์จะไม่อัปเดตเนื่องจาก React ไม่พบความแตกต่าง เพราะต้องมีคีย์ในการเปรียบเทียบผลการแข่งขันเก่า
Georgi

15

คุณต้องชัดเจนว่าการเปลี่ยนเส้นทางจะไม่ทำให้เกิดการรีเฟรชหน้าคุณต้องจัดการด้วยตัวเอง

import theThingsYouNeed from './whereYouFindThem'

export default class Project extends React.Component {

    componentWillMount() {

        this.state = {
            id: this.props.router.params.id
        }

        // fire action to update redux project store
        this.props.dispatch(fetchProject(this.props.router.params.id))
    }

    componentDidUpdate(prevProps, prevState) {
         /**
         * this is the initial render
         * without a previous prop change
         */
        if(prevProps == undefined) {
            return false
        }

        /**
         * new Project in town ?
         */
        if (this.state.id != this.props.router.params.id) {
           this.props.dispatch(fetchProject(this.props.router.params.id))
           this.setState({id: this.props.router.params.id})
        }

    }

    render() { <Project .../> }
}

ขอบคุณวิธีนี้ใช้ได้ผลสำหรับฉัน แต่การตั้งค่า eslint ของฉันกำลังบ่นเกี่ยวกับเรื่องนี้: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/…คุณคิดอย่างไรเกี่ยวกับเรื่องนี้?
konekoya

ใช่ที่จริงไม่ปฏิบัติที่ดีที่สุดขออภัยเกี่ยวกับที่หลังจากการฝึกอบรมเรื่อง "fetchProject" ลดจะอัปเดตอุปกรณ์ประกอบฉากของคุณนะผมก็ใช้วิธีนี้จะทำให้จุดของฉันโดยไม่ต้องใส่เข้าไปในสถานที่ Redux
chickenchilli

สวัสดี chickenchilli ฉันยังติดอยู่ในนี้จริงๆ ... คุณมีคำแนะนำอื่น ๆ หรือแบบฝึกหัดที่แนะนำหรือไม่? หรือฉันจะยึดแนวทางแก้ไขของคุณในตอนนี้ ขอบคุณสำหรับความช่วยเหลือของคุณ!
konekoya

14

ถ้าคุณมี:

<Route
   render={(props) => <Component {...props} />}
   path="/project/:projectId/"
/>

ใน React 16.8 ขึ้นไปโดยใช้ hooksคุณสามารถทำได้:

import React, { useEffect } from "react";
const Component = (props) => {
  useEffect(() => {
    props.fetchResource();
  }, [props.match.params.projectId]);

  return (<div>Layout</div>);
}
export default Component;

ในนั้นคุณจะเรียกใช้การfetchResourceโทรใหม่เมื่อมีprops.match.params.idการเปลี่ยนแปลงเท่านั้น


4
นี่เป็นคำตอบที่ดีกว่าเนื่องจากคำตอบอื่น ๆ ส่วนใหญ่อาศัยคำตอบที่เลิกใช้แล้วและไม่ปลอดภัยในขณะนี้componentWillReceiveProps
pjb

10

จากคำตอบของ @wei, @ Breakpoint25 และ @PaulusLimma ฉันสร้างส่วนประกอบทดแทนนี้สำหรับไฟล์<Route>. การดำเนินการนี้จะต่อเชื่อมหน้าใหม่เมื่อ URL เปลี่ยนไปโดยบังคับให้ส่วนประกอบทั้งหมดในเพจถูกสร้างและต่อเชื่อมอีกครั้งไม่ใช่เพียงแค่แสดงผลซ้ำ componentDidMount()เบ็ดเริ่มต้นทั้งหมดและอื่น ๆ ทั้งหมดจะถูกเรียกใช้ในการเปลี่ยนแปลง URL ด้วย

แนวคิดคือการเปลี่ยนkeyคุณสมบัติคอมโพเนนต์เมื่อ URL เปลี่ยนไปและสิ่งนี้บังคับให้ React เมาท์คอมโพเนนต์อีกครั้ง

คุณสามารถใช้แทนดรอปอินได้<Route>เช่นนี้:

<Router>
  <Switch>
    <RemountingRoute path="/item/:id" exact={true} component={ItemPage} />
    <RemountingRoute path="/stuff/:id" exact={true} component={StuffPage} />
  </Switch>
</Router>

<RemountingRoute>องค์ประกอบที่ถูกกำหนดไว้เช่นนี้

export const RemountingRoute = (props) => {
  const {component, ...other} = props
  const Component = component
  return (
    <Route {...other} render={p => <Component key={p.location.pathname + p.location.search}
                                              history={p.history}
                                              location={p.location}
                                              match={p.match} />}
    />)
}

RemountingRoute.propsType = {
  component: PropTypes.object.isRequired
}

สิ่งนี้ได้รับการทดสอบด้วย React-Router 4.3


5

คำตอบของ @ wei ใช้งานได้ดี แต่ในบางสถานการณ์ฉันพบว่าดีกว่าที่จะไม่ตั้งค่าคีย์ของส่วนประกอบภายใน แต่กำหนดเส้นทางเอง นอกจากนี้หากพา ธ ไปยังคอมโพเนนต์เป็นแบบคงที่ แต่คุณเพียงแค่ต้องการให้คอมโพเนนต์ทำการเมานต์ใหม่ทุกครั้งที่ผู้ใช้ไปที่มัน (อาจจะทำการโทร api-call ที่ componentDidMount ()) การตั้งค่า location.pathname เป็นคีย์ของเส้นทางก็สะดวก ด้วยเส้นทางนี้และเนื้อหาทั้งหมดจะได้รับการติดตั้งใหม่เมื่อมีการเปลี่ยนแปลงสถานที่

const MainContent = ({location}) => (
    <Switch>
        <Route exact path='/projects' component={Tasks} key={location.pathname}/>
        <Route exact path='/tasks' component={Projects} key={location.pathname}/>
    </Switch>
);

export default withRouter(MainContent)

5

นี่คือวิธีที่ฉันแก้ไขปัญหา:

วิธีนี้ได้รับแต่ละรายการจาก API:

loadConstruction( id ) {
    axios.get('/construction/' + id)
      .then( construction => {
        this.setState({ construction: construction.data })
      })
      .catch( error => {
        console.log('error: ', error);
      })
  }

ฉันเรียกเมธอดนี้จาก componentDidMount วิธีนี้จะถูกเรียกเพียงครั้งเดียวเมื่อฉันโหลดเส้นทางนี้เป็นครั้งแรก:

componentDidMount() {   
    const id = this.props.match.params.id;
    this.loadConstruction( id )
  }

และจาก componentWillReceiveProps ซึ่งจะถูกเรียกใช้ตั้งแต่ครั้งที่สองที่เราโหลดเส้นทางเดียวกัน แต่ ID ต่างกันและฉันเรียกวิธีแรกเพื่อโหลดสถานะใหม่จากนั้นส่วนประกอบจะโหลดรายการใหม่

componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.id !== this.props.match.params.id) {
      const id = nextProps.match.params.id
      this.loadConstruction( id );
    }
  }

วิธีนี้ใช้งานได้ แต่มีปัญหาที่คุณต้องเพิ่ม componentWillReceiveProps () สำหรับจัดการ re-renders ไปยังทุกส่วนประกอบที่กำลังโหลดข้อมูล
Juha Syrjälä

ใช้ componentDidUpdate (prevProps) componentWillUpdate () componentWillReceiveProps () เลิกใช้แล้ว
Mirek Michalak

0

หากคุณกำลังใช้ Class Component คุณสามารถใช้componentDidUpdate

componentDidMount() {
    const { projectId } = this.props.match.params
    this.GetProject(id); // Get the project when Component gets Mounted
 }

componentDidUpdate(prevProps, prevState) {
    const { projectId } = this.props.match.params

    if (prevState.projetct) { //Ensuring This is not the first call to the server
      if(projectId !== prevProps.match.params.projectId ) {
        this.GetProject(projectId); // Get the new Project when project id Change on Url
      }
    }
 }

componentDidUpdate เลิกใช้แล้วตอนนี้จะเป็นการดีถ้าคุณสามารถแนะนำทางเลือกอื่นได้เช่นกัน
ซาฮิล

คุณแน่ใจหรือไม่? ComponentDidUpdate ไม่ใช่และจะไม่ถูกเลิกใช้งานในเวอร์ชันต่อ ๆ ไปคุณสามารถแนบลิงค์ได้หรือไม่? ฟังก์ชันที่จะเลิกใช้งานดังต่อไปนี้: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas

0

คุณสามารถใช้วิธีการที่ให้ไว้:

useEffect(() => {
  // fetch something
}, [props.match.params.id])

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

ไม่ใช่วิธีที่ดีที่สุด แต่ดีพอที่จะจัดการกับสิ่งที่คุณกำลังมองหาตามKent C Dodds : พิจารณาเพิ่มเติมว่าคุณต้องการให้เกิดอะไรขึ้น


-3

react-router เสียเนื่องจากต้องต่อเชื่อมส่วนประกอบใหม่ในการเปลี่ยนตำแหน่ง

ฉันจัดการเพื่อค้นหาการแก้ไขข้อบกพร่องนี้แม้ว่า:

https://github.com/ReactTraining/react-router/issues/1982#issuecomment-275346314

โดยสรุป (ดูลิงค์ด้านบนสำหรับข้อมูลทั้งหมด)

<Router createElement={ (component, props) =>
{
  const { location } = props
  const key = `${location.pathname}${location.search}`
  props = { ...props, key }
  return React.createElement(component, props)
} }/>

สิ่งนี้จะทำให้มันถูกเชื่อมต่ออีกครั้งเมื่อมีการเปลี่ยนแปลง URL

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