vue mixin vs HOC實例2

https://github.com/coolriver/coolriver-site/blob/master/markdown/vue-mixin-hoc.md

組件復用場景

什么時候會需要組件復用呢?空談很虛(Talk is cheap, show me the code!),直接給出實際場景:
有一個使用了vue-router和vuex的單面應用制圈。在N個(下面以兩個為例子)獨立頁面功能完成后葱峡,需要增加權限控制的功能谆膳。有的頁面需要特定的用戶權限才能進入责静,否則如果強行輸入url進入的話,會提示“沒有權限訪問本頁面”厕九。

場景介紹

下面是沒有權限控制時挣饥,系統(tǒng)主要的幾個代碼文件:

頁面入口: main.js

import Vue from 'vue';
import store from './store';
import router from './routes';
import App from './app';

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
});

頁面根組件: app.vue

<template>
  <div id="app">
    <div class="app-page" v-if="user.userLoaded">
      <div class="app-page-cnt">
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  name: 'app',
  data() {
    return {};
  },
  methods: {
    // vuex中的action除师,會從接口請求包含權限的用戶信息,并保存到store中的user字段
    // store中具體的代碼因為比較簡單扔枫,并且在這里不重要汛聚,所以就不展示了,可以自己腦補
    ...mapActions(['getUserDetail']),
  },
  computed: {
    // store中的user信息,在未從接口獲取返回之前短荐,為{ userLoaded: false }
    ...(mapState(['user'])),
  },
  created() {
    this.getUserDetail();
  }
};
</script>

路由配置: routes.js

import Router from 'vue-router';

// 以下是組件異步加載的寫法, 功能上等同于直接import
const Page1 = resolve => require(['./page1'], resolve);
const Page2 = resolve => require(['./page2'], resolve);

export default {
  routes: [
    { path: '/page1', component: Page1 },
    { path: '/page2', component: Page2 },
  ]
}

頁面組件: page1.vue, page2.vue

/**************** page1.vue ****************/
export default {
  template: `<div>歡迎訪問傳說中的 page1 !</div>`
}

/**************** page2.vue ****************/
export default {
  template: `<div>歡迎訪問傳說中的 page2 !</div>`
}

不考慮復用

在當前場景下倚舀,要對page1和page2兩個頁面添加權限控制,不考慮復用時可以這么粗暴地在page1.vue和page2.vue上進行如下改造來實現

頁面組件: page1.vue, page2.vue

/**************** page1.vue ****************/
export default {
  template: `
    <div v-if="hasRight">歡迎訪問傳說中的 page1 !</div>
    <div v-else>不好意思忍宋,由于不夠帥痕貌,你沒有權限訪問本頁面</div>
  `,
  computed: {
    hasRight() { // 判斷用戶是否有權限進入本頁面的計算屬性
      // 這里的user是之前在app中通過接口返回注入store的用戶信息
      const { rightList } = this.$store.state.user;
      return rightList.indexOf('RIGHT_PAGE_1');
    }
  }
}

/**************** page2.vue ****************/
export default {
  template: `
    <div v-if="hasRight">歡迎訪問傳說中的 page2 !</div>
    <div v-else>不好意思,由于不夠帥糠排,你沒有權限訪問本頁面</div>
  `,
  computed: {
    hasRight() { // 判斷用戶是否有權限進入本頁面的計算屬性
      // 這里的user是之前在app中通過接口返回注入store的用戶信息
      const { rightList } = this.$store.state.user;
      return rightList.indexOf('RIGHT_PAGE_2');
    }
  }
}

以上的方式舵稠,在只有兩個頁面的時候,可能不覺得麻煩。如果頁面多了之后柱查,就十分難維護了,同時會有大量的重復代碼云石。魯迅說過: 不要重復你自己(Do not repeat yourself)
[圖片上傳失敗...(image-56436b-1566719930294)]

