React组件通信

React组件之间几种通信方式

Posted by Aaronwn on March 22, 2018

组件通信类型

  • 父组件->子组件
  • 子组件->父组件
  • 兄弟组件间通信
  • 跨级组价间通信

父组件->子组件

parent 组件传给 child 组件,符合 react 的单向数据流理念,自上到下传递 props。

// 父组件
class Parent extends Component {
  constructor() {
    super();
    this.state = {
      value: ""
    };
  }

  handleChange = e => {
    this.value = e.target.value;
  };

  handleClick = () => {
    this.setState({
      value: this.value
    });
  };

  render() {
    return (
      <div>
        我是parent
        <input onChange={this.handleChange} />
        <div className="button" onClick={this.handleClick}>
          通知
        </div>
        <div>
          <Child value={this.state.value} />
        </div>
      </div>
    );
  }
}
// 子组件
class Child extends Component {
  render() {
    const { value } = this.props;
    return <div>我是Child,得到传下来的值:{value}</div>;
  }
}

父组件做的就是定义好 state ,定义好事件函数,input onChange 的时候,去缓存 value 值,然后点击 button 的时候,改变 state , 子组件只负责展示 value 。

子组件->父组件

child 组件通知 parent 组件, 主要是依靠 parent 传下来的 callback 函数执行,改变 parent 组件的状态,或者把 child 自己的 state 通知 parent 。分两种情况:

  • state 定义在 parent 组件
// parent
class Parent extends Component {
  constructor() {
    super();
    this.state = {
      value: ""
    };
  }

  setValue = value => {
    this.setState({
      value
    });
  };

  render() {
    return (
      <div>
        <div>我是parent, Value是:{this.state.value}</div>
        <Child setValue={this.setValue} />
      </div>
    );
  }
}
class Child extends Component {
  handleChange = e => {
    this.value = e.target.value;
  };

  handleClick = () => {
    const { setValue } = this.props;
    setValue(this.value);
  };

  render() {
    return (
      <div>
        我是Child
        <div className="card">
          state 定义在 parent
          <input onChange={this.handleChange} />
          <div className="button" onClick={this.handleClick}>
            通知
          </div>
        </div>
      </div>
    );
  }
}

parent 组件把改变 state 的 setValue 函数传给 child ,child 组件自己处理内部的状态(这里是表单的 value 值),当 child 组件分发消息的时候, 执行 parent 的 setValue 函数,从而改变了 parent 的 state,state 发生变化, parent 组件执行 re-render 。

  • state 定义在 child 组件
// parent

class Parent extends Component {

  onChange = value => {
    console.log(value, '来自 child 的 value 变化');
  }

  render() {
    return (
      <div>
        <div>我是parent
        <Child onChange={this.onChange} />
      </div>
    );
  }
}
class Child extends Component {
  constructor() {
    super();
    this.state = {
      childValue: ""
    };
  }

  childValChange = e => {
    this.childVal = e.target.value;
  };

  childValDispatch = () => {
    const { onChange } = this.props;
    this.setState(
      {
        childValue: this.childVal
      },
      () => {
        onChange(this.state.childValue);
      }
    );
  };

  render() {
    return (
      <div>
        我是Child
        <div className="card">
          state 定义在 child
          <input onChange={this.childValChange} />
          <div className="button" onClick={this.childValDispatch}>
            通知
          </div>
        </div>
      </div>
    );
  }
}

有时候 state 是需要定义在 child 组件的,比如弹窗, CheckBox 这种开关性质的,逻辑是重复的,state 定义在组件内部更好维护, 复用性更好。但是 child 的 state 是需要告知我的 parent 组件的, 同样还是执行 parent 传下来的 change 函数。

兄弟组件间通信

有时候可能出现页面中的某两部分通信,比如省市的级联选择,点击 button 改变颜色等等,组件并不是父子级,没有嵌套关系的时候。这种时候通常是依赖共有的顶级 Container 处理或者第三方的状态管理器。其实原理都是相通的,兄弟 A 的 value 发生变化,分发的时候把 value 值告诉一个中间者 C ,C 会自动告知 B,实现 B 的自动 render 。

// container
class Container extends Component {
  constructor() {
    super();
    this.state = {
      value: ""
    };
  }

  setValue = value => {
    this.setState({
      value
    });
  };

  render() {
    return (
      <div>
        <A setValue={this.setValue} />
        <B value={this.state.value} />
      </div>
    );
  }
}
// 兄弟A
class A extends Component {
  handleChange = e => {
    this.value = e.target.value;
  };

  handleClick = () => {
    const { setValue } = this.props;
    setValue(this.value);
  };

  render() {
    return (
      <div className="card">
        我是Brother A, <input onChange={this.handleChange} />
        <div className="button" onClick={this.handleClick}>
          通知
        </div>
      </div>
    );
  }
}
// 兄弟B
const B = props => (
  <div className="card">
    我是Brother B, value是:
    {props.value}
  </div>
);
export default B;

组件 A 中的表单 value 值,告知了父级 Container 组件(通过 setValue 函数改变 state),组件 B 依赖于 Container 传下来的 state,会做出同步更新。这里的中间者是 Container。

跨级组件间通信

上面的方式,如果嵌套少还可以,如果嵌套特别多,比如一级导航栏下的二级导航栏下的某个按钮,要改变页面中 content 区域的 table 里的某个列的值…他们同属于一个 page 。这样传递 props 就会很痛苦,每一层组件都要传递一次。

// 顶级公共组件
class Context extends Component {
  constructor() {
    super();
    this.state = {
      value: ""
    };
  }

  setValue = value => {
    this.setState({
      value
    });
  };

  getChildContext() {
    // 必需
    return {
      value: this.state.value,
      setValue: this.setValue
    };
  }
  render() {
    return (
      <div>
        <AParent />
        <BParent />
      </div>
    );
  }
}
// 必需
Context.childContextTypes = {
  value: PropTypes.string,
  setValue: PropTypes.func
};
// B 的 parent
class BParent extends Component {
  render() {
    return (
      <div className="card">
        <B />
      </div>
    );
  }
}

// B
class B extends Component {
  render() {
    return (
      <div>
        我是parentB 下的 B, value是:
        {this.context.value}
      </div>
    );
  }
}

B.contextTypes = {
  value: PropTypes.string
};

Redux || Mobx

Redux 或者 Mobx 是第三方的状态管理器,是这里我们通信的中间者。大型项目最直接的就是上库… 更方便,更不容易出错。 但其实小项目就没什么必要了。东西比较多,这里不再阐述它们的实现和做了什么。

总结

react 特殊的自上而下的单向数据流,和 state 的特性,造就以这样的思想实现组件通信。除去发布订阅和 Redux 等,其他的都是 props 自上而下传递的理念,子组件需要的总是通过父组件传递下来的,关于 state 的定义,还是看具体的应用场景了。