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

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} />)
} />
Точка останова25
источник
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

катамфетамин
источник