React Mixins入门指南
對于很多初級的前端工程師對mixins的概念并不是很了解,也沒有在React中嘗試使用過Mixins,這邊文章基本會按照Mixins的作用、用途、原理等多個方面介紹React中Mixins的使用。
首先解釋一下什么是Mixins,在一些大型項目中經常會存在多個組件需要使用相同的功能的情況,如果在每個組件中都重復性的加入相同的代碼,那么代碼的維護性將會變的非常差,Mixins的出現就是為了解決這個問題。可以將通用共享的方法包裝成Mixins方法,然后注入各個組件實現,我們首先給出一個Mixins簡單的例子:
上述代碼就實現了一個簡單的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中有相同的函數
組件中含有多個mixin,不同的mixin中含有相同名字的非生命周期函數,React會拋出異常(不是后面的函數覆蓋前面的函數)。
組件中含有多個mixin,不同的mixin中含有相同名字的生命周期函數,不會拋出異常,mixin中的相同的生命周期函數(除render方法)會按照createClass中傳入的mixins數組順序依次調用,全部調用結束后再調用組件內部的相同的聲明周期函數。
mixin中設置props或state
組件中含有多個mixin,不同的mixin中的默認props或初始state中不存在相同的key值時,則默認props和初始state都會被合并。
組件中含有多個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中對象的屬性其實分為兩種: 數據屬性和訪問器屬性
數據屬性
數據屬性有四個特性:
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。
面向方法的@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上。
例如:
面向類的@decorator
當我們對一個class使用@decorator時,接受到的參數target是類本身。例如:
講完了@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)中進行判斷,通過判斷nextProps和nextState與this.pros和this.state是否完全相同(淺比較),如果相同則返回false,表示不重新渲染,如果不相同,則返回true,使得組件重新渲染(當然你也可以不使用mixin,而使用React.PureComponent也可以達到相同的效果)。并且現在有非常多的現成的庫提供如上的功能,例如react-addons-pure-render-mixin中提供了PureRenderMixin方法,首先我們可以在項目下運行:
然后在代碼中可以如下使用
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了!
總結
以上是生活随笔為你收集整理的React Mixins入门指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据表记录包含表索引和数值,请对表索引相
- 下一篇: 阿里云 apt-get update 4