Реагировать: почему дочерний компонент не обновляется при изменении проп

137

Почему в следующем примере псевдокода Child не выполняет повторную визуализацию, когда Контейнер изменяет foo.bar?

Container {
  handleEvent() {
    this.props.foo.bar = 123
  },

  render() {
    return <Child bar={this.props.foo.bar} />
}

Child {
  render() {
    return <div>{this.props.bar}</div>
  }
}

Даже если я позвоню forceUpdate()после изменения значения в контейнере, Child все равно покажет старое значение.

Туомас Тойвонен
источник
5
Это твой код? Похоже, это недействительный код React
Я думаю, что значение props не должно изменяться в компоненте контейнера, вместо этого оно должно быть изменено в родительском компоненте с помощью setState, и это состояние должно быть сопоставлено с props контейнера
Piyush Patel
Используйте такой оператор распространения: <Child bar = {... this.props.foo.bar} />
Сурав Сингх,
@AdrianWydmanski и другие 5 человек, проголосовавших за: en.wikipedia.org/wiki/Pseudocode
Дэвид Ньюкомб,
Свойства @PiyushPatel обновляются, когда компонент повторно отрисовывается на месте, как показывает пример псевдокода. Другой пример этого - что-то вроде using <Route exact path="/user/:email" component={ListUserMessagePage} />, ссылка на той же странице обновит реквизиты без создания нового экземпляра и запуска обычных событий жизненного цикла.
Дэвид Ньюкомб

Ответы:

96

Обновите дочерний элемент, чтобы атрибут 'key' был равен имени. Компонент будет перерисовываться каждый раз при изменении ключа.

Child {
  render() {
    return <div key={this.props.bar}>{this.props.bar}</div>
  }
}
полина-с
источник
6
Спасибо за это. Я использовал redux store и не мог повторно отрендерить мой компонент, пока не добавил ключ. (Я использовал библиотеку uuid для генерации случайного ключа, и это сработало).
Maiya
Используя хуки, мой ребенок не будет повторно отображать при установке состояния в существующем массиве. Мой (вероятно, плохая идея) взлом состоял в том, чтобы одновременно установить состояние фиктивной опоры и добавить ее в массив зависимостей useEffect: [props.notRenderingArr, props.dummy] . Если длина массива всегда изменяется, вы можете установить для массива зависимостей useEffect значение [props.notRenderingArr.length].
гипноз
5
это было то, что мне тоже было нужно. Я недоумеваю, когда это keyобязательно, а когда просто setState? У меня есть дети, которые полагаются на состояние родителей, но не запускают повторную визуализацию ...
dcsan
Отличный ответ. Но почему такое поведение?
Ришав,
1
Я использовал индекс в качестве ключа, но он не работал должным образом. Теперь я использую uuid, и он идеален :) Спасибо @Maiya
Али Рехман
91

Потому что потомки не перерисовываются, если свойства родителя меняются, но если изменяется его СОСТОЯНИЕ :)

Вы показываете следующее: https://facebook.github.io/react/tips/communicate-between-components.html

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

Вам нужно установить какое-то состояние для родителя, а затем повторно отобразить дочерний элемент в состоянии изменения родителя. Это могло помочь. https://facebook.github.io/react/tips/expose-component-functions.html

Франсуа Ришар
источник
6
Почему setState вызывает повторную визуализацию, а forceUpdate - нет? Также немного не по теме, но как обновляются компоненты, когда реквизиты передаются из redux, а его состояние обновляется через действие?
Туомас Тойвонен
1
props не обновляются, даже если вы обновляете компонент, который вы передали, все еще там. Flux - еще одна тема, да :)
Франсуа Ришар,
3
state! = props, вам нужно узнать об этом больше. Вы не обновляете реквизит,
Франсуа Ришар,
вы можете увидеть это прямо в исходном коде, это просто не тот же процесс
Webwoman
так что то, что вы здесь сказали, было либо изменено, либо я неправильно понял, кстати, я задал вопрос об этом, stackoverflow.com/questions/58903921/…
ILoveReactAndNode
52

У меня такая же проблема. Это мое решение, я не уверен, что это хорошая практика, скажите мне, если нет:

state = {
  value: this.props.value
};

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

UPD: Теперь то же самое можно делать с помощью React Hooks: (только если компонент - это функция)

const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
Petrichor
источник
3
Это определенно правильный ответ, не говоря уже о самом простом
Гильерме Феррейра
1
Я тоже использую этот подход.
rawsly 01
@ vancy-брюки В componentDidUpdateэтом случае идет в дочерний компонент.
Ник Фрискель
4
Вам не нужно и не следует преобразовывать props в состояние дочернего компонента. Просто используйте реквизит напрямую. В FunctionComponentsнем будут автоматически обновляться дочерние компоненты, если свойства изменятся.
oemera
1
Этот подход также работает, когда у вас есть свойства в дочерних компонентах, производных от родительского состояния
Chefk5
12

Согласно философии React, компонент не может менять свои свойства. они должны быть получены от родителя и должны быть неизменными. Только родитель может изменять свойства своих потомков.

