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


135

Чому в наступному прикладі псевдокоду 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()після зміни значення в контейнері, дитина все одно показує старе значення.


5
Це ваш код? Здається, що це не дійсний код React

Я думаю, що значення реквізиту не повинно змінюватися в компоненті контейнера, а замість цього має бути змінено батьківський компонент setState, і цей стан повинен бути відображений на реквізиті контейнерів
Piyush Patel,

Використовуйте такий оператор розширення як <Дочірня панель = {... this.props.foo.bar} />
Sourav Singh

@AdrianWydmanski та інші 5 людей, які виступили з пропозицією: en.wikipedia.org/wiki/Pseudocode
Девід Ньюкомб

Реквізити @PiyushPatel оновлюються, коли компонент повторно відображається на місці, як показує приклад псевдо-коду. Іншим прикладом цього є щось на зразок використання <Route exact path="/user/:email" component={ListUserMessagePage} />, посилання на тій же сторінці оновить реквізит, не створюючи нового екземпляра та запускаючи звичні події життєвого циклу.
Девід Ньюкомб

Відповіді:


94

Оновіть дитину, щоб атрибут 'ключ' дорівнював імені. Компонент буде повторно відображатися щоразу, коли ключ змінюється.

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

5
Дякую за це Я використовував магазин redux і не міг змусити мій компонент повторно відтворювати, поки я не додав ключ. (Я використовував бібліотеку uuid для створення випадкового ключа, і він працював).
Майя

Використовуючи гачки, моя дитина не буде відображатись під час встановлення стану на існуючому масиві. Мій (ймовірно , погана ідея) хак був одночасно встановити стан фіктивної опори і додати , що в масиві залежності від useEffect: [props.notRenderingArr, props.dummy]. Якщо довжина масиву завжди змінюється, ви можете встановити масив залежності useEffect на [props.notRenderingArr.length].
гіпноз

5
це було те, що мені теж було потрібно. Я спантеличений, коли це keyпотрібно, а просто setState? У мене є кілька дітей, які покладаються на стан батьків, але вони не викликають повторного візуалізації ...
dcsan

Чудова відповідь. Але чому така поведінка?
Рішав

1
Я використовував індекс як ключ, але він не працював належним чином. Зараз я використовую uuid, і це ідеально :) Дякую @Maiya
Алі Рехман

90

Тому що діти не здаються, якщо змінюються реквізити батьків, але якщо його СТАТЕЖ змінюється :)

Що ви показуєте, це таке: https://facebook.github.io/react/tips/communicate-between-components.html

Він передаватиме дані від батька до дитини через реквізити, але логіки відтворення там немає.

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


5
Чому саме так, що setState спричинить повторну візуалізацію, але forceUpdate ні? Також мало теми, але як оновлюються компоненти, коли реквізити передаються від редукса, і стан оновлюється шляхом дії?
Туомас Тойвонен

реквізит не оновлюється, навіть якщо ви оновлюєте компонент, реквізит, який ви передали, все ще є. Flux - інша тема так :)
Франсуа Річард

3
state! = реквізит вам потрібно прочитати більше про це. Ви не оновлюєте реквізит
Франсуа Річард,

ви можете бачити це безпосередньо у вихідному коді, це просто не той самий процес
Веб-жінка

так що ви сказали тут або було змінено або я не правильно розумію, я зробив питання для цього, до речі, stackoverflow.com/questions/58903921 / ...
ILoveReactAndNode

50

У мене була така ж проблема. Це моє рішення, я не впевнений, що це найкраща практика, скажіть, якщо ні:

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

3
Це однозначно правильна відповідь, не кажучи вже про найпростішу
Феррейра

1
Я також використовую такий підхід.
rawsly

@ vancy-pants У componentDidUpdateцьому випадку йдеться про компонент "Діти".
Нік Фріскель

4
Вам не потрібно і не слід перетворювати реквізити на стан у дочірньому компоненті. Просто використовуйте реквізит безпосередньо. У FunctionComponentsньому автоматично буде оновлено дочірні компоненти, якщо реквізит зміниться.
oemera

1
Цей підхід також працює, коли у вас є реквізити дочірніх компонентів, похідних від батьківського стану
Chefk5

10

За словами філософії React, компонент не може змінити свої реквізити. вони повинні бути отримані від батьків і повинні бути незмінні. Тільки батько може змінити реквізит своїх дітей.

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

також прочитайте цю тему Чому я не можу оновити реквізит у react.js?


Чи це також не означає, що контейнер не може змінювати реквізит, який він отримує з магазину redux?
Туомас Тойвонен

3
Контейнер не отримує реквізит безпосередньо з магазину, вони постачаються, читаючи частину дерева редукційного стану (плутаючи ??). !! плутанина полягає в тому, що ми не знаємо про магічну функцію з'єднання методом react-redux. якщо ви бачите вихідний код методу підключення, в основному він створює компонент вищого порядку, який має стан з властивістю storeState і підписаний на сховище редукцій. як зміни storeState відбувається повторне відображення, і змінені реквізити надходять до контейнера, читаючи змінений стан. сподіваюся, що це відповість на питання
Sujan Thakare

Гарне пояснення, роздуми над компонентом вищого порядку допомагають мені зрозуміти, що відбувається
Туомас Тойвонен

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>
        }
    }
}

Здається, ваш код недійсний. Я не можу перевірити цей код.


Чи не повинно бути <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}
         />

3

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

React хоче бути ефективним при створенні дочірніх компонентів. Він не відобразить новий компонент, якщо він такий же, як і інший дочірній, завдяки чому завантаження сторінки відбувається швидше.

Додавання ключа змушує реагувати на надання нового компонента, тим самим скидаючи стан для цього нового компонента.

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


2

Використовуйте функцію setState. Так ви могли зробити

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

всередині методу обробки ручки.

Після оновлення стану він спричинить зміни, і відбудеться повторне відображення.


1
Це має бути this.setState ({foo.bar:123}) так?
Charith Jayasanka

0

Ви, мабуть, повинні зробити дитину функціональним компонентом, якщо вона не підтримує жодного стану і просто надає реквізит, а потім викликає його від батьків. Альтернативою цьому є те, що ви можете використовувати гачки з функціональним компонентом (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

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