Повторно отрендерить компонент React при изменении свойства


90

Я пытаюсь отделить презентационный компонент от компонента контейнера. У меня есть SitesTableи SitesTableContainer. Контейнер отвечает за запуск действий redux для получения соответствующих сайтов на основе текущего пользователя.

Проблема в том, что текущий пользователь выбирается асинхронно после того, как компонент контейнера изначально визуализирован. Это означает, что компонент-контейнер не знает, что ему нужно повторно выполнить код в своей componentDidMountфункции, который обновит данные для отправки в SitesTable. Я думаю, мне нужно повторно отобразить компонент контейнера, когда один из его свойств (пользователь) изменяется. Как мне это сделать правильно?

class SitesTableContainer extends React.Component {
    static get propTypes() {
      return {
        sites: React.PropTypes.object,
        user: React.PropTypes.object,
        isManager: React.PropTypes.boolean
      }
     }

    componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

    render() {
      return <SitesTable sites={this.props.sites}/>
    }
}

function mapStateToProps(state) {
  const user = userUtils.getCurrentUser(state)

  return {
    sites: state.get('sites'),
    user,
    isManager: userUtils.isManager(user)
  }
}

export default connect(mapStateToProps)(SitesTableContainer);

у вас есть другие доступные функции, такие как componentDidUpdate, или, возможно, та, которую вы ищете, componentWillReceiveProps (nextProps), если вы хотите запускать что-то при изменении
свойств

Зачем вам нужно повторно отображать SitesTable, если он не меняет свои свойства?
QoP

@QoP, в котором отправляются действия, componentDidMountизменит sitesузел в состоянии приложения, которое передается в SitesTable. Узел SitesStable sitesбудет изменен .
Дэвид

О, я понял, я напишу ответ.
QoP

1
Как добиться этого в функциональном компоненте
yaswanthkoneri

Ответы:


115

Вы должны добавить условие в свой componentDidUpdateметод.

Пример используется fast-deep-equalдля сравнения объектов.

import equal from 'fast-deep-equal'

...

constructor(){
  this.updateUser = this.updateUser.bind(this);
}  

componentDidMount() {
  this.updateUser();
}

componentDidUpdate(prevProps) {
  if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID  (this.props.user.id !== prevProps.user.id)
  {
    this.updateUser();
  }
} 

updateUser() {
  if (this.props.isManager) {
    this.props.dispatch(actions.fetchAllSites())
  } else {
    const currentUserId = this.props.user.get('id')
    this.props.dispatch(actions.fetchUsersSites(currentUserId))
  }  
}

Использование хуков (React 16.8.0+)

import React, { useEffect } from 'react';

const SitesTableContainer = ({
  user,
  isManager,
  dispatch,
  sites,
}) => {
  useEffect(() => {
    if(isManager) {
      dispatch(actions.fetchAllSites())
    } else {
      const currentUserId = user.get('id')
      dispatch(actions.fetchUsersSites(currentUserId))
    }
  }, [user]); 

  return (
    return <SitesTable sites={sites}/>
  )

}

Если сравниваемая опора является объектом или массивом, вы должны использовать useDeepCompareEffectвместо useEffect.


Обратите внимание, что JSON.stringify можно использовать только для этого вида сравнения, если он стабилен (по спецификации это не так), поэтому он дает одинаковый вывод для тех же входных данных. Я рекомендую сравнивать свойства id объектов пользователя или передавать userId в реквизитах и ​​сравнивать их, чтобы избежать ненужных перезагрузок.
Ласло Кардинал,

4
Обратите внимание, что метод жизненного цикла componentWillReceiveProps является устаревшим и, вероятно, будет удален в React 17. Использование комбинации componentDidUpdate и нового метода getDerivedStateFromProps является рекомендуемой стратегией от команды разработчиков React. Подробнее в их сообщении в блоге: reactjs.org/blog/2018/03/27/update-on-async-rendering.html
michaelpoltorak

@QoP Второй пример, с React Hooks, будет ли он отключаться и повторно монтироваться при userизменении? Насколько это дорого?
Роботрон

30

ComponentWillReceiveProps()в будущем будет прекращена из-за ошибок и несоответствий. Альтернативным решением для повторного рендеринга компонента при изменении реквизита является использование ComponentDidUpdate()и ShouldComponentUpdate().

ComponentDidUpdate()вызывается всякий раз, когда компонент обновляет И, если ShouldComponentUpdate()возвращает истину (если ShouldComponentUpdate()не определено, возвращается trueпо умолчанию).

shouldComponentUpdate(nextProps){
    return nextProps.changedProp !== this.state.changedProp;
}

componentDidUpdate(props){
    // Desired operations: ex setting state
}

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

componentDidUpdate(prevProps){
    if(prevProps.changedProp !== this.props.changedProp){
        this.setState({          
            changedProp: this.props.changedProp
        });
    }
}

Если кто-то попытается установить состояние без условного обозначения или без определения, ShouldComponentUpdate()компонент будет бесконечно повторно отображать


2
За этот ответ нужно проголосовать (по крайней мере, на данный момент), поскольку он скоро componentWillReceivePropsустарел и не рекомендуется использовать.
AnBisw

Вторая форма (условный оператор внутри componentDidUpdate) работает для меня, потому что я хочу, чтобы другие изменения состояния все еще происходили, например, закрытие флэш-сообщения.
Little Brain

11

Вы можете использовать KEYуникальный ключ (комбинацию данных), который изменяется с помощью свойств, и этот компонент будет повторно отображен с обновленными свойствами.


4
componentWillReceiveProps(nextProps) { // your code here}

Думаю, это то мероприятие, которое вам нужно. componentWillReceivePropsсрабатывает всякий раз, когда ваш компонент получает что-то через props. Оттуда вы можете проверить, а затем делать все, что хотите.


11
componentWillReceivePropsустарело *
Maihan Nijat

2

Я бы порекомендовал взглянуть на этот мой ответ и посмотреть, имеет ли он отношение к тому, что вы делаете. Если я понимаю вашу настоящую проблему, это то, что вы просто неправильно используете свое асинхронное действие и не обновляете «хранилище» redux, которое автоматически обновит ваш компонент с новыми реквизитами.

Этот раздел вашего кода:

componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

Не должен запускаться в компоненте, его следует обработать после выполнения вашего первого запроса.

Взгляните на этот пример из redux-thunk :

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

Вам не обязательно использовать redux-thunk, но это поможет вам разобраться в подобных сценариях и написать соответствующий код.


верно, я понял. Но куда именно вы отправляете makeASandwichWithSecretSauce в своем компоненте?
Дэвид

Я свяжу вас с репозиторием с соответствующим примером, вы используете response-router в своем приложении?
TameBadger

@David также был бы признателен за ссылку на этот пример, у меня в основном та же проблема.
SamYoungNY

0

Дружественный метод использования следующий: после обновления пропа он автоматически перерисовывает компонент:

render {

let textWhenComponentUpdate = this.props.text 

return (
<View>
  <Text>{textWhenComponentUpdate}</Text>
</View>
)

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.