Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Mixins入门指南 #5

Open
MrErHu opened this issue Jun 14, 2017 · 0 comments
Open

React Mixins入门指南 #5

MrErHu opened this issue Jun 14, 2017 · 0 comments
Labels

Comments

@MrErHu
Copy link
Owner

MrErHu commented Jun 14, 2017

  对于很多初级的前端工程师对mixins的概念并不是很了解,也没有在React中尝试使用过Mixins,这边文章基本会按照Mixins的作用、用途、原理等多个方面介绍React中Mixins的使用。
  首先解释一下什么是Mixins,在一些大型项目中经常会存在多个组件需要使用相同的功能的情况,如果在每个组件中都重复性的加入相同的代码,那么代码的维护性将会变的非常差,Mixins的出现就是为了解决这个问题。可以将通用共享的方法包装成Mixins方法,然后注入各个组件实现,我们首先给出一个Mixins简单的例子:

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时,我们必须给每个组件中都加入

getDefaultProps: function () {
    return {displayName: "component"};
}

  当然我们,我们通过实现一个mixin函数,就可以实现这个功能,并且在createClass方法使用mixin非常简单:

var mixinDefaultProps = {
    getDefaultProps: function(){
        return {displayName: 'component'}
    }
}

var ExampleComponent = React.createClass({
    mixins: [mixinDefaultProps],
    render: function(){
        return <div>{this.props.displayName}</div>
    }
});

  这样我们就实现了一个最简单的mixin函数,通过给每一个组件配置mixin,我们就实现了不同组件之间共享相同的方法。需要注意的是:

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到底是什么?

Decorator

  ES7的Decorator语法类似于Python中的Decorator,在ES7中也仅仅只是一个语法糖,@decorator主要有两种,一种是面向于类(class)的@decorator,另一种是面向于方法(function)的@decorator。并且@decorator实质是利用了ES5中的Object.defineProperty

Object.defineProperty  

  关于Object.defineProperty不是很了解的同学其实非常推荐看一下《JavaScript高级程序设计》的第六章第一节,大概总结一下:在ES5中对象的属性其实分为两种: 数据属性访问器属性

数据属性

数据属性有四个特性:

  1. configurable: 属性是否可删除、重新定义
  2. enumerable: 属性是否可枚举
  3. writable: 属性值是否可修改
  4. value: 属性值
访问器属性
  1. configurable: 属性是否可删除、重新定义
  2. enumerable: 属性是否可枚举
  3. get: 读取属性调用
  4. 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

面向方法的@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了!

@MrErHu MrErHu added the React label Jun 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant