「Vue」與「React」--使用上的區別

WirelessSprucetec發表於2019-01-03

作者: 落塵

Vue和React都是目前最流行、生態最好的前端框架之一,之所以用“與”字來做標題,也是為了避免把他們放在對立面。畢竟框架本身沒有優劣之分,只有適用之別,選擇符合自身業務場景、團隊基礎的技術才是我們最主要的目的。

本文希望通過對比兩個框架在使用上的區別,能使只用其中一個框架進行開發的開發者快速瞭解和運用另一個框架,已應對不同技術棧的需求,無論你是為了維護老系統還是為了適應新的生態(React-Native)。

那我們現在就從下面這幾個步驟來分別聊聊Vue和React的區別:

1. 如何開始第一步?

Vue和React分別都有自己的腳手架工具:vue-cli和create-react-app,兩者都能幫助我們快速建立本地的開發環境,具體步驟如下:

vue-cli 3.*

npm install -g @vue/cli
vue create my-project  //node版本推薦8.11.0+

// 如果你還是習慣之前2.*版本的話,再安裝一個工具即可
npm install -g @vue/cli-init
vue init webpack my-project

複製程式碼

生成的專案結構大同小異,只不過3.*版本將原先直接暴露出來的webpack配置封裝到了自己的vue.config.js配置檔案中,包括常見baseUrl、outputDir、devServer.proxy等。

Vue-2:

vue-2圖

Vue-3:

vue-3圖

create-react-app

npm install -g create-react-app
npx create-react-app my-project
複製程式碼

npm run eject前圖

可以看出create-react-app的封裝做的更為徹底一些,基本將所有的webpack配置都隱藏了起來,僅由package.json來控制。但對於需要靈活配置的場景,這樣的封裝顯然是不合適的,你可以執行npm run eject將封裝的配置再次釋放出來。

npm run eject後圖

2. 如何解決最基本的開發需求?

使用腳手架我們可以非常順利的搭建起本地的開發環境,那下一步就是我們將使用它們來進行業務開發。在此之前,我們可以先問一個問題,什麼是最基本的開發需求?

我們可以簡單總結為三個方面,而面對新框架你也可以從這三個方面入手,看框架是如何處理的,從而達到快速上手的目的。

  • 模板渲染:將從後端獲取的資料(或者是已經在JS中的資料)根據一定的規則渲染到DOM中。
  • 事件監聽:監聽使用者的操作行為,例如點選、移動、輸入等。
  • 處理表單:將使用者操作行為所產生的資料影響儲存下來,併傳送給後端,處理返回結果。

我們先來看Vue是如何處理這三方面的

// 模板渲染,可以直接使用data物件中的資料,利用指令來處理渲染邏輯
<template>
  <div class="hello">
    <div v-for="(item, index) in list" :key="index">
      {{ item.title }}
    </div>
    // 處理表單,將使用者輸入的內容賦值到title上
    <input v-model="title" />	 
    // 事件監聽,使用者點選後觸發methods中的方法
    <button @click="submit">提交</button>  
  </div>
</template>

<script>
export default {
  data () {
    return {
      list: [
        { title: 'first' },
        { title: 'second' }
      ],
      title: ''
    }
  },
  methods: {
    submit () {
      this.list.push({ title: this.title })
      this.title = ''
    }
  }
}
</script>

複製程式碼

而React的書寫方式為:

import React, { Component } from 'react';

export default class HelloWorld extends Component {
  state = {
    list: [
      { title: 'first' },
      { title: 'second' },
    ],
    title: '',
  };

  setTitle = (e) => {
    // 需要手動呼叫setState來進行重繪,否則input的value不會改變
    this.setState({ title: e.target.value })
  }

  submit = (e) => {
    const { title, list } = this.state;
    list.push({ title });
    this.setState({ list, title: '' });
  }

  render() {
    const { title, list } = this.state;
    return (
      <div className="App">
        // react會使用jsx的方式來進行模板的渲染,可混合使用js和html標籤
		//  {}解析js,()解析html
        { list.map((item, index) => (
            <div key={index}>{ item.title }</div>
          )
        )}
        // 事件監聽 + 表單處理
        <input value={title} onChange={this.setTitle} />
        <button onClick={this.submit}>增加</button>
      </div>
    );
  }
}
複製程式碼

從上面兩個例子中我們可以看出,Vue的寫法稍微簡單一下,不需要手動利用setState這樣類似的特定函式來觸發資料與模板的同步。但也正由於手動觸發,React顯得更加自由一些,我們可以根據實際情況去處理,適當減少不必要的重繪,從而對效能進行優化。

3. 生命週期有什麼不一樣?

