Стан оновлення змін реквізиту у формі React


184

У мене виникають проблеми з формою React та належним управлінням державою. У мене є поле введення часу у формі (в модальному режимі). Початкове значення встановлюється у вигляді змінної стану в getInitialStateі передається з батьківського компонента. Це само по собі прекрасно працює.

Проблема виникає, коли я хочу оновити значення початкового часу за замовчуванням через батьківський компонент. Саме оновлення відбувається в батьківському компоненті через setState start_time: new_time. Однак у моїй формі значення початкового часу за замовчуванням ніколи не змінюється, оскільки воно визначається лише один раз у getInitialState.

Я намагався використати, componentWillUpdateщоб примусити змінити стан setState start_time: next_props.start_time, який насправді спрацював, але дав мені Uncaught RangeError: Maximum call stack size exceededпомилки.

Отже, моє запитання полягає в тому, який правильний спосіб оновлення стану в цьому випадку? Чи я якось думати про це неправильно?

Поточний код:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time”)

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange

Відповіді:


287

компонентWillReceiveProps описується з моменту реакції 16: замість цього використовуйте getDerivedStateFromProps

Якщо я правильно розумію, у вас є батьківський компонент, який переходить start_timeдо ModalBodyкомпонента, який присвоює його власному стану? І ви хочете оновити цей час від батьківського, а не дочірнього компонента.

Реакт має кілька порад щодо вирішення цього сценарію. (Зауважте, це стара стаття, яка з тих пір була видалена з Інтернету. Ось посилання на поточний документ про реквізити компонентів ).

Використання реквізиту для генерування стану getInitialStateчасто призводить до дублювання "джерела істини", тобто там, де реальні дані. Це тому, що getInitialStateвикликається лише при першому створенні компонента.

Коли це можливо, обчислюйте значення на ходу, щоб гарантувати, що згодом вони не вийдуть із синхронізації та спричинить проблеми з технічним обслуговуванням.

В основному, кожен раз, коли ви призначаєте батьки propsдитині, stateметод візуалізації не завжди викликається оновленням підтримки. Ви повинні викликати його вручну, використовуючи componentWillReceivePropsметод.

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

84
Застаріло станом на Реакт 16
чувак

7
@dude Це ще не застаріло, те, про що ви посилаєтесь, - це лише голова для подальшого використання. Цитую[..]going to be deprecated in the future
paddotk

7
@poepje Це може бути ще не застарілим, але це вважається небезпечним чинним стандартом, і, ймовірно, його слід уникати
розгортається

12
Отже, яким повинен бути новий спосіб зробити це після того, як компонент WillReceiveProps був припинений?
Борис Д.

5
@Boris Тепер команда реагує, в основному, каже, щоб ви були напхані. Вони дають вам новий метод, який називається getDerivedStateFromProps. Проблема полягає в тому, що це статичний метод. Це означає, що ви не можете зробити нічого асинхронного для оновлення стану (тому що вам доведеться негайно повернути новий стан), а також не можете отримати доступ до методів чи полів класу. Ви також можете використовувати запам'ятовування, але це не відповідає кожному випадку використання. В черговий раз команда реагуючих хоче примусити їх вести справи. Це надзвичайно дурне і недієздатне дизайнерське рішення.
ig-dev

76

Мабуть, все змінюється .... переважна функція - тепер getDerivedStateFromProps () .

class Component extends React.Component {
  static getDerivedStateFromProps(props, current_state) {
    if (current_state.value !== props.value) {
      return {
        value: props.value,
        computed_prop: heavy_computation(props.value)
      }
    }
    return null
  }
}

(вище код від danburzo @ github)


7
FYI, вам також потрібно повернутися, nullякщо нічого не зміниться так відразу після вашого, якщо ви повинні піти зreturn null
Ilgıt Yıldırım

@ IlgıtYıldırım - відредагували код, оскільки 4 особи відхилили ваш коментар - чи дійсно це має значення?
ErichBSchulz

Є досить хороший ресурс, який getDerivedStateFromPropsдетально розглядає
розгортається

2
getDerivedStateFromProps змушений стати статичним. Це означає, що ви не можете зробити нічого асинхронного для оновлення стану, а також не можете отримати доступ до методів або полів класу. В черговий раз команда реагуючих хоче примусити їх вести справи. Це надзвичайно дурне і недієздатне дизайнерське рішення.
ig-dev