красивое объяснение состояния и реквизита

также прочтите эту ветку Почему я не могу обновить реквизиты в react.js?

Суджан Тхакаре
источник
Разве это не означает, что контейнер не может изменять реквизиты, которые он получает из хранилища redux?
Туомас Тойвонен
3
Контейнер не получает реквизиты напрямую из магазина, они предоставляются путем чтения части дерева состояний редукции (сбивает с толку ??). !! Путаница возникает из-за того, что мы не знаем о магической функции, соединяемой с помощью response-redux. если вы видите исходный код метода подключения, в основном он создает компонент более высокого порядка, который имеет состояние со свойством storeState и подписан на хранилище redux. при изменении storeState происходит весь повторный рендеринг, и измененные реквизиты передаются в контейнер путем чтения измененного состояния. надеюсь, что это ответит на вопрос
Суджан Тхакар
Хорошее объяснение, размышления о компоненте более высокого порядка помогают мне понять, что происходит,
Туомас Тойвонен
7

Вам следует использовать setStateфункцию. В противном случае состояние не сохранит ваше изменение, независимо от того, как вы используете forceUpdate.

Container {
    handleEvent= () => { // use arrow function
        //this.props.foo.bar = 123
        //You should use setState to set value like this:
        this.setState({foo: {bar: 123}});
    };

    render() {
        return <Child bar={this.state.foo.bar} />
    }
    Child {
        render() {
            return <div>{this.props.bar}</div>
        }
    }
}

Ваш код кажется недействительным. Я не могу протестировать этот код.

JamesYin
источник
Не должно быть <Child bar={this.state.foo.bar} />?
Джош Бродхерст,
Правильно. Обновляю ответ. Но, как я уже сказал, это может быть неверный код. Просто скопируйте из вопроса.
JamesYin
5

При создании компонентов React из functionsи useState.

const [drawerState, setDrawerState] = useState(false);

const toggleDrawer = () => {
      // attempting to trigger re-render
      setDrawerState(!drawerState);
};

Это не работает

         <Drawer
            drawerState={drawerState}
            toggleDrawer={toggleDrawer}
         />

Это работает (добавление ключа)

         <Drawer
            drawerState={drawerState}
            key={drawerState}
            toggleDrawer={toggleDrawer}
         />
Nelles
источник
4

Подтверждено, добавление ключа работает. Я просмотрел документацию, чтобы попытаться понять, почему.

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

Добавление ключа заставляет React отрисовывать новый компонент, таким образом сбрасывая состояние для этого нового компонента.

https://reactjs.org/docs/reconciliation.html#recursing-on-children

tjr226
источник
1

Используйте функцию setState. Так ты мог бы сделать

       this.setState({this.state.foo.bar:123}) 

внутри метода обработки события.

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

Индранель Бенде
источник
1
Это должно быть this.setState ({foo.bar:123}), верно?
Чарит Джаясанка
Это даже не будет компилироваться ⬆. Я думаю, вы имели в виду: this.setState ({foo: {bar: 123}})
Avius
0

Вероятно, вам следует сделать Child как функциональный компонент, если он не поддерживает какое-либо состояние и просто отображает реквизиты, а затем вызывает его из родителя. Альтернативой этому является то, что вы можете использовать хуки с функциональным компонентом (useState), что приведет к повторной визуализации компонента без состояния.

Также не следует изменять пропа, поскольку они неизменяемы. Поддерживать состояние компонента.

Child = ({bar}) => (bar);
сатьям сони
источник
0

Я столкнулся с той же проблемой. У меня естьTooltip компонент, который получал showTooltipопору, которую я обновлял для Parentкомпонента на основе ifусловия, он обновлялся в Parentкомпоненте, но Tooltipкомпонент не отображался.

const Parent = () => {
   let showTooltip = false;
   if(....){ showTooltip = true; }
   return(
      <Tooltip showTooltip={showTooltip}></Tooltip>
   )
}

Ошибка, которую я сделал, - это объявление о сдаче showTooltipв аренду.

Я понял, что делаю неправильно. Я нарушал принципы работы рендеринга. Замена хуков сработала.

const [showTooltip, setShowTooltip] =  React.useState<boolean>(false);
Дивяншу Рават
источник
0

определить измененные свойства в mapStateToProps метода подключения дочернего компонента.

function mapStateToProps(state) {
  return {
    chanelList: state.messaging.chanelList,
  };
}

export default connect(mapStateToProps)(ChannelItem);

В моем случае канал channelList обновлен, поэтому я добавил chanelList в mapStateToProps

Раджеш Насит
источник
0
export default function DataTable({ col, row }) {
  const [datatable, setDatatable] = React.useState({});
  useEffect(() => {
    setDatatable({
      columns: col,
      rows: row,
    });
  /// do any thing else 
  }, [row]);

  return (
    <MDBDataTableV5
      hover
      entriesOptions={[5, 20, 25]}
      entries={5}
      pagesAmount={4}
      data={datatable}
    />
  );
}

в этом примере используется useEffectдля изменения состояния при propsизменении.

ибрахим альсимири
источник