Компонент не перемонтовується при зміні параметрів маршруту


99

Я працюю над додатком для реагування, використовуючи response-router. У мене є сторінка проекту, яка має URL-адресу наступним чином:

myapplication.com/project/unique-project-id

Коли компонент проекту завантажується, я запускаю запит даних для цього проекту з події componentDidMount. Зараз я зіткнувся з проблемою, коли, якщо я переключаюсь безпосередньо між двома проектами, так змінюється лише ідентифікатор ...

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

componentDidMount не запускається знову, тому дані не оновлюються. Чи є інша подія життєвого циклу, яку я мав би використовувати для ініціювання запиту даних, або інша стратегія вирішення цієї проблеми?


1
Не могли б ви опублікувати код компонентів?
Pierre Criulanscy

Відповіді:


81

Якщо посилання спрямовує на той самий маршрут із іншим параметром, це не перемонтування, а натомість отримання нового реквізиту. Отже, ви можете скористатися componentWillReceiveProps(newProps)функцією і шукати newProps.params.projectId.

Якщо ви намагаєтеся завантажити дані, я б рекомендував отримати дані, перш ніж маршрутизатор обробляє відповідність, використовуючи статичні методи на компоненті. Перегляньте цей приклад. Мега-демонстрація React Router . Таким чином, компонент завантажує дані та автоматично оновлюється, коли змінюються параметри маршруту, не потребуючи при цьому сподіватися componentWillReceiveProps.


2
Дякую. Я зміг отримати цю роботу за допомогою компонентаWillReceiveProps. Я все ще не дуже розумію потік даних React Router Mega Demo. Я бачу статичні fetchData, визначені для деяких компонентів. Як ці статики викликаються з боку клієнта маршрутизатора?
Сузір’я

1
Чудове запитання. Перевірте файл client.js та файл server.js. Під час циклу роботи маршрутизатора вони викликають файл fetchData.js і передають stateйому. Спочатку це трохи заплутано, але потім стає трохи зрозумілішим.
Brad B

12
FYI: "componentWillReceiveProps" вважається спадщиною
Айдан Даган,

4
Вам слід використовувати ComponentDidUpdate з React 16, оскільки компонентWillReceiveProps застарілий
Pato Loco

1
Це працює, але проблема полягає в тому, що вам потрібно додати componentWillReceiveProps () або componentDidUpdate () для обробки повторних відтворень до кожного компонента, який завантажує дані. Наприклад, подумайте про сторінку, яка має кілька компонентів, які завантажують дані на основі одного і того ж параметра шляху.
Juha Syrjälä

98

Якщо вам потрібно перемонтувати компонент при зміні маршруту, ви можете передати унікальний ключ атрибуту ключа компонента (ключ пов'язаний з вашим шляхом / маршрутом). Отже, кожного разу, коли змінюється маршрут, ключ також змінюватиметься, що запускає компонент React для демонтажу / повторної монтажу. Я отримав ідею з цієї відповіді


2
Блискуче! Дякуємо за цю просту та елегантну відповідь
Z_z_Z

Тільки запитую, чи це не вплине на продуктивність програми?
Alpit Anand,

1
@AlpitAі це широке питання. Повторне встановлення, безумовно, повільніше, ніж рендерінг, оскільки викликає більше методів життєвого циклу. Тут я просто відповідаю, як зробити так, щоб повторний монтаж відбувся при зміні маршруту.
вей

Але це не величезний вплив? Яка ваша порада, чи можемо ми використовувати його безпечно без особливого ефекту?
Alpit Anand

@AlpitAnand Ваше запитання занадто широке та конкретне для програми. Так, для деяких програм це було б успіхом у виконанні, і в цьому випадку слід спостерігати за зміною реквізиту самостійно та оновлювати лише ті елементи, які слід оновити. Однак для більшості вищезазначене є гарним рішенням
Кріс,

84

Ось моя відповідь, подібна до наведеної вище, але з кодом.

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

4
Ключ тут відіграє найбільшу роль, оскільки зображення, на яке ви маєте посилання на сторінці профілю, після натискання його просто перенаправляє на той самий маршрут, але з різними параметрами, АЛЕ компонент не оновлюється, оскільки React не може знайти різниці, тому що повинен бути КЛЮЧ для порівняння старого результату
Георгій Пеєв

14

Ви повинні чітко усвідомлювати, що зміна маршруту не призведе до оновлення сторінки, вам доведеться обробляти це самостійно.

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" ваш редуктор все одно оновить ваш реквізит, я просто використав це, щоб сказати свою думку, не ставлячи редукс на місце
chickenchilli

Привіт курячий чіллі, я насправді все ще застряг у цьому ... чи є у вас інші пропозиції чи рекомендовані підручники? Або я поки що буду дотримуватися вашого рішення. Спасибі за вашу допомогу!
konekoya

12

Якщо у вас є:

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

У React 16.8 і вище за допомогою гачків ви можете зробити:

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,

8

На основі відповідей @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 за допомогою 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, який буде викликаний з другого разу, коли ми завантажуємо той самий маршрут, але інший ідентифікатор, і я викликаю перший метод для перезавантаження стану, а потім компонент завантажує новий елемент.

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

Це працює, але має проблему, що вам потрібно додати componentWillReceiveProps () для обробки повторних відтворень до кожного компонента, який завантажує дані.
Juha Syrjälä

Використовуйте componentDidUpdate (prevProps). componentWillUpdate () componentWillReceiveProps () застаріли.
Мірек Міхалак

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])

коли компонент гарантовано відтворюється після зміни маршруту, тоді ви можете передавати реквізит як залежність

Не найкращий спосіб, але достатньо хороший, щоб впоратися з тим, що ви шукаєте, згідно з Кентом Доддсом : Подумайте більше про те, що ви хочете, щоб сталося.


-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-адреси

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.