Vue3.0 進階、環境搭建、相關API的使用


原文: 本人掘金文章

關注公眾號: 微信搜索 前端工具人 ; 收貨更多的干貨

一、 官方中文文檔鏈接

https://vue3js.cn/docs/zh/

二、開篇

  • vue3.0 從去年預熱到9月18號晚上,已正式發布 vue3.0 beta 版本;
  • beta 版意味着 vue3.0 開業正式投入到項目中了;大家可以開心的學習了(前端新技術你繼續出,我還學得動...因為要生活要吃飯!!!);
  • 前端技術生態一直不斷的更新換代,許多人都覺得亞歷山大,學不動了;但不是學不動就可以不學了么。。。 0.0;學不動也得學,不然適應自己的只會是淘汰

三、vue2.0 項目的建議

引用官方文檔作者的話:

提示:
我們仍在開發 Vue 3 的專用遷移版本,該版本的行為與 Vue 2 兼容,運行時警告不兼容。如果你計划遷移一個非常重要的 Vue 2 應用程序,我們強烈建議你等待遷移版本完成以獲得更流暢的體驗

目前作者的意思是:對於 vue2.0 的項目強烈不建議升到 vue3.0;因為目前的beta版本以及現有的框架及插件,不是很支持和兼容vue3.0語法; 所以肯定有很多預想不到的問題;對於躍躍欲試升級vue3.0的小伙伴們,只能等待官方的兼容版本開發完,在做遷移; 畢竟線上項目不是開玩笑的,出了一個bug都可能是重大損失。 這個鍋家里沒礦的基本背不動...

四、介紹

Vue3帶來些什么? 參考至: 公眾號:前端早讀課文章