39

componentWillReceiveProps відміняється, оскільки його використання "часто призводить до помилок та невідповідностей".

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

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

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Про його виконання:

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


1
Ключ, секрет! Відмінно працює в React 16, як згадувалося вище
Даррен Суїні

ключ не працюватиме, якщо це об’єкт і у вас немає унікальної рядки
user3468806

Ключ працює для об'єктів, я це зробив. Звичайно, у мене був унікальний рядок для ключа.
цуйп

@ user3468806 Якщо це не складний об'єкт із зовнішніми посиланнями, ви можете JSON.stringify(myObject)отримати унікальний ключ від вашого об'єкта.
Roy Prins

24

Також доступний компонентDidUpdate .

Підписник функції:

componentDidUpdate(prevProps, prevState, snapshot)

Використовуйте це як можливість працювати з DOM після оновлення компонента. Початково не дзвонить render.

Див Ви напевно , не потрібно похідний держава статті, яка описує антішаблоном для обох componentDidUpdateі getDerivedStateFromProps. Я вважаю це дуже корисним.


Я закінчую використовувати, componentDidUpdateтому що це просто і більше підходить для більшості випадків.
KeitelDOG

14

Новим способом цього є використання UseEffect замість компонентаWillReceiveProps старим способом:

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

стає наступним у функціональному гачку, керованим компонентом:

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

ми встановлюємо стан за допомогою setState, використовуючи useEffect, ми перевіряємо на зміни зазначеної опори та вживаємо дії для оновлення стану при зміні опори.


5

Вам, ймовірно, не потрібна похідна держава

1. Встановіть ключ від батьківського

Коли ключ змінюється, React створить новий екземпляр компонента, а не оновить поточний. Клавіші зазвичай використовуються для динамічних списків, але вони також корисні тут.

2. Використовуйте getDerivedStateFromProps/componentWillReceiveProps

Якщо ключ чомусь не працює (можливо, компонент дуже дорого ініціалізувати)

Використовуючи getDerivedStateFromPropsви можете скинути будь-яку частину стану, але наразі це здається незначним (v16.7) !, див . Посилання вище для використання


2

З документації про реакцію: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

Стан стирання, коли зміна реквізиту - це антитіл

Оскільки React 16, компонентWillReceiveProps застарілий. З документації про реагування в цьому випадку рекомендований підхід

  1. Повністю контрольований компонент: ParentComponentз ModalBodyналежатиме start_timeдержаві. Це не мій переважний підхід у цьому випадку, оскільки я думаю, що модаль повинен володіти цим станом.
  2. Повністю неконтрольований компонент з ключем: це мій переважний підхід. Приклад з документації про реакцію: https://codesandbox.io/s/6v1znlxyxn . Ви б повністю володіли start_timeдержавою зі свого ModalBodyі використовували так getInitialStateсамо, як ви це зробили. Щоб скинути start_timeстан, ви просто зміните ключ наParentComponent


0

Використовуйте пам'ять

Виведення стану оп - це пряме маніпулювання реквізитом, не потрібне справжнє виведення. Іншими словами, якщо у вас є опора, яку можна використовувати або трансформувати безпосередньо, немає необхідності зберігати реквізит у стані .

Зважаючи на те, що значення стану start_timeпросто опора start_time.format("HH:mm"), інформація, що міститься в опорі, вже сама по собі є достатньою для оновлення компонента.

Однак якщо ви хотіли лише форматувати дзвінки під час зміни опори, правильним способом зробити це за останньою документацією було б через Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont- need-derived-state.html # what-about-запам'ятовування


-1

Я думаю, що використання рефлексу для мене безпечне, не потрібно дбати про якийсь метод вище.

class Company extends XComponent {
    constructor(props) {
        super(props);
        this.data = {};
    }
    fetchData(data) {
        this.resetState(data);
    }
    render() {
        return (
            <Input ref={c => this.data['name'] = c} type="text" className="form-control" />
        );
    }
}
class XComponent extends Component {
    resetState(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
                if ( obj[property] !== this.data[property].state.value )
                    this.data[property].setState({value: obj[property]});
                else continue;
            }
            continue;
        }
    }
}

Я думаю, що ця відповідь є виразним (код важко читабельний і не має ніяких пояснень / посилання на проблему ОП) і не вирішує проблеми ОП, які полягають у тому, як впоратися з початковим станом.
netchkin
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.