react 與 Vue的一些比較

原文地址css

組件開發

特性對比

衆所周知,Vue和React都有那麼一個特性,那就是可讓咱們進行組件化開發,這樣可讓代碼獲得更好的重用以及解耦,在架構定位中這個應該叫縱向分層吧。可是,兩個框架開發組件的寫法都有所不一樣(這個不一樣是基於個人開發習慣),下面先看一下不一樣的地方。html

首先是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的寫法: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>

這裏的視圖渲染咱們先忽略,下一節在詳細對比。node

prop對比:

Vue的prop必須在props字段裏聲明。React的prop不強制聲明,聲明時也可使用prop-types對其聲明約束。
Vue的prop聲明事後掛在在組件的this下,須要的時候在this中獲取。React的prop存在組件的props字段中,使用的時候直接在this.props中獲取。
組件狀態對比,Vue爲data,React爲state:react

Vue的狀態data須要在組件的data字段中以函數的方式聲明並返回一個對象。React的狀態state能夠直接掛載在組件的state字段下,在使用以前初始化便可。
Vue的狀態data聲明後掛在在this下面,須要的是時候在this中獲取。React的狀態state存在組件的state字段中,使用的時候直接在this.state中獲取。
Vue的狀態更新能夠直接對其進行賦值,視圖能夠直接獲得同步。React的狀態更新必須使用setState,不然視圖不會更新。
而後是組件方法對比:webpack

Vue的方法須要在methods字段下聲明。React的方法用方法的方式聲明在組件下便可。
Vue與React使用方法的方式相同,由於都是掛載在組件中,直接在this中獲取便可。
計算屬性computed對比:es6

Vue有計算屬性在computed字段中聲明。React中無計算屬性特性,須要其餘庫如mobx輔助完成。
Vue的計算屬性聲明後掛載在this下,須要的時候在this中獲取。
監聽數據對比:web

Vue中能夠在watch字段中對prop、data、computed進行對比,而後作相應的操做。在React全部變化須要在聲明週期componentWillReveiveProps中手動將state和prop進行對比。
對比完後發現,其實Vue給個人我的感受就是本身在寫配置,只不過配置是以函數的形式在寫,而後Vue幫你把這些配置好的東西掛載到組件下面。並且prop、data、computed、方法全部都是掛載組件下,其實單單從js語法上很難以理解,好比說我在computed中,想獲取data的text數據,使用的是this.text來獲取,若是拋開vue,單單用js語法來看,其實this大多狀況是指向computed對象的,因此我的以爲這樣的語法是反面向對象的。vuex

這個時候在反過來看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-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變量由編譯器自動注入到做用域中,自動注入詳情見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的提案,都帶給了前端不同的編程體驗,大概也是前端的一個比較大的革命吧,咱們應該擁抱這樣的革命變化。

相關文章
相關標籤/搜索