生命週期一直是Vue和React中非常重要的概念,明確的表達了元件從建立、掛載、更新、銷燬的各個過程。那兩者在細節上有什麼區別呢,我們可以通過它們各自官網的圖來看一下。

Vue的生命週期如下圖:

vue生命週期

React的生命週期如下圖:

react生命週期

主體流程大致都可以分為:

  • 建立元件:建立元件狀態等一些前置工作,如果需要請求資料的話,大部分在此時進行。
  • 掛載元件:將元件真正掛載到Document中,可以獲取真實的DOM進行操作。
  • 更新元件: 元件自身的狀態,外部接受的引數發生變化時。
  • 解除安裝元件: 當元件要被移除當前Document時,通常會銷燬一些定時器等脫離於元件之外的事件。

4. Vue比React多了哪些常見屬性

Vue之所以給人上手簡單、開發便捷的感覺,有一部分原因就在於封裝了很多常見的操作,你只需要簡單的使用一些屬性就可以達到目的。便捷和自由有的時候就是一對反義詞,你在獲得一些特性的時候就會犧牲一些特性,就像演算法上常見的空間換時間這樣的策略。

那我們就來看看Vue的哪些常見屬性是React中沒有的:

computed

計算屬性經常用於集中管理多個狀態的結果,避免將過多的計算邏輯暴露在模板或其他引用的地方。我們也無需知道該計算屬性依賴的屬性何時、如何變化,Vue能自動監聽變化結果並重新計算。例如:

computed: {
  fullName () {
    return this.firstName + ' ' + this.lastName;
  }
}
複製程式碼

####watch: 和計算屬性類似,可以指定監聽某個狀態變化,並獲得該屬性變化前後的值。例如:

watch: {
  name (val, oldVal) {	// 監聽某個屬性發生變化
  ....
  }
}
複製程式碼

而在React中,你需要實現這一功能,你可能需要在componentWillReceiveProps時自己去判斷屬性是否發生了變化,或者在setState的同時觸發變化後的業務邏輯。例如:

componentWillReceiveProps(nextProps) {
  if (nextProps.name != this.props.name) {  // props中的某個屬性發生了變化
    ....
  }
}
複製程式碼

####指令 Vue中的指令主要用於封裝對DOM的操作,例如模板中的v-if/v-else/v-for等;React本身利用jsx來操作DOM,所以也就沒有這一概念。我們舉一個自定義指令的例子,來提現下指令的作用:

<img v-lazy="img_url" />
複製程式碼
directives: {
  'lazy': {
	inserted: function (el, binding) {
	  var body = document.body;
	  var offsetTop = el.offsetTop;
	  var parent = el.offsetParent;
	  // 獲取繫結元素對於body頂部的距離
	  while (parent && parent.tagName != 'body') {
	    offsetTop += parent.offsetTop;
	    parent = parent.offsetParent;
	  }
	  // 若出現在可視區域內,則直接賦值src
	  if (body.scrollTop + body.clientHeight > offsetTop && body.scrollTop < offsetTop) {
	    el.src = binding.value;
	  } else {
	  	 // 若暫未出現,則監聽window的scroll事件 
	    var scrollFn = function () {
	      // 出現在區域內才賦值src,並取消事件監聽
	      if (body.scrollTop + body.clientHeight > offsetTop && body.scrollTop < offsetTop) {
	        el.src = binding.value;
	        window.removeEventListener('scroll', scrollFn)
	      }
	    }
	    window.addEventListener('scroll', scrollFn)
	  }
	}
  }
}
複製程式碼

這裡其實我們也可以試想一下,如果使用React的話,我們可以如何實現圖片懶載入這個功能?

5. 元件的區別

元件目前可以說是兩個框架開發中最基本的單位,每個專案都包含一個根元件,然後再以路由進行細分,從頁面直到功能更為單一的元件。而如何處理專案中多個元件的關係、如何高效的複用元件也就成了框架所需要考慮的事情,下面就和大家對比下兩者在處理這種情況下的異同。

元件通訊

元件通訊是元件關係中最常見的一種,主要表現在父元件向子元件傳遞資料,子元件呼叫父元件方法影響父元件。我們分別舉例來說明兩者在寫法上的區別:

Vue的寫法:

// 父元件
<template>
  <div class="parent">
   <div v-for="(msg, index) in msgs" :key="index">
      {{ msg.content }}
    </div>
    // 通過v-bind可以繫結父元件狀態傳遞給子元件
    // 並且可以自定義事件將方法傳遞給子元件
    <child :last="last" @add="add"></child>
  </div>
</template>

<script>
import Child from '@/components/PropsEventChild'

