性能提升
- 打包大小減少41%
- 初次渲染快55%,更新快133%
- 內存使用減少54%
- 原因:重寫虛擬dom的優(yōu)化和treeshaking的優(yōu)化
Componsition API(API組合)
- ref 和 reactive
- computed 和watch
- 新的生命周期函數(shù)
- 自定義函數(shù)--hooks函數(shù)
- Teleport - 瞬移組件的位置
- Suspense - 異步加載組件的新福音
- 全局API的修改和優(yōu)化
- 更多的誓言性特性
更好的Typescript 支持
- vue3.0源碼就是ts寫的
為什么要有vue3.0?
vue2 遇到的難題
- 隨著功能的增長,復雜組件的代碼變得難以維護
Mixin 的缺點
- 命名沖突
- 不清楚暴露吃了變量的作用
- 重用到其他component經(jīng)常會遇到問題
對typeScript 的支持非常有限
- 原因是:依賴this指向上下文油额,主要是沒考慮的ts的集成
總結:vue3 完美解決上訴2.0的問題
ts+ vue Cli 項目搭建環(huán)境
- 選擇多項選擇可以更好的支持ts
- 之后選擇版本3.0就行
安裝好用的vscode插件
- eslint 語法檢查
3 vue3.0新特性介紹
3.1 ref的妙用
通過vue 3.0實現(xiàn)一個計數(shù)器
- 使用ref一般傳入的是原始值谆焊,比如count初始化為0
- setup函數(shù)中定義的對象及方法都是響應式的
<template>
<h1>{{ count }}</h1>
<h1>{{ double }}</h1>
<button @click="increase">點擊+1</button>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from "vue";
// 實現(xiàn)計數(shù)器
export default defineComponent({
name: "App",
setup() {
// 方法和ref,computed的api的使用
// 無法使用this
// 創(chuàng)建響應式對象 count茸歧,但是模板上通過ref會直接將里面的value展示出來
const count = ref(0);
// computed是一個函數(shù),參數(shù)是回調
const double = computed(() => {
return count.value * 2;
});
// 定義一個函數(shù)暖夭,count是對象睦授,所以要寫value++
const increase = () => {
count.value++;
};
// 導出count
return {
count,
increase,
double,
};
},
});
</script>
- 注意:setup方法中無法使用this
- 在setup 方法中定義的函數(shù)两芳,變量需要使用return導出才能在template中使用
- 通過refAPI定義的變量,是一個對象去枷,操作值得時候需要操作value
3.2 reactive函數(shù)的用法
- 將上面的計算器代碼優(yōu)化下
<template>
<h1>{{ count }}</h1>
<h1>{{ double }}</h1>
<button @click="increase">點擊+1</button>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
// 定義data的數(shù)據(jù)類型
interface DataProps {
count: number;
double: number;
increase: () => void;
}
// reactive 也是一個函數(shù)盗扇,可以批量歸類變量
// 實現(xiàn)計數(shù)器
export default defineComponent({
name: "App",
setup() {
定義一個reactive對象
const data: DataProps = reactive({
count: 0,
increase: () => {
data.count++;
},
double: computed(() => data.count * 2),
});
// 導出data,正常展示count
// return {
// data,
// };
// 使用toRefs將數(shù)據(jù)變成響應式對象
const refData = toRefs(data);
// 優(yōu)化展示,取出來就不是響應式 使用toRefs函數(shù)將數(shù)據(jù)變成響應式對象,數(shù)據(jù)改變模板更新
// 點擊無反應
// return {
// ...data,
// };
//
return {
...refData,
};
},
});
- 使用reactive可以將變量及函數(shù)整理在一個對象中
- 然后在setup中導出data,zai template中使用data.count調用
- 當使用es6的結構方法優(yōu)化時祷肯,會出現(xiàn)失去響應式的情況,模板無法更新
- 結合toRefs將數(shù)據(jù)變成響應式對象
- 使用reactive 的同時記得使用toRefs函數(shù)變成響應式屬性
3.3 vue3響應式對象的新花樣
使用es6 proxy 實現(xiàn)響應式疗隶,完美支持數(shù)組和對象的改變響應式
// vue2.0
Object.defineProperty(data,'count',{
get(){},
set(){}
})
// vue3
new proxy(data,{
get(key){},
set(key,value){}
}
)
const data: DataProps = reactive({
count: 0,
increase: () => {
data.count++;
},
double: computed(() => data.count * 2),
numbers: [1, 2, 3],
person: {},
});
// vue 2.0 直接修改值不更新 3.0 直接修改會是響應式
data.person.name = "violet";
data.numbers[0] = 8;
3.3 生命周期
生命周期名字替換: 更好的語義化
beforeDestory => beforeUnmount
destroyed => unmounted :銷毀完畢
- 這些生命周期在setup中使用佑笋,需要加'on'關鍵字
- setup函數(shù)是跟create的一起運行的,只會執(zhí)行一次
setup() {
onMounted(() => {
console.log("mounted");
});
// 點擊事件更新數(shù)據(jù)
onUpdated(() => {
console.log("update");
});
// 調式
onRenderTracked(() => {
console.log("onRenderTracked");
}),
// 更新時斑鼻,才會觸發(fā)蒋纬,記錄了那些值發(fā)生了變化
onRenderTriggered((event) => {
console.log(event, "onRenderTriggered");
});
}
3.4 watch 偵測變化
- watch 函數(shù),前面是監(jiān)聽的值坚弱,后面是執(zhí)行的方法
- setup跟created只執(zhí)行一次蜀备,所以,點擊值發(fā)生變化荒叶,不會去更新title
<template>
<el-button @click="upDatedGreeting">點擊</el-button>
</template>
<script lang="ts">
setup() {
const greetings = ref("");
const upDatedGreeting = () => {
greetings.value += "violet";
};
// watch 回調函數(shù)里面接入兩個參數(shù)碾阁,變化值及舊的值
watch(greetings, (newval, oldavl) => {
console.log("new", newval, "old", oldavl); // new violet old
document.title = "1" + greetings.value;
});
// watch 監(jiān)聽多個值,第一個參數(shù)可以是數(shù)組
watch([greetings, data], (newval, oldavl) => {
console.log("new", newval, "old", oldavl); // new violet old
document.title = "1" + greetings.value;
}); //["violet", Proxy]0: "violet"1: Proxy {count: 1, double: ComputedRefImpl, numbers: Array(3), person: {…}, increase: ?}length: 2__proto__: Array(0)
}
</script>
- 也可以監(jiān)聽data里面的某個變量
注意直接監(jiān)聽 data.count會失去響應式
at; 可以將數(shù)組里面的第二個參數(shù)變成getter函數(shù)
// 會報警告Invalid watch source: 0 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
watch([greetings, data.count], (newval, oldavl) => {
console.log("new", newval, "old", oldavl);
document.title = "1" + greetings.value;
});
- 此時根據(jù)警告提示些楣,可以使用getter函數(shù)
watch([greetings, () => data.count], (newval, oldavl) => {
console.log("new", newval, "old", oldavl); // 0: "violetviolet" 1:0
document.title = "1" + greetings.value;
});
3.5 使用模塊化
- 實現(xiàn)鼠標追蹤器
- 效果脂凶,點擊鼠標更新鼠標位置
- 使用ref實現(xiàn)
// helloword
<template>
<div class="hello">
<h1>X:{{ x }} Y:{{ y }}</h1>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "HelloWorld",
setup() {
const x = ref(0);
const y = ref(1);
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
onMounted(() => {
document.addEventListener("click", updateMouse);
});
onUnmounted(() => {
document.removeEventListener("click", updateMouse);
});
return {
x,
y,
};
},
});
</script>
- 將這個功能模塊化使用
- 在src下新建hooks文件夾
// 新建userMousePosition.ts
import { onMounted, onUnmounted, ref } from 'vue'
function useMousePosition() {
const x = ref(0);
const y = ref(0);
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
onMounted(() => {
document.addEventListener("click", updateMouse);
});
onUnmounted(() => {
document.removeEventListener("click", updateMouse);
});
// 導出x,y
return {
x,
y,
};
}
export default useMousePosition
- 在helloword中引用
<template>
<div class="hello">
<h1>X:{{ x }} Y:{{ y }}</h1>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
// 引入函數(shù)
import useMousePosition from "../hooks/userMousePosition";
export default defineComponent({
name: "HelloWorld",
setup() {
// 得函數(shù)返回x,y
const { x, y } = useMousePosition();
// 導出使用
return {
x,
y,
};
},
});
</script>
- 使用reactive改造這個模塊
// userMousePosition.ts
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'
function useMousePosition() {
const data = reactive({
x: 0,
y: 0,
})
const updateMouse = (e: MouseEvent) => {
data.x = e.pageX
data.y = e.pageY
}
onMounted(() => {
document.addEventListener('click', updateMouse)
})
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
// 注意使用toRefs將data轉換成響應式數(shù)據(jù)
const refsData = toRefs(data)
return {
...refsData,
}
}
export default useMousePosition
3.6 hooks模塊化理解
- 異步請求模塊
- 在src/hooks中新建useUrlLoader.ts文件
import { reactive, toRefs } from 'vue'
import axios from 'axios'
function useURLloader(url: string) {
const data = reactive({
result: null,
loading: true,
loaded: false,
error: null,
})
axios
.get(url)
.then(res => {
data.loading = false
data.result = res.data
data.loaded = true
})
.catch(err => {
data.error = err
data.loading = false
})
const refsData = toRefs(data)
return {
...refsData,
}
}
export default useURLloader
- 在loading.vue中使用
<template>
<div class="box">
<h1 v-if="loading">Loading!...</h1>
<img v-if="loaded" :src="result.message" alt="" />
<p v-if="error">{{ error }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import useURLloader from '../hooks/useUrlLoader'
export default defineComponent({
name: 'isLoading',
setup() {
const { result, loading, loaded, error } = useURLloader(
'https://dog.ceo/api/breeds/image/random'
)
return {
loading,
loaded,
result,
error,
}
},
})
</script>
-
結果
2. 模塊化結合ts --- 泛型改造
報錯(待解決): result: <T | null>null ==
error Use 'as T | null' instead of '<T | null>'
// useUrlLoader.ts
import { reactive, toRefs } from 'vue';
import axios from 'axios';
function useURLloader<T>(url: string) {
const data = reactive({
result: <T | null>null,
loading: true,
loaded: false,
error: null,
});
axios
.get(url)
.then(res => {
data.loading = false;
data.result = res.data;
data.loaded = true;
})
.catch(err => {
data.error = err;
data.loading = false;
});
const refsData = toRefs(data);
return {
...refsData,
};
}
export default useURLloader;
- 在頁面中使用
<template>
<div class="box">
<h1 v-if="loading">Loading!...</h1>
<img v-if="loaded" :src="result.message" alt="" />
<p v-if="error">{{ error }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue';
import useURLloader from '../hooks/useUrlLoader';
// 提前將接口返回的參數(shù)類型定義好
interface UrlProps {
message: string;
state: string;
}
export default defineComponent({
name: 'isLoading',
setup() {
const { result, loading, loaded, error } = useURLloader<UrlProps>(
'https://dog.ceo/api/breeds/image/random'
);
watch(result, () => {
if (result.value) {
console.log(result.value.message, 'value');
}
});
return {
loading,
loaded,
result,
error,
};
},
});
</script>
3.7 ts對vue3的加持
- defineComponent服務ts 而存在的
- 兼容vue2.0的3.0的語法提示
3.8 Teleport - 瞬間移動(新增加標簽)
- 場景:全局彈窗的使用,使用v-if
- 問題:dialog 被包裹在其他組件之中愁茁,容易被干擾
-樣式問題邊的容易混亂 - 使用<Teleport></Teleport>
// 在index.html 中新建一個id為model的根節(jié)點與app層級一樣
// index.html
<div id="model"></div>
// model 中掛載到model節(jié)點上
<teleport to="#model"></teleport>
- 新建model
// model.vue
<template>
<teleport to="#model">
<div class="center" v-if="isOpen">
<h2><slot>this is a modal</slot></h2>
<button @click="buttonClick">Close</button>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
isOpen: Boolean,
},
emits: {
// 向外發(fā)送的事件名稱蚕钦,并且驗證函數(shù)參數(shù)
// 'close-modal': (payload: any) => {
// return payload.type === 'close';
// },
'close-modal': null, // 這個事件不需要驗證
},
setup(props, context) {
// 驗證發(fā)送參數(shù)是否正確
const buttonClick = () => {
context.emit('close-modal', false);
// context.emit('close-modal', { type: 'close' });
};
return {
buttonClick,
};
},
});
</script>
<style>
.center {
width: 200px;
height: 200px;
border: 2px solid #eee;
background: white;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
</style>
- 子父組件調用
<template>
<model :isOpen="modelIsOpen" @close-modal="openModel">我的彈窗</model>
<el-button @click="openModel(true)">打開彈窗</el-button>
</template>
<script lang="ts">
import Model from './components/Model.vue';
import { defineComponent,ref} from 'vue';
export default defineComponent({
name: 'App',
components: { HelloWorld, Loading, Model },
setup() {
const modelIsOpen = ref(false);
const openModel = (val: boolean) => {
modelIsOpen.value = val;
};
return {
openModel,
modelIsOpen,
};
})
</script>
3.9 Suspense - 異步請求
語法:
<Suspense>
<template #default>
<!-- <async-show /> -->
<dog-show />
</template>
<template #fallback> <h1>加載中.....</h1></template>
</Suspense>
- 異步組件的困境
- Suspense 是Vue3推出的一個內置的特殊組件
- 如果使用Suspense,要返回一個promise
- 使用Suspense實現(xiàn)一個簡單異步請求
//AsyncShow.vue
<template>
<div>
<h1>{{ result }}</h1>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
return new Promise(resolve => {
setTimeout(() => {
return resolve({
result: 42,
});
}, 3000);
});
},
});
</script>
- 父組件調用
import AsyncShow from './components/AsyncShow.vue';
// 支持具名插槽,第一個default是異步請求data鹅很,#fallback是數(shù)據(jù)會返回前展示的data
<Suspense>
<template #default>
<async-show />
</template>
<template #fallback> <h1>加載中.....</h1></template>
</Suspense>
- 結果如下:先出現(xiàn)加載中.....,3s之后出現(xiàn)42
2.Suspense支持多個請求
- 新建一個DogShow.vue 組件
// DogShow.vue
<template>
<div>
<img :src="result && result.message" alt="" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import axios from 'axios';
export default defineComponent({
// 里面可以采用await
async setup() {
const res = await axios.get('https://dog.ceo/api/breeds/image/random');
return {
result: res.data,
};
},
});
</script>
- 在父組件中引用
import DogShow from './components/DogShow.vue';
import AsyncShow from './components/AsyncShow.vue';
<Suspense>
<template #default>
<div>
<async-show />
<dog-show />
</div>
</template>
<template #fallback> <h1>加載中.....</h1></template>
</Suspense>
// 展示錯誤
<p>{{ error }}</p>
- 結果如下嘶居,先出現(xiàn)加載中....,等兩個異步組件成功返回展示對應的數(shù)據(jù)
- 如果有一個異步請求失敗,可以使用onErrorCaptured去監(jiān)聽錯誤
import { defineComponent, onErrorCaptured, ref } from 'vue';
export default defineComponent({
name: 'App',
components: { HelloWorld, Model, AsyncShow, DogShow },
setup() {
const error = ref(null);
// 通過這個生命周期函數(shù)可以監(jiān)聽Suspense里面的錯誤
onErrorCaptured((err: any) => {
error.value = err;
return true;
});
error,
};
- 結果展示如下:
AsyncShow展示42促煮,DogShow接口失敗邮屁,報錯
3.10 Global API Change(全局API修改)
Vue2全局API遇到的問題
- 在單元測試中,全局配置非常容易污染全局環(huán)境
- 在不同的apps中菠齿,共享一份有不同配置的Vue對象佑吝,也變的非常困難
vue3.0 解決了直接修改vue,防止污染
- 全局配置修改:
- Vue.config => app.config
- config.productionTip 被刪除
- config.ignoredElements 改名為 config.isCustomElement
- config.keyCodes被刪除
- 全局注冊類API
- Vue.component -> app.component
- Vue.directtive -> app.directtive
- 行為類API
- Vue.mixins -> app.mixins
- Vue.use -> app.use
- Global API Treeshaking
// vue2 全局導入,API掛載在Vue原型上
import Vue from 'vue'
Vue.nextTick(()=> {})
const obj = Vue.observable()
// vue3 實名導出泞当,方便Treeshaking
import Vue,{nextTick,observable} from 'vue'
Vue.nextTick // undefined
nextTick(()=> {})
const obj = observable()