本篇文章帶給大家的內容是關於傳統組件間通訊與React組件間通訊的分析對比(程式碼範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。
在React中最小的邏輯單元是組件,組件之間如果有耦合關係就會進行通信,本文將會介紹React中的組件通信的不同方式
通過歸納範,可以將任意組件間的通信歸類為四種類型的組件間通信,分別是父子組件,爺孫組件,兄弟組件和任意組件,
需要注意的是前三個也可以算作任意組件的範疇,所以最後一個是萬能方法
父子組件間的通信分為父組件向子組件通信和子組件向父組件通信兩種情況,下面先來介紹父元件向子元件通信,
傳統做法分為兩種情況,分別是初始化時的參數傳遞和實例階段的方法調用,例子如下
class Child { constructor(name) { // 获取dom引用 this.$p = document.querySelector('#wp'); // 初始化时传入name this.updateName(name); } updateName(name) { // 对外提供更新的api this.name = name; // 更新dom this.$p.innerHTML = name; } } class Parent { constructor() { // 初始化阶段 this.child = new Child('yan'); setTimeout(() => { // 实例化阶段 this.child.updateName('hou'); }, 2000); } }
在React中將兩個情況統一處理,全部透過屬性來完成,之所以能夠這樣是因為React在屬性更新時會自動重新渲染子元件,
下面的範例中,2秒後子元件會自動重新渲染,並取得新的屬性值
class Child extends Component { render() { return <p>{this.props.name}</p> } } class Parent extends Component { constructor() { // 初始化阶段 this.state = {name: 'yan'}; setTimeout(() => { // 实例化阶段 this.setState({name: 'hou'}) }, 2000); } render() { return <Child name={this.state.name} /> } }
下面來看子元件如何向父元件通信,傳統做法有兩種,一種是回呼函數,另一種是為子元件部署訊息介面
先來看回呼函數的例子,回呼函數的優點是非常簡單,缺點就是必須在初始化的時候傳入,並且不可撤回,並且只能傳入一個函數
class Child { constructor(cb) { // 调用父组件传入的回调函数,发送消息 setTimeout(() => { cb() }, 2000); } } class Parent { constructor() { // 初始化阶段,传入回调函数 this.child = new Child(function () { console.log('child update') }); } }
下面來看看訊息介面方法,首先需要一個可以發布和訂閱訊息的基類,例如下面實作了一個簡單的EventEimtter
,實際生產中可以直接使用別人寫好的類別庫,例如@jsmini/event,子元件繼承訊息基類,就有了發布訊息的能力,然後父元件訂閱子元件的訊息,即可實現子元件向父元件通訊的功能
訊息介面的優點就是可以隨處訂閱,並且可以多次訂閱,還可以取消訂閱,缺點是略顯麻煩,需要引入訊息基底類別
// 消息接口,订阅发布模式,类似绑定事件,触发事件 class EventEimtter { constructor() { this.eventMap = {}; } sub(name, cb) { const eventList = this.eventMap[name] = this.eventMap[name] || {}; eventList.push(cb); } pub(name, ...data) { (this.eventMap[name] || []).forEach(cb => cb(...data)); } } class Child extends EventEimtter { constructor() { super(); // 通过消息接口发布消息 setTimeout(() => { this.pub('update') }, 2000); } } class Parent { constructor() { // 初始化阶段,传入回调函数 this.child = new Child(); // 订阅子组件的消息 this.child.sub('update', function () { console.log('child update') }); } }
Backbone.js就同時支援回呼函數和訊息介面方式,但React中選擇了比較簡單的回呼函數模式,下面來看一下React的例子
class Child extends Component { constructor(props) { setTimeout(() => { this.props.cb() }, 2000); } render() { return <p></p> } } class Parent extends Component { render() { return <Child cb={() => {console.log('update')}} /> } }
#父子組件其實可以算是爺爺組件的一種特例,這裡的爺爺組件不光指爺爺和孫子,而是泛指祖先與後代組件通信,可能隔著很多層級,我們已經解決了父子組件通信的問題,根據化歸法,很容易得出爺孫組件的答案,那就是層層傳遞屬性麼,把爺孫組件通信分解為多個父子元件通訊的問題
層層傳遞的優點是非常簡單,用已有知識就能解決,問題是會浪費很多程式碼,非常繁瑣,中間作為橋樑的元件會引入很多不屬於自己的屬性
在React中,透過context可以讓祖先組件直接把屬性傳遞到後代組件,有點類似星際旅行中的蟲洞一樣,透過context這個特殊的橋樑,可以跨越任意層次向後代元件傳遞訊息
怎麼在需要通訊的元件之間開啟這個蟲洞呢?需要雙向聲明,也就是在祖先組件聲明屬性,並在後代組件上再次聲明屬性,然後在祖先組件上放上屬性就可以了,就可以在後代組件讀取屬性了,下面看一個例子
import PropTypes from 'prop-types'; class Child extends Component { // 后代组件声明需要读取context上的数据 static contextTypes = { text: PropTypes.string } render() { // 通过this.context 读取context上的数据 return <p>{this.context.text}</p> } } class Ancestor extends Component { // 祖先组件声明需要放入context上的数据 static childContextTypes = { text: PropTypes.string } // 祖先组件往context放入数据 getChildContext() { return {text: 'yanhaijing'} } }
context的優點是可以省去層層傳遞的麻煩,並且透過雙向宣告控制了資料的可見性,對於層數很多時,不失為一種方案;但缺點也很明顯,就像全域變數一樣,如果不加節制很容易造成混亂,而且也容易出現重名覆蓋的問題
個人的建議是對一些所有組件共享的只讀信息可以採用context來傳遞,比如登錄的用戶信息等
小貼士:React Router路由就是透過context來傳遞路由屬性的
如果兩個元件是兄弟關係,可以透過父元件作為橋樑,來讓兩個元件之間通信,這其實就是主模組模式
下面的例子中,兩個子元件透過父元件來實現顯示數字同步的功能
class Parent extends Component { constructor() { this.onChange = function (num) { this.setState({num}) }.bind(this); } render() { return ( <p> <Child1 num={this.state.num} onChange={this.onChange}> <Child2 num={this.state.num} onChange={this.onChange}> </p> ); } }
主模組模式的優點就是解耦,把兩個子組件之間的耦合關係,解耦成子組件和父組件之間的耦合,把分散的東西收集在一起好處非常明顯,能帶來更好的可維護性和可擴展性
任意元件包含上面的三種關係元件,上面三種關係應該優先使用上面介紹的方法,對於任意的兩個元件間通信,總共有三種辦法,分別是共同祖先法,訊息中間件和狀態管理
基于我们上面介绍的爷孙组件和兄弟组件,只要找到两个组件的共同祖先,就可以将任意组件之间的通信,转化为任意组件和共同祖先之间的通信,这个方法的好处就是非常简单,已知知识就能搞定,缺点就是上面两种模式缺点的叠加,除了临时方案,不建议使用这种方法
另一种比较常用的方法是消息中间件,就是引入一个全局消息工具,两个组件通过这个全局工具进行通信,这样两个组件间的通信,就通过全局消息媒介完成了
还记得上面介绍的消息基类吗?下面的例子中,组件1和组件2通过全局event进行通信
class EventEimtter { constructor() { this.eventMap = {}; } sub(name, cb) { const eventList = this.eventMap[name] = this.eventMap[name] || {}; eventList.push(cb); } pub(name, ...data) { (this.eventMap[name] || []).forEach(cb => cb(...data)); } } // 全局消息工具 const event = new EventEimtter; // 一个组件 class Element1 extends Component { constructor() { // 订阅消息 event.sub('element2update', () => {console.log('element2 update')}); } } // 另一个组件。 class Element2 extends Component { constructor() { // 发布消息 setTimeout(function () { event.pub('element2update') }, 2000) } }
消息中间件的模式非常简单,利用了观察者模式,将两个组件之间的耦合解耦成了组件和消息中心+消息名称的耦合,但为了解耦却引入全局消息中心和消息名称,消息中心对组件的侵入性很强,和第三方组件通信不能使用这种方式
小型项目比较适合使用这种方式,但随着项目规模的扩大,达到中等项目以后,消息名字爆炸式增长,消息名字的维护成了棘手的问题,重名概率极大,没有人敢随便删除消息信息,消息的发布者找不到消息订阅者的信息等
其实上面的问题也不是没有解决办法,重名的问题可以通过制定规范,消息命名空间等方式来极大降低冲突,其他问题可以通过把消息名字统一维护到一个文件,通过对消息的中心化管理,可以让很多问题都很容易解决
如果你的项目非常大,上面两种方案都不合适,那你可能需要一个状态管理工具,通过状态管理工具把组件之间的关系,和关系的处理逻辑从组建中抽象出来,并集中化到统一的地方来处理,Redux就是一个非常不错的状态管理工具
除了Redux,还有Mobx,Rematch,reselect等工具,本文不展开介绍,有机会后面单独成文,这些都是用来解决不同问题的,只要根据自己的场景选择合适的工具就好。
以上是傳統組件間通訊與React組件間通訊的分析比較(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!