為了利用權限控制的公共邏輯唉工,接下來我們先嘗試使用官方推薦的mixin方式來進行優(yōu)化。

使用mixin實現復用

在使用mixin前汹忠,先把那個分散在各種頁面組件中的無權限提示淋硝,提取到單獨的組件中以便復用 提取出錯誤提示組件: no-right-tips.vue

export default {
  template: `<div>不好意思,由于不夠帥宽菜,你沒有權限訪問本頁面</div>`,
  name: 'no-right-tips'
}

接來下我們創(chuàng)建一個用于權限控制的mixin, 目標是使頁面組件(page1, page2)不用關心權限校驗是如何運行的谣膳。在這個例子中,只需要把hasRight這個計算屬性提取到mixin中铅乡。
right-mixin.js

export default {
  computed: {
    hasRight() { // 判斷用戶是否有權限進入本頁面的計算屬性
      // 這里的user是之前在app中通過接口返回注入store的用戶信息
      const { rightList } = this.$store.state.user;
      return rightList.indexOf('RIGHT_PAGE_?'); // 注意這里继谚,無法確定各個頁面的權限標志
    }
  }
}

注意看上面代碼的最后一行注釋。我們希望把權限驗證放到mixin中阵幸,但問題是不同頁面所需要的權限是不一樣的啊花履,無法將RIGHT_PAGE_1之類的具體權限寫死在mixin中。怎么辦呢挚赊?機智的你應該可以想到诡壁,用函數包一層啊:
right-mixin.js

export default rightType => ({ // rightType作為參數傳入,返回特定mixin
  computed: {
    hasRight() { // 判斷用戶是否有權限進入本頁面的計算屬性
      // 這里的user是之前在app中通過接口返回注入store的用戶信息
      const { rightList } = this.$store.state.user;
      return rightList.indexOf(rightType); // 問題解決荠割,美滋滋
    }
  }
})

上面的所說的 用函數包一層妹卿,聽起來好low是吧?我們來給這種方式起個高逼格一點的名字吧蔑鹦,我們稱上面的方式為 高階mixin夺克。是不是瞬間聽起來不一樣了?
[圖片上傳失敗...(image-2da783-1566719930293)]
這個名字聽起來是不是和我們后面要講的 高階組件 如出一轍举反?
我們先不糾結名字了懊直,看看我們上面的方式如何在頁面組件中使用吧:

page1.vue, page2.vue

/**************** page1.vue ****************/
import NoRightTips from './no-right-tips';
import rightmixin from './right-mixin';

export default {
  mixin: [rightmixin('RIGHT_PAGE_1')],
  template: `
    <div v-if="hasRight">歡迎訪問傳說中的 page1 !</div>
    <no-right-tips v-else></no-right-tips>
  `,
  components: {
    NoRightTips
  }
}

/**************** page2.vue ****************/
import NoRightTips from './no-right-tips';
import rightmixin from './right-mixin';

export default {
  mixin: [rightmixin('RIGHT_PAGE_2')],
  template: `
    <div v-if="hasRight">歡迎訪問傳說中的 page2 !</div>
    <no-right-tips v-else></no-right-tips>
  `,
  components: {
    NoRightTips
  }
}

經過mixin改造和錯誤提示的組件提取之后,代碼看起來復用度提高了火鼻,職責也分明了∈夷遥現在頁面組件不用關心權限是怎么檢驗的,只用管從mixin提供的computed屬性中判斷檢驗結果魁索,并在沒有權限時直接展示公共的錯誤提示組件融撞。感覺不錯!

使用高階組件(HOC)實現復用

鋪墊了這么多粗蔚,終于要進入主題了尝偎!在使用高階組件之前,先簡單描述一下它。我們上面起了一個高逼格的名字: 高階mixin致扯,用來表示被函數包了一層的普通mixin肤寝。是有一定依據的。

