摘要:但非常不幸,并不原生支持。這個單詞相信都很熟悉,高階函數(shù)在函數(shù)式編程是一個基本概念,它描述的是這樣一種函數(shù),接受函數(shù)作為輸入,或是輸出一個函數(shù)。比如常用的工具方法都是高階函數(shù)。恰與的定義完全一致。這種不同很可能會導(dǎo)致問題的產(chǎn)生。
在 React component 構(gòu)建過程中,常常有這樣的場景,有一類功能要被不同的 Component 公用,然后看得到文檔經(jīng)常提到 Mixin(混入) 這個術(shù)語。此文就從 Mixin 的來源、含義、在 React 中的使用說起。
使用 Mixin 的緣由Mixin 的特性一直廣泛存在于各種面向?qū)ο笳Z言。尤其在腳本語言中大都有原生支持,比如 Perl、Ruby、Python,甚至連 Sass 也支持。先來看一個在 Ruby 中使用 Mixin 的簡單例子,
module D def initialize(name) @name = name end def to_s @name end end module Debug include D def who_am_i? "#{self.class.name} (##{self.object_id}): #{self.to_s}" end end class Phonograph include Debug # ... end class EightTrack include Debug # ... end ph = Phonograph.new("West End Blues") et = EightTrack.new("Real Pillow") puts ph.who_am_i? # Phonograph (#-72640448): West End Blues puts et.who_am_i? # EightTrack (#-72640468): Real Pillow
在 ruby 中 include 關(guān)鍵詞即是 mixin,是將一個模塊混入到一個另一個模塊中,或是一個類中。為什么編程語言要引入這樣一種特性呢?事實上,包括 C++ 等一些年齡較大的 OOP 語言,有一個強大但危險的多重繼承特性。現(xiàn)代語言為了權(quán)衡利弊,大都舍棄了多重繼承,只采用單繼承。但單繼承在實現(xiàn)抽象時有著諸多不便之處,為了彌補缺失,如 Java 就引入 interface,其它一些語言引入了像 Mixin 的技巧,方法不同,但都是為創(chuàng)造一種 類似多重繼承 的效果,事實上說它是 組合 更為貼切。
在 ES 歷史中,并沒有嚴(yán)格的類實現(xiàn),早期 YUI、MooTools 這些類庫中都有自己封裝類實現(xiàn),并引入 Mixin 混用模塊的方法。到今天 ES6 引入 class 語法,各種類庫也在向標(biāo)準(zhǔn)化靠攏。
封裝一個 Mixin 方法看到這里,我們既然知道了廣義的 mixin 方法的作用,那不妨試試自己封裝一個 mixin 方法來感受下。
const mixin = function(obj, mixins) { const newObj = obj; newObj.prototype = Object.create(obj.prototype); for (let prop in mixins) { if (mixins.hasOwnProperty(prop)) { newObj.prototype[prop] = mixins[prop]; } } return newObj; } const BigMixin = { fly: () => { console.log("I can fly"); } }; const Big = function() { console.log("new big"); }; const FlyBig = mixin(Big, BigMixin); const flyBig = new FlyBig(); // "new big" flyBig.fly(); // "I can fly"
對于廣義的 mixin 方法,就是用賦值的方式將 mixins 對象里的方法都掛載到原對象上,就實現(xiàn)了對對象的混入。
是否看到上述實現(xiàn)會聯(lián)想到 underscore 中的 extend 或 lodash 中的 assign 方法,或者說在 ES6 中一個方法 Object.assign()。它的作用是什么呢,MDN 上的解釋是把任意多個的源對象所擁有的自身可枚舉屬性拷貝給目標(biāo)對象,然后返回目標(biāo)對象。
因為 JS 這門語言的特別,在沒有提到 ES6 Classes 之前沒有真正的類,僅是用方法去模擬對象,new 方法即為創(chuàng)建一個實例。正因為這樣地弱,它也那樣的靈活,上述 mixin 的過程就像對象拷貝一樣。
那問題是 React component 中的 mixin 也是這樣的嗎?
React createClassReact 最主流構(gòu)建 Component 的方法是利用 createClass 創(chuàng)建。顧名思義,就是創(chuàng)造一個包含 React 方法 Class 類。這種實現(xiàn),官方提供了非常有用的 mixin 屬性。我們就先來看看它來做 mixin 的方式是怎樣的。
import React from "react"; import PureRenderMixin from "react-addons-pure-render-mixin"; React.createClass({ mixins: [PureRenderMixin], render() { returnfoo; } });
以官方封裝的 PureRenderMixin 來舉例,在 createClass 對象參數(shù)中傳入一個 mixins 的數(shù)組,里面封裝了我們所需要的模塊。mixins 也可以增加多個重用模塊,使用多個模塊,方法之間的有重合會對普通方法和生命周期方法有所區(qū)分。
在不同的 mixin 里實現(xiàn)兩個名字一樣的普通方法,在常規(guī)實現(xiàn)中,后面的方法應(yīng)該會覆蓋前面的方法。那在 React 中是否一樣會覆蓋呢。事實上,它并不會覆蓋,而是在控制臺里報了一個在 ReactClassInterface 里的 Error,說你在嘗試定義一個某方法在 component 中多于一次,這會造成沖突。因此,在 React 中是不允許出現(xiàn)重名普通方法的 Mixin。
如果是 React 生命周期定義的方法呢,是會將各個模塊的生命周期方法疊加在一起,順序執(zhí)行。
因為,我們看到 createClass 實現(xiàn)的 mixin 為 Component 做了兩件事:
工具方法
這是 mixin 的基本功能,如果你想共享一些工具類方法,就可以定義它們,直接在各個 Component 中使用。
生命周期繼承,props 與 state 合并
這是 react mixin 特別也是重要的功能,它能夠合并生命周期方法。如果有很多 mixin 來定義 componentDidMount 這個周期,那 React 會非常智能的將它們都合并起來執(zhí)行。
同樣地,mixins 也可以作用在 getInitialState 的結(jié)果上,作 state 的合并,同時 props 也是這樣合并。
未來的 React Classes當(dāng) ECMAScript 發(fā)展到今天,這已經(jīng)是一個百家爭鳴的時代,各種優(yōu)異的語言特性都出現(xiàn)在 ES6 和 ES7 的草案中。
React 在發(fā)展過程中一直崇尚擁抱標(biāo)準(zhǔn),盡管它自己看上去是一個異類。當(dāng) React 0.13 釋出的時候,React 增加并推薦使用 ES6 Classes 來構(gòu)建 Component。但非常不幸,ES6 Classes 并不原生支持 mixin。盡管 React 文檔中也未能給出解決方法,但如此重要的特性沒有解決方案,也是一件十分困擾的事。
為了可以用這個強大的功能,還得想想其它方法,來尋找可能的方法來實現(xiàn)重用模塊的目的。先回歸 ES6 Classes,我們來想想怎么封裝 mixin。
讓 ES6 Class 與 Decorator 跳舞要在 Class 上封裝 mixin,就要說到 Class 的本質(zhì)。ES6 沒有改變 JavaScript 面向?qū)ο蠓椒ɑ谠偷谋举|(zhì),不過在此之上提供了一些語法糖,Class 就是其中之一,換湯不換藥。
對于 Class 具體用法可以參考 MDN。目前 Class 僅是提供一些基本寫法與功能,隨著標(biāo)準(zhǔn)化的進(jìn)展,相信會有更多的功能加入。
那對于實現(xiàn) mixin 方法來說就沒什么不一樣了。但既然講到了語法糖,就來講講另一個語法糖 Decorator,正巧可以來實現(xiàn) Class 上的 mixin。
Decorator 在 ES7 中定義的新特性,與 Java 中的 pre-defined Annotations 相似。但與 Java 的 annotations 不同的是 decorators 是被運用在運行時的方法。在 Redux 或其他一些應(yīng)用層框架中漸漸用 decorator 實現(xiàn)對 component 的『修飾』。現(xiàn)在,我們來用 decorator 來現(xiàn)實 mixin。
core-decorators.js 為開發(fā)者提供了一些實用的 decorator,其中實現(xiàn)了我們正想要的 @minxin。我們來解讀一下核心實現(xiàn)。
import { getOwnPropertyDescriptors } from "./private/utils"; const { defineProperty } = Object; function handleClass(target, mixins) { if (!mixins.length) { throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`); } for (let i = 0, l = mixins.length; i < l; i++) { // 獲取 mixins 的 attributes 對象 const descs = getOwnPropertyDescriptors(mixins[i]); // 批量定義 mixin 的 attributes 對象 for (const key in descs) { if (!(key in target.prototype)) { defineProperty(target.prototype, key, descs[key]); } } } } export default function mixin(...mixins) { if (typeof mixins[0] === "function") { return handleClass(mixins[0], []); } else { return target => { return handleClass(target, mixins); }; } }
它實現(xiàn)部分的源代碼十分簡單,它將每一個 mixin 對象的方法都疊加到 target 對象的原型上以達(dá)到 mixin 的目的。這樣,就可以用 @mixin 來做多個重用模塊的疊加了。
import React, { Component } from "React"; import { mixin } from "core-decorators"; const PureRender = { shouldComponentUpdate() {} }; const Theme = { setTheme() {} }; @mixin(PureRender, Theme) class MyComponent extends Component { render() {} }
細(xì)心的讀者有沒有發(fā)現(xiàn)這個 mixin 與 createClass 上的 mixin 有區(qū)別。上述實現(xiàn) mixin 的邏輯和最早實現(xiàn)的簡單邏輯是很相似的,之前直接給對象的 prototype 屬性賦值,但這里用了 getOwnPropertyDescriptor 和 defineProperty 這兩個方法,有什么區(qū)別呢?
事實上,這樣實現(xiàn)的好處在于 defineProperty 這個方法,也是定義與賦值的區(qū)別,定義則是對已有的定義,賦值則是覆蓋已有的定義。所以說前者并不會覆蓋已有方法,后者是會的。本質(zhì)上與官方的 mixin 方法都很不一樣,除了定義方法級別的不能覆蓋之外,還得加上對生命周期方法的繼承,以及對 State 的合并。
再回到 decorator 身上,上述只是作用在類上的方法,還有作用在方法上的,它可以控制方法的自有屬性,也可以作 decorator 工廠方法。在其它語言里,decorator 用途廣泛,具體擴展不在本文討論的范圍。
講到這里,對于 React 來說我們自然可以用上述方法來做 mixin。但 React 開發(fā)社區(qū)提出了『全新』的方式來取代 mixin,那就是 Higher-Order Components。
Higher-Order Components(HOCs)Higher-Order Components(HOCs)最早由 Sebastian Markb?ge(React 核心開發(fā)成員)在 gist 提出的一段代碼。
Higher-Order 這個單詞相信都很熟悉,Higher-Order function(高階函數(shù))在函數(shù)式編程是一個基本概念,它描述的是這樣一種函數(shù),接受函數(shù)作為輸入,或是輸出一個函數(shù)。比如常用的工具方法 map、reduce、sort 都是高階函數(shù)。
而 HOCs 就很好理解了,將 Function 替代成 Component 就是所謂的高階組件。如果說 mixin 是面向 OOP 的組合,那 HOCs 就是面向 FP 的組合。先看一個 HOC 的例子,
import React, { Component } from "React"; const PopupContainer = (Wrapper) => class WrapperComponent extends Component { componentDidMount() { console.log("HOC did mount") } componentWillUnmount() { console.log("HOC will unmount") } render() { return; } }
上面例子中的 PopupContainer 方法就是一個 HOC,返回一個 React Component。值得注意的是 HOC 返回的總是新的 React Component。要使用上述的 HOC,那可以這么寫。
import React, { Component } from "React"; class MyComponent extends Component { render() {} } export default PopupContainer(MyStatelessComponent);
封裝的 HOC 就可以一層層地嵌套,這個組件就有了嵌套方法的功能。對,就這么簡單,保持了封裝性的同時也保留了易用性。我們剛才講到了 decorator,也可以用它轉(zhuǎn)換。
import React, { Component } from "React"; @PopupContainer class MyComponent extends Component { render() {} } export default MyComponent;
簡單地替換成作用在類上的 decorator,理解起來就是接收需要裝飾的類為參數(shù),返回一個新的內(nèi)部類。恰與 HOCs 的定義完全一致。所以,可以認(rèn)為作用在類上的 decorator 語法糖簡化了高階組件的調(diào)用。
如果有很多個 HOC 呢,形如 f(g(h(x)))。要不很多嵌套,要不寫成 decorator 疊羅漢。再看一下它,有沒有想到 FP 里的方法?
import React, { Component } from "React"; // 來自 https://gist.github.com/jmurzy/f5b339d6d4b694dc36dd let as = T => (...traits) => traits.reverse().reduce((T, M) => M(T), T); class MyComponent extends as(Component)(Mixin1, Mixin2, Mixin3(param)) { }
絕妙的方法!或用更好理解的 compose 來做
import React, { Component } from "React"; import R from "ramda"; const mixins = R.compose(Mixin3(param), Mixin2, Mixin1); class MyComponent extends mixins(Component) {}
講完了用法,這種 HOC 有什么特殊之處呢,
從侵入 class 到與 class 解耦,React 一直推崇的聲明式編程優(yōu)于命令式編程,而 HOCs 恰是。
調(diào)用順序不同于 React Mixin,上述執(zhí)行生命周期的過程類似于 堆棧調(diào)用: didmount -> HOC didmount -> (HOCs didmount) -> (HOCs will unmount) -> HOC will unmount -> unmount;
HOCs 對于主 Component 來說是 隔離 的,this 變量不能傳遞,以至于不能傳遞方法,包括 ref。但可以用 context 來傳遞全局參數(shù),一般不推薦這么做,很可能會造成開發(fā)上的困擾。
當(dāng)然,HOCs 不僅是上述這一種方法,我們還可以利用 Class 繼承 來寫,再來一個例子,
const PopupContainer = (Wrapper) => class WrapperComponent extends Wrapper { static propTypes = Object.assign({}, Component.propTypes, { foo: React.PropTypes.string, }); componentDidMount() { super.componentDidMount && super.componentDidMount(); console.log("HOC did mount") } componentWillUnmount() { super.componentWillUnmount && super.componentWillUnmount(); console.log("HOC will unmount") } }
其實,這種方法與第一種構(gòu)造是完全不一樣的。區(qū)別在哪,仔細(xì)看 Wrapper 的位置處在了繼承的位置。這種方法則要通用得多,它通過繼承原 Component 來做,方法都是可以通過 super 來順序調(diào)用。因為依賴于繼承的機制,HOC 的調(diào)用順序和 隊列 是一樣的。
didmount -> HOC didmount -> (HOCs didmount) -> will unmount -> HOC will unmount -> (HOC will unmount)
細(xì)心的你是否已經(jīng)看出 HOCs 與 React Mixin 的順序是反向的,很簡單,將 super 執(zhí)行放在后面就可以達(dá)到正向的目的,盡管看上去很怪。這種不同很可能會導(dǎo)致問題的產(chǎn)生。盡管它是未來可能的選項,但現(xiàn)在看還有不少問題。
總結(jié)未來的 React 中 mixin 方案 已經(jīng)有偽代碼現(xiàn)實,還是利用繼承特性來做。
而繼承并不是 "React Way",Sebastian Markb?ge 認(rèn)為實現(xiàn)更方便地 Compsition(組合)比做一個抽象的 mixin 更重要。而且聚焦在更容易的組合上,我們才可以擺脫掉 "mixin"。
對于『重用』,可以從語言層面上去說,都是為了可以更好的實現(xiàn)抽象,實現(xiàn)的靈活性與寫法也存在一個平衡。在 React 未來的發(fā)展中,期待有更好的方案出現(xiàn),同樣期待 ES 未來的草案中有增加 Mixin 的方案。就今天來說,怎么去實現(xiàn)一個不復(fù)雜又好用的 mixin 是我們思考的內(nèi)容。
資源Decorators in ES7
The Provider and Higher-Order Component patterns with React
Mixins Are Dead. Long Live Composition
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/86249.html
摘要:主要講述我一步一步優(yōu)化性能的過程。。才能將的性能發(fā)揮到極致要是各位看官用過一段時間的,而沒有用那么本文非常適合你。那么多么浪費性能,好。。下一篇我講寫,,如何用 一行代碼勝過千言萬語。。這篇文章呢。。主要講述我一步一步優(yōu)化react性能的過程。。為啥要用immutable.js呢。毫不夸張的說。有了immutable.js(當(dāng)然也有其他實現(xiàn)庫)。。才能將react的性能發(fā)揮到極致!要是...
摘要:前端日報精選十問幫你理清前端工程師及大前端團隊的成長問題譯讀完細(xì)則之后學(xué)到的件事掘金怎么寫一個組件庫一眾成翻譯還有這操作一個能生成思維導(dǎo)圖的開源搜索引擎知乎專欄中文前端推薦第天值得收藏的基礎(chǔ)教程知乎專欄第期沒有的一天轉(zhuǎn)載中回調(diào)地 2017-06-15 前端日報 精選 十問sofish:幫你理清前端工程師及大前端團隊的成長問題![譯] 讀完 flexbox 細(xì)則之后學(xué)到的 11 件事 -...
摘要:更新了個版本,最新正式版是語言的下一代標(biāo)準(zhǔn),早已在年月正式發(fā)布。基本不支持移動端瀏覽器對的支持情況版起便可以支持的新特性。比較通用的工具方案有,,,等。 1、ECMAScript是什么? 和 JavaScript 有著怎樣的關(guān)系? 1996 年 11 月,Netscape 創(chuàng)造了javascript并將其提交給了標(biāo)準(zhǔn)化組織 ECMA,次年,ECMA 發(fā)布 262 號標(biāo)準(zhǔn)文件(ECMA-...
摘要:在業(yè)務(wù)統(tǒng)一的情況下,僅僅修改組件用于配置的就可以滿足業(yè)務(wù)需求。避免了復(fù)雜的圖表配置,而將圖表進(jìn)行有效拆分,通過聲明式的標(biāo)簽進(jìn)行組合,從而使圖表更具擴展性。而對于顆粒度最細(xì)的組件,我們希望它是純粹的,木偶式的組件。 在前端業(yè)務(wù)開發(fā)中,組件化已經(jīng)成為我們的共識。沉淀和復(fù)用組件,是提高開發(fā)效率的利器。但在組件復(fù)用的過程中,我們往往會遇到這樣的問題,組件相似,卻在結(jié)構(gòu)或交互上有些許差別,需要對...
摘要:前言我是,如果你還不認(rèn)識我,不妨先看看技術(shù)的前世今生一平靜的生活已經(jīng)有一段日子了。傳送門技術(shù)的前世今生一技術(shù)的前世今生三 前言:我是JavaScript,如果你還不認(rèn)識我,不妨先看看《Web技術(shù)的前世今生(一)》 平靜的生活已經(jīng)有一段日子了。 這一天,HTML大哥面露不悅地走過來問我: Js,你是打算和我們分家嗎? 大哥,您這說的哪里話,我什么地方做的不對么?我一臉茫然地回答道。 哼,...
閱讀 2576·2023-04-25 17:33
閱讀 653·2021-11-23 09:51
閱讀 2961·2021-07-30 15:32
閱讀 1408·2019-08-29 18:40
閱讀 1951·2019-08-28 18:19
閱讀 1472·2019-08-26 13:48
閱讀 2247·2019-08-23 16:48
閱讀 2281·2019-08-23 15:56