对于不少初级的前端工程师对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了!