2016-08-26 3 views
4

TLDR: Общие требования к конструкции, по-видимому, невозможны в Реагировании без нарушения Принципов реагирования или игнорирования стандартных возможностей инкапсуляции.Изменения в управляемых компонентах формы в реактиве

У меня есть форма, которая отображает существующие данные из API-интерфейса сервера и позволяет пользователю редактировать поля и обновлять данные на сервере. Это происходит динамически, так как пользователь меняет входные значения формы, а не требует, чтобы они «отправляли» форму после каждого редактирования.

Для некоторых входных форматов изменение значения производится немедленно (например, флажок, переключатель, выбор). Для других изменение значения производится постепенно - наиболее очевидно для ввода текста. Я бы не хотел, чтобы на каждом нажатии клавиши был запрос на сервер, так как это приведет к ошибкам проверки на стороне сервера, которые генерируются для неполных значений. Вместо этого сервер будет обновляться после того, как пользователь покинет поле ввода текста. Было бы также напрасно отправлять запрос сервера, если значения не изменяются.

В Реагировании ключевым принципом дизайна является то, что вы поддерживаете единственный источник истины/состояния и прокладываете это через компоненты с помощью реквизита. Компоненты не должны сохранять свое собственное состояние как копию реквизита, но вместо этого должны оказывать реквизиты непосредственно [1]. «Единственным источником» будет родительский компонент, который извлекает и передает данные с сервера и на сервер.

Для вынесенного <input> элемента, чтобы сохранить свое значение в курс изменений сервера, он должен использовать атрибут value, так как defaultValue вычисляется только на первую визуализацию [2]. Принципы проектирования React подразумевают, что я должен установить <input value={this.props.value} />. Чтобы это отвечало на ввод пользователя, должен быть также предоставлен обработчик onChange, барботирующий изменение до родительского компонента, которое обновит состояние и приведет к повторной обработке <input> с обновленным props.

Тем не менее, я бы не хотел запускать запрос сервера в обработчике onChange, так как это будет срабатывать при каждом нажатии клавиши. Мне нужно было бы вызвать запрос сервера вместо этого на событие onBlur, предположив, что значение изменилось с onFocus. Требование этого для некоторых элементов, а не для других означает, что родительскому компоненту понадобятся два обработчика: обработчик onChange для обновления состояния для всех дочерних компонентов и запуска запроса сервера для определенных полей и onBlur для запуска запроса сервера для других полей , Требование, чтобы родительский компонент знал, какой из компонентов дочерней формы должен показать, какое поведение кажется неудачным для инкапсуляции должным образом. Детальные компоненты должны иметь возможность контролировать свои собственные значения и решать, когда следует генерировать событие «сделать что-то».

Я не вижу способа достижения этого, не нарушая одного из принципов React, скорее всего, поддерживая состояние в каждом компоненте формы. Что-то вроде этого:

class TextInput extends React.Component { 
    constructor(props) { 
     super(props); 
     this.initialValue = this.props.value; 
     this.setState({value: this.props.value}); 
    } 
    componentWillReceiveProps = (nextProps) => { 
     this.initialValue = nextProps.value; 
     this.setState({value: nextProps.value}); 
    }; 
    handleFocus = (e) => { 
     this.initialValue = e.target.value; 
    }; 
    handleChange = (e) => { 
     this.setState({value: e.target.value}); 
    }; 
    handleBlur = (e) => { 
     if (e.target.value !== this.initialValue && 
      this.props.handleChange) { 
       this.props.handleChange(e); 
     } 
    }; 
    render() { 
     return (
      <input type="text" 
        value={this.state.value} 
        onFocus={this.handleFocus} 
        onChange={this.handleChange} 
        onBlur={this.handleBlur} /> 
     ); 
    } 
} 

class FormHandler extends React.Component { 
    componentDidMount() { 
     // fetch from API... 
     this.setState(apiResponse); 
    } 
    handleChange = (e) => { 
     // update API with e.target.value.... 
    }; 
    render() { 
     return (<TextInput value={this.state.value} 
          handleChange={this.handleChange} />); 
    } 
} 

Есть ли лучший способ достижения этой цели, не нарушая принципы РЕАКТА о капельном реквизита вниз, чтобы быть оказано?

Дальнейшее чтение различных попыток разрешить это в [3-4].

Другие вопросы SO от людей, борющихся с аналогичной проблемой в [5-6].

[1] https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html

[2] https://facebook.github.io/react/docs/forms.html#default-value

[3] https://discuss.reactjs.org/t/how-to-pass-in-initial-value-to-form-fields/869

