關(guān)于 Vue3 + setup + ts 使用技巧的總結(jié)

當(dāng)使用?setup?的時(shí)候,組件直接引入就可以了,不需要再自己手動(dòng)注冊(cè)
<template>
??<Child?/>
template>
<script?setup?lang="ts">
import?Child?from?"./Child.vue";
script>
2. ref 和 reactive
ref?一般用于基本的數(shù)據(jù)類型,比如?string,boolean?,reactive?一般用于對(duì)象 ref 的地方其實(shí)也是調(diào)用的?reactive?實(shí)現(xiàn)的。
<template>
??<h1>{{?title?}}h1>
??<div>
????{{?data?}}
??div>
template>
<script?setup?lang="ts">
import?{?ref,?reactive?}?from?"vue";
const?title?=?ref("title");
const?data?=?reactive({
??userName:?"xiaoming",
??age:?18,
});
script>
3. defineEmits 和 defineProps 獲取父組件傳過來值和事件
//?第一種不帶默認(rèn)值props
const?props?=?defineProps<{
??foo:?string
??bar?:?number
}>()
//?第二種帶默認(rèn)值props
export?interface?ChildProps?{
??foo:?string
??bar?:?number
}
const?props?=?withDefaults(defineProps(),?{
???foo:?"1qsd"
??bar?:?3
})
//?第一種獲取事件
const?emit?=?defineEmits<{
??(e:?'change',?id:?number):?void
??(e:?'update',?value:?string):?void
}>()
//?第二種獲取事件
const?emit?=?defineEmits(["dosth"])
4. 使用 useAttrs 和 useSlots
useAttrs?可以獲取父組件傳過來的?id?、class?等值。useSlots?可以獲得插槽的內(nèi)容。例子中,我們使用?useAttrs?獲取父組件傳過來的?id?、class、useSlots?獲取插槽的內(nèi)容。
父組件:
<template>
??<div?class="father">{{?fatherRef?}}div>
??<Child?:fatherRef="fatherRef"?@changeVal="changeVal"?class="btn"?id="111">
????<template?#test1>
??????<div>1223div>
????template>
??Child>
template>
<script?setup?lang="ts">
import?{?ref?}?from?"vue";
import?Child?from?"./Child.vue";
const?fatherRef?=?ref("1");
function?changeVal(val:?string)?{
??fatherRef.value?=?val;
}
script>
<style?lang="scss"?scoped>
.father?{
??margin-top:?40px;
??margin-bottom:?40px;
}
.btn?{
??font-size:?20px;
??color:?red;
}
style>
子組件:
<template>
??
??<div?v-bind="attrs">
????<slot?name="test1">11slot>
????<input?type="text"?v-model="inputVal"?/>
??div>
template>
<script?setup?lang="ts">
import?{?computed,?useAttrs,?useSlots?}?from?"vue";
const?props?=?defineProps<{
??fatherRef:?string;
}>();
const?emits?=?defineEmits(["changeVal"]);
const?slots?=?useSlots();
const?attrs?=?useAttrs();
console.log(122,?attrs,?slots);
const?inputVal?=?computed({
??get()?{
????return?props.fatherRef;
??},
??set(val:?string)?{
????emits("changeVal",?val);
??},
});
script>
使用自定義指令
在?setup?里邊自定義指令的時(shí)候,只需要遵循vNameOfDirective? 這樣的命名規(guī)范就可以了
比如如下自定義?focus?指令,命名就是?vMyFocus,使用的就是?v-my-focus
自定義指令
<script?setup?lang="ts">
const?vMyFocus?=?{
??onMounted:?(el:?HTMLInputElement)?=>?{
????el.focus();
????//?在元素上做些操作
??},
};
script>
<template>
??<input?v-my-focus?value="111"?/>
template>
5. 使用 defineExpose 子組件傳父組件
子組件
<template>
??<div?class="child">div>
template>
<script?setup?lang="ts">
import?{?ref,?reactive?}?from?"vue";
function?doSth()?{
??console.log(333);
}
defineExpose({?doSth?});
script>
父組件
<template>
??<div?class="father"?@click="doSth1">222div>
??<Child?ref="childRef">Child>
template>
<script?setup?lang="ts">
import?{?ref,?reactive?}?from?"vue";
import?Child?from?"./Child.vue";
const?childRef?=?ref();
function?doSth1()?{
??childRef.value.doSth();
}
script>
6. 父組件傳子組件
父組件
<template>
??<div?class="father">div>
??<Child?@doSth="doSth">Child>
template>
<script?setup?lang="ts">
import?{?ref,?reactive?}?from?"vue";
import?Child?from?"./Child.vue";
function?doSth()?{
??console.log(112);
}
script>
子組件
<template>
??<div?class="child">2222div>
template>
<script?setup?lang="ts">
import?{?ref,?reactive,?onMounted?}?from?"vue";
const?emits?=?defineEmits(["doSth"]);
onMounted(()?=>?{
??emits("doSth");
});
script>
7. toRefs
當(dāng)從父組件向子組件傳?props?的時(shí)候,必須使用?toRefs?或者?toRef?進(jìn)行轉(zhuǎn)一下,這是為什么呢?
這里是因?yàn)槿绻皇褂?toRefs?轉(zhuǎn)一次的話,當(dāng)父組件中的?props?改變的時(shí)候,子組件如果使用了 Es6 的解析,會(huì)失去響應(yīng)性。
可以看下如下例子
父組件
<template>
??<div?class="father"?@click="changeVal">{{?fatherRef?}}div>
??<Child?:fatherRef="fatherRef">Child>
template>
<script?setup?lang="ts">
import?{?ref,?reactive?}?from?"vue";
import?Child?from?"./Child.vue";
const?fatherRef?=?ref(1);
function?changeVal()?{
??fatherRef.value?=?2;
}
script>
<style?lang="scss"?scoped>
.father?{
??margin-bottom:?40px;
}
style>
子組件
<template>
??<div?class="child"?@click="changeVal">{{?fatherRef?}}div>
template>
<script?setup?lang="ts">
import?{?ref,?reactive,?onMounted,?toRefs?}?from?"vue";
const?props?=?defineProps<{
??fatherRef:?any;
}>();
const?{?fatherRef?}?=?props;
function?changeVal()?{
??fatherRef.value?=?34;
}
script>
可以看到當(dāng)父組件如果點(diǎn)擊之后,因?yàn)槭褂?const { fatherRef } = props;進(jìn)行解析,就失去了響應(yīng)性
所以當(dāng)父組件變成 2 的時(shí)候,子組件還是 1。
這里有兩種解決辦法
使用? const { fatherRef } = toRefs(props);在模版中中使用? props.fatherRef
8. 子組件使用 v-model
8.1 可以在子組件中使用 computed,實(shí)現(xiàn)雙向綁定
父組件
<template>
??<div?class="father">{{?fatherRef?}}div>
??<Child?:fatherRef="fatherRef"?@changeVal="changeVal">Child>
template>
<script?setup?lang="ts">
import?{?ref?}?from?"vue";
import?Child?from?"./Child.vue";
const?fatherRef?=?ref("1");
function?changeVal(val:?string)?{
??fatherRef.value?=?val;
}
script>
<style?lang="scss"?scoped>
.father?{
??margin-top:?40px;
??margin-bottom:?40px;
}
style>
子組件
<template>
??
??<input?type="text"?v-model="inputVal"?/>
template>
<script?setup?lang="ts">
import?{?computed?}?from?"vue";
const?props?=?defineProps<{
??fatherRef:?string;
}>();
const?emits?=?defineEmits(["changeVal"]);
const?inputVal?=?computed({
??get()?{
????return?props.fatherRef;
??},
??set(val:?string)?{
????emits("changeVal",?val);
??},
});
script>
8.2 可以從父組件傳遞值和改變值的方法,然后子組件也可以使用 v-model
例子中父組件傳遞?modelValue?和?update:modelValue?方法 父組件:
<template>
??<Child?:modelValue="searchText"?@update:modelValue="changeVal">?Child>
template>
<script?setup?lang="ts">
import?{?ref?}?from?"vue";
import?Child?from?"./Child.vue";
const?searchText?=?ref(1);
function?changeVal(val:?number)?{
??searchText.value?=?val;
}
script>
<style?lang="scss"?scoped>
.father?{
??margin-top:?40px;
??margin-bottom:?40px;
}
.btn?{
??font-size:?20px;
??color:?red;
}
style>
子組件:
<template>
??
??
??<input?v-model="modelValue"?/>
template>
<script?setup?lang="ts">
import?{?computed,?useAttrs,?useSlots?}?from?"vue";
const?props?=?defineProps<{
??modelValue:?number;
}>();
//?const?emits?=?defineEmits(["changeVal"]);
script>
9. 遞歸組件
組件本身是可以調(diào)用組件自身的,也就是遞歸。vue3 中使用文件名稱自動(dòng)注冊(cè)為組件的名稱,比如名為 ?Child.vue? 的組件可以在其模板中用 ?? 引用它自己。這里需要注意的是需要設(shè)置條件語句,用來中斷遞歸,不然遞歸會(huì)無限遞歸下去。
父組件
<template>
??<Child?:modelValue="searchText"?@update:modelValue="changeVal">?Child>
template>
<script?setup?lang="ts">
import?{?ref?}?from?"vue";
import?Child?from?"./Child.vue";
const?searchText?=?ref(1);
function?changeVal(val:?number)?{
??searchText.value?=?val;
}
script>
<style?lang="scss"?scoped>
.father?{
??margin-top:?40px;
??margin-bottom:?40px;
}
.btn?{
??font-size:?20px;
??color:?red;
}
style>
子組件
<template>
??<input?v-model="modelValue"?/>
??<Child
????:modelValue="test"
????@update:modelValue="changeTest"
????v-if="modelValue?>?2"
??>Child>
template>
<script?setup?lang="ts">
import?{?computed,?useAttrs,?useSlots,?ref?}?from?"vue";
const?props?=?defineProps<{
??modelValue:?number;
}>();
const?test?=?ref(0);
function?changeTest(val:?number)?{
??test.value?=?val;
}
//?const?emits?=?defineEmits(["changeVal"]);
script>
<style?lang="scss"?scoped>
.child?{
??position:?relative;
}
style>
10. vue3 ts 獲取組件 ref 實(shí)例
通過ref直接拿到dom引用
<template>
????<div?class="demo1-container">
????????<div?ref="sectionRef"?class="ref-section">div>
????div>
template>
<script?setup?lang="ts">
import?{ref}?from?'vue'
const?sectionRef?=?ref()
script>
通過對(duì)div元素添加了ref屬性,為了獲取到這個(gè)元素,我們聲明了一個(gè)與ref屬性名稱相同的變量sectionRef,然后我們通過 sectionRef.value 的形式即可獲取該div元素
通過父容器的ref遍歷拿到dom引用
<template>
????<div?class="demo2-container">
????????<div?ref="listRef"?class="list-section">
????????????<div?@click="higherAction(index)"?class="list-item"?v-for="(item,?index)?in?state.list"?:key="index">
????????????????<span>{{item}}span>
????????????div>
????????div>
????div>
template>
<script?setup?lang="ts">
import?{?ref,?reactive?}?from?'vue'
const?listRef?=?ref()
script>
通過對(duì)父元素添加了ref屬性,并聲明了一個(gè)與ref屬性名稱相同的變量listRef,此時(shí)通過listRef.value會(huì)獲得包含子元素的dom對(duì)象 此時(shí)可以通過listRef.value.children[index]的形式獲取子元素dom
通過:ref將dom引用放到數(shù)組中
<template>
??<div?class="demo2-container">
??????<div?class="list-section">
??????????<div?:ref="setRefAction"?@click="higherAction(index)"?class="list-item"?v-for="(item,?index)?in?state.list"?:key="index">
??????????????<span>{{item}}span>
??????????div>
??????div>
??div>
??template>
??<script?setup?lang="ts">
??import?{?reactive?}?from?'vue'
??const?state?=?reactive({
??????list:?[1,?2,?3,?4,?5,?6,?7],
??????refList:?[]?as?Array script>
??})
??const?setRefAction?=?(el:?any)?=>?{
??????state.refList.push(el);
??}
??通過:ref循環(huán)調(diào)用
setRefAction方法,該方法會(huì)默認(rèn)接收一個(gè)el參數(shù),這個(gè)參數(shù)就是我們需要獲取的div元素 此時(shí)可以通過state.refList[index]的形式獲取子元素dom通過子組件emit傳遞ref
<template>
????<div?ref="cellRef"?@click="cellAction"?class="cell-item">
????????<span>{{item}}span>
????div>
template>
<script?setup?lang="ts">
import?{ref}?from?'vue';
const?props?=?defineProps({
????item:?Number
})
const?emit?=?defineEmits(['cellTap']);
const?cellRef?=?ref();
const?cellAction?=?()?=>?{
????emit('cellTap',?cellRef.value);
}
script>
通過對(duì)子組件添加了ref屬性,并聲明了一個(gè)與ref屬性名稱相同的變量cellRef,此時(shí)可以通過emit將cellRef.value作為一個(gè)dom引用傳遞出去
tsx 等 render 組件中獲取的方式更簡(jiǎn)單
import?{?defineComponent,?ref,?onMounted?}?from?"@vue/runtime-core";
import?{?ElForm?}?from?"element-plus";
export?default?defineComponent({
??setup()?{
????const?$form?=?reftypeof?ElForm>>(null);
????onMounted(()?=>?{
??????$form.value?.validate;?//?類型正確
????});
????return?()?=>?</ElForm>;
??},
});
需要注意的是,如果使用 expose 暴露方法出去,無法獲取到對(duì)應(yīng)的類型,您需要自定義類型?github.com/vuejs/rfcs/…[1]
//?組件?MyForm
import?{?defineComponent,?ref,?onMounted?}?from?"@vue/runtime-core";
import?{?ElForm?}?from?"element-plus";
type?ELEForm?=?InstanceType<typeof?ElForm>;
//?在外界通過?ref?獲取組件實(shí)例?請(qǐng)使用這個(gè)類型
export?interface?MyFormExpose?{
??validate:?ELEForm["validate"];
}
export?default?defineComponent({
??name:?"MyForm",
??setup(props,?{?expose?})?{
????const?$form?=?reftypeof?ElForm>>(null);
????expose({
??????validate:?(callback)?=>?$form.value?.validate(callback),
????}?as?MyFormExpose);
????return?()?=>?</ElForm>;
??},
});
<template>
??<MyForm?:ref="$form"?/>
template>
<script>
import?{?defineComponent,?ref,?onMounted?}?from?'@vue/runtime-core'
import?MyForm,?{?MyFormExpose?}?from?'@/components/MyForm'
export?default?defineComponent({
??components:?{?MyForm?}
??setup(){
????const?$form?=?reftypeof ?MyForm>?&?MyFormExpose>(null)
????onMounted(()?=>?{
???????$form.value?.validate?//?類型正確
????})
??}
})
script>
參考資料
https://github.com/vuejs/rfcs/pull/210#issuecomment-727067392:?https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs%2Fpull%2F210%23issuecomment-727067392