在數學和計算機科學中抖僵,高階函數是至少滿足下列一個條件的函數:

  • 接受一個或多個函數作為輸入
  • 輸出一個函數
  • 再看看關于React中HOC的定義
  • 老版定義(原始內容找不到鲤看,只能從以前的博文中查證一二): Higher-Order Components (HOCs) are JavaScript functions which add functionality to existing component classes.
  • 新版定義: a higher-order component is a function that takes a component and returns a new component.

根據上面的定義,我們可以引申為:通過函數向現有XXX添加功能耍群,就是高階XXX义桂。在上面mixin的例子中,通用函數蹈垢,給普通mixin提供了可配置的權限檢測參數慷吊,所以可稱之為高階mixin。

到這里曹抬,其實高階函數的定義已經在上面帶出來了溉瓶。根據react官方文檔里最新的定義: 高階組件是一個方法,這個方法接收一個原始組件作為參數谤民,并返回新的組件嚷闭。我想這個命名和定義應該也是參照高階函數的吧。

大家可能覺得奇怪赖临,為什么我要用react官方里的定義來說明高階組件呢胞锰?是因為高階組件最開始就是在react中提出來的。關于高階組件的歷史兢榨,我們可以后面再討論嗅榕。不如先看一下,如何使用高階組件來實現上面場景中的組件復用功能吵聪。

我們創(chuàng)建如下高階組件:
right-hoc.js

import NoRightTips from './no-right-tips';

export default (Comp, rightType) => ({
  components: {
    Comp,
    NoRightTips,
  },
  computed: {
    hasRight() {
      const { rightList } = this.$store.state.user;
      return rightList.indexOf(rightType);
    }
  },
  render(h) {
    return this.hasRight ? h(Comp, {}) : h(NoRightTips, {});
  }
})

接下來去掉頁面組件中已經提取到高階組件中的部分邏輯:
page1.vue, page2.vue

/**************** page1.vue ****************/
export default {
  template: `<div>歡迎訪問傳說中的 page1 !</div>`,
}

/**************** page2.vue ****************/
export default {
  template: `<div>歡迎訪問傳說中的 page2 !</div>`,
}

發(fā)現沒有凌那?所有的權限相關代碼都抽出來了!組件回歸到的之前沒有權限功能時的樣子吟逝,是不是很清爽帽蝶。那hoc在哪里與組件結合起來呢?答案是在routes里块攒,在使用組件的地方:
路由配置: routes.js

import Router from 'vue-router';
import rightHoc from './right-hoc';

// 以下是組件異步加載的寫法, 功能上等同于直接import
const Page1 = resolve => require(['./page1'], resolve);
const Page2 = resolve => require(['./page2'], resolve);

export default {
  routes: [
    { path: '/page1', component: rightHoc(Page1, 'RIGHT_PAGE_1') },
    { path: '/page2', component: rightHoc(Page2, 'RIGHT_PAGE_2') },
  ]
}

使用高階組件同樣實現了組件復用励稳。而且看起來似乎更優(yōu)雅?我們來對比一下高階組件和mixin兩種方式囱井,在以上場景中的區(qū)別:
HOC

  • 增加了一個hoc文件驹尼, hoc文件中引入no-right-tips
  • 路由配置中,使用頁面組件的地方引入并使用了hoc

mixin:

  • 增加了一個mixin文件
  • 每個組件代碼中庞呕,引入mixin新翎、no-right-tips, 并且增加相應的模板邏輯(v-if)

我認為程帕,在本文的場景中,使用HOC相比使用mixin有以下優(yōu)勢:

  1. 減少對原始組件的入侵地啰,降低耦合愁拭。HOC中,原始組件只用考慮自身邏輯亏吝,不用考慮敛苇,也感知不到HOC對它做了什么。而mixin顺呕,組件在內部需要使用mixin的計算屬性(更復雜的mixin還會用到生命周期和methods方法).
  2. 權限控制方便集中管理,直接在routes配置中管理各個頁面配置括饶,而不是分散在各個頁面組件內部株茶。
  3. 避免命名沖突。如果頁面自己有自己內部的權限控制图焰,剛好也有個computed屬性叫hasRight呢启盛?在HOC下沒問題,但mixin就不行了技羔。

