假设我有一个React类P,它呈现两个子类,C1和C2。 C1包含一个输入字段。我将把这个输入字段称为Foo。 我的目标是让C2对Foo中的更改做出反应。

我想出了两个解决办法,但感觉都不太对。

第一个解决方案:

给P分配一个状态state.input。 在P中创建一个onChange函数,它接收一个事件并设置state.input。 将这个onChange作为一个props传递给C1,并让C1将this.props.onChange绑定到Foo的onChange。

这个作品。每当Foo的值发生变化时,它就会触发P中的setState,这样P就有了要传递给C2的输入。

但出于同样的原因,感觉不太对:我正在从子元素设置父元素的状态。这似乎违背了React的设计原则:单向数据流。 这是我应该怎么做,还是有一个更反应自然的解决方案?

第二个解决方案:

把Foo放到P中。

但是,当我构造我的应用程序——把所有表单元素放在最高级别类的渲染中——时,这是我应该遵循的设计原则吗?

就像在我的例子中,如果我有一个C1的大渲染,我真的不想仅仅因为C1有一个表单元素就把C1的整个渲染放到P的渲染中。

我该怎么做呢?


当前回答

使用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的详细信息,请参见Refs和ForwardRef。

其他回答

正确的做法是把状态放在父组件中,避免ref之类的 一个问题是在输入字段时避免不断更新所有子字段 因此,每个子组件都应该是一个组件(而不是PureComponent),并实现shouldComponentUpdate(nextProps, nextState) 这样,当输入表单字段时,只有该字段更新

下面的代码使用来自ES的@bound注释。下一个babel-plugin-transform-decorators- BabelJS 6的遗产和class-properties(注释在成员函数上设置这个值,类似于bind):

/*
© 2017-present Harald Rudell <harald.rudell@gmail.com> (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} />
  }
}

我很惊讶,在我写作的时候,没有一个简单的惯用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的设计原则:单向数据流。

任何受控输入(React中使用表单的惯用方式)在onChange回调中更新父状态,仍然不会泄露任何信息。

例如,仔细看看C1组件。在C1和内置输入组件处理状态变化的方式上,您看到有什么显著的区别吗?你不应该,因为根本就没有。提升状态并传递value/onChange对是原始React的惯用方法。而不是像一些答案所暗示的那样使用裁判。

你应该学习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的详细信息,请参见Refs和ForwardRef。

解释了将数据从父节点传递到子节点的概念,反之亦然。

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