Thành phần không kết nối lại khi các thông số tuyến đường thay đổi


97

Tôi đang làm việc trên một ứng dụng phản ứng sử dụng bộ định tuyến phản ứng. Tôi có một trang dự án có url như sau:

myapplication.com/project/unique-project-id

Khi thành phần dự án tải, tôi kích hoạt yêu cầu dữ liệu cho dự án đó từ sự kiện componentDidMount. Bây giờ tôi đang gặp phải một vấn đề trong đó nếu tôi chuyển đổi trực tiếp giữa hai dự án thì chỉ id thay đổi như thế này ...

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

componentDidMount không được kích hoạt lại nên dữ liệu không được làm mới. Có sự kiện vòng đời nào khác mà tôi nên sử dụng để kích hoạt yêu cầu dữ liệu của mình hoặc một chiến lược khác để giải quyết vấn đề này không?


1
Bạn có thể đăng mã thành phần của bạn?
Pierre Criulanscy

Câu trả lời:


81

Nếu liên kết đang hướng đến cùng một tuyến đường chỉ với một thông số khác, nó sẽ không kết nối lại mà thay vào đó nhận các đạo cụ mới. Vì vậy, bạn có thể sử dụng componentWillReceiveProps(newProps)chức năng và tìm kiếm newProps.params.projectId.

Nếu bạn đang cố gắng tải dữ liệu, tôi khuyên bạn nên tìm nạp dữ liệu trước khi bộ định tuyến xử lý trận đấu bằng cách sử dụng các phương thức tĩnh trên thành phần. Kiểm tra ví dụ này. React Router Mega Demo . Bằng cách đó, thành phần sẽ tải dữ liệu và tự động cập nhật khi các thông số tuyến đường thay đổi mà không cần dựa vào componentWillReceiveProps.


2
Cảm ơn bạn. Tôi đã có thể làm cho điều này hoạt động bằng cách sử dụng componentWillReceiveProps. Tôi vẫn chưa thực sự hiểu về luồng dữ liệu React Router Mega Demo. Tôi thấy Dữ liệu tìm nạp tĩnh được xác định trên một số thành phần. Làm thế nào những tĩnh này được gọi từ phía máy khách của bộ định tuyến?
Chòm sao

1
Câu hỏi tuyệt vời. Kiểm tra tệp client.js và tệp server.js. Trong chu trình chạy của bộ định tuyến, chúng gọi tệp fetchData.js và chuyển statetới nó. Nó hơi khó hiểu lúc đầu nhưng sau đó trở nên rõ ràng hơn một chút.
Brad B

12
FYI: "componentWillReceiveProps" được coi là di sản
Idan Dagan

4
Bạn nên sử dụng ComponentDidUpdate với Phản ứng 16 kể từ componentWillReceiveProps bị phản đối
Pato Loco

1
Điều này hoạt động nhưng có vấn đề là bạn cần thêm componentWillReceiveProps () hoặc componentDidUpdate () để xử lý kết xuất cho mọi thành phần đang tải dữ liệu. Ví dụ: hãy nghĩ về một trang có một số thành phần đang tải dữ liệu dựa trên cùng một tham số đường dẫn.
Juha Syrjälä

97

Nếu bạn thực sự cần số lượng lại thành phần khi tuyến đường thay đổi, bạn có thể chuyển một khóa duy nhất cho thuộc tính khóa của thành phần (khóa được liên kết với đường dẫn / tuyến đường của bạn). Vì vậy, mỗi khi tuyến đường thay đổi, khóa cũng sẽ thay đổi kích hoạt thành phần React để ngắt kết nối / kết nối lại. Tôi có ý tưởng từ câu trả lời này


2
Xuất sắc! Cảm ơn bạn vì câu trả lời đơn giản và thanh lịch này
Z_z_Z

Chỉ cần hỏi, điều này sẽ không ảnh hưởng đến hiệu suất của ứng dụng?
Alpit Anand

1
@AlpitVà đây là một câu hỏi rộng. Việc gắn lại chắc chắn chậm hơn kết xuất vì nó kích hoạt nhiều phương pháp vòng đời hơn. Ở đây tôi chỉ trả lời cách thực hiện số tiền còn lại khi tuyến đường thay đổi.
wei

Nhưng nó không phải là một tác động lớn? Lời khuyên của bạn là gì chúng ta có thể sử dụng nó một cách an toàn mà không ảnh hưởng nhiều không?
Alpit Anand

@AlpitAnand Câu hỏi của bạn quá rộng và ứng dụng cụ thể. Đối với một số ứng dụng, có, đó sẽ là một cú đánh về hiệu suất, trong trường hợp đó, bạn nên xem các đạo cụ tự thay đổi và chỉ cập nhật các phần tử cần cập nhật. Mặc dù vậy, phần lớn ở trên là một giải pháp tốt
Chris

83

Đây là câu trả lời của tôi, tương tự như một số câu trên nhưng có mã.

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

4
Chìa khóa ở đây đóng vai trò lớn nhất, bởi vì hình ảnh mà bạn có Liên kết đến trong trang hồ sơ, khi nhấp vào nó chỉ chuyển hướng đến cùng một tuyến, nhưng với các thông số khác nhau, NHƯNG thành phần không cập nhật, vì React không thể tìm thấy sự khác biệt, bởi vì phải có một TỪ KHÓA để so sánh kết quả cũ
Georgi Peev