React中的HOC現狀

其實最早在React中僵闯,也是使用mixin來實現組件功能復用的,但從v0.13.0開始藤滥,React的ES6 class組件寫法中就不支持mixin了鳖粟。這應該算是比較大的特性調整了。在此之后拙绊,已經使用了React的項目向图,可以繼續(xù)使用React.createClass定義組件的方式來繼續(xù)使用mixin,如果要使用ES6 class并且實現同樣的組件復用标沪,就必須使用HOC了榄攀。
React為什么做了這個決定呢?人家不是沒事搞事情金句,而是有原因的檩赢。官方博客專門發(fā)文列舉mixin可能帶來的一些問題: mixin Considered Harmful。這篇文章里結合實際例子列舉了mixin在React中可能帶來的幾個問題违寞,并且給出了mixin遷移到HOC的一些指導贞瞒。原文是英文,并且篇幅較長趁曼,所以這里簡單地把文章里提到的mixin可能帶來的幾個問題列舉一下:

  1. mixin會導致依賴不明確
    mixin會調用組件內部方法/數據憔狞,組件會調用mixin方法/數據, 無法保證雙方方法穩(wěn)定存在.
    多個mixin同時作用時,依賴關系對于被mixin的組件來說會更困惑

  2. mixin會導致命名沖突
    多個mixin和組件本身彰阴,方法名稱會有命名沖突風險瘾敢,如果遇到了,不得不重命名某些方法

  3. mixin會帶來滾雪球般的復雜度
    原文中列舉了一個復雜的mixin例子,我沒看懂簇抵。庆杜。。碟摆。

也就是說晃财,現在React體系中mixin已經不推薦使用,而推薦使用HOC典蜕。下圖是《深入React技術椂鲜ⅲ》一書中關于mixin和HOC的對比

Vue中的HOC現狀

相比于React,Vue目前還是使用mixin作為官方的組件復用方式愉舔。我在探索Vue中HOC的時候钢猛,發(fā)現很少有相關描述和實踐和文章。在百度里搜不出來轩缤,在google里也只能搜出寥寥幾個命迈。在我找到的資源中,有一個vuejs的github issue十分有代表價值: Discussion: Best way to create a HOC

在上面的issue討論中火的,我很高興有相同的志士也在想Vue中如何使用HOC壶愤。雖然我上面的例子簡單地實現了HOC,但是實際的場景可能更復雜馏鹤,涉及屬性傳遞征椒,slots等問題。而上面的issue就是在討論這個問題湃累。目前這個issue已經關閉陕靠,結論有兩個:

  1. 暫時由熱心人士產出了一個npm包: vue-hoc來幫助Vue方便地實現HOC.
  2. 官方暫時不考慮將HOC加入vue core中,因為覺得相比于mixin的優(yōu)勢不夠巨大脱茉。

后話

HOC在React被認為是更好的mixin替代方式剪芥。最初HOC也是在React社區(qū)中產生的,然后由官方進行采納和推廣琴许。在Vue中税肪,我不清楚是因為沒人想到這個問題還是什么,HOC很少有人關注榜田。所以我寫了這篇文章益兄,做了自己的HOC實踐,感覺效果不錯箭券。同時净捅,我也在知乎了提了相關的問題: 為何在React中推薦使用HOC,而不是mixins來實現組件復用辩块。但在Vue中蛔六,很少有HOC的嘗試荆永?,希望有大神能解答国章。

