React 组件数据流 && 组件间沟通

使用React我们首先要知道如何传递数据,组件如何沟通,才能展示我们想要的数据。下面的列子都是使用ES6语法,不懂的同学需要先学习ES6语法。

数据流

React是单向数据流,从父节点传递到子节点(通过props)。如果顶层的某个props改变了,React会重渲染所有的子节点(未做性能优化)。严格意义上React只提供,也强烈建议使用这种数据交流方式。

Props

props是property的缩写,可以理解为HTML标签的attribute。请把props当做只读的(不可以使用this.props直接修改props),props是用于整个组件树中传递数据和配置。在当前组件访问props,使用this.props。在什么情况下可以使用props,请看组件生命周期

class Component {
  constructor(props){
    super(props);
  }
  render(){
    return (
        <div title={this.props.title}></div>
    )
  }
}
<Component title="test"/>//调用title就传进去了

PropTypes

PropsTypes是React中用来定义props的类型,不符合定义好的类型会报错。建议可复用组件要使用prop验证!接着上面的列子设置PropsTypes如下:

class Component {
  ...
}
Component.PropsType = {
  title: React.PropTypes.string,}

React.PropTypes提供很多验证器 (validator) 来验证传入数据的有效性。官方定义的验证器如下,不是使用ES6语法。

React.createClass({
  propTypes: {
    // 可以声明 prop 为指定的 JS 基本类型。默认
    // 情况下,这些 prop 都是可传可不传的。
    optionalArray: React.PropTypes.array,optionalBool: React.PropTypes.bool,optionalFunc: React.PropTypes.func,optionalNumber: React.PropTypes.number,optionalObject: React.PropTypes.object,optionalString: React.PropTypes.string,optionalSymbol: React.PropTypes.symbol,// 所有可以被渲染的对象:数字,
    // 字符串,DOM 元素或包含这些类型的数组(or fragment) 。
    optionalNode: React.PropTypes.node,// React 元素
    optionalElement: React.PropTypes.element,// 你同样可以断言一个 prop 是一个类的实例。
    // 用 JS 的 instanceof 操作符声明 prop 为类的实例。
    optionalMessage: React.PropTypes.instanceOf(Message),// 你可以用 enum 的方式
    // 确保你的 prop 被限定为指定值。
    optionalEnum: React.PropTypes.oneOf(['News','Photos']),// 指定的多个对象类型中的一个
    optionalUnion: React.PropTypes.oneOfType([
      React.PropTypes.string,React.PropTypes.number,React.PropTypes.instanceOf(Message)
    ]),// 指定类型组成的数组
    optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),// 指定类型的属性构成的对象
    optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),// 特定形状参数的对象
    optionalObjectWithShape: React.PropTypes.shape({
      color: React.PropTypes.string,fontSize: React.PropTypes.number
    }),// 你可以在任意东西后面加上 `isRequired`
    // 来确保 如果 prop 没有提供 就会显示一个警告。
    requiredFunc: React.PropTypes.func.isRequired,// 不可空的任意类型
    requiredAny: React.PropTypes.any.isRequired,// 你可以自定义一个验证器。如果验证失败需要返回一个 Error 对象。
    // 不要直接使用 `console.warn` 或抛异常,
    // 因为这在 `oneOfType` 里不起作用。
    customProp: function(props,propName,componentName) {
      if (!/matchme/.test(props[propName])) {
        return new Error('Validation failed!');
      }
    }
  },/* ... */
});

defaultProps

如何设置组件默认的props

//React提供的crateClass创建方式
var Component = React.createClass({
  getDefaultProps(){
    return {
      //这里设置defaultProps
    }
  }
})
//ES6
class Component {
  ...
}
Component.defaultProps = {}
//ES7 stage-0
class Component {
  static defaultProps = {
    
  }
  ...
}

state

每个组件都有属于自己的statestateprops的区别在于前者之只存在于组件内部,只能从当前组件调用this.setState修改state值(不可以直接修改this.state)。一般我们更新子组件都是通过改变state值,更新新子组件的props值从而达到更新。

那如何设置默认state?

//React提供的crateClass创建方式
var Component = React.createClass({
  getInitialState(){
    return {
      //这里设置初始state值
    }
  }
})
//ES6 && ES7
class Component {
  constructor(){
    this.state = {}//在ES6中的构造函数中初始化,可以之直接赋值,在其他方法中,只能使用this.setState
  }
  ...
}

props和state使用方式

尽可能使用props当做数据源,state用来存放状态值(简单的数据),如复选框、下拉菜单等。

组件沟通

组件沟通因为React的单向数据流方式会有所限制,下面述说组件之间的沟通方式。