14

Bạn phải rõ ràng rằng thay đổi lộ trình sẽ không gây ra việc làm mới trang, bạn phải tự xử lý.

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

Cảm ơn, giải pháp này phù hợp với tôi. Nhưng cài đặt eslint của tôi đang phàn nàn về điều này: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/… Bạn nghĩ gì về điều này?
konekoya

Vâng đó là thực sự không thực hành tốt nhất, xin lỗi về điều đó, sau khi cử các "fetchProject" giảm tốc của bạn sẽ cập nhật props của bạn dù sao, tôi chỉ sử dụng này để làm cho quan điểm của tôi mà không đặt vào vị trí Redux
chickenchilli

Xin chào Chickenchilli, Tôi thực sự vẫn còn mắc kẹt trong vấn đề này ... bạn có đề xuất khác hoặc hướng dẫn được đề xuất không? Hoặc tôi sẽ theo giải pháp của bạn bây giờ. Cảm ơn bạn đã giúp đỡ!
konekoya

12

Nếu bạn có:

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

Trong React 16.8 trở lên, sử dụng hook , bạn có thể làm:

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

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

Trong đó, bạn chỉ kích hoạt một fetchResourcecuộc gọi mới bất cứ khi nào props.match.params.idthay đổi.


4
Đây là câu trả lời hay hơn khi hầu hết các câu trả lời khác dựa vào câu trả lời hiện không được dùng nữa và không an toàncomponentWillReceiveProps
pjb 23/03 '19

7

Dựa trên câu trả lời của @wei, @ Breakpoint25 và @PaulusLimma, tôi đã tạo thành phần thay thế này cho <Route>. Điều này sẽ đếm lại trang khi URL thay đổi, buộc tất cả các thành phần trong trang phải được tạo và gắn kết lại, chứ không chỉ được hiển thị lại. Tất cả componentDidMount()và tất cả các hook khởi động khác cũng được thực thi trên thay đổi URL.

Ý tưởng là thay đổi thuộc tính thành phần keykhi URL thay đổi và điều này buộc React phải gắn kết lại thành phần.

Bạn có thể sử dụng nó để thay thế <Route>cho ví dụ như sau:

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

Thành <RemountingRoute>phần được định nghĩa như thế này:

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
}

Điều này đã được thử nghiệm với React-Router 4.3.


5

Câu trả lời của @ wei hoạt động tuyệt vời, nhưng trong một số tình huống, tôi thấy tốt hơn là không đặt khóa của thành phần bên trong mà hãy tự định tuyến. Ngoài ra, nếu đường dẫn đến thành phần là tĩnh, nhưng bạn chỉ muốn thành phần đếm lại mỗi khi người dùng điều hướng đến nó (có lẽ để thực hiện một cuộc gọi api tại componentDidMount ()), bạn nên đặt location.pathname thành khóa của tuyến. Với tuyến đường và tất cả nội dung của nó được ghi lại khi vị trí thay đổi.

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

Đây là cách tôi giải quyết vấn đề:

Phương thức này nhận từng mục từ API:

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

Tôi gọi phương thức này từ componentDidMount, phương thức này sẽ được gọi chỉ một lần, khi tôi tải tuyến đường này lần đầu tiên:

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

Và từ componentWillReceiveProps sẽ được gọi kể từ lần thứ hai chúng tôi tải cùng một tuyến đường nhưng khác ID, và tôi gọi phương thức đầu tiên để tải lại trạng thái và sau đó thành phần sẽ tải mục mới.

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

Điều này hoạt động nhưng có vấn đề là bạn cần thêm componentWillReceiveProps () để xử lý kết xuất cho mọi thành phần đang tải dữ liệu.
Juha Syrjälä

Sử dụng componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () không được dùng nữa.
Mirek Michalak

0

Nếu bạn đang sử dụng Class Component, bạn có thể sử dụng 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 hiện không được dùng nữa, sẽ rất tốt nếu bạn cũng có thể đề xuất một số thay thế.
Sahil

Bạn có chắc chắn về điều đó không? ComponentDidUpdate không và sẽ không được chấp nhận trong phiên bản tới, bạn có thể đính kèm một số liên kết không? các hàm không được dùng nữa như sau: componentWillMount - UNSAFE_componentWillMount componentWillReceiveProps - UNSAFE_componentWillReceiveProps componentWillUpdate - UNSAFE_componentWillUpdate
Angel Mas

0

Bạn có thể sử dụng phương pháp được cung cấp:

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

khi thành phần được đảm bảo sẽ hiển thị lại sau khi thay đổi tuyến thì bạn có thể chuyển các đạo cụ dưới dạng phụ thuộc

Không phải là cách tốt nhất nhưng đủ tốt để xử lý những gì bạn đang tìm kiếm theo Kent C Dodds : Hãy xem xét thêm những gì bạn muốn đã xảy ra.


-3

react-router bị hỏng vì nó phải gắn lại các thành phần khi thay đổi vị trí.

Mặc dù vậy, tôi đã cố gắng tìm ra bản sửa lỗi cho lỗi này:

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

Tóm lại (xem liên kết ở trên để biết thông tin đầy đủ)

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

Điều này sẽ làm cho nó trở lại với bất kỳ thay đổi URL nào

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.