關于以上場景為什么不用router鉤子來做統(tǒng)一權限控制的補充說明

  1. 為什么不用beforeEach全局路由勾子來檢驗權限具钥? 因為,包含權限的用戶信息是在app.vue中異步加載液兽,并且存儲到vuex store中的骂删。在beforeEach函數中沒有找到可以訪問store中異步填入的數據的方法。要做的話四啰,只能在beforeEach函數里宁玫,將next方法放在獲取用戶信息的ajax回調里,以實現等待用戶信息加載完畢再判斷路由是否有權限進入的效果柑晒。如果真這樣做了欧瘪,在每次點擊鏈接導航至其它路由前,豈不是都要執(zhí)行ajax請求了敦迄?當然,可以做用戶信息數據緩存凭迹,但這樣就把事情變更復雜了不是嗎罚屋?而且,在ajax數據到達前嗅绸,路由下控制的頁面組件是完全阻塞住的脾猛,想展示Loading態(tài)都不行。

  2. 為什么要把用戶信息放在vuex store中鱼鸠? 因為除了權限檢驗需要用到用戶信息之外猛拴,實際在其它模塊組件(header模塊,和側邊欄菜單模塊)中也需要用于用戶信息蚀狰。統(tǒng)一在app.vue的mounted中請求并保存在vuex store中愉昆,可以方便地提供給各組件進行用戶數據共享復用。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末麻蹋,一起剝皮案震驚了整個濱河市跛溉,隨后出現的幾起案子,更是在濱河造成了極大的恐慌扮授,老刑警劉巖芳室,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異刹勃,居然都是意外死亡堪侯,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門荔仁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伍宦,“玉大人芽死,你說我怎么就攤上這事”⒅簦” “怎么了收奔?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滓玖。 經常有香客問我坪哄,道長,這世上最難降的妖魔是什么势篡? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任翩肌,我火速辦了婚禮,結果婚禮上禁悠,老公的妹妹穿的比我還像新娘传泊。我一直安慰自己,他們只是感情好就漾,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布店印。 她就那樣靜靜地躺著,像睡著了一般瓷产。 火紅的嫁衣襯著肌膚如雪站玄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天濒旦,我揣著相機與錄音株旷,去河邊找鬼。 笑死尔邓,一個胖子當著我的面吹牛晾剖,可吹牛的內容都是我干的。 我是一名探鬼主播梯嗽,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼齿尽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灯节?” 一聲冷哼從身側響起雕什,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎显晶,沒想到半個月后贷岸,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡磷雇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年偿警,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唯笙。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡螟蒸,死狀恐怖盒使,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情七嫌,我是刑警寧澤少办,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站诵原,受9級特大地震影響英妓,放射性物質發(fā)生泄漏。R本人自食惡果不足惜绍赛,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一蔓纠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吗蚌,春花似錦腿倚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至箩言,卻和暖如春硬贯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背分扎。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工澄成, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胧洒,地道東北人畏吓。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像卫漫,于是被迫代替她去往敵國和親菲饼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內容

  • 探索Vue高階組件 高階組件(HOC)是 React 生態(tài)系統(tǒng)的常用詞匯列赎,React 中代碼復用的主要方式就是使用...
    君惜丶閱讀 980評論 0 2
  • 在項目里宏悦,我們經常會使用組件庫進行快速開發(fā),然而在過程中包吝,又難免會遇到對組件庫的改造和拓展饼煞,如何優(yōu)雅且簡單的進行重...
    videring閱讀 661評論 0 0
  • 今天的React題沒有太多的故事…… 半個月前出了248個Vue的知識點,受到很多朋友的關注诗越,都強烈要求再出多些R...
    浪子神劍閱讀 10,089評論 6 106
  • 在多個不同的組件中需要用到相同的功能砖瞧,其解決辦法有兩種:mixin和高階組件。 1嚷狞、mixin mixin一直被廣...
    南風知我意ZD閱讀 8,172評論 1 5
  • 本文為2016年11月9日块促,『前端之巔』微信群在線分享活動總結整理而成荣堰,轉載請在文章開頭處注明來自『前端之巔』公眾...
    尾尾閱讀 10,610評論 3 32