React - правильный способ передать состояние элемента элемента в sibling/parent?

  • Предположим, что у меня есть класс React P, который отображает два дочерних класса C1 и C2.
  • C1 содержит поле ввода. Я буду ссылаться на это поле ввода как Foo.
  • Моя цель - позволить C2 реагировать на изменения в Foo.

Я придумал два решения, но ни один из них не чувствует себя совершенно правильно.

Первое решение:

  • Назначить P состояние, state.input.
  • Создайте функцию onChange в P, которая принимает событие и устанавливает state.input.
  • Передайте этот onChange в C1 как props, а C1 свяжите this.props.onChange с onChange Foo.

Это работает. Всякий раз, когда изменяется значение Foo, он вызывает a setState в P, поэтому P будет иметь вход для перехода к C2.

Но по той же причине это не совсем правильно: я устанавливаю состояние родительского элемента из дочернего элемента. Это, по-видимому, предает принцип проектирования потока данных: однонаправленный поток.
Является ли это тем, как я должен это делать, или существует ли более естественное решение React?

Второе решение:

Просто поместите Foo в P.

Но это принцип дизайна, которому я должен следовать, когда я создаю приложение для всех элементов формы в render класса самого высокого уровня?

Как и в моем примере, если у меня есть большой рендеринг C1, я действительно не хочу ставить целое render из C1 в render из P только потому, что C1 имеет элемент формы.

Как мне это сделать?

Ответ 1

Итак, если я правильно понимаю вас, ваше первое решение предполагает, что вы сохраняете состояние в своем корневом компоненте? Я не могу говорить за создателей Реакта, но, как правило, я считаю это правильным решением.

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

Сохраняя состояние в иерархии и обновляя его посредством eventing, поток данных по-прежнему в значительной степени однонаправлен, вы просто отвечаете на события в корневом компоненте, вы не получаете данные там через два вы указываете компоненту Root, что "эй, что-то произошло здесь, проверьте значения" или передаете состояние некоторых данных в дочернем компоненте вверх, чтобы обновить состояние. Вы изменили состояние в C1, и вы хотите, чтобы C2 знал об этом, поэтому, обновив состояние в компоненте Root и повторную визуализацию, реквизиты C2 теперь синхронизированы, поскольку состояние было обновлено в корневом компоненте и передано вдоль.

class Example extends React.Component {
  constructor (props) {
    super(props)
    this.state = { data: 'test' }
  }
  render () {
    return (
      <div>
        <C1 onUpdate={this.onUpdate.bind(this)}/>
        <C2 data={this.state.data}/>
      </div>
    )
  }
  onUpdate (data) { this.setState({ data }) }
}

class C1 extends React.Component {
    render () {
      return (
        <div>
          <input type='text' ref='myInput'/>
          <input type='button' onClick={this.update.bind(this)} value='Update C2'/>
        </div>
      )
    }
    update () {
      this.props.onUpdate(this.refs.myInput.getDOMNode().value)
    }
})

class C2 extends React.Component {
    render () {
      return <div>{this.props.data}</div>
    }
})

ReactDOM.renderComponent(<Example/>, document.body)

Ответ 2

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

Я рекомендую вам прочитать

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

Flux отвечает на вопрос , почему следует структурировать ваше приложение React таким образом (в отличие от как, чтобы его структурировать). Реагирование - это только 50% системы, и с помощью Flux вы можете увидеть всю картину и посмотреть, как они образуют целостную систему.

Вернемся к вопросу.

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

Однако, может ли позволить обработчик запускать setState в P может быть правильным или неправильным в зависимости от вашей ситуации.

Если приложение является простым конвертером Markdown, C1 является сырым входом, а C2 является выходом HTML, то ОК, чтобы позволить C1 запускать setState в P, но некоторые могут утверждать, что это не рекомендуемый способ сделать это.

Однако, если приложение является списком todo, C1 - это вход для создания нового todo, C2 - список todo в HTML, вы, вероятно, хотите, чтобы обработчик прошел на два уровня выше P - до dispatcher, которые позволяют store обновить data store, которые затем отправляют данные в P и заполняют представления. См. Статью Flux. Вот пример: Flux - TodoMVC

Как правило, я предпочитаю способ, описанный в примере списка дел. Чем меньше у вас в приложении, тем лучше.

Ответ 3

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

Ответ 4

Вам следует изучить библиотеку Redux и ReactRedux. Он будет структурировать ваши состояния и реквизиты в одном хранилище, и вы сможете получить к ним доступ позже в своих компонентах.

Ответ 5

  1. Правильно сделать так, чтобы в родительском компоненте было состояние, чтобы избежать ссылки, а что нет.
  2. Проблема состоит в том, чтобы избежать постоянного обновления всех детей при вводе в поле
  3. Следовательно, каждый дочерний элемент должен быть компонентом (как в PureComponent) и должен реализовывать shouldComponentUpdate(nextProps, nextState)
  4. Таким образом, при вводе в поле формы обновляется только это поле

В приведенном ниже коде используются аннотации @bound из ES.Next babel-plugin-transform-decorators-legacy BabelJS 6 и свойства класса (аннотация устанавливает это значение для функций-членов, аналогично bind):

/*
© 2017-present Harald Rudell <[email protected]> (http://www.haraldrudell.com)
All rights reserved.
*/
import React, {Component} from 'react'
import {bound} from 'class-bind'