詳細文檔請參考: 官方中文文檔鏈接

  • 更快
    • 重構了Virtual DOM
      • 標記靜態內容,並區分動態內容
      • 更新時只diff動態的部分
    • 重構了 雙向數據綁定
      • Object.defineProperty() --> Proxy API
      • Proxy 對於復雜的數據結構減少了循環遞歸的監聽;初始渲染循環遞歸是非常耗性能的;
      • Proxy 對於數組的變異方法(會修改原數組),不在需要單獨用數組原生方法重寫、處理
      • 語法也比defineProperty簡潔多了,直接監聽某個屬性即可;
    • 事件緩存
      • vue2中,針對綁定事件,每次觸發都要重新生成全新的function去更新;
      • Vue3中,提供了事件緩存對象cacheHandlers,當cacheHandlers開啟的時候,編譯會自動生成一個內聯函數,將其變成一個靜態節點,這樣當事件再次觸發時,就無需重新創建函數直接調用緩存的事件回調方法即可
  • 更小 (Tree shaking支持)
    • 簡而言之: 不會把所有的都打包進來,只會打包你用到的api;大項目你會發現熱加載、初始渲染提升了很多
    • 很大程度的減少了開發中的冗余代碼,提升編譯速度
  • 更易於維護
    • Vue3Flow遷移到TypeScript
      • 多人協同開發的情況下,用了 TypeScript 之后的酸爽你會吐槽,為什么早不出現 TypeScript
    • 代碼目錄結構遵循monorepo
      • 核心觀點: 代碼分割到一個個小的模塊中, 開發者大部分只是工作在少數的幾個文件夾,並且也只會編譯自己負責的模塊;而不是整個項目編譯
  • 新功能和特性 Composition API
    • 不要在意越來越像react-hook;畢竟別人的優點是值得自己學習的;
    • Composition API 函數式開發,很大程度的提高組件、業務邏輯的復用性;高度解耦;提升代碼質量、開發效率;減少代碼體積
  • 提升開發效率 vite 的支持 (當然目前來說 vite 功能還不夠強大和穩定, 但尤大把它作為vue3官方構建工具,那肯定尤大會完善它的; 可自由選擇webpack還是vite
    • vite在開發環境下基於瀏覽器原生 ES imports 開發,在生產環境下基於Rollup打包
      • 快速的冷啟動
      • 即時的模塊熱更新
      • 真正的按需編譯
    • vue2.0 相信很多小伙伴都是結合webpack開發; 但是有沒有發現初期項目小的時候很爽運行、編譯、熱加載都很快; 項目一大... 打包、運行、改個功能熱加載的時候... 我們先去上個廁所/接個水;忙的時候挺煩這環節
    • vue3.0 結合 vite作者的介紹是不跟項目體積龐大而影響,開始啥樣現在也啥樣; 當然誇張是誇張了點, 但是相差應該不大;

五、 環境搭建

// 對於 Vue 3,應該 npm 上可用的 Vue CLI v4.5 作為 @vue/cli@next
yarn global add @vue/cli@next
# OR
npm install -g @vue/cli@next

// 創建項目
npm init vite-app <project-name>
# OR
yarn create vite-app <project-name>

// 下載依賴及運行項目, 已 npm 方式為例; 詳細步驟官方文檔的安裝頁都有
cd <project-name>
npm install
npm run dev // 項目就能跑起來並且訪問了

拓展: 項目引入其他插件比如 vue-router4.0、vuex4.0、typescript等請參考 Vue3.0環境搭建

六、 語法介紹

上手之前應該先閱讀一遍 vue3 對於 vue2 的一些變更; 詳情點擊 官網文檔重大變更

vue2中使用的是Options API; vue3中的是Composition API 簡稱純函數式API

6.1、 setup

vue3 組件入口為 setup(){} 函數作為入口, 默認只執行一次;執行順序在 beforeCreate 之后 created 之前;

...
// 使用props和this
setup (props, ctx) {
  // props 組件間傳遞的參數; 
  // ctx 組件的實例的執行上下文(可以理解為 vue2 this) 
     /* 可執行 下面等操作:例 ctx.$emit() 
     attrs: Object
     emit: ƒ ()
     listeners: Object
     parent: VueComponent
     refs: Object
     root: Vue 
     */
  // 注意 steup 中沒有this了, 拿不到this
}

6.2、 生命周期

我記得早期是說 vue3 中是移除掉了 beforeCreatecreated兩個生命周期; 但是實踐的時候我發現還是可以寫的; 因為vue2vue3 寫法目前相兼容;

created () { console.log('created') }
setup (props, ctx) {
  console.log('setup')
  // mounted 新寫法 記住一句話 所有的方式都是以函數的形式呈現
  onMounted(() => {})
}
mounted () { console.log('mounted') }
// 執行順序 setup created mounted

雖然兼容但盡量不要這樣寫;向前看齊嘛; 強烈推薦全部都放在steup函數中

6.3、 reactive、ref、tofefs、isRef

創建響應式對象 reactive、ref、tofefs 用法, 對應 vue2 中的 data 推薦寫法3

// 寫法一:響應式數據一多, return 要很多次; 使用數據的時候要通過state拿到
<template>
  <div>
    <p>{{state.count}}</p>
  </div>
</template>
import {reactive} from 'vue'
...
setup(props, ctx) {
  const state = reactive({ 
    count: 0
  })
  return { state }
}
// 寫法二
<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>
import {reactive} from 'vue'
...
setup(props, ctx) {
  const state = reactive({ 
    count: 0
  })
  return {
    count: state.count
  }
}
// 寫法三:推薦 通過 toRefs 代理對象, 再通過解構的方式取值
<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>
import {reactive, toRefs} from 'vue'
...
setup(props, ctx) {
  const state = reactive({ 
    count: 0
  })
  return {
    ...toRefs(state)
  }
}
// 寫法四:通過 ref() 函數包裝, 返回值是一個對象,對象上只包含一個 value 屬性, 就是要的屬性值
<template>
  <div>
    <p>{{count}}</p>
    <p>{{count1}}</p>
  </div>
</template>
import {reactive, toRefs, ref} from 'vue'
...
setup(props, ctx) {
  // 父組件傳遞count屬性
  // 寫法1
  const count = ref(props.count)
  console.log(count.value) // 對應props.count的值
  // 寫法2 
  const state = reactive({ 
    count1: ref(props.count)
  })
  return {
    count,
    ...toRefs(state)
  }
}
// isRef 來判斷某個值是否為 ref() 創建出來的對象
import { ref, isRef } from 'vue';
export default {
  setup(props, ctx) {
    const refCount = ref(0)
    const count = isRef(refCount) ? refCount : 1
  }
};

6.4、 computed

例子場景:結合 vue-router 根據當前路勁為count賦值, 也擴展下vue-router的用法

<template>
  <div>
    <p>{{count}}</p>
    <p>{{count1}}</p>
  </div>
</template>
import {reactive, toRefs, computed} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
  const route = useRoute()
  const state = reactive({ 
    // 計算屬性 寫法1
    count: computed(() => {
      return route.path
    })
  })
  // 計算屬性 寫法2
  const count1 = computed(() => {
    return route.path
  })
  return {
    ...toRefs(state),
    // 計算屬性不需要通過 toRefs 結構, 因為他就是一個具體的值就是響應式的
    count1
  }
}

