React的組件協同使用實現方式

開發人員不用太過於關註UI層面的實現細節,考慮最多的也就是組件與組件之間的數據通信瞭。那麼,在React開發中,有哪些場景的組件協同?又如何去實現組件的協同使用呢?

組件的協同本質上是對組件的一種組織、管理方式。

目的是使得系統 邏輯清晰、代碼模塊化、封裝細節、代碼可復用。

組件的協同分為兩種方式:嵌套、抽離、發佈訂閱模式。

嵌套

組件嵌套的本質就是父子關系,即為父組件和子組件之間的通信。

總的來說有兩種場景:

  • 父子組件通信
  • 兄弟組件通信

父子組件通信

首先我們先來看看最常用的一個手段,通過props屬性。以父子組件為例,父組件隻需要將數據以props的方式傳遞給子組件,子組件可以直接通過this.props來獲取,比如:

// 父組件 Parent
export default class Parent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      message: '傳給子組件的消息'
    }
  }

  // 消息回調
  onMessage(messageFromChildren) {
    console.log(messageFromChildren);
  }

  render() {
    const { message } = this.state;
    return (
      <div>
        <Children message={ message } onMessage={ this.onMessage.bind(this) } />
      </div>
    );
  }
}
// 子組件 Children
export default class Children extends React.Component {
  constructor(props) {
    super(props);
  }

  handleClick() {
    this.props.onMessage('來自子組件的消息');
  }

  render() {
    const { message } = this.props;
    return (
      <div>
        <p>{ message }</p>
        <button onClick={ this.handleClick.bind(this) }>click</button>
      </div>
    );
  }
}

當然,如果Children子組件需要傳遞數據給到父組件,可以使用回調方式,父組件將方法的引用通過props傳遞給到子組件,如上代碼中的handleClick裡調用瞭onMessage。當父組件的state更新時,Children組件會重新渲染,使用最新的message數據。

bind的作用就是給函數增加默認的參數,第一個傳參將替代方法裡面的this

兄弟組件通信

兄弟組件不能直接相互通信,需要通過父組件來中轉一下,進行狀態提升。兄弟組件將需要共享的數據提升至共同的直接父組件中,然後就跟普通的父子組件通信一樣瞭。比如:

// 父組件 Parent
export default class Parent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      messageFromA: '',
      messageFromB: ''
    }
  }

  onMessage(messageFromChildren, from) {
    console.log(messageFromChildren);

    this.setState({
      [from == 'A' ? 'messageFromA' : 'messageFromB']: messageFromChildren
    });
  }

  render() {
    const { messageFromA,  messageFromB} = this.state;
    return (
      <div>
        <ChildrenA message={ messageFromB } onMessage={ this.onMessage.bind(this) } />
        <ChildrenB message={ messageFromA } onMessage={ this.onMessage.bind(this) } />
      </div>
    );
  }
}
// 子組件ChildrenA
export default class ChildrenA extends React.Component {
  constructor(props) {
    super(props);
  }

  handleClick() {
    this.props.onMessage('來自A子組件的消息', 'A');
  }

  render() {
    const { message } = this.props;
    return (
      <div className="p-a b-a">
        <p>{ message }</p>
        <button onClick={this.handleClick.bind(this)}>clickA</button>
      </div>
    );
  }
}
// 子組件 ChildrenB
export default class ChildrenB extends React.Component {
  constructor(props) {
    super(props);
  }

  handleClick() {
    this.props.onMessage('來自B子組件的消息', 'B');
  }

  render() {
    const { message } = this.props;
    return (
      <div className="p-a b-a">
        <p>{ message }</p>
        <button onClick={this.handleClick.bind(this)}>clickB</button>
      </div>
    );
  }
}

當點擊clickA的時候,子組件B接收到瞭子組件A的消息,反之亦然。

通過props的組件通信比較簡單,但也有其自身的缺陷,當組件層級大於3層時,這種方式就不適合瞭,首先是深層級的傳遞對到維護來說簡直就是噩夢,需要一層一層的看才能知道數據的來源及流向。其次的話,假如不止A、B子組件,還有C子組件的,A、B組件引發的父組件state更新會觸發C子組件的更新,但事實上,C子組件並沒有接收任何數據,容易造成資源浪費。

// Parent組件
  render() {
    const { messageFromA,  messageFromB} = this.state;
    return (
      <div>
        <ChildrenA message={ messageFromB } onMessage={ this.onMessage.bind(this) } />
        <ChildrenB message={ messageFromA } onMessage={ this.onMessage.bind(this) } />
        <ChildrenC />
      </div>
    );
  }
// 子組件 ChildrenC
export default class ChildrenC extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidUpdate() {
    console.log('ChildrenC updated');
  }


  render() {
    return (
      <div>ChildrenC</div>
    );
  }
}

抽離

Mixin

這裡要介紹的抽離主要是指Mixin。

假設有多個組件使用相同的getDefaultProps方法,我們就可以定義如下Mixin:

var DefaultNameMixin = {
    getDefaultProps: function () {
        return {name: "Tom"};
    }
};

Mixin相當於組件的一個擴展,它的本質就是一組方法的集合,使用這個 mixin 的組件能夠自由的使用這些方法(就像在組件中定義的一樣)。使用Mixin的目的就是橫向抽離出組件的相似代碼。

與Mixin思路相似的概念還有:AOP、插件等

