假设我有一个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的渲染中。

我该怎么做呢?


当前回答

你应该学习Redux和ReactRedux库。它将在一个存储中构建您的状态和道具,您可以稍后在组件中访问它们。

其他回答

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

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

五年后,随着React Hooks的引入,现在有了更优雅的方式来使用useContext钩子。

你在全局范围内定义context,在父组件中导出变量、对象和函数,然后在应用中提供的上下文中包装子组件,并在子组件中导入任何你需要的东西。下面是概念的证明。

import React, { useState, useContext } from "react";
import ReactDOM from "react-dom";
import styles from "./styles.css";

// Create context container in a global scope so it can be visible by every component
const ContextContainer = React.createContext(null);

const initialAppState = {
  selected: "Nothing"
};

function App() {
  // The app has a state variable and update handler
  const [appState, updateAppState] = useState(initialAppState);

  return (
    <div>
      <h1>Passing state between components</h1>

      {/* 
          This is a context provider. We wrap in it any children that might want to access
          App's variables.
          In 'value' you can pass as many objects, functions as you want. 
           We wanna share appState and its handler with child components,           
       */}
      <ContextContainer.Provider value={{ appState, updateAppState }}>
        {/* Here we load some child components */}
        <Book title="GoT" price="10" />
        <DebugNotice />
      </ContextContainer.Provider>
    </div>
  );
}

// Child component Book
function Book(props) {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // In this child we need the appState and the update handler
  const { appState, updateAppState } = useContext(ContextContainer);

  function handleCommentChange(e) {
    //Here on button click we call updateAppState as we would normally do in the App
    // It adds/updates comment property with input value to the appState
    updateAppState({ ...appState, comment: e.target.value });
  }

  return (
    <div className="book">
      <h2>{props.title}</h2>
      <p>${props.price}</p>
      <input
        type="text"
        //Controlled Component. Value is reverse vound the value of the variable in state
        value={appState.comment}
        onChange={handleCommentChange}
      />
      <br />
      <button
        type="button"
        // Here on button click we call updateAppState as we would normally do in the app
        onClick={() => updateAppState({ ...appState, selected: props.title })}
      >
        Select This Book
      </button>
    </div>
  );
}

// Just another child component
function DebugNotice() {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // but in this child we only need the appState to display its value
  const { appState } = useContext(ContextContainer);

  /* Here we pretty print the current state of the appState  */
  return (
    <div className="state">
      <h2>appState</h2>
      <pre>{JSON.stringify(appState, null, 2)}</pre>
    </div>
  );
}

const rootElement = document.body;
ReactDOM.render(<App />, rootElement);

您可以在代码沙盒编辑器中运行此示例。

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

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