6.5、watch 、watchEffect

例子場景:同 computed 一樣

watchEffectwatch 有什么不同:

  1. watchEffect 不需要指定監聽的屬性,他會自動的收集依賴, 只要我們回調中引用到了 響應式的屬性, 那么當這些屬性變更的時候,這個回調都會執行
  2. watch 只能監聽指定的屬性
  3. watch 可以獲取到新值與舊值,而 watchEffect 不行
  4. watchEffect 在組件初始化的時候就會執行一次用以收集依賴(與computed同理),后續收集的依賴發生變化,這個回調才會再次執行
// watch 用法 監聽單個屬性
<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>
import {reactive, toRefs, watch} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
  const route = useRoute()
  const state = reactive({ 
    count: 0,
  })
  // 監聽路由路勁, immediate 是否立即執行一次
  watch(() => route.path, (newValue) => {
    state.count = newValue
  }, { immediate: true })
  
  return {
    ...toRefs(state),
  }
}
// watch 用法 監聽ref數據源
<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>
import {reactive, toRefs, ref, watch} from 'vue'
...
setup(props, ctx) {
  // 定義數據源
  let count = ref(0);
  // 指定要監視的數據源
  watch(count, (count, prevCount) => {
    console.log(count, prevCount)
  })
  setInterval(() => {
    count.value += 2
  }, 2000)
  console.log(count.value)
  return {
    count
  }
}
// watch 用法 監聽多個屬性
<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>
import {reactive, toRefs, watch} from 'vue'
...
setup(props, ctx) {
  const state = reactive({ 
    name: 'vue',
    age: 3
  })
  watch(
    // 監聽name、 age
    [() => state.name, () => state.age],
    // 如果屬性改變、則執行以下回調
    ([newName, newAge], [oldname, oldAge]) => {
      console.log(oldname, oldname)
      console.log(oldAge, oldAge)
    },
    { lazy: true} // 在 watch 被創建的時候,不執行回調函數中的代碼
  )
  setTimeout(() => {
    state.name = 'react'
    state.age += 1
  }, 3000)
  return {
    ...toRefs(state),
  }
}
// watchEffect 用法
<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>
import {reactive, toRefs, ref, watchEffect} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
  const route = useRoute()
  const state = reactive({ 
    count: 0,
  })
  // 當 route.path 變化時就會執行打印, 有點類似 react-hook 的 useEffect 第二個參數效果
  watchEffect(() => {
    count = route.path
    console.log(route.path)
  })
  // watchEffect、 watch 都可以主動停止監聽
  const stop = watchEffect(() => { 
    count = route.path
    console.log(route.path)
  })
  // 在某個時機下 執行 stop() 停止watchEffect監聽
  if (...) { stop() }
  return {
    ...toRefs(state),
  }
}

七、 Vue3中移除的一些API和方法

7.1 取消KeyboardEvent.keyCode

Vue2.x中,綁定鍵盤事件會用到如下代碼:

<!-- keyCode version -->
<input v-on:keyup.13="submit" />

<!-- alias version -->
<input v-on:keyup.enter="submit" />

或者是:

Vue.config.keyCodes = {
  f1: 112
}
<!-- keyCode version -->
<input v-on:keyup.112="showHelpText" />

<!-- custom alias version -->
<input v-on:keyup.f1="showHelpText" />

在事件中,給keyup配置一個指定按鈕的keyCode(數字)在Vue3中將不會生效,但是依然可以使用別名,例如:

<input v-on:keyup.delete="confirmDelete" />

7.2 移除 $on,$off 和 $once方法

Vue2.x中可以通過EventBus的方法來實現組件通信:

var EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
...
this.$EventBus.$on()  this.$EventBus.$emit()

這種用法在Vue3中就不行了,在Vue3中移除了 $on,$off等方法(參考rfc),而是推薦使用mitt方案來代替:

import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// fire an event
emitter.emit('foo', { a: 'b' })

7.3 移除filters

Vue3中,移除了組件的filters項,可以使用methods的或者computed來進行替代:

<template>
  <p>{{ accountBalance | currencyUSD }}</p>
</template>
<script>
  export default {
    filters: {
      currencyUSD(value) {
        return '$' + value
      }
    }
  }
</script>

替換為:

<template>
  <p>{{ accountInUSD }}</p>
</template>
<script>
  export default {
    props: {
      accountBalance: {
        type: Number,
        required: true
      }
    },
    computed: {
      accountInUSD() {
        return '$' + this.accountBalance
      }
    }
  }
</script>

八、Vue3中改變的API和寫法

8.1 實例初始化

vue2.x中通過new Vue()的方法來初始化:

import App from './App.vue'
new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

vue3Vue不再是一個構造函數,通過createApp方法初始化:

import App from './App.vue'
createApp(App).use(store).mount('#app')

8.2 全局API調用方式改變

Vue2.x中,大部分全局API都是通過Vue.xxx或者Vue.abc()方式調用,例如:

  import Vue from 'vue'
  Vue.mixin()
  Vue.use()

而在Vue3中,這些方式將會改變,取而代之的是如下:

  import { createApp } from 'vue'
  const app = createApp({})
  app.mixin()
  app.use()

同時,可以只引入一些需要的API,不需要的不用引入,這樣也符合Three Shaking的要求,例如:

  import { nextTick,reactive,onMounted } from 'vue'
  nextTick(() => {
  })
  onMounted(() => {
  })

由於Vue3中全局API都會通過app.xxx的方法調用,所以之前通過Vue.prototype.xxx綁定的全局方法和變量將無法使用,可以采用如下方式來代替:

  //在main.js中:
  app.config.globalProperties.http = function(){}

  //在vue組件中:
  this.http()

8.3 render方法修改

Vue2.x中,有時會自定義render方法來返回模板內容,如下:

export default {
  render(h) {
    return h('div')
  }
}

Vue3中,h通過vue來引入,如下:

import { h } from 'vue'
export default {
  render() {
    return h('div')
  }
}

8.4 新的異步組件創建方式

Vue2.x中,尤其是在Vue Router中,會經常使用到異步組件,借助webpack的打包方式,可以將一個組件的代碼進行異步獲取,例如:

const asyncPage = () => import('./NextPage.vue')
const asyncPage = {
  component: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
}

Vue3中,提供了defineAsyncComponent()方法創建異步組件,同時可以返回一個Promise對象來自己控制加載完成時機,如下:

import { defineAsyncComponent } from 'vue'
const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
})
const asyncComponent = defineAsyncComponent(
  () =>
    new Promise((resolve, reject) => {
      /* ... */
    })
)

九、暫時性的結尾:

  1. 寫了大半天,寫的有點啰嗦,新API基本都寫了幾種編碼方式,根據自己的愛好取舍
  2. 后續慢慢加入自己對一些新API認識及用法
  3. 本編文章部分內容參考鏈接有:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM