假设我有一个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。

其他回答

你应该学习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。

正确的做法是把状态放在父组件中,避免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} />
  }
}

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

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

现在已经使用React构建了一个应用程序,我想分享一些关于半年前我问过的问题的想法。

我建议你读一读

React中的思考 通量

第一篇文章对于理解如何构建React应用非常有帮助。

Flux回答了为什么要这样构造React应用程序(而不是如何构造它)的问题。React只占整个系统的50%,而Flux可以让你看到整个系统,看到它们如何构成一个连贯的系统。

回到刚才的问题。

至于我的第一个解决方案,完全可以让处理程序走相反的方向,因为数据仍然是单向的。

但是,让处理程序触发P中的setState是否正确取决于您的情况。

如果应用程序是一个简单的Markdown转换器,C1是原始输入,C2是HTML输出,那么让C1触发P中的setState是可以的,但有些人可能会认为这不是推荐的方法。

然而,如果应用程序是一个待办事项列表,C1是创建新待办事项的输入,C2是HTML中的待办事项列表,你可能想要处理程序比P高两级——到调度程序,它让存储更新数据存储,然后将数据发送给P并填充视图。请参阅Flux的文章。这里有一个例子:Flux - TodoMVC

一般来说,我更喜欢todo列表示例中描述的方式。应用程序的状态越少越好。