const m = 'Form'

export default class Parent extends Component {
  state = {one: 'One', two: 'Two'}

  @bound submit(e) {
    e.preventDefault()
    const values = {...this.state}
    console.log('${m}.submit:', values)
  }

  @bound fieldUpdate({name, value}) {
    this.setState({[name]: value})
  }

  render() {
    console.log('${m}.render')
    const {state, fieldUpdate, submit} = this
    const p = {fieldUpdate}
    return (
      <form onSubmit={submit}> {/* loop removed for clarity */}
        <Child name='one' value={state.one} {...p} />
        <Child name='two' value={state.two} {...p} />
        <input type="submit" />
      </form>
    )
  }
}

class Child extends Component {
  value = this.props.value

  @bound update(e) {
    const {value} = e.target
    const {name, fieldUpdate} = this.props
    fieldUpdate({name, value})
  }

  shouldComponentUpdate(nextProps) {
    const {value} = nextProps
    const doRender = value !== this.value
    if (doRender) this.value = value
    return doRender
  }

  render() {
    console.log('Child${this.props.name}.render')
    const {value} = this.props
    const p = {value}
    return <input {...p} onChange={this.update} />
  }
}

Ответ 6

Я удивлен, что в данный момент я не отвечаю на идиоматическое решение React. Итак, вот один (сравните размер и сложность с другими):

class P extends React.Component {
    state = { foo : "" };

    render(){
        const { foo } = this.state;

        return (
            <div>
                <C1 value={ foo } onChange={ x => this.setState({ foo : x })} />
                <C2 value={ foo } />
            </div>
        )
    }
}

const C1 = ({ value, onChange }) => (
    <input type="text"
           value={ value }
           onChange={ e => onChange( e.target.value ) } />
);

const C2 = ({ value }) => (
    <div>Reacting on value change: { value }</div>
);

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

Любой контролируемый input (идиоматический способ работы с формами в React) обновляет родительское состояние в onChange и по-прежнему ничего не onChange.

Посмотрите внимательно на компонент C1, например. Видите ли вы существенную разницу в том, как C1 и встроенный компонент input обрабатывают изменения состояния? Вы не должны, потому что нет ни одного. Поднятие состояния и передача пар value/onChange идиоматична для необработанного React. Не использование ссылок, как предполагают некоторые ответы.

Ответ 7

С React> = 16.3 вы можете использовать ref и forwardRef, чтобы получить доступ к дочернему DOM от его родителя. Больше не используйте старый способ реферирования.
Вот пример использования вашего случая:

import React, { Component } from 'react';

export default class P extends React.Component {
   constructor (props) {
      super(props)
      this.state = {data: 'test' }
      this.onUpdate = this.onUpdate.bind(this)
      this.ref = React.createRef();
   }

   onUpdate(data) {
      this.setState({data : this.ref.current.value}) 
   }

   render () {
      return (
        <div>
           <C1 ref={this.ref} onUpdate={this.onUpdate}/>
           <C2 data={this.state.data}/>
        </div>
      )
   }
}

const C1 = React.forwardRef((props, ref) => (
    <div>
        <input type='text' ref={ref} onChange={props.onUpdate} />
    </div>
));

class C2 extends React.Component {
    render () {
       return <div>C2 reacts : {this.props.data}</div>
    }
}

См Refs и ForwardRef подробной информации о ссылках и forwardRef.

Ответ 8

Объясняется концепция передачи данных от родителя к ребенку и наоборот.

import React, { Component } from "react";
import ReactDOM from "react-dom";

// taken refrence from https://gist.github.com/sebkouba/a5ac75153ef8d8827b98

//example to show how to send value between parent and child

//  props is the data which is passed to the child component from the parent component

class Parent extends Component {
  constructor(props) {
    super(props);

    this.state = {
      fieldVal: ""
    };
  }

  onUpdateParent = val => {
    this.setState({
      fieldVal: val
    });
  };

  render() {
    return (
      // To achieve the child-parent communication, we can send a function
      // as a Prop to the child component. This function should do whatever
      // it needs to in the component e.g change the state of some property.
      //we are passing the function onUpdateParent to the child
      <div>
        <h2>Parent</h2>
        Value in Parent Component State: {this.state.fieldVal}
        <br />
        <Child onUpdate={this.onUpdateParent} />
        <br />
        <OtherChild passedVal={this.state.fieldVal} />
      </div>
    );
  }
}

class Child extends Component {
  constructor(props) {
    super(props);

    this.state = {
      fieldValChild: ""
    };
  }

  updateValues = e => {
    console.log(e.target.value);
    this.props.onUpdate(e.target.value);
    // onUpdateParent would be passed here and would result
    // into onUpdateParent(e.target.value) as it will replace this.props.onUpdate
    //with itself.
    this.setState({ fieldValChild: e.target.value });
  };

  render() {
    return (
      <div>
        <h4>Child</h4>
        <input
          type="text"
          placeholder="type here"
          onChange={this.updateValues}
          value={this.state.fieldVal}
        />
      </div>
    );
  }
}

class OtherChild extends Component {
  render() {
    return (
      <div>
        <h4>OtherChild</h4>
        Value in OtherChild Props: {this.props.passedVal}
        <h5>
          the child can directly get the passed value from parent by this.props{" "}
        </h5>
      </div>
    );
  }
}

ReactDOM.render(<Parent />, document.getElementById("root"));