[4] https://blog.iansinnott.com/managing-state-and-controlled-form-fields-with-react/

[5] How do I reset the defaultValue for a React input

[6] Using an input field with onBlur and a value from state blocks input in Reactjs JSX?

ответ

3

Вы явно потратили много времени, размышляя об этом и просматривая связанные обсуждения. Для чего это стоит, я думаю, что вы на самом деле более -думаете проблему. Реакт, по своей сути, представляет собой практический инструмент . Это подталкивает вас в определенных направлениях, и есть определенные подходы, которые считаются идиоматическими, но он обеспечивает ряд побегов, которые позволяют вам выйти из основных подходов в соответствии с вашим конкретным вариантом использования. Итак, если вам нужно архивировать вещи определенным образом, чтобы ваше приложение выглядело так, как вы хотите, не тратьте время на беспокойство о том, «абсолютно ли он полностью соответствует принципам Реакт». Никто не собирается кричать на вас за то, что он отклонился от какого-то мифического идеала :)

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

1

Итак, вы на правильном пути. Я знаю, как сначала это путают такие вещи.

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

Мы собираемся явно обработать все для этого компонента через реквизиты. Все материалы, относящиеся к состоянию этого компонента (значение, обработчики), будут перемещаться в ваш FormHandler. Причина, по которой я иду по этому маршруту, заключается в том, что он значительно упрощает работу с данными из API, сохраняя при этом ваш компонент TextInput достаточно гибким, чтобы повторно использовать его в любом месте вашего веб-приложения.

Итак, мы избавимся от множества вещей! Здесь мы идем:

class TextInput extends React.Component { 
    render() { 
     return (
      <input 
       type="text" 
       onBlur={() => this.props.handleBlur()} 
       onChange={(e) => this.props.handleChange(e.target.value)} 
       value={this.props.value} 
      /> 
     ); 
    } 
} 

Вот и все! Да. Итак, как я уже говорил ранее, мы собираемся переместить много вещей в ваш компонент FormHandler. Это будет выглядеть так.

class FormHandler extends React.Component { 
    constructor(props) { 
     super(props); 
     this.state = { 
      value: this.props.value 
     }); 

     this.handleBlur = this.handleBlur.bind(this) 
     this.handleChange = this.handleChange.bind(this) 
    } 
    componentDidMount() { 
     // fetch from API... 
     this.setState(apiResponse); 
    } 
    handleBlur() { 
     // Send a request to API using value four inside this.state.value. 
     // This provides the user with instant feedback on the form, 
     // but still lets you do an async call to API to make sure things actually get updated. 
    } 
    handleChange = (input) => { 
     this.setState({value: input}); 
    }; 
    render() { 
     return (
      <TextInput 
       handleBlur={this.handleBlur} 
       handleChange={this.handleChange} 
       value={this.state.value} 
      />); 
    } 
} 

Итак, здесь вы можете установить state.value в вашем FormHandler, чтобы быть все по умолчанию значение, которое вы хотите. Или просто дождитесь ответа от API, который будет передан после того, как все будет обновлено (не самый идеальный пользовательский интерфейс), вы можете приступить к добавлению загрузочных прядильщиков и всего этого, если хотите).

РЕДАКТИРОВАТЬ: Исправить, как мы обработали функцию связывания внутри FormHandler на основе обсуждения в Reddit.

+0

Я не уверен, что это не та же проблема, только с расположением методов, перемещаемых до FormHandler. FormHandler все еще устанавливает свое внутреннее состояние из переданных ему реквизитов (предположительно React no-no). – practual

+0

Ах, право. Кажется, я пропустил это. Я бы удалил, где я сказал начальное состояние значения === 'this.props.value'. Я предполагаю, что значение исходит из API, поэтому я бы просто установил его по умолчанию и затем обновил его, как только вызов API происходит внутри 'compnentDidMount'. – Dave

0

Я тоже люблю другие ответы :)

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

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

Однако, так как я знаю, что мы используем MobX, вы должны увидеть также: https://medium.com/@mweststrate/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e#.uak5sug89

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

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

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

Согласовано.

От @markerikson:

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

Я подозреваю, что он прав.

+0

Статья MobX - хорошая и хорошая демонстрация того, как начать работу с декораторами MobX вместе с существующим компонентом React. Тем не менее, вы все еще устанавливаете состояние MobX в переданные в реквизитах, что не уклоняется от «антипаттерна». Я подозреваю, что наиболее подходящий момент - это @ markerikson's overthinking. Я понимаю основное правило, и я понимаю, почему я его нарушаю, что, я думаю, достаточно хорошо. – practual

Смежные вопросы