export default {
  components: {
    Child
  },

  data () {
    return {
      name: 'parent',
      msgs: []
    }
  },

  computed: {
    last () {
      const { msgs } = this
      return msgs.length ? msgs[msgs.length - 1] : undefined
    }
  },

  methods: {
    add (msg) {
      this.msgs.push(msg)
    }
  }
}
</script>

// 子元件
<template>
  <div class="child">
    <input v-model="content" placeholder="請輸入" />
    <button @click="submit">提交</button>
  </div>
</template>

<script>
export default {
  // 此處需要定義接受引數的名稱
  props: ['last'],

  data () {
    return {
      content: ''
    }
  },

  methods: {
    submit () {
      const time = new Date().getTime()
      const { last } = this
      if (last && (time - last.time < 10 * 1000)) {
        alert('你發言太快了')
        return
      }
      // 通過$emit的方式可以呼叫父元件傳遞過來的自定義事件,從而修改父元件的狀態
      this.$emit('add', { content: this.content, time })
      this.content = ''
    }
  }
}
</script>

複製程式碼

React寫法:

// 父元件
import React, { Component } from 'react';
import Child from './Child'

export default class Parent extends Component {
  state = {
    msgs: []
  };

  get last () {
    const { msgs } = this.state;
    return msgs.length ? msgs[msgs.length - 1] : undefined;
  }

  add = (msg) => {
    const { msgs } = this.state;
    msgs.push(msg);
    this.setState({ msgs });
  };

  render() {
    const { msgs } = this.state;
    return (
      <div className="Parent">
        { msgs.map((item, index) => (
            <div key={index}>{ item.content }</div>
          )
        )}
        // 直接傳遞引數和方法
        <Child last={this.last} onClick={this.add}/>
      </div>
    );
  }
}

// 子元件
import React, { Component } from 'react';

export default class Child extends Component {
  state = {
    content: ''
  };

  setContent = (e) => {
    this.setState({ content: e.target.value })
  }

  submit = (e) => {
    const { props: { last }, state: { content } } = this
    const time = new Date().getTime()
    if (last && (time - last.time < 10 * 1000)) {
      alert('你發言太快了')
      return
    }
    // 直接呼叫傳遞過來的方法
    this.props.onClick({ content, time })
    this.setState({ content: '' })
  }

  render() {
    const { content } = this.state;
    return (
      <div className="Child">
        <input value={content} onChange={this.setContent} />
        <button onClick={this.submit}>增加</button>
      </div>
    );
  }
}

複製程式碼

元件巢狀

實際開發當中經常會使用到一些提供單純的UI功能,但內容需要業務自行定義的元件,例如Modal、Table等。這種型別的元件往往和會業務元件進行巢狀,但不影響業務元件的狀態。Vue提供了這樣寫法,可以將業務元件替換掉UI元件中特定的一部分,這裡就不贅述程式碼了,直接上一下例子:

Vue Slot元件

Vue Slot使用方式

React中沒有<slot>這種語法,但元件本身可以將標籤內的部分以props.children的方式傳遞給子元件,例如:

import React, { Component } from 'react';
import Wrapper from './Wrapper'

export default class Demo extends Component {

  state = {
    content: '我是Demo元件中的內容'
  };

  render() {
    return (
      <div>
        <Wrapper>
          <div>{ this.state.content }</div>
        </Wrapper>
      </div>
    );
  }
}

import React, { Component } from 'react';

export default class Wrapper extends Component {
  render() {
    return (
      <section>
        <header>我是Wrapper頭部</header>
        { this.props.children }
        <footer>我是Wrapper尾部</footer>
      </section>
    );
  }
}
複製程式碼

可以看出this.props.children代表的就是父元件(class Demo)中的被包裹的標籤<div>{ this.state.content }</div>

無狀態元件

有的時候我們可能希望元件只是起一個渲染的作用,並不需要狀態變化或者生命週期這種監聽。為了節約效能,React可以只使用函式來接受和使用props:

const Wrapper = (props) => (
  <section>
    <header>我是Wrapper頭部</header>
    { props.children }
    <footer>我是Wrapper尾部</footer>
  </section>
)
複製程式碼

Vue當中有種類似的用法,在template標籤中加上functional,模板渲染後不會對資料進行監聽:

<template functinal>
  <section>
    <header>我是Wrapper頭部</header>
    { props.children }
    <footer>我是Wrapper尾部</footer>
  </section>
 </template>
 // 除了template標籤之外,該元件已無法使用Vue例項,也就是不能在該.vue檔案中使用<script></script>標籤。
複製程式碼

跨元件通訊

所謂的跨元件通訊只指不通過props傳遞獲取父元件中定義的資料,這樣深層的子元件就能直接訪問到頂層的資料。

