搜集資源
Vue3 入門指南與實戰案例
Vue在線演練場
Vue3.0中Ref與Reactive的區別是什么
Composition API RFC
Vue.js 中使用defineAsyncComponent 延遲加載組件
2022年必會Vue3.0學習 (強烈建議)
Vue2與Vue3的對比
- 對TypeScript支持不友好(所有屬性都放在了this對象上,難以推倒組件的數據類型)
- 大量的API掛載在Vue對象的原型上,難以實現TreeShaking。
- 新推出了CompositionAPI
- 更方便的支持了 jsx
- Vue 3 的 Template 支持多個根標簽,Vue 2 不支持
- 對虛擬DOM進行了重寫、對模板的編譯進行了優化操作
24. 在less中如何使用響應式變量
width:v-bind(styleProgressWidth);
23. @eventName="foo" 后面有無括號的差別
@click有括號:傳指定參數
@click無括號:默認傳event參數
22. 判斷slot命名插槽是否定義,定義才展示
<slot v-if="$slots.customButton" name="customButton"></slot>
<Button v-else :type="buttonType" :disabled="disabled" :loading="loading">
<template #icon><UploadOutlined /></template> {{ btnText }}
</Button>
21. 用toRefs解構ref定義的對象會丟失響應式
<MaterialModal
ref="materialModalRef"
title="選擇素材"
:contentId="materialModalInfo.contentId"
:isEdit="materialModalInfo.isEdit"
:onSave="handleMaterialSave"
:validateMaterialMaxLimit="validateMaterialMaxLimit"
/>
// 處理點擊添加內容素材按鈕事件
const handleAddMaterial = () => {
// 重點,不會立即生效,在nextTick之后才會生效
materialModalInfo.isEdit = false;
console.log('handleAddMaterial', materialModalInfo);
handleOpenMaterialModal();
};
// 素材彈窗引用
const materialModalRef = ref();
// 打開素材彈窗
const handleOpenMaterialModal = () => {
nextTick(() => {
const initFormData = materialModalInfo.isEdit ? chooseMaterialList.value : [];
materialModalRef.value?.init(initFormData, TYPE.picture, ALL_TYPES.slice(1));
});
};
20. 用toRefs解構ref定義的對象會丟失響應式
import {toRefs} from 'vue';
const foo=ref({bar:'xxx'});
const {bar}=toRefs(foo);
<template>{{bar}}</template>
19. 獲取更加詳細的報錯
app.config.errorHandler = (err, vm, info) => {
console.log('[全局異常]', err, vm, info)
}
18. useVModel語法糖
import { useVModel } from '@vueuse/core'
const props = defineProps<{
modelValue: string
}>();
const emit = defineEmits(['update:modelValue']);
// 帶有類型定義的寫法
const emit = defineEmits<{
(e: 'update:value', params: any): void;
}>();
const data = useVModel(props, 'modelValue', emit);
17. vue組件 即便用withDefaults設置了默認值, TProps對屬性要寫設置為可選,否則會有告警提示
16. keep-alive用法示例
<template>
<router-view v-slot="{ Component }">
<transition>
<keep-alive :include="includeList">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</template>
<script lang="ts"> export default {name:'AppEntry'} </script>
<script lang="ts" setup>
const router = useRouter();
const includeList = ref(['AuthByManagerList']);
router.beforeEach((to, from) => {
if (from.path === '/authByManagerResult' && to.path === '/authByManagerList') {
// 從客戶認證結果頁跳列表頁,要清除緩存
includeList.value = [];
} else if (from.path === '/authByManagerList' && to.path === '/authByManagerDetail') {
// 從列表頁進入詳情頁,要緩存列表頁
includeList.value = ['AuthByManagerList'];
}
});
</script>
15.vue3中的props在模板中會同步更新,在setup函數中只更新一次。當props值再次發生改變是,在setup中要用watch才能監聽到props的變化,如果props某個屬性是對象,要設置深度監聽,才能監聽到變化。
14.v-model可以定義多個
<van-list
v-model:loading="loading"
v-model:error="error"
error-text="請求失敗,點擊重新加載"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item" :title="item" />
</van-list>
13.context的屬性
setup(props, context) {
context.attrs
context.slots
context.parent
context.root
context.emit
context.refs
return {
}
}
12.computed屬性完整寫法
watch的套路是:既要指明監聽的屬性,也要指明監聽的回調函數。
watchEffect的套路是:不需要指明監聽的屬性,監聽中的回調函數用到了那個屬性,就監聽那個屬性。
watchEffect跟computed有點像:
computed注重是計算出來的值,所以必須要有返回值。
watchEffect更注重是過程,所以不用寫返回值。
const person = reactive({
fistName:"Mr",
lastName:"long"
})
// 計算屬性簡寫
let fullName = computed(()=>{
return person.fistName + '-' + person.lastName
})
// 計算屬性完整寫法
let fullName = computed({
get(){
return person.fistName + '-' + person.lastName
},
set(value){
const newArr = value.split('-')
person.fistName = newArr[0]
person.lastName = newArr[1]
}
})
const sltEle = computed( ()=>{
return function(index){
console.log('index',index);
}
})
computed 接收參數的語法
<template>
<div>
<div v-for="(item,index) in arr" :key="index" @click="sltEle(index)">
{{item}}
</div>
</div>
</template>
const sltEle = computed(()=>(index)=>{
console.log('index',index);
})
11.watch和watchEffect的區別
- watch 是需要傳入偵聽的數據源,而 watchEffect 是自動收集數據源作為依賴。
- watch 可以訪問偵聽狀態變化前后的值,而 watchEffect 沒有,watchEffect獲取的改變后的值。
- watch 是屬性改變的時候執行,當然也可以immediate,而 watchEffect 是默認會執行一次,然后屬性改變也會執行。
let count = ref(0)
let countObj = reactive({count: 0})
watch(count, (newVal, oldVal) =>{console.log(newVal, oldVal)} )
// 只能監聽ref、reactiveObject, function, array, 想監聽reactive的某個屬性,需要轉換成函數
watch(() => countObj.count, (newVal, oldVal) => {console.log(oldVal, newVal)}, {})
// watch監聽reactiveObject時,舊值無法獲取正確獲取
watch (countObj, (newVal, oldVal) => {console.log(newVal, oldVal);})
// 監聽多個值,可以寫成數組的形式
watch ([oneName, twoName], ([oneNewName, twoNewName], [oneOldName, twoOldName]) => {
console.log(oneNewName, oneOldName, twoNewName, twoOldName)
})
watch監聽ref值,不用加.value,watch監聽對象的某個屬性值時,書寫方式是watch([()=>obj.propA],()=>{})
// 情況一監聽ref響應式數據
watch(count,(newValue,oldValue)=>{
console.log(newValue,oldValue)
},{immediate:true}) // immediate 立即監聽
// 情況二 監聽多個ref響應式數據
watch([count,name],(newValue,oldValue) =>{
console.log(newValue,oldValue) // 此時value的數據是數組
})
// 情況三 監聽reactvie響應式數據
// 如果watch監聽是reactive定義的響應式數據,則無法獲取正確的oldValue,且強制開啟深度監聽。
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue) // 兩個值一致都是一樣的
})
// 情況四 監聽reactive定義的響應式數據的某個屬性(基礎數據類型)
watch(()=>person.name,(newValue,oldValue) =>{
console.log(newValue,oldValue)
})
// 情況五 監聽多個reactive定義的多個響應式數據的屬性(基礎數據類型)
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log(newValue,oldValue)
})
// 情況六 監聽reactive定義的響應式數據的某個屬性(復雜數據類型)
watch(() => person.class,(newValue,oldValue) =>{
// 此時的class 為 { b:{c:20 } }, 想要監聽c值的變化 則需要開啟deep深度監聽
console.log(newValue,oldValue)
},{deep:true})
10.useSlots用法
父組件
<template>
<!-- 子組件 -->
<ChildTSX>
<!-- 默認插槽 -->
<p>I am a default slot from TSX.</p>
<!-- 命名插槽 -->
<template #msg>
<p>I am a msg slot from TSX.</p>
</template>
</ChildTSX>
</template>
<script setup lang="ts">
import ChildTSX from '@cp/context/Child.tsx'
</script>
子組件
<script lang="ts" setup>
// 注意:這是一個 .tsx 文件
import { useSlots } from 'vue'
const slots = useSlots()
const ChildTSX = ()=>{
// 渲染組件
return () => (
<div>
{/* 渲染默認插槽 */}
<p>{ slots.default ? slots.default() : '' }</p>
{/* 渲染命名插槽 */}
<p>{ slots.msg ? slots.msg() : '' }</p>
</div>
)
}
}
export default ChildTSX
</script>
9.jsx語法,要借助插件@vitejs/plugin-vue-jsx
<script lang="tsx" setup>
const jsxNode = () => {
return <div>text</div>;
};
</script>
<template>
<jsxNode />
</template>
8. 動態class的設置方法
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 :class="`red ${msg === 'Hello World!' && 'green'}`">{{ msg }}</h1>
<input v-model="msg">
</template>
<style scoped>
.red{
color:red;
}
.green{
color:green
}
</style>
7.父子組件通信 props/emit方式
父組件
<template>
<Child
ref="child"
title="用戶信息"
:index="1"
:uid="userInfo.id"
:user-name="userInfo.name"
@update-age="updateAge"
/>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import Child from '@cp/Child.vue'
type TMember={
id: number,
name: string
}
const child = ref<typeof Child | null>()
const userInfo: TMember = {
id: 1,
name: 'Petter'
}
// 父組件調用子組件的方法
onMounted(() => child.value?.childMethod)
const updateAge = (age: number) => {
console.log(age);
}
</script>
子組件
<template>
<p>標題:{{ title }}</p>
<p>索引:{{ index }}</p>
<p>用戶id:{{ uid }}</p>
<p>用戶名:{{ userName }}</p>
</template>
<script lang="ts" setup>
import { withDefaults, defineProps, defineEmits, toRefs, defineExpose } from 'vue';
const props = withDefaults(defineProps<{
title: string;
index: number;
uid: number;
userName: string;
}>(), { userName: 'zhangsan' });
// 復雜類型賦初始值
const props = withDefaults(defineProps<PropsType>(), {
value: () => {
return {
memberList: [],
deptList: [],
positionList: [],
};
},
});
const { title, index, uid, userName } = toRefs(props);
// 接受子組件傳進來的方法
const emit = defineEmits(['update-age']);
setTimeout(() => {
emit('update-age', 22);
}, 2000);
const childMethod = () => {
console.log('我是子組件的方法')
}
// 子組件暴露方法給父組件
defineExpose({ childMethod })
</script>
6.使用ref操作dom
<template>
<!-- 掛載DOM元素 -->
<p ref="msg">留意該節點,有一個ref屬性</p>
<!-- 掛載DOM元素 -->
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
// 定義掛載節點,聲明的類型詳見下方附表
const msg = ref<HTMLElement | null>();
// 請保證視圖渲染完畢后再執行節點操作 e.g. onMounted / nextTick
onMounted(() => {
// 比如獲取DOM的文本
console.log(msg?.value?.innerText);
});
</script>
5.props用法
<script lang="ts" setup>
import {withDefaults,defineProps} from 'vue';
const props = withDefaults(defineProps<{
btnName: string;
noBtn?: boolean;
doAction: () => void;
classStyle?: string;
config:any[];
}>(),{
noBtn:true, // 設置默認值
classStyle:''
config:()=>[],
});
const mActionClass=`m-action ${props.classStyle}`;
<script>
4.生命周期函數
vue2和vue3生命周期對比
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
<template>
<div class="home">
<p>{{ count }}</p>
<p>{{ state.a }}</p>
<button @click="add">加1</button>
</div>
</template>
<script lang="ts" setup>
import {
ref,
reactive,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered,
onActivated,
onDeactivated,
} from "vue";
const count = ref(1);
const state = reactive({ a: 10 });
const add = () => {
count.value += 1;
state.a = state.a + 1;
};
onBeforeMount(() => {
console.log("onBeforeMount");
});
onMounted(() => {
console.log("onMounted");
});
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
onUpdated(() => {
console.log("onUpdated");
});
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
});
onUnmounted(() => {
console.log("onUnmounted");
});
onErrorCaptured((evt) => {
console.log("onErrorCaptured", evt);
});
// 只執行一次,有幾個響應式api,執行幾次
onRenderTracked((evt) => {
console.log("onRenderTracked", evt);
});
// 行為如同onUpdated,每次有數據更新都會執行
onRenderTriggered((evt) => {
console.log("onRenderTriggered", evt);
});
// keep-alive要用到的函數
onActivated(() => {
console.log("onActivated");
});
onDeactivated(() => {
console.log("onDeactivated");
});
</script>
4. shallowReactive, shallowRef, readonly,shallowReadonly,toRaw,markRaw函數區別
4.1 shallowReactive、shallowRef的之間的區別
shallowReactive:淺監視
shallowRef:不做監視
4.2 readonly和shallowReadonly
readonly:只讀屬性的數據,深度只讀
const state2 = readonly(state)
shallowReadonly:只讀的數據,淺只讀的
4.3 toRaw和markRaw
toRaw將代理對象變成普通對象,數據變化,界面不會進行更新
const user = toRaw(state);
markRaw標記的對象數據,從此以后都不能在成為代理對象了
const likes = ['吃',‘喝’]
state.likes = markRaw(likes)
<template>
<h4>
<button @click="triggerShallowReactiveRender">
改變第一層才會渲染
</button>
</h4>
<p>{{ shallowReactiveState.a }}</p>
<h4>
<button @click="triggerShallowRefRender">
失去響應式
</button>
</h4>
<p>{{ shallowRefState.a }}</p>
</template>
<script setup>
import { readonly,reactive, shallowReactive, shallowRef, toRaw } from "vue";
// shallowRef 與shallowReactive
// shallowRef 與shallowReactive創建的是非遞歸的響應對象,shallowReactive創建的數據第一層數據改變會重新渲染dom
const shallowReactiveState = shallowReactive({
a: "initShallowReactiveState",
b: {
c: "c",
},
});
//如果不改變第一層 只改變其他的數據 頁面不會重新渲染,例如:
shallowReactiveState.b.c = 2;
//改變第一層的數據會導致頁面重新渲染
const triggerShallowReactiveRender=()=>{
shallowReactiveState.a = "changeShallowReactiveState";
}
// shallowRef創建的對象沒有響應式特性
const shallowRefState = shallowRef({
a: "initShallowRefState",
b: {
c: "c",
},
});
const triggerShallowRefRender=()=>{
//失去響應式--除非對整個對象重新賦值或者使用triggerRef(shallowRefState)觸發頁面更新
shallowRefState.a = "changeShallowRefState";
}
// toRaw ---只修改數據不渲染頁面
var obj = { name: "test" };
var toRawState = reactive(obj);
var raw = toRaw(toRawState);
//並不會引起頁面的渲染,而obj.name,toRawState.name的值均已改變
setTimeout(()=>raw.name = "zs",2000);
// 不允許修改對象的值
const readonlyState = readonly({a:1,b:2});
// 賦值會引起警告
readonlyState.a=2;
</script>
3. toRefs和toRef的用途
借助toRefs可以在template部分,直接使用結構后的對象單個鍵值,寫法簡潔,卻不失響應式。
toRef是轉換reactive對象的單個值
<template>
<ul class="user-info">
<li class="item">
<span class="key">ID:</span>
<span class="value">{{ id }}</span>
</li>
<li class="item">
<span class="key">name:</span>
<span class="value">{{ name }}</span>
</li>
<li class="item">
<span class="key">age:</span>
<span class="value">{{ age }}</span>
</li>
<li class="item">
<span class="key">gender:</span>
<span class="value">{{ gender }}</span>
</li>
</ul>
</template>
<script lang="ts" setup>
import { reactive, toRef, toRefs } from "vue";
interface Member {
id: number;
name: string;
age: number;
gender: string;
}
// 定義一個reactive對象
const userInfo: Member = reactive({
id: 1,
name: "Petter",
age: 18,
gender: "male",
});
// 結構reactive對象,它的字段全部是ref變量
const { id, age, gender } = toRefs(userInfo);
const name: string = toRef(userInfo, 'name');
// 2s后更新userInfo
setTimeout(() => {
userInfo.id = 2;
userInfo.name = "Tom";
userInfo.age = 20;
}, 2000);
</script>
2. 響應式數據
ref可以定義任何類型的數據,但是定義數據和對象時,在script部分使用時,賦值和取值,不如reactive方便
reactive只能用於數組,對象類型。
ref的本質是通過reactive創建的,Ref(10)=>Reactive({value:10});
reactive的本質是將每一層的數都解析成proxy對象,reactive 的響應式默認都是遞歸的,改變某一層的值都會遞歸的調用一遍,重新渲染dom。
<template>
<p>{{count}}</p>
<button @click="add">加1</button>
<p>{{info.name}}</p>
<button @click="changeName">改姓名</button>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
interface Info{
name:string;
age:number;
}
// 響應式基本類型用ref
const count = ref<number>(0);
// 響應式引用類型用reactive
const info:Info = reactive({ age: 100,name:'zhangsan' });
const add = () => {
// 基本類型賦值時,是賦值給value屬性
count.value += 1;
};
const changeName = () => {
info.name="lisi";
};
</script>
1.路由取值和跳轉
<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
// 獲取路由信息
console.log(route.query.id);
// 編程式路由跳轉
const jump = ()=> {
router.push({ path: `/about` , query:{id:'xxx}});
};
</script>