父子组件沟通

这种方式是最常见的,也是最简单的。

  • 父组件更新组件状态

父组件更新子组件状态,通过传递props,就可以了。

  • 子组件更新父组件状态

这种情况需要父组件传递回调函数给子组件,子组件调用触发即可。

代码示例:

class Child extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  render(){
    return (
      <div>
        {this.props.text}
        <br />
        <button onClick={this.props.refreshParent}>
            更新父组件
        </button>
      </div>
    )
  }
}
class Parent extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  refreshChild(){
    return (e)=>{
      this.setState({
        childText: "父组件沟通子组件成功",})
    }
  }
  refreshParent(){
    this.setState({
      parentText: "子组件沟通父组件成功",})
  }
  render(){
    return (
      <div>
        <h1>父子组件沟通</h1>
        <button onClick={this.refreshChild()} >
            更新子组件
        </button>
        <Child 
          text={this.state.childText || "子组件未更新"} 
          refreshParent={this.refreshParent.bind(this)}
        />
        {this.state.parentText || "父组件未更新"}
      </div>
    )
  }
}

codepen例子React组件之父子组件沟通

兄弟组件沟通

当两个组件有相同的父组件时,就称为兄弟组件(堂兄也算的)。按照React单向数据流方式,我们需要借助父组件进行传递,通过父组件回调函数改变兄弟组件的props

方式一

通过props传递父组件回调函数。

class Brother1 extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  render(){
    return (
      <div>
        <button onClick={this.props.refresh}>
            更新兄弟组件
        </button>
      </div>
    )
  }
}
class Brother2 extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  render(){
    return (
      <div>
         {this.props.text || "兄弟组件未更新"}
      </div>
    )
  }
}
class Parent extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  refresh(){
    return (e)=>{
      this.setState({
        text: "兄弟组件沟通成功",})
    }
  }
  render(){
    return (
      <div>
        <h2>兄弟组件沟通</h2>
        <Brother1 refresh={this.refresh()}/>
        <Brother2 text={this.state.text}/>
      </div>
    )
  }
}

codepen例子:React组件之兄弟组件沟通

方式二

但是如果组件层次太深(如下图),上面的兄弟组件沟通方式就效率低了(不建议组件层次太深)。

React提供了一种上下文方式(挺方便的),可以让子组件直接访问祖先的数据或函数,无需从祖先组件一层层地传递数据到子组件中。

class Brother1 extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  render(){
    
    return (
      <div>
        <button onClick={this.context.refresh}>
            更新兄弟组件
        </button>
      </div>
    )
  }
}
Brother1.contextTypes = {
  refresh: React.PropTypes.any
}
class Brother2 extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  render(){
    return (
      <div>
         {this.context.text || "兄弟组件未更新"}
      </div>
    )
  }
}
Brother2.contextTypes = {
  text: React.PropTypes.any
}
class Parent extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  getChildContext(){
    return {
      refresh: this.refresh(),text: this.state.text,}
    }
  
  refresh(){
    return (e)=>{
      this.setState({
        text: "兄弟组件沟通成功",})
    }
  }
  render(){
    return (
      <div>
        <h2>兄弟组件沟通</h2>
        <Brother1 />
        <Brother2 text={this.state.text}/>
      </div>
    )
  }
}
Parent.childContextTypes = {
  refresh: React.PropTypes.any,text: React.PropTypes.any,}

codepen例子:React组件之兄弟组件沟通2

全局事件

For communication between two components that don't have a parent-child relationship,you can set up your own global event system. Subscribe to events incomponentDidMount(),unsubscribe incomponentWillUnmount(),and callsetState()when you receive an event.Fluxpattern is one of the possible ways to arrange this.

官网中提到可以使用全局事件来进行组件间的通信,官网推荐Flux(Facebook官方出的),还有Relay、Redux、trandux等第三方类库。这些框架思想都一致,都是统一管理组件state变化情况,达到数据可控目的。本人使用了Redux,建议要会其中一种。对于EventEmitter或PostalJS这类的第三方库是不建议使用的,这类全局事件框架并没有统一管理组件数据变化,用多了会导致数据流不可控。

这里就不细说,请选择其中一种类库,深入学习下。

总结

简单的组件交流我们可以使用上面非全局事件的简单方式,但是当项目复杂,组件间层次越来越深,上面的交流方式就不太合适(当然还是要用到的,简单的交流)。强烈建议使用Flux、Relay、Redux、trandux等类库其中一种,这些类库不只适合React,像Angular等都可以使用。

参考文章

相关文章

react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接...
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc ...