详解从react转职到vue开发的项目准备
前言
首先,为什么我需要做这个项目准备工作呢?因为常年习惯react开发的我,最近突然接手了一个vue项目,而之前基本没有过vue的实践,这么突兀让还在沉溺于react开发的我进行vue开发,甚是不习惯,那自然我需要想办法让vue开发尽量与react相似,这样大概让自己在开发过程中更得心应手吧。
组件开发
特性对比
众所周知,vue和react都有那么一个特性,那就是可以让我们进行组件化开发,这样可以让代码得到更好的重用以及解耦,在架构定位中这个应该叫纵向分层吧。但是,两个框架开发组件的写法都有所不同(这个不同是基于我的开发习惯),下面先看一下不同的地方。
首先是react,个人习惯于es6的写法(从来没用过es5的createclass的写法):
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的写法:
<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>
这里的视图渲染我们先忽略,下一节在详细对比。
prop对比:
- vue的prop必须在props字段里声明。react的prop不强制声明,声明时也可以使用prop-types对其声明约束。
- vue的prop声明过后挂在在组件的this下,需要的时候在this中获取。react的prop存在组件的props字段中,使用的时候直接在this.props中获取。
组件状态对比,vue为data,react为state:
- vue的状态data需要在组件的data字段中以函数的方式声明并返回一个对象。react的状态state可以直接挂载在组件的state字段下,在使用之前初始化即可。
- vue的状态data声明后挂在在this下面,需要的是时候在this中获取。react的状态state存在组件的state字段中,使用的时候直接在this.state中获取。
- vue的状态更新可以直接对其进行赋值,视图可以直接得到同步。react的状态更新必须使用setstate,否则视图不会更新。
然后是组件方法对比:
- vue的方法需要在methods字段下声明。react的方法用方法的方式声明在组件下即可。
- vue与react使用方法的方式相同,因为都是挂载在组件中,直接在this中获取即可。
计算属性computed对比:
- vue有计算属性在computed字段中声明。react中无计算属性特性,需要其他库如mobx辅助完成。
- vue的计算属性声明后挂载在this下,需要的时候在this中获取。
监听数据对比:
- vue中可以在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组件,它带来了很多便利:
- methods,钩子都可以直接写作class的方法
- computed属性可以直接通过get来获得
- 初始化data可以声明为class的属性
- 其他的都可以放到component装饰器里
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/apppreset
,该预设包含了@babel/plugin-proposal-class-properties
和@babel/plugin-proposal-decorators
两个插件,另外还包含了动态分割加载chunks支持@babel/plugin-syntax-dynamic-import
,同时也包含了@babel/envpreset
,.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>
vue的jsx使用注意点
写到这里,也基本上发现其写法已经与react的class写法雷同了。那么vue的jsx和react的jsx有什么不同呢。
在react的jsx语法需要react支持,也就是说,在你使用jsx的模块中,必须引进react。
而vue的jsx语法需要vue的createelement支持,也就是说在你的jsx语法的作用域当中,必须存在变量h,变量h为createelement
的别名,这是vue生态系统中的一个通用惯例,在render中h变量由编译器自动注入到作用域中,自动注入详情见,如果没有变量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 }}
- 使用原生的vnode指令数据格式,如:
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的形式去编写,这个时候我又找了另外一个包来改写我的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的提案,都带给了前端不一样的编程体验,大概也是前端的一个比较大的革命吧,我们应该拥抱这样的革命变化。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 详述游戏新站如何打造过万人气
下一篇: *飞车注册码序列号