這篇文章是我在 vue官網 以及一些其餘地方學習v3的一些學習記錄與心得,若是有理解不到地方歡迎你們指正。javascript
在v3版本中將data的返回值進行了標準化,只接受返回Object的Function, 而在v2版本中同時支持返回Object和返回Object的Function。html
v3版本中關於Object的合併是覆蓋,而不是合併。前端
const mixinObject = {
data(){
return {
user:{
id:1,
name:'jack'
}
}
}
}
const CompA = {
mixins: [mixinObject],
data() {
return {
user: {
id: 2
}
}
}
}
// 結果是
user: {
id: 2
}
複製代碼
在v2版本中,一般使用$on, $off來實現全局事件總線,使用$once綁定監聽一個自定義事件,其實這些在實際項目中用到的也不會特別多,在v3版本移除後,能夠經過一些外部庫來實現相關需求。例如 mittvue
v3版本中刪除了過濾器, {{ calcNum | filter }}
將不在支持,官方建議使用computed
屬性替換過濾器(well done ~)。java
在v3版本中支持了能夠有多個根節點的組件,能夠減小節點層級深度。但也但願開發者可以明確語義。react
<template>
<header></header>
<main></main>
<footer></footer>
</template>
複製代碼
在將v3版的變化以前咱們先來回顧一下v2版本的函數式組件,它有兩種建立方式: functional
attribute 和 { functional : true }
選項,代碼分別以下git
// attribute 方式
<template functional>
<img :src="src" />
</template>
<script> ... </script>
// 選項方式
export default = {
functional:true,
props:{
...
},
render:function(createElement,context){
...
/** context傳遞了一些參數: props,slots,parent,listeners,injections **/
}
}
複製代碼
v2版本中組件有兩種組件類型:有狀態組件和函數式組件(無狀態組件),相對於有狀態組件,函數式組件不須要被實例化,無狀態,沒有生命週期鉤子,它們的渲染速度遠高於有狀態組件。每每一般被適用於功能單一的靜態展現組件從而優化性能。除此以外,函數式組件還有返回多個根節點的能力。github
在v3版本中,有狀態組件和函數式組件之間的性能差別已經大大減小,在大部分場景下幾乎能夠忽略不計。因此函數式組件惟一的優點就在一返回多節點的能力,但這種一般運用比較少,且組件每每比較簡單,具體語法糖以下:vue-cli
// 函數建立
import { h } from 'vue'
const DynamicHeading = (props, context) => {
// context是一個包含 attrs,slots,emit的對象
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
// 單文件組件(SFC)
<template>
<component v-bind:is="`h${$props.level}`" v-bind="$attrs" />
</template>
<script> export default { props: ['level'] } </script>
複製代碼
v3版本中新增了一個新的全局APIcreateApp
,經過ES模塊的方式引入。調用createApp
返回一個應用實例,該應用實例暴露出來全局API,這是Vue3的新概念,主要解決了不一樣"app"之間可以共享資源(配置,全局組件,指令等等)。咱們來對比一下v3和v2分別建立應用的改變。typescript
// v2 建立
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
// v3 建立
import { createApp } from 'vue'
import App from './App.vue'
var app = createApp(App);
app.mount('#app')
複製代碼
在"app"之間共享配置的一種方式是工廠模式:
import { createApp } from 'vue'
import App1 from './App1.vue'
import App2 from './App2.vue'
const createMyApp = options => {
const app = createApp(options)
app.directive('focus' /* ... */)
return app
}
createMyApp(App1).mount('#foo')
createMyApp(App2).mount('#bar')
複製代碼
v3將能夠全局改變Vue行爲的API從原來的Vue構造函數上轉移到了實例上了。列表以下
v2全局API | v3全局API |
---|---|
Vue.config | app.config |
Vue.config.productionTip | 移除 |
Vue.config.ignoredElements | app.config.ignoredElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
除此以外,還有一些全局API和內部組件都作了重構,考慮到tree-shaking,只能經過ES模塊的方式導入,意味着當你的應用程序中沒用到的組件和接口都不會被打包。受影響的API列表以下:
// 錯誤使用
import Vue from 'vue'
Vue.nextTick(() => {
// 一些和DOM有關的東西
})
// 正確使用
import { nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有關的東西
})
複製代碼
重頭戲!!!
這是v3中的新概念,vue給咱們提供了一些新的api,這些新的api在一個地方使用,而這個地方就是vue給咱們新提供了一個從組件初始化到銷燬的過程當中咱們可以作一些事情的地方,我將之理解爲鉤子函數——Setup
,在Setup裏,你能夠作本來你能在vue組件裏面能作的全部事情,好比你在created
,mounted
,computed
,watch
,methods
裏面作的全部事情,都能在Setup
裏面完成,那要怎麼才能作到呢,就是要經過vue提供的那些新的API。
那這個東西主要解決什麼事情呢?咱們都知道組件的做用就是對功能的提取以及代碼的複用。這使得咱們的程序在靈活性和可維護性上能走的更遠,可是這還不夠,當一個組件內部的邏輯很複雜的時候,咱們將邏輯分別散落在 created
,mounted
,methods
,computed
,watch
裏面,而後整個文件代碼長達好幾百行。這對於沒有編寫這個組件的人來講,嘗試去理解這些邏輯代碼無疑是最頭疼的事情。而Setup
正是解決了這個事情,將全部的邏輯都集中起來在Setup
中處理。從今之後,當你想製造一輛汽車的時候,你不再用去全世界進口各類零件,你在Setup
工廠中就能完成。
讓咱們來見識一下Setup
和那些新的API
的使用以及做用(據說這種東西才被稱之爲乾貨???):
若是你聽懂了我上面所說的,那我開局這麼寫你應該也能理解了:
<template>
<div class="demo"> </div>
</template>
<script> export default { name:"Demo", data(){ return {} }, setup () { console.log('run'); } } </script>
// 當運行起來打印了run
複製代碼
Setup
能夠返回一個對象,你能夠在組件的其餘地方訪問這個對象中的屬性。
注意:在執行
Setup
的時候還沒有建立組件實例,因此在Setup
中沒有this
。不過它提供了兩個接收參數——props
和context
。在Setup
中沒法訪問組件中其餘的任何屬性。
// 調用Demo組件
<Demo :name="'jac" a="1" b="2"></Demo>
// Demo 組件
<template> <div class="demo"> </div> </template>
<script> export default { name:"Demo", props:{ name:String, }, data(){ return {} }, setup (props,context) { console.log('props',props); // {name:jac} console.log('attrs', context.attrs); // {a:1,b:2} console.log('slots', context.slots); // {} console.log('emit', context.emit); // function let shareProp = 'hallo,v3'; // 返回兩個屬性 shareProp, changeProp return { shareProp } }, mounted() { this.shareProp = 'changed'; }, } </script>
複製代碼
咱們會發現試圖並無更新,咱們發現setup
返回的對象不是響應式的,響應式咱們應該不陌生,在data()選項中的property都是響應式的,那是應爲Vue在組件初始化的過程當中就已經對data()中的property建立了依賴關係。因此當property發生變化時,視圖即會自動更新,這就是響應式。那怎麼讓它變成響應式的呢?
咱們能夠經過ref
,reactive
建立響應式狀態,
咱們使用ref
能夠建立基礎數據類型和複雜數據類型的響應式狀態,使用reactive
只能建立複雜數據類型的響應式狀態,若是使用建立數據類型不正確,控制檯會給出對應的警告value cannot be made reactive: **
。那ref
和reactive
的區別到底在哪裏呢?
const refNum = ref(10);
const refObj = ref({
name:'jac',
age:20
});
const reactiveNum = reactive(10);
const reactiveObj = reactive({
name: 'jac',
age: 20
})
console.log('refNum',refNum);
console.log('refObj',refObj);
console.log('reactiveNum', reactiveNum);
console.log('reactiveObj', reactiveObj);
複製代碼
結果以下:
當ref
建立的是複雜數據類型的時候內部其實也是用reactive建立的。因此ref
也是能夠建立複雜數據類型的響應狀態的,只是在setup
中寫法會有所不一樣。
setup(){
const refObj = ref({
name:'jac',
age:20
});
const reactiveObj = reactive({
name: 'jac',
age: 20
})
// ref 方式的更新
const changeRefObj = () => {
refObj.value.name="mac"
}
// reactive 方式的更新
const changeReactiveObj = () => {
reactiveObj.name = 'mac'
}
return {
...
}
}
複製代碼
注意: 經過
ref
對值進行了包裹,在Setup
中你須要使用變量.value的方式進行訪問和設置值,從Setup
中暴露出去的對象你能夠直接經過this.變量訪問。
<template>
...
</template>
<script> import { ref,reactive } from 'vue'; export default { ... setup (props,context) { ... let shareProp = ref('hallo,v2'); let info = reactive({name:'jac'}); const changeProp = ()=>{ shareProp.value = 'hallow,v3'; } return { shareProp, ... } }, mounted() { console.log(this.shareProp) }, } </script>
複製代碼
小夥伴能夠根據本身的編碼習慣選擇運用。
有時候咱們想經過解構的方式從一個複雜的響應式變量中剝離出一些變量時,咱們的代碼多是這樣的:
<template>
<button @click="changeObj">my name is {{info.name}}</button>
</template>
<script> import { ref, provide, reactive } from 'vue'; export default { name: 'Demo', setup(){ const info = reactive({name:'jac',sex:'男',age:18}); return { info } }, methods:{ changeObj(){ let { name,sex } = this.info; name = 'make' // 視圖不會更新 } } } </script>
複製代碼
這樣會使咱們解構出來的兩個property失去響應性,這個時候咱們須要使用toRef
和toRefs
從中解構出來,toRef
用於解構出單個property的響應式變量,toRefs
是將源對象中全部property都建立響應式變量,在經過解構的方式建立咱們對應的變量。
<template>
<button @click="changeObj">my name is {{info.name}}</button>
</template>
<script> import { ref, provide, reactive, toRef, toRefs} from 'vue'; export default { name: 'Demo', setup(){ const info = reactive({name:'jac',sex:'男',age:18}); return { info } }, methods:{ changeObj(){ // toRef // let name = toRef(this.info,'name'); // arg1: 源對象, arg2: 屬性名稱 // name.value = 'make' // toRefs // let { name } = toRefs(this.info); // arg1: 源對象 // name.value = 'make' // 視圖成功刷新 } } } </script>
複製代碼
在Setup
中咱們還能watch
屬性,建立獨立的computed
,還能夠註冊各類生命週期鉤子,因爲Setup
執行的階段是圍繞beforeCreate
和created
和進行的,因此本來在這兩個生命週期中作的事情都可以放在Setup
中處理。
<template>
...
</template>
<script> import { ref, toRefs, watch, computed, onMounted } from 'vue'; export default { name:"Demo", props:{ name:String, }, ... setup (props,context) { console.log('我首先執行'); // 經過toRefs方法建立props的響應式變量 const { name: listenName } = toRefs(props); const count = ref(0); const changCount = ()=>{ count.value++; } const totalCount = computed(() => { return `總計數:${count.value}` }) // watch 會監聽listenName變量,當listenName變量改變的時候會觸發回調方法 watch(listenName,(newValue, oldValue)=>{ console.log(listenName.value); }) onMounted(()=>{ console.log('onMounted也執行了,結果輸出以下'); console.log(count.value); changCount(); console.log(totalCount.value) }) return { count, changCount, totalCount, } }, created() { console.log('created執行了'); }, mounted() { console.log('mounted執行了'); }, } </script>
// 輸出結果以下:
我首先執行
created執行了
onMounted也執行了,結果輸出以下
0
總計數:1
mounted執行了
複製代碼
看的出來,Setup
中註冊的生命週期鉤子函數要比外面註冊的鉤子函數先執行!
provide
和inject
一般狀況下咱們在業務場景中使用很少,但在寫插件或組件庫的時候仍是有用的,provide
和inject
用於在"祖"組件提供屬性,在子組件中注入使用。在setup中怎麼使用呢?
// demo組件中提供
<template>
<Test />
</template>
<script> import { provide } from 'vue'; export default { name:"Demo", setup (props,context) { provide('name','jac') }, } </script>
// test組件中注入使用
<template> ... </template>
<script> import { inject } from 'vue'; export default { name:"Test", setup (props,context) { console.log(inject('name')); // jac }, } </script>
複製代碼
以上是setup
的基本應用,須要好好的感覺它還須要在多種場景下去實際運用,才能更好的理解它棒在哪裏,好比下面這段代碼:
// a.js
import { ref, onMounted } from 'vue'
export default function doSomething(refName,fn){
const a = ref(0);
const b = ref(0);
// ...,
onMounted(() => {
fn();
})
return {
a,
b
}
}
//b.js
import doSomething from './a.js';
setup(){
const {a,b} = doSomething('box',()=>{
console.log('執行');
})
}
複製代碼
我隨便舉的一個例子,假設你有一段邏輯在多個頁面都須要用到,那咱們能夠將這段邏輯抽離出來,這樣讓咱們的代碼更精簡,咱們不只可讓組件複用,在也能更大幅度的提升一些業務代碼的複用,還能集中處理業務代碼,我相信對於咱們的開發體驗仍是代碼質量,都大有裨益。
v3中改變了v-model默認屬性值和觸發方法,value
=>modelVale
,input
=>update
。在自定義的組件中,容許咱們同時設置多個v-model。
// Demo.vue
export default {
name: "Demo",
props: {
title:String,
label:String
},
methods: {
titleEmit(value) {
this.$emit('update:title', value)
},
labelEmit(value) {
this.$emit('update:label', value)
}
},
}
// 調用Demo.vue
<Demo v-model:title="" v-model:label="" />
複製代碼
v3推薦使用Typescript
,Typescript
不只支持ES6
的特性,還具有類型推導,能夠幫助咱們在開發的過程當中就避免不少類型錯誤,在前端愈來愈複雜的如今,Typescript
可以支撐應用走的更遠,在可維護性和擴展性上都更有優秀,擁抱改變。
怎麼建立vue3+typescript的應用呢?
1.用vue-cli
yarn global add @vue/cli@next
# OR
npm install -g @vue/cli@next
// 下面循序漸進的建立
複製代碼
2.採用vite(我的推薦,簡單省事)
yarn create vite-app my-vue-ts --template vue-ts
複製代碼
這一節咱們就從應用層面簡單的瞭解了一下v3的一些改變,沒有深挖原理與實現。
v3 一鏡999次 卡!