首先,为何我须要作这个项目准备工做呢?由于常年习惯React开发的我,最近忽然接手了一个Vue项目,而以前基本没有过Vue的实践,这么突兀让还在沉溺于React开发的我进行Vue开发,甚是不习惯,那天然我须要想办法让Vue开发尽可能与React类似,这样大概让本身在开发过程当中更驾轻就熟吧。css
原文地址html
众所周知,Vue和React都有那么一个特性,那就是可让咱们进行组件化开发,这样可让代码获得更好的重用以及解耦,在架构定位中这个应该叫纵向分层吧。可是,两个框架开发组件的写法都有所不一样(这个不一样是基于个人开发习惯),下面先看一下不一样的地方。前端
首先是React,我的习惯于es6的写法(历来没用过es5的createClass的写法):vue
import React, { Component } from 'react'; import propTypes from 'prop-types'; export default class Demo extends Component { state = { text: 'hello world' }; static propTypes = { title: PropTypes.String } static defaultProps = { title: 'React Demo' } setText = e => { this.setState({ text: '点击了按钮' }) } componentWillReveiveProps(nextProps) { console.log(`标题从 ${this.props.title} 变为了 ${nextProps.title}`) } render() { const { title } = this.props; const { text } = this.state; return <div> <h1>{title}</h1> <span>{text}<span> <button onClick={this.setText}>按钮<button> </div> } }
下面是常见vue的写法:node
<template> <div> <h1>{{title}}</h1> <span>{{text}}<span> <button @click="setText">按钮</button> </div> </template> <script> export default { props: { title: { type: String, default: 'Vue Demo' } }, watch: { title(newTitle, oldTitle) { console.log(`标题从 ${oldTile} 变为了 ${newTitle}`) } }, data() { return { text: 'hello world' } }, methods: { setText(e) { this.text = '点击了按钮'; } } } </script>
这里的视图渲染咱们先忽略,下一节在详细对比。react
prop对比:webpack
prop-types
对其声明约束。组件状态对比,Vue为data,React为state:git
setState
,不然视图不会更新。而后是组件方法对比:es6
methods
字段下声明。React的方法用方法的方式声明在组件下便可。计算属性computed对比:github
computed
字段中声明。React中无计算属性特性,须要其余库如mobx辅助完成。监听数据对比:
watch
字段中对prop、data、computed进行对比,而后作相应的操做。在React全部变化须要在声明周期componentWillReveiveProps
中手动将state和prop进行对比。对比完后发现,其实Vue给个人我的感受就是本身在写配置,只不过配置是以函数的形式在写,而后Vue帮你把这些配置好的东西挂载到组件下面。并且prop、data、computed、方法全部都是挂载组件下,其实单单从js语法上很难以理解,好比说我在computed中,想获取data的text数据,使用的是this.text来获取,若是抛开vue,单单用js语法来看,其实this大多状况是指向computed对象的,因此我的以为这样的语法是反面向对象的。
这个时候在反过来看React的class写法,原本就是属于面向对象的写法,状态state归状态,属性prop归属性,方法归方法,想获取什么内容,经过this直接获取,更接近于JavaScript编程,相对来讲比较好理解。
针对Vue的反面向对象,咱们能够更改其写法,经过语法糖的形式,将其咱们本身的写法编译成Vue须要的写法。
vue-class-component 是Vue英文官网推荐的一个包,能够以class的模式写vue组件,它带来了不少便利:
vue-property-decorator 这个包彻底依赖于vue-class-component,提供了多个装饰器,辅助完成prop、watch、model等属性的声明。
因为使用的是装饰器语法糖,咱们须要在咱们webpack的babel编译器中对齐进行支持。
首先是class语法支持,针对babel6及更低的版本,须要配置babel的plugin中添加class语法支持插件babel-plugin-transform-class-properties
,针对babel7,须要使用插件@babel/plugin-proposal-class-properties
对class进行语法转换。
而后是装饰器语法支持,针对babel6及更低的版本,须要配置babel的plugin中添加装饰器语法支持插件babel-plugin-transform-decorators-legacy
,针对babel7,须要使用插件@babel/plugin-proposal-decorators
对装饰器进行语法转换。
针对bable6,配置.babelrc以下
{ "presets": ["env", "stage-1"], "plugins": [ "transform-runtime", "syntax-dynamic-import", "transform-class-properties", // 新增class语法支持 "transform-decorators-legacy" // 新增装饰器语法支持 ] }
对于bable7,官方推荐直接使用@vue/app
preset,该预设包含了@babel/plugin-proposal-class-properties
和@babel/plugin-proposal-decorators
两个插件,另外还包含了动态分割加载chunks支持@babel/plugin-syntax-dynamic-import
,同时也包含了@babel/env
preset,.babelrc配置以下:
{ "presets": [ ["@vue/app", { "loose": true, "decoratorsLegacy": true }] ] }
编译插件准备好以后,咱们对上面的Vue组件进行改写,代码以下
<template> <div> <h1>{{title}}</h1> <span>{{text}}<span> <button @click="setText">按钮</button> </div> </template> <script> import { Vue, Component, Watch, Prop } from 'vue-property-decorator'; @Component export default class Demo extends Vue { text = 'hello world'; @Prop({type: String, default: 'Vue Demo'}) title; @Watch('title') titleChange(newTitle, oldTitle) { console.log(`标题从 ${oldTile} 变为了 ${newTitle}`) } setText(e) { this.text = '点击了按钮'; } } </script>
到此为止,咱们的组件改写完毕,相对先前的“写配置”的写法,看起来相对来讲要好理解一些吧。
注意:Vue的class的写法的methods仍是没办法使用箭头函数进行的,详细缘由这里就不展开,大概就是由于Vue内部挂载函数的方式的缘由。
针对视图的开发,Vue推崇html、js、css分离的写法,React推崇all-in-js,全部都在js中进行写法。
固然各有各的好处,如Vue将其进行分离,代码易读性较好,可是在html中没法完美的展现JavaScript的编程能力,而对于React的jsx写法,由于有JavaScript的编程语法支持,让咱们更灵活的完成视图开发。
对于这类不灵活的状况,Vue也对jsx进行了支持,只须要在babel中添加插件babel-plugin-transform-vue-jsx
、babel-plugin-syntax-jsx
、babel-helper-vue-jsx-merge-props
(babel6,对于babel7,官方推荐的@vue/app
预设中已包含了jsx的转化插件),咱们就能够像React同样,在组件中声明render函数并返回jsx对象,以下咱们对上一节的组件进行改造:
<script> import { Vue, Component, Watch, Prop } from 'vue-property-decorator'; @Component export default class Demo extends Vue { title = 'hello world'; @Prop({type: String, default: 'Vue Demo'}) title; @Watch('title') titleChange(newTitle, oldTitle) { console.log(`标题从 ${oldTile} 变为了 ${newTitle}`) } setText(e) { this.text = '点击了按钮'; } render() { const { title, text } = this; return <div> <h1>{title}</h1> <span>{text}<span> <button onClick={this.setText}>按钮<button> </div> } } </script>
写到这里,也基本上发现其写法已经与React的class写法雷同了。那么Vue的jsx和React的jsx有什么不一样呢。
在React的jsx语法须要React支持,也就是说,在你使用jsx的模块中,必须引进React。
而Vue的jsx语法须要Vue的createElement支持,也就是说在你的jsx语法的做用域当中,必须存在变量h
,变量h
为createElement
的别名,这是Vue生态系统中的一个通用惯例,在render中h变量由编译器自动注入到做用域中,自动注入详情见plugin-transform-vue-jsx,若是没有变量h,须要从组件中获取并声明,代码以下:
const h = this.$createElement;
这里借助官方的一个例子,基本包含了全部Vue的jsx经常使用语法,以下:
// ... render (h) { return ( <div // normal attributes or component props. id="foo" // DOM properties are prefixed with `domProps` domPropsInnerHTML="bar" // event listeners are prefixed with `on` or `nativeOn` onClick={this.clickHandler} nativeOnClick={this.nativeClickHandler} // other special top-level properties class={{ foo: true, bar: false }} style={{ color: 'red', fontSize: '14px' }} key="key" ref="ref" // assign the `ref` is used on elements/components with v-for refInFor slot="slot"> </div> ) }
可是,Vue的jsx语法没法支持Vue的内建指令,惟一的例外是v-show,该指令可使用v-show={value}的语法。大多数指令均可以用编程方式实现,好比v-if
就是一个三元表达式,v-for
就是一个array.map()
等。
若是是自定义指令,可使用v-name={value}
语法,可是该语法不支持指令的参数arguments
和修饰器modifier
。有如下两个解决方法:
v-name={{ value, modifier: true }}
const directives = [ { name: 'my-dir', value: 123, modifiers: { abc: true } } ] return <div {...{ directives }}/>
那么,咱们何时使用jsx,何时template呢?很明显,面对那么复杂多变的视图渲染,咱们使用jsx语法更能驾轻就熟,面对简单的视图,咱们使用template能开发得更快。
针对状态管理,Vue的Vuex和React的Redux很雷同,都是Flow数据流。
对于React来讲,state须要经过mapStateToProps将state传入到组件的props中,action须要经过mapDispatchToProps将action注入到组件的props中,而后在组件的props中获取并执行。
而在Vue中,store在组件的$store中,能够直接this.$store.dispatch(actionType)
来分发action,属性也能够经过mapState,或者mapGetter把state或者getter挂载到组件的computed下,更粗暴的能够直接this.$store.state
或者this.$store.getter
获取,很是方便。
咱们为了更贴切于es6的class写法,更好的配合vue-class-component
,咱们须要经过其余的方式将store的数据注入到组件中。
vuex-class,这个包的出现,就是为了更好的讲Vuex与class方式的Vue组件链接起来。
以下,咱们声明一个store
import Vuex from 'vuex'; const store = new Vuex.Store({ modules: { foo: { namespaced: true, state: { text: 'hello world', }, actions: { setTextAction: ({commit}, newText) => { commit('setText', newText); } }, mutations: { setText: (state, newText) => { state.text = newText; } } } } })
针对这个store,咱们改写咱们上一章节的组件
<template> <div> <h1>{{title}}</h1> <span>{{text}}<span> <button @click="setText">按钮</button> </div> </template> <script> import { Vue, Component, Watch, Prop } from 'vue-property-decorator'; import { namespace } from 'vuex-class'; const fooModule = namespace('foo'); @Component export default class Demo extends Vue { @fooModule.State('text') text; @fooModule.Action('setTextAction') setTextAction; @Prop({type: String, default: 'Vue Demo'}) title; @Watch('title') titleChange(newTitle, oldTitle) { console.log(`标题从 ${oldTile} 变为了 ${newTitle}`) } setText(e) { this.setTextAction('点击了按钮'); } } </script>
这里能够发现,store声明了一个foo模块,而后在使用的时候从store中取出了foo模块,而后使用装饰器的形式将state和action注入到组件中,咱们就能够省去dispatch的代码,让语法糖帮咱们dispatch。这样的代码,看起来更贴切与面向对象。。。好吧,我认可这个代码越写越像Java了。
然而,以前的我并非使用Redux开发React的,而是Mobx,因此这种 dispatch -> action -> matation -> state 的形式对我来讲也不是很爽,我仍是更喜欢把状态管理也以class的形式去编写,这个时候我又找了另一个包vuex-module-decorators来改写个人store.module。
下面咱们改写上面的store:
import Vuex from 'vuex'; import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'; @Module class foo extends VuexModule { text = 'hello world' @Mutation setText(text) { this.text = text; } @Action({ commit: 'setText' }) setTextAction(text) { return text; } } const store = new Vuex.Store({ modules: { foo: foo }) export default store;
这样,咱们的项目准备基本上完毕了,把Vue组件和Vuex状态管理以class的形式来编写。大概是我以为es5的写法显得不太优雅吧,没有es6的写法那么高端。
class语法和装饰器decorators语法都是ES6的提案,都带给了前端不同的编程体验,大概也是前端的一个比较大的革命吧,咱们应该拥抱这样的革命变化。