Я придумал два решения, но ни один из них не чувствует себя совершенно правильно.
Первое решение:
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 имеет элемент формы.
Как мне это сделать?
Итак, если я правильно понимаю вас, ваше первое решение предполагает, что вы сохраняете состояние в своем корневом компоненте? Я не могу говорить за создателей Реакта, но, как правило, я считаю это правильным решением.
Поддержание состояния - одна из причин (по крайней мере, я думаю), что 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)
C2
есть getInitialState
для данных и внутри render
он использует this.state.data
?
Используя 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
Как правило, я предпочитаю способ, описанный в примере списка дел. Чем меньше у вас в приложении, тем лучше.
Первое решение, с сохранением состояния в родительском компоненте, является правильным. Однако для решения более сложных проблем следует подумать о некоторой библиотеке управления состоянием. Наиболее популярной из всех, которые используются для реагирования, является redux.
Вам следует изучить библиотеку Redux и ReactRedux. Он будет структурировать ваши состояния и реквизиты в одном хранилище, и вы сможете получить к ним доступ позже в своих компонентах.
С 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.
Я удивлен, что в данный момент я не отвечаю на идиоматическое решение 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. Не использование ссылок, как предполагают некоторые ответы.
Объясняется концепция передачи данных от родителя к ребенку и наоборот.
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"));
shouldComponentUpdate(nextProps, nextState)
В приведенном ниже коде используются аннотации @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} />
}
}