React中提供了Context這樣的機制來實現,具體程式碼:

定義Context

設定Provider,定義資料

設定Consumer,獲取資料

Vue中也有類似的機制,但官方文件中表示這個特性本意在於給高階外掛/元件庫提供用例。並不推薦直接用於應用程式程式碼中。,不過我們還是可以簡單瞭解下它的使用方法:

// 祖先級元件
export default {
	name: 'App',

    provide: {
      theme: 'meicai'
    }
}

// 子元件,獲取provide中定義的資料
export default {
  inject: ['theme']
}
複製程式碼

React高階元件

高階元件是React中的一個概念,簡稱HOC(Higher Order Component),定義是:高階元件就是一個函式,且該函式接受一個元件作為引數,並返回一個新的元件。例如下面這個例子:

const withHeader = (WrappedComponent) =>
  class WrapperComponent extends Component {
    render() {
      return <section>
        <header><h1>頂部資訊</h1></header>
        <WrappedComponent {...this.props} />
      </section>
    }
}
複製程式碼

其中引數WrappedComponent為React元件,我們可以在其他元件中使用裝飾器的語法來使用這個高階元件:

import React, { Component } from 'react';
import withHeader from './withHeader'

@withHeader  // 裝飾器寫法
class Section extends Component {

  state = {
    content: '我是SectionOne'
  };

  render() {
    return (
      <div>
        { this.state.content }
      </div>
    );
  }
}
複製程式碼

這樣,這個Section元件最後render的效果即包含了withHeader中新增的頂部資訊等元素,也包括了自己本身的DOM結構。

除了從結構上對子元件進行增強外,高階元件也可以通過注入props的方式增強子元件的功能,在ant-design的Form元件中,就有類似的用法,以下我們舉個簡化的例子,實現給受控元件繫結值並且設定onChange:

import React, { Component } from 'react';
// 表單高階元件
const Form = (Wrapped) =>
  class WrapperComponent extends Component {

    state = {
      fields: {}
    };

    getFieldValue = (name) => {
      return this.state.fields[name];
    };

    setFieldValue = (name, value) => {
      const fields = this.state.fields;
      fields[name] = value;
      this.setState({ fields });
    };

    getFieldDecorator = (name, value) => {
      const { fields } = this.state;
      if (fields[name] === undefined) {
        fields[name] = value || '';
        this.setState({ fields })
      }
      return (WrappedInput) =>
        React.cloneElement(WrappedInput,
          Object.assign({}, WrappedInput.props, { value: fields[name], onChange: (e) => this.setFieldValue(name, e.target.value) }));
    };

    render () {
      const { getFieldValue, getFieldDecorator } = this;
      // 注入新的props物件,將高階元件的方法傳入到子元件中
      const form = {
        state: this.state,
        getFieldValue,
        getFieldDecorator,
      };
      return (<Wrapped {...this.props} form={form}></Wrapped>);
    }
  };

export default Form

// 使用方式
import React, { Component } from 'react';
import Form from './form'

class Demo extends Component {
  checkValue = (e) => {
    const { getFieldValue } = this.props.form;
    console.log(getFieldValue('title'));
  }

  render() {
    const { getFieldDecorator } = this.props.form;
    return (
      <div>
        { getFieldDecorator('title')(<input />) }
        <button onClick={this.checkValue}>獲取輸入框值</button>
      </div>
    );
  }
}

export default Form(Demo)

複製程式碼

##6. 狀態管理 狀態管理是在處理大型專案中元件通訊常用的設計模式,Vue和React都採取了這種模式並擁有自己的實現方式。兩者在大概念上沒有什麼的不同,從兩者的流程圖上也能看出來:

Vue狀態管理流程圖:

vuex

React狀態管理流程圖:

mobx

兩張圖都標明瞭整個過程為一個單項資料流,從狀態對映檢視,檢視(元件)操作動作,動作影響狀態。

所以這節就簡單對比下兩者在使用的區別:

Vue:

定義狀態及動作

使用狀態並含有動作的元件

React:

定義狀態及動作

使用狀態並含有動作的元件

7. 小結

最後我們回顧下,本文主要從以下這幾個方面對比了Vue和React的不同:

  • 處理渲染、事件和表單的不同
  • 常見屬性的不同
  • 元件及元件間關係的不同
  • 狀態管理使用的不同

希望從這幾個方面切入可以幫大家快速的瞭解另一門框架,本文所包含案例


原文連結: tech.meicai.cn/detail/78, 也可微信搜尋小程式「美菜產品技術團隊」,乾貨滿滿且每週更新,想學習技術的你不要錯過哦。

相關文章