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


102

Я работаю над реактивным приложением с использованием реактивного маршрутизатора. У меня есть страница проекта с URL-адресом:

myapplication.com/project/unique-project-id

Когда компонент проекта загружается, я инициирую запрос данных для этого проекта из события componentDidMount. Теперь я столкнулся с проблемой, когда, если я переключаюсь напрямую между двумя проектами, только идентификатор изменяется следующим образом ...

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

componentDidMount не запускается снова, поэтому данные не обновляются. Есть ли еще одно событие жизненного цикла, которое я должен использовать для запуска моего запроса данных, или другую стратегию для решения этой проблемы?


1
Не могли бы вы опубликовать код своих компонентов?
— Пьер Криуланси

Ответы:


83

Если ссылка ведет на тот же маршрут с другим параметром, она не перемонтируется, а вместо этого получает новые реквизиты. Итак, вы можете использовать componentWillReceiveProps(newProps)функцию и искать newProps.params.projectId.

Если вы пытаетесь загрузить данные, я бы рекомендовал получить данные до того, как маршрутизатор обработает совпадение, используя статические методы компонента. Посмотрите этот пример. React Router Mega Demo . Таким образом, компонент загружает данные и автоматически обновляется при изменении параметров маршрута, не полагаясь на них componentWillReceiveProps.


2
Спасибо. Мне удалось заставить это работать с помощью componentWillReceiveProps. Я до сих пор не совсем понимаю поток данных React Router Mega Demo. Я вижу статические данные fetchData, определенные для некоторых компонентов. Как эта статика вызывается со стороны клиента маршрутизатора?
— Созвездие

1
Отличный вопрос. Просмотрите файлы client.js и server.js. Во время цикла работы маршрутизатора они вызывают файл fetchData.js и передают stateему. Сначала это немного сбивает с толку, но потом становится немного понятнее.
— Брэд Би

12
К вашему сведению: «componentWillReceiveProps» считается устаревшим
— Идан Даган

4
Вы должны использовать ComponentDidUpdate с React 16, поскольку componentWillReceiveProps устарел
— Пато Локо

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

102

Если вам действительно нужно перемонтировать компонент при изменении маршрута, вы можете передать уникальный ключ ключевому атрибуту вашего компонента (ключ связан с вашим путем / маршрутом). Таким образом, каждый раз, когда маршрут изменяется, ключ также будет меняться, что запускает компонент React для размонтирования / повторного монтирования. Я понял идею из этого ответа


3
Гениально! Спасибо за этот простой и элегантный ответ
— Z_z_Z 03

Просто спрашиваю, не повлияет ли это на производительность приложения?
— Alpit Anand

1
@AlpitAn и это широкий вопрос. Повторный монтаж определенно медленнее, чем повторный рендеринг, поскольку запускает больше методов жизненного цикла. Здесь я как раз отвечаю, как сделать перемонтирование при изменении маршрута.
— wei

Но это не большое влияние? Что вы посоветуете, можем ли мы использовать его безопасно и без особого эффекта?
— Алпит Ананд

1
@AlpitAnand Ваш вопрос слишком общий и зависит от приложения. Для некоторых приложений да, это может снизить производительность, и в этом случае вам следует наблюдать за изменением свойств и обновлять только те элементы, которые должны обновляться. Однако для большинства это хорошее решение
— Крис

86

Вот мой ответ, похожий на некоторые из вышеперечисленных, но с кодом.

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

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

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" ваш редуктор все равно обновит ваши реквизиты, я просто использовал это, чтобы
— выразить

Привет, цыпленок, я на самом деле все еще застрял в этом ... у вас есть другие предложения или рекомендуемые уроки? Или я пока буду придерживаться вашего решения. Спасибо за вашу помощь!
— konekoya

14

Если у вас есть:

<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

9

На основании ответов @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
— Ангел Мас

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.