對於不少初級的前端工程師對mixins的概念並非很瞭解,也沒有在React中嘗試使用過Mixins,這邊文章基本會按照Mixins的做用、用途、原理等多個方面介紹React中Mixins的使用。
首先解釋一下什麼是Mixins,在一些大型項目中常常會存在多個組件須要使用相同的功能的狀況,若是在每一個組件中都重複性的加入相同的代碼,那麼代碼的維護性將會變的很是差,Mixins的出現就是爲了解決這個問題。能夠將通用共享的方法包裝成Mixins方法,而後注入各個組件實現,咱們首先給出一個Mixins簡單的例子:javascript
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 manMixins = { speak: function (){ console.log("I'm "+this.name); } }; const Man = function() { this.name = 'wang'; }; const manCanSpeak = mixin(Man,manMixins); const man = new manCanSpeak(); man.speak(); //'I'm wang'
上述代碼就實現了一個簡單的mixin
函數,其實質就是將mixins
中的方法遍歷賦值給newObj.prototype
,從而實現mixin
返回的函數建立的對象都有mixins
中的方法。在咱們大體明白了mixin
做用後,讓咱們來看看如何在React使用mixin
。前端
假設咱們全部的React組件的props
中都含有一個默認的displayName
,在使用React.createClass
時,咱們必須給每一個組件中都加入java
getDefaultProps: function () { return {displayName: "component"}; }
固然咱們,咱們經過實現一個mixin
函數,就能夠實現這個功能,而且在createClass
方法使用mixin
很是簡單:react
var mixinDefaultProps = { getDefaultProps: function(){ return {displayName: 'component'} } } var ExampleComponent = React.createClass({ mixins: [mixinDefaultProps], render: function(){ return <div>{this.props.displayName}</div> } });
這樣咱們就實現了一個最簡單的mixin
函數,經過給每個組件配置mixin
,咱們就實現了不一樣組件之間共享相同的方法。須要注意的是:git
組件中含有多個mixin
,不一樣的mixin
中含有相同名字的非生命週期函數,React會拋出異常(不是後面的函數覆蓋前面的函數)。github
組件中含有多個mixin
,不一樣的mixin
中含有相同名字的生命週期函數,不會拋出異常,mixin
中的相同的生命週期函數(除render
方法)會按照createClass
中傳入的mixins
數組順序依次調用,所有調用結束後再調用組件內部的相同的聲明周期函數。npm
組件中含有多個mixin
,不一樣的mixin
中的默認props
或初始state
中不存在相同的key值時,則默認props
和初始state
都會被合併。數組
組件中含有多個mixin
,不一樣的mixin
中默認props
或初始state
中存在相同的key值時,React會拋出異常。性能優化
目前幾乎不多有人會使用React.createClass
的方式使用React,JSX + ES6成了標配,可是JavaScript在ES6以前是原生不支持的mixin
的,ES7引入了decorator,首先介紹一下decorator究竟是什麼?babel
ES7的Decorator語法相似於Python中的Decorator,在ES7中也僅僅只是一個語法糖,@decorator主要有兩種,一種是面向於類(class)的@decorator,另外一種是面向於方法(function)的@decorator。而且@decorator實質是利用了ES5中的Object.defineProperty
。
Object.defineProperty
關於Object.defineProperty
不是很瞭解的同窗其實很是推薦看一下《JavaScript高級程序設計》的第六章第一節,大概總結一下:在ES5中對象的屬性其實分爲兩種: 數據屬性和訪問器屬性
數據屬性有四個特性:
configurable
: 屬性是否可刪除、從新定義
enumerable
: 屬性是否可枚舉
writable
: 屬性值是否可修改
value
: 屬性值
configurable
: 屬性是否可刪除、從新定義
enumerable
: 屬性是否可枚舉
get
: 讀取屬性調用
set
: 設置屬性調用
Object.defineProperty(obj, prop, descriptor)
的三個參數是定義屬性的對象、屬性名和描述符,描述符自己也是Object,其中的屬性就是數據屬性或者訪問器屬性規定的參數,舉個栗子:
var person = {}; Object.defineProperty(person,'name',{ configurable: true, enumerable: true, writable: true, value: 'wang' }); console.log(person.name);//wang
瞭解了Object.defineProperty
,咱們分別看下面向於類(class)的@decorator和麪向於方法(function)的@decorator。
class語法其實僅僅只是ES6的一個語法糖而已,class其實質是function。而且class中的內部方法會經過Object.defineProperty
定義到function.prototype,例如:
class Person { speak () { console.log('I am Person!') } }
會被Babel轉成:
function Person(){} Object.defineProperty(Person.prototype,'speak',{ value: function () { 'I am Person!' }, enumerable: false, configurable: true, writable: true })
Decorator函數接受的參數與Object.defineProperty
相似,與對類(class)的方法使用@decorator,接受到的方法分別是類的prototype,內部方法名和描述符,@decorator會在調用Object.defineProperty
前劫持,先調用Decorator函數,將返回的descriptor定義到類的prototype上。
例如:
function readonly(target, key, descriptor) { //能夠經過修改descriptor參數實現各類功能 descriptor.writable = false return descriptor } class Person { @readonly speak () { return 'I am Person!' } } const person = new Person(); person.speak = ()=>{ console.log('I am human') }
當咱們對一個class使用@decorator時,接受到的參數target是類自己。例如:
function name (target) { target.name = 'wang' } @name class Person {} console.log(Dog.name) //'wang'
講完了@decorator,如今讓咱們回到JSX中,react-mixin和 core-decorators兩個庫都提供了mixin函數可用。大體讓咱們看一下core-decorators庫中mixin的大體代碼:
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++) { const descs = getOwnPropertyDescriptors(mixins[i]); const keys = getOwnKeys(descs); for (let j = 0, k = keys.length; j < k; j++) { const key = keys[j]; if (!(hasProperty(key, 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); }; } }
@mixin使用以下:
import { mixin } from 'core-decorators'; const SingerMixin = { sing(sound) { alert(sound); } }; const FlyMixin = { fly() {}, land() {} }; @mixin(SingerMixin, FlyMixin) class Bird { singMatingCall() { this.sing('tweet tweet'); } } var bird = new Bird(); bird.singMatingCall();
咱們能夠看到mixin
函數至關於採用Currying的方式接受mixins
數組,返回
return target => { return handleClass(target, mixins); };
而handleClass
函數的大體做用就是採用defineProperty
將mixins數組中的函數定義在target.prototype上,這樣就實現了mixin的要求。
講了這麼多Mixin的東西,那麼Mixin在React中有什麼做用呢?Mixin的做用無非就是在多個組件中共享相同的方法,實現複用,React中的Mixin也是相同的。好比你的組件中可能有共同的工具方法,爲了不在每一個組件中都有相同的定義,你就能夠採用Mixin。下面依舊舉一個現實的例子。
React的性能優化一個很是常見的方法就是減小組件沒必要要的render
,通常咱們能夠在生命週期shouldComponentUpdate(nextProps, nextState)
中進行判斷,經過判斷nextProps
和nextState
與this.pros
和this.state
是否徹底相同(淺比較),若是相同則返回false,表示不從新渲染,若是不相同,則返回true,使得組件從新渲染(固然你也能夠不使用mixin,而使用React.PureComponent也能夠達到相同的效果)。而且如今有很是多的現成的庫提供如上的功能,例如react-addons-pure-render-mixin
中提供了PureRenderMixin方法,首先咱們能夠在項目下運行:
npm install --save react-addons-pure-render-mixin;
而後在代碼中能夠以下使用
import PureRenderMixin from 'react-addons-pure-render-mixin'; import {decorate as mixin} from 'react-mixin' @mixin(PureRenderMixin) class FooComponent extends React.Component { constructor(props) { super(props); } render() { return <div className={this.props.className}>foo</div>; } }
固然你也能夠這樣寫:
var PureRenderMixin = require('react-addons-pure-render-mixin'); React.createClass({ mixins: [PureRenderMixin], render: function() { return <div className={this.props.className}>foo</div>; } });
甚至這樣寫:
import PureRenderMixin from 'react-addons-pure-render-mixin'; class FooComponent extends React.Component { constructor(props) { super(props); this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); } render() { return <div className={this.props.className}>foo</div>; } }
由於@decorator是ES7的用法,因此必須使用Babel才能使用,因此咱們須要在.babelrc
文件中設置:
{ "presets": ["es2015", "stage-1"], "plugins": [ "babel-plugin-transform-decorators-legacy" ] }
並安裝插件:
npm i babel-cli babel-preset-es2015 babel-preset-stage-1 babel-plugin-transform-decorators
以後咱們就能夠盡情體驗ES7的decorator了!