详解从react转职到vue开发的项目准备


Posted in Javascript onJanuary 14, 2019

前言

首先,为什么我需要做这个项目准备工作呢?因为常年习惯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-component 是Vue英文官网推荐的一个包,可以以class的模式写vue组件,它带来了很多便利:

  • methods,钩子都可以直接写作class的方法
  • computed属性可以直接通过get来获得
  • 初始化data可以声明为class的属性
  • 其他的都可以放到Component装饰器里

vue-property-decorator

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-jsxbabel-plugin-syntax-jsxbabel-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变量由编译器自动注入到作用域中,自动注入详情见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 }}
  • 使用原生的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,这个包的出现,就是为了更好的讲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的提案,都带给了前端不一样的编程体验,大概也是前端的一个比较大的革命吧,我们应该拥抱这样的革命变化。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
IE中jquery.form中ajax提交没反应解决方法分享
Sep 11 Javascript
jQuery 选择表格(table)里的行和列及改变简单样式
Dec 15 Javascript
jquery ajax 局部刷新小案例
Feb 08 Javascript
input标签内容改变的触发事件介绍
Jun 18 Javascript
jQuery实现html表格动态添加新行的方法
May 28 Javascript
JavaScript中的Number数字类型学习笔记
May 26 Javascript
jsp 自动编译机制详细介绍
Dec 01 Javascript
Vue响应式添加、修改数组和对象的值
Mar 20 Javascript
微信小程序数据存储与取值详解
Jan 30 Javascript
vue translate peoject实现在线翻译功能【新手必看】
Jun 07 Javascript
Angular4.x Event (DOM事件和自定义事件详解)
Oct 09 Javascript
js实现固定区域内的不重叠随机圆
Oct 24 Javascript
node全局变量__dirname与__filename的区别
Jan 14 #Javascript
微信小程序时间轴实现方法示例
Jan 14 #Javascript
浅谈webpack devtool里的7种SourceMap模式
Jan 14 #Javascript
关于vue的npm run dev和npm run build的区别介绍
Jan 14 #Javascript
用npm-run实现自动化任务的方法示例
Jan 14 #Javascript
详解vue-cli 2.0配置文件(小结)
Jan 14 #Javascript
使用vue-cli脚手架工具搭建vue-webpack项目
Jan 14 #Javascript
You might like
动画 《Pokemon Sword·Shield》系列WEB动画《薄明之翼》第2话声优阵容公开!
2020/03/06 日漫
解密ThinkPHP3.1.2版本之模块和操作映射
2014/06/19 PHP
Yii2使用dropdownlist实现地区三级联动功能的方法
2016/07/18 PHP
PHP实现文件下载【实例分享】
2017/04/28 PHP
二级域名转向类
2006/11/09 Javascript
JScript的条件编译
2007/05/29 Javascript
鼠标右击事件代码(asp.net后台)
2011/01/27 Javascript
JS异常处理的一个想法(sofish)
2013/03/14 Javascript
把Node.js程序加入服务实现随机启动
2015/06/25 Javascript
基于jquery实现轮播特效
2016/04/22 Javascript
JS动态计算移动端rem的解决方案
2016/10/14 Javascript
JS中使用gulp实现压缩文件及浏览器热加载功能
2017/07/12 Javascript
highcharts 在angular中的使用示例代码
2017/09/20 Javascript
AngularJS实现自定义指令及指令配置项的方法
2017/11/20 Javascript
详解js模板引擎art template数组渲染的方法
2018/10/09 Javascript
nodejs实现范围请求的实现代码
2018/10/12 NodeJs
Echarts.js无法引入问题解决方案
2020/10/30 Javascript
使用Python3编写抓取网页和只抓网页图片的脚本
2015/08/20 Python
Pycharm学习教程(5) Python快捷键相关设置
2017/05/03 Python
利用Python-iGraph如何绘制贴吧/微博的好友关系图详解
2017/11/02 Python
Python cookbook(数据结构与算法)根据字段将记录分组操作示例
2018/03/19 Python
对python 命令的-u参数详解
2018/12/03 Python
python opencv实现证件照换底功能
2019/08/19 Python
django中media媒体路径设置的步骤
2019/11/15 Python
Python偏函数Partial function使用方法实例详解
2020/06/17 Python
Python日志器使用方法及原理解析
2020/09/27 Python
Python环境使用OpenCV检测人脸实现教程
2020/10/19 Python
利于python脚本编写可视化nmap和masscan的方法
2020/12/29 Python
ivx平台开发之不用代码实现一个九宫格抽奖功能
2021/01/27 HTML / CSS
如何写出高性能的JSP和Servlet
2013/01/22 面试题
高校十八大报告感想
2014/01/27 职场文书
文明班级申报材料
2014/12/24 职场文书
2015年大学班主任工作总结
2015/04/30 职场文书
岗位聘任协议书
2015/09/21 职场文书
2016年小学生寒假总结
2015/10/10 职场文书
化工厂员工工作总结
2015/10/15 职场文书