例子中,DefaultNameMixin中包含getDefaultProps方法。除瞭直接定義外,Mixin還可以嵌套使用,也就是可以在定義Mixin時使用另一個Mixin:

var DefaultCommonMixin = {
    mixins:[DefaultNameMixin], //use Mixin
    getDefaultProps: function () {
        return {food: "rice"};
    }
};

例子中,在DefaultCommonMixin的定義中嵌套使用瞭DefaultNameMixin,因此DefaultCommonMixin包含瞭DefaultNameMixin中定義的getDefaultProps方法,此時DefaultCommonMixin中有兩個方法。使用Mixin時,隻需將Mixin加入到mixins屬性對應的數組中,Mixin的使用語法為mixins:[Mixin1,Mixin2……]。

使用Mixin時,隻需將Mixin加入到mixins屬性對應的數組中,Mixin的使用語法為mixins:[Mixin1,Mixin2……]。

var Component = React.createClass({
    mixins:[DefaultCommonMixin], //use Mixin
    render:function(){
        return <h1>{this.props.name} like {this.props.food}</h1>;
    }
}); 

ReactDOM.render(
    <Component/>,
    document.getElementById("example")
);

一個組件可以使用多個Mixin,同時,Mixin也可使用在多個組件中。

使用Mixin時,需要註意不要在多個地方設置相同的Prop和State。同時,在不同的Mixin中定義相同的方法,或者Mixin和組件中包含瞭相同的方法時,也會拋出異常,但是這種情況對componentDidMount等生命周期方法不適用(render方法除外,多次定義render方法也會拋出異常)。

如果在一個組件的多個地方定義瞭相同的生命周期方法,這些方法的執行順序為:Mixin中的方法會優先執行(根據mixins中的順序從左到右的順序),然後執行組件中定義的方法。

Mixin的優點:

代碼復用:抽離出通用代碼,減少開發成本,提高開發效率

即插即用:可以直接使用許多現有的Mixin來編寫自己的組件

適應性強:改動一次代碼,影響多個組件

Mixin的缺點:

編寫難度高:Mixin可能被用在各種環境中,兼容多種環境就需要更多的邏輯和代碼,通用的代價是提高復雜度。

降低代碼可讀性:組件的優勢在於將邏輯和界面直接結合在一起,Mixin本質上會分散邏輯,理解起來難度更大。

React的LinkedStateMixin

'use strict';  
  
// 構建對象{value,requestChange},value為初始值,requestChange為方法,須手動調用  
// 在本模塊中,value為state[key]的初始值,requestChange用於更新state[key]  
var ReactLink = require('./ReactLink');  
  
// 設定屬性key後,返回函數,該函數接受value,內部調用組件component.setState方法,更新state[key]=value  
var ReactStateSetters = require('./ReactStateSetters');  
  
/** 
 * 針對react手動調用setState方法的單向數據流,提供雙向綁定 
 * 使用linkState(key).requestChange(value)傳值後自動調用setState方法,更新state 
 * 
 * 示例 
 * var LinkedStateMixin = require('react-addons-linked-state-mixin'); 
 
 * var WithoutLink = React.createClass({ 
 *   mixins: [LinkedStateMixin], 
 *   getInitialState: function() { 
 *     return {message: 'Hello!'}; 
 *   }, 
 *   render: function() { 
 *     var valueLink = this.linkState('message'); 
 *     var handleChange = function(e) { 
 *       valueLink.requestChange(e.target.value); 
 *     }; 
 *     return <input type="text" value={valueLink.value} onChange={handleChange} />; 
 *   } 
 * }); 
 */  
var LinkedStateMixin = {  
  // ReactStateSetters.createStateKeySetter方法用於構建linkState(key)返回對象的requestChange方法  
  // 實現傳值後自動調用setState方法,更新state  
  linkState: function (key) {  
    return new ReactLink(this.state[key], ReactStateSetters.createStateKeySetter(this, key));  
  }  
};  
  
module.exports = LinkedStateMixin;  

使用上述的Mixin:

import LinkedStateMixin from 'react-addons-linked-state-mixin'

mixins: [React.addons.LinkedStateMixin],

LinkedStateMixin僅僅是一個onChange/setState()模式的簡單包裝和約定。它不會從根本上改變數據在你的React應用中如何流動,也就是說其實LinkedStateMixin本質還是單向流,隻是通過onChange將數據更改傳遞給React,然後內部數據改變就自動調用setState來進行更新。

使用示例:

var WithLink = React.createClass({
  mixins: [LinkedStateMixin],
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    return <input type="text" valueLink={this.linkState('message')} />;
  }
});

LinkedStateMixin給你的React組件添加一個叫做linkState()的方法。linkState()返回一個包含React state當前的值和用來改變它的回調函數的valueLink對象。

valueLink 對象可以在樹中作為props被向上傳遞或者向下傳遞,所以它在組件層和狀態層建立起雙向綁定是非常簡單的。

註意:

對於checkboxvalue屬性,有一個特殊的行為,如果checkbox被選中(默認是on),value屬性值將會在表單提交的時候發送出去。當checkbox被選中或者取消選中的時候,value屬性是不會更新的。對於checkbox,你應該使用checkLink而不是valueLink

<checkbox type=”checkbox” checkedLink={this.linkState(‘booleanValue’)} />

Reference:

1. http://schifred.iteye.com/blog/2361478

2. http://blog.csdn.net/qq_18661257/article/details/68951561

到此這篇關於React的組件協同使用的文章就介紹到這瞭,更多相關React組件協同內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: