React Mixins入門指南

  對於不少初級的前端工程師對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.createClass

  假設咱們全部的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中有相同的函數

  1. 組件中含有多個mixin,不一樣的mixin中含有相同名字的非生命週期函數,React會拋出異常(不是後面的函數覆蓋前面的函數)。
  2. 組件中含有多個mixin,不一樣的mixin中含有相同名字的生命週期函數,不會拋出異常,mixin中的相同的生命週期函數(除render方法)會按照createClass中傳入的mixins數組順序依次調用,所有調用結束後再調用組件內部的相同的聲明周期函數。

mixin中設置props或state

  1. 組件中含有多個mixin,不一樣的mixin中的默認props或初始state中不存在相同的key值時,則默認props和初始state都會被合併。
  2. 組件中含有多個mixin,不一樣的mixin中默認props或初始state中存在相同的key值時,React會拋出異常。

JSX

  目前幾乎不多有人會使用React.createClass的方式使用React,JSX + ES6成了標配,可是JavaScript在ES6以前是原生不支持的mixin的,ES7引入了decorator,首先介紹一下decorator究竟是什麼?github

Decorator

  ES7的Decorator語法相似於Python中的Decorator,在ES7中也僅僅只是一個語法糖,@decorator主要有兩種,一種是面向於類(class)的@decorator,另外一種是面向於方法(function)的@decorator。而且@decorator實質是利用了ES5中的Object.definePropertynpm

Object.defineProperty  

  關於Object.defineProperty不是很瞭解的同窗其實很是推薦看一下《JavaScript高級程序設計》的第六章第一節,大概總結一下:在ES5中對象的屬性其實分爲兩種: 數據屬性訪問器屬性數組

數據屬性

數據屬性有四個特性:性能優化

  1. configurable: 屬性是否可刪除、從新定義
  2. enumerable: 屬性是否可枚舉
  3. writable: 屬性值是否可修改
  4. value: 屬性值
    訪問器屬性
  5. configurable: 屬性是否可刪除、從新定義
  6. enumerable: 屬性是否可枚舉
  7. get: 讀取屬性調用
  8. set: 設置屬性調用

  Object.defineProperty(obj, prop, descriptor)的三個參數是定義屬性的對象、屬性名和描述符,描述符自己也是Object,其中的屬性就是數據屬性或者訪問器屬性規定的參數,舉個栗子:babel

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。

面向方法的@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')
}複製代碼

面向類的@decorator

  當咱們對一個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在React中的做用

  講了這麼多Mixin的東西,那麼Mixin在React中有什麼做用呢?Mixin的做用無非就是在多個組件中共享相同的方法,實現複用,React中的Mixin也是相同的。好比你的組件中可能有共同的工具方法,爲了不在每一個組件中都有相同的定義,你就能夠採用Mixin。下面依舊舉一個現實的例子。
  React的性能優化一個很是常見的方法就是減小組件沒必要要的render,通常咱們能夠在生命週期shouldComponentUpdate(nextProps, nextState)中進行判斷,經過判斷nextPropsnextStatethis.prosthis.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了!

相關文章
相關標籤/搜索