defineProps
用于接收父組件傳遞的屬性值。
父組件:
<!-- 父組件 -->
<template><Child1 str="字符串" :num="num" />-----------------<Child2 str="字符串" :num="num" />
</template><script setup>
import { ref } from 'vue';
import Child1 from './views/Child1.vue';
import Child2 from './views/Child2.vue';const num = ref(18)setInterval(() => {num.value++
}, 1000);
</script>
子組件1:
<!-- Child1 -->
<template><div><p>{{ str }}</p><!-- 兩種寫法都可以 --><p>{{ props.str }}</p><p>{{ num }}</p><p>{{ obj }}</p><p>{{ fun }}</p><!-- 會報錯 --><!-- v-model cannot be used on a prop, because local prop bindings are not writable. --><!-- <input v-model="str" /> --><!-- 在 input 框中輸入值并不會引起 str 的變化 --><input v-model="newStr" /></div>
</template><script setup>
import { ref } from 'vue';const props = defineProps({str: String,/*** 通過 defineProps 定義的 props 是響應式的。* 這意味著當父組件傳遞給子組件的 prop 值發生變化時,* 子組件中的相應 prop 也會自動更新,* 并且任何依賴于這些 props 的計算屬性、偵聽器(watchers)或模板都會更新。*/num: {type: Number,default: 0},obj: {type: Object,default: () => {return {}}},fun: {type: Function,default: null}
});console.log("child1 created props:", props, typeof props); // Proxy 對象
console.log("child1 created str:", props.str, typeof props.str); // 字符串
console.log("child1 created num:", props.num);
console.log("child1 created obj:", props.obj, typeof props.obj); // object
console.log("child1 created fun:", props.fun); // objectconst newStr = ref(props.str)
console.log("child2 newStr:", newStr.value)
</script>
子組件2:
<!-- Child2 -->
<template><div><p>{{ str }}</p><!-- 這里就不能這樣寫了,會報錯 --><!-- <p>{{ props.str }}</p> --><p>{{ num }}</p><p>{{ obj }}</p><p>{{ fun }}</p></div>
</template><script setup>
import { ref, computed } from 'vue';const { str, num, obj, fun } = defineProps({str: String,num: {type: Number,default: 0},obj: {type: Object,default: () => {return {}}},fun: {type: Function,default: null}
});console.log("child2 created str:", str);
console.log("child2 created num:", num);
console.log("child2 created obj:", obj);
console.log("child2 created fun:", fun);/*** 在 Vue 3 中,通過 defineProps 定義的 props 是只讀的,不能直接修改。* 這是為了確保數據流保持單向,即父組件傳遞給子組件的數據不會被子組件意外地改變。* 非要修改只能使用計算屬性或者創建一個響應式變量*/
// str = '改變字符串' // 會報錯const changeStr1 = computed(() => {return `使用計算屬性改變${str}`
})
console.log("child2 changeStr1:", changeStr1.value);const changeStr2 = ref(str)
changeStr2.value = `使用響應式變量改變${str}`
console.log("child2 changeStr2:", changeStr2.value);
</script>
defineProps() 返回的是一個 Proxy 對象,它既不是 ref 對象,也不是 reactive 對象。
defineProps() 返回的對象的屬性值是普通數據類型或普通對象,也不是 ref/reactive 對象。
defineEmits
用在子組件中。表面上看它的作用似乎是用于子組件調用父組件的方法。
更準確的說法是用來定義子組件可以發出的事件,父組件可以通過監聽這些事件來響應子組件的行為。
它實際上是在告訴父組件:當‘我’使用 emit
的時候,你父組件需要做出相應的響應,你想怎么響應是你父組件自己的事情。
<!-- 子組件 -->
<template><button @click="handleClick">子組件按鈕</button>
</template><script setup>
// 定義可以發出的事件
const emit = defineEmits(['update'])function handleClick() {// 發出 'update' 事件給父組件emit('update', '我是參數')
}
</script>
<!-- 父組件 -->
<template><!-- 監聽子組件的 'update' 事件 --><Child1 @update="handleUpdate" />
</template><script setup>
import Child1 from './views/Child1.vue';function handleUpdate(params) {// 父組件做出響應:打印參數值console.log("params:", params);
}
</script>
defineExpose
用在子組件中,用于暴露子組件實例的方法和數據。它可以讓父組件通過 ref
獲取子組件的特定方法或數據。
<!-- 子組件 -->
<template>子組件
</template><script setup>
import { ref } from 'vue';const str = ref('子組件字符串')
const fun = function () {console.log("子組件方法觸發...");
}
const other = '其他'defineExpose({str, // ES6 簡化寫法num: 18,fun: fun
})
</script>
<!-- 父組件 -->
<template><Child1 ref="child1" /><button @click="handleClick">父組件按鈕</button>
</template><script setup>
import { onMounted, ref } from 'vue';
import Child1 from './views/Child1.vue';const child1 = ref(null)console.log("created str:", child1.value.str); // 第 15 行,報錯onMounted(() => {console.log("mounted str:", child1.value.str); // 子組件字符串
})function handleClick() {console.log("str:", child1.value.str); // 子組件字符串console.log("num:", child1.value.num); // 18child1.value.fun() // 子組件方法觸發...console.log("other:", child1.value.other); // undefined
}
</script>
為什么第 15 行會報錯?
因為在 setup
函數執行時,子組件還沒有被掛載到 DOM 上,組件的實例也沒有準備好。因此,此時 child1.value
為 null
。
為什么 child1.value.other 是 undefined?
因為子組件中沒有通過 defineExpose
來暴露 other
defineAsyncComponent
異步組件。可以理解為延遲加載或按需加載。比如當一個大型頁面中包含很多個子組件,如果一次性加載所有內容勢必會導致頁面渲染速度過慢,這時候就可以對那些不需要第一時間就加載的、或者需要經過某些操作后才加載的子組件使用異步組件。
同步組件寫法:
<!-- App.vue -->
<template><button @click="handleClick">加載子組件</button><SyncComponent v-if="show" />
</template><script setup>
import { ref } from 'vue';
import SyncComponent from "./views/Child1.vue";const show = ref(false)function handleClick() {show.value = true
}
</script>
異步組件寫法:
<!-- App.vue -->
<template><button @click="handleClick">加載子組件</button><AsyncComponent v-if="show" />
</template><script setup>
import { defineAsyncComponent, ref } from 'vue';const AsyncComponent = defineAsyncComponent(() => import('./views/Child1.vue'))
const show = ref(false)function handleClick() {show.value = true
}</script>
檢查控制臺發現:無論同步組件還是異步組件,頁面元素都沒有渲染子組件內容。
那么他們有什么區別?
區別是你可以在控制臺的 Network 中發現:
-
同步組件會在頁面初始化時一次性加載 App.vue 和 Child1.vue
-
異步組件在頁面初始化時只加 App.vue,當點擊按鈕時再加載 Child1.vue。
知道了 defineAsyncComponent 的作用,下面詳細介紹 defineAsyncComponent 的用法:
defineAsyncComponent 方法接收一個返回 Promise 的加載函數:
import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() => {return new Promise((resolve, reject) => {// ...從服務器獲取組件resolve(/* 獲取到的組件 */)})
})
ES6 模塊動態導入也會返回一個 Promise,所以也可以這樣寫:
import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() =>import('./components/MyComponent.vue')
)
異步組件會將接收到的 props 和插槽傳給內部組件,所以你可以使用這個異步的包裝組件無縫地替換原始組件,同時實現延遲加載。
異步操作不可避免地會涉及到加載和錯誤狀態,因此 defineAsyncComponent 也支持在高級選項中處理這些狀態:
import LoadingComponent from './views/Loading.vue';
import ErrorComponent from './views/Error.vue';const AsyncComp = defineAsyncComponent({// 加載函數loader: () => import('./views/Child1.vue'),// 加載異步組件時使用的組件loadingComponent: LoadingComponent,// 展示加載組件前的延遲時間,默認為 200msdelay: 2000,// 加載失敗后展示的組件errorComponent: ErrorComponent,// 如果提供了一個 timeout 時間限制,并超時了,// 也會顯示加載失敗后展示的組件,默認值是:Infinitytimeout: 3000
})
注意:delay: 2000 并不是指等待 2s 后才開始加載異步組件,而是指在異步組件開始加載后,等待 2s 再顯示 loadingComponent。
當使用服務器端渲染時還可以配置:在空閑時進行激活、在可見時激活、自定義策略等等…這里不做拓展,詳情可以直接訪問官網。
Suspense
[s??spens]
Suspense 是一個包裹異步組件的容器組件,用來處理異步組件加載期間的 UI 狀態。
Suspense 組件有兩個插槽:
-
#default
:默認插槽,這個插槽用于放置異步組件。當異步組件加載完成后,#default 插槽中的內容將被渲染。 -
#fallback
:備用插槽,當異步組件正在加載時,#fallback 插槽中的內容會被渲染。
父組件:
<!-- 父組件 -->
<template><div>我是父組件內容</div><Suspense><AsyncComponent /><template #fallback><!-- <h1>正在加載中...</h1> --><LoadingComponent /></template></Suspense>
</template><script setup>
import LoadingComponent from "./views/LoadingComponent.vue";
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(function () {return new Promise(resolve => {setTimeout(() => {resolve(import('./views/AsyncComponent.vue'))}, 5000);});
})
</script>
異步子組件:
<!-- 異步子組件 -->
<template><div>我是異步子組件</div>
</template><script setup>
import { onMounted } from 'vue';
onMounted(() => {console.log("子組件 onMounted 執行");
})
</script>
備用組件 LoadingComponent:
<!-- 備用組件 -->
<template><div>加載中...</div>
</template>
5 秒后頁面內容替換:
注意點:#default 和 #fallback 兩個插槽都只允許一個直接子節點。
<template #fallback> Loading... </template>
<template #fallback> <LoadingComponent />
</template>
<template #fallback> <h1>正在加載中...</h1>
</template>
都是可以的,但是
<template #fallback>哈哈<h1>正在加載中...</h1>
</template>
就會報錯。因為它有兩個直接子節點。
Suspense 組件會觸發三個事件:pending
、fallback
、resolve
。
-
pending
事件是在進入掛起狀態時觸發。 -
fallback
事件是在 #fallback 插槽的內容顯示時觸發。 -
resolve
事件是在 #default 插槽完成獲取新內容時觸發。
<template><div>我是父組件內容</div><Suspense @pending="handlePending" @fallback="handleFallback" @resolve="handleResolve"><AsyncComponent /><template #fallback><h1>正在加載中...</h1></template></Suspense>
</template><script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(function () {return new Promise(resolve => {setTimeout(() => {resolve(import('./views/AsyncComponent.vue'))}, 5000);});
})let num = 1setInterval(() => {console.log(num++);
}, 1000)function handlePending() {console.log("pending...");
}
function handleFallback() {console.log("fallback...");
}
function handleResolve() {console.log("resolve...");
}
</script>
defineAsyncComponent 的實際作用:
手摸手教你利用defineAsyncComponent實現長頁面按需加載組件
路由懶加載:
const routes = [{path: '/dashboard',component: defineAsyncComponent(() => import('./views/Dashboard.vue'))},{path: '/profile',component: defineAsyncComponent(() => import('./views/Profile.vue'))}
];
通過 Vue Router 的懶加載機制,只有在用戶訪問特定路由時,相關頁面組件才會被加載。
拓展:CommonJS 的 require() 也可以實現路由懶加載。
defineOptions
在 Vue 3.3 及之后的版本中,defineOptions 是一個新引入的宏(macro),它允許開發者在 <script setup> 語法糖中聲明組件的選項(options)。
這個特性解決了之前需要額外編寫一個非 setup 的<script>標簽來配置選項的問題。
// 設置組件名并禁止屬性繼承
defineOptions({name: 'MyComponent', // 組件名稱inheritAttrs: false // 禁止屬性繼承
});
注意:
可以在<script setup>之外使用<script>標簽來配置選項,但是<script setup>和普通<script>中的 setup() 函數不能同時用來定義響應式數據。
例如:
<template><div>{{ name }}{{ age }}</div>
</template><script setup>
import { ref } from 'vue';
const name = ref('張三');
</script><script>
import { ref } from 'vue';
export default {setup() {const age = ref(10);return { age };}
}
</script>
同時使用了<script setup>和 setup(),則頁面不會按預期展示。
<template><div>{{ name }}{{ age }}</div>
</template><script setup>
import { ref } from 'vue';
const name = ref('張三');
const age = ref(10);
</script><script>
export default {// 這里可以配置選項式 API 的內容,比如 props、emits、components 等props: {// 示例 props 配置someProp: String},emits: ['someEvent'],components: {// 示例組件配置// SomeComponent}
}
</script>
在這個示例里,<script setup>負責定義響應式數據和邏輯,普通<script>負責配置選項式 API 的內容。這樣就能把兩種語法風格結合起來使用。
defineComponent
從 API 名稱來看,意思是定義一個組件,是 Vue 3 中引入的一個輔助函數,主要用于 TypeScript 項目中。它允許你在定義組件選項時獲得更好的類型推斷和 IDE(如 VSCode)中的自動補全功能。通過使用 defineComponent,IDE 能夠識別這是一個 Vue 組件,并據此提供 Vue 特有的 API 提示和類型檢查。
什么意思呢?
在 Vue2 中,我們會習慣這樣寫:
export default {//...
}
這個時候,對于開發工具而言,{} 只是一個普通的 Object 對象,開發工具不會對一個普通對象做任何特殊的處理。
但是增加一層 defineComponet 的話:
export default defineComponent({//...
})
你實際上就是在告訴開發工具,我使用的是 Vue3,你需要給我一些 Vue3 相關的自動提示。這樣在你寫代碼的時候,開發工具會給出更多的一些自動提示幫你補全代碼。
核心源碼:
var Vue = (function (exports) {// 定義組件function defineComponent(options) {return isFunction(options) ? { setup: options, name: options.name } : options;}exports.defineComponent = defineComponent;
}({}));
參數 options 是一個選項對象或 setup 函數。
什么是選項對象?
Vue2 中寫的:
export default {data() {// ...},methods: {// ...}
}
這些就是選項對象。
Vue3中defineComponent 的作用詳解
Vue 中的 defineComponent
Vue3源碼解析-defineComponent
defineSlots
與 defineComponent 類似,輔助功能,用于類型檢查和 IDE 的自動補全,主要用于 TypeScript 環境下。
defineCustomElement
defineCustomElement 作用是定義一個自定義元素。在 Vue 中我們可以自己寫 .vue 文件封裝組件,那么它存在的意義是什么呢?
在 Vue 3 中,defineCustomElement 是一種特殊的 API,它允許你將 Vue 組件轉換為 Web Components,這樣它們就能在任何現代瀏覽器中作為原生的自定義 HTML 元素使用,而不僅僅是在 Vue 應用中使用。與常規的 Vue 組件不同,使用 defineCustomElement 創建的組件可以獨立于 Vue 環境運行,也可以在非 Vue 項目中使用。
這個方法接收的參數和 defineComponent 完全相同。但它會返回一個繼承自 HTMLElement
的自定義元素構造器:
import { defineCustomElement } from 'vue'const MyVueElement = defineCustomElement({// 這里是同平常一樣的 Vue 組件選項props: {},emits: {},template: `...`,// defineCustomElement 特有的:注入進 shadow root 的 CSSstyles: [`/* inlined css */`]
})// 注冊自定義元素
// 注冊之后,所有此頁面中的 `<my-vue-element>` 標簽都會被升級
customElements.define('my-vue-element', MyVueElement)
有興趣的可以看看 Web Component 的相關知識 【zh-CN】。可以將 Web Components 簡單理解為一個自定義的 HTML 標簽。
用的很少,就不做具體研究。
Vue 與 Web Components
Vue3中defineCustomElement的使用
defineModel
僅在 3.4+ 中可用
v-model
Vue 系列之:自定義雙向數據綁定
defineModel 就是簡化 v-model 實現過程
下面使用 defineModel 來實現雙向數據綁定的例子:
父組件:
<!-- 父組件 -->
<template><div><p>Count in parent: {{ count }}</p><Children v-model="count" /></div>
</template><script setup>
import { ref, watch } from 'vue'
import Children from './Children.vue';const count = ref(1)watch(count, (newVal, oldVal) => {console.log(newVal, oldVal, typeof newVal) // number 類型
})
</script>
子組件:
<!-- 原子組件代碼 -->
<template><div><!-- 這里不能直接 v-model="modelValue" 會報編譯錯誤v-model cannot be used on a prop, because local prop bindings are not writable.--><el-select v-model="selectValue" @change="handleChange"><el-option label="選項1" :value="1" /><el-option label="選項2" :value="2" /><el-option label="選項3" :value="3" /></el-select></div>
</template><script setup>
import { ref, defineProps, defineEmits } from 'vue'const props = defineProps({modelValue: Number
})const emit = defineEmits(['update:modelValue'])const selectValue = ref(props.modelValue)function handleChange() {console.log('選項變化了');emit('update:modelValue', selectValue.value);
}
</script>
<!-- 使用 defineModel 的子組件代碼 -->
<template><div><el-select v-model="selectValue" @change="handleChange"><el-option label="選項1" :value="1" /><el-option label="選項2" :value="2" /><el-option label="選項3" :value="3" /></el-select></div>
</template><script setup>
import { ref, defineProps, defineEmits } from 'vue'// const props = defineProps({
// modelValue: Number
// })// const emit = defineEmits(['update:modelValue'])// const selectValue = ref(props.modelValue)// function handleChange() {
// console.log('選項變化了');
// emit('update:modelValue', selectValue.value);
// }const selectValue = defineModel()function handleChange() {console.log('選項變化了:', selectValue.value);// emit('update:modelValue', selectValue.value);
}
</script>
非常簡單!
defineModel 就是封裝了之前的實現過程:在子組件內定義了一個叫 selectValue
的 ref 變量(當然也可以取別的變量名)和名字叫 modelValue
的 props,并且 watch
了 props 中的 modelValue
。當父組件改變 modelValue
的值后會同步更新 selectValue
變量的值;當子組件改變 selectValue
變量的值后會調用 update:modelValue
事件,父組件收到這個事件后就會更新父組件中對應的變量值。
defineModel 中的 type 和 default
默認情況就使用:
const model = defineModel();
如果想定義類型:
const model = defineModel({ type: String })
類型 + 默認值:
const model = defineModel({ type: String, default: "張三" });
自定義屬性名:
<!-- 父組件 -->
<Children v-model:aa="count"></Children>
// 子組件
const model = defineModel('aa', { type: String, default: "張三" })
綁定多個屬性:
<!-- 父組件 -->
<Children v-model:name="myName" v-model:age="myAge"></Children>
// 子組件
const model1 = defineModel('name')
const model2 = defineModel('age', { type: Number, default: 8 })
setup() 和 <script setup> 的區別
編譯
在編譯時 setup() 難以進行深度靜態分析, 因為它的返回值是動態的(比如返回的對象可能包含運行時才能確定的屬性或方法)。
<script setup>是編譯時語法糖,它的頂層綁定(變量、函數、import 等)是直接暴露給模板的,編譯器可以明確知道哪些內容會被模板使用,從而進行更多優化,例如更好的 Tree-shaking:未在模板中使用的代碼可以被標記并移除。
總結:
Vue 編譯器可以對<script setup>語法糖內部的代碼進行靜態分析,從而進行更多的編譯時優化,減少運行時的開銷,提高組件的渲染性能。因此在性能優化上 setup() 不如<script setup>
上下文
setup() 函數通過參數 props 和 context 來訪問組件的屬性和上下文。
-
props 就是 Vue2 中組件中的 props,指父組件傳遞來的參數
-
context 有三個屬性 attrs slots emit 分別對應 Vue2 中的 attrs 屬性、slots 插槽、$emit 事件
子組件接收父組件傳遞的值:
setup():
<script>
export default {props: {num: {type: Number,default: 1}},setup (props) {console.log(props)}
}
</script>
<script setup>:
<script setup>
import { defineProps } from 'vue'
const props = defineProps({num: {type: Number,default: 1}
})
</script>
子組件給父組件傳值:
setup():
<script>
export default {setup (props, context) {const sendNum = () => {context.emit('submit', 1200)}return { sendNum }}
}
</script>
<script setup>:
<script setup>
import { defineProps, defineEmits } from 'vue'
const emit = defineEmits(['submit'])
const sendNum = () => {emit('submit', 1000)
}
</script>
<script setup>使用 defineProps 和 defineEmits 宏來訪問組件的屬性和觸發自定義事件,不需要手動接收 props 和 context。
return
setup() 函數是一個標準的組件選項(Component Option),由 Vue 運行時直接解析。需顯式返回對象,其屬性暴露給模板。
即:setup() 中的內容需要顯式地 return 才能在模板中訪問(屬性和方法都需要 return):
<template><div><p>{{ message }}</p><button @click="changeMessage">測試</button></div>
</template><script>
import { ref } from 'vue';
export default {setup() {const message = ref('Hello, Vue 3!');const changeMessage = () => {message.value = 'Message changed!';};return { message, changeMessage };}
};
</script>
例如上面這段代碼,如果沒有 return,則功能無法實現。
如果使用 <script setup> 則不需要 return:
<template><div><p>{{ message }}</p><button @click="changeMessage">測試</button></div>
</template><script setup>
import { ref } from 'vue';
const message = ref('Hello, Vue 3!');
const changeMessage = () => {message.value = 'Message changed!';
};
</script>
這是因為:
編譯器會對 <script setup>塊進行靜態分析和轉換,生成等效的 setup() 函數。編譯后的代碼會提取頂層變量(包括 import 的組件),形成 setup() 的返回對象。
即:<script setup> 是 setup() 的語法糖,在 <script setup> 中定義的變量和方法會自動暴露給模板,無需手動 return。
expose
父組件:
<template><div><Children1 ref="child1" /><Children2 ref="child2" /><button @click="handleClick">按鈕</button></div>
</template><script setup>
import { ref } from 'vue';
import Children1 from './Children1.vue';
import Children2 from './Children2.vue';
const child1 = ref(null);
const child2 = ref(null);
const handleClick = () => {console.log("child1:", child1);console.log("child1 message:", child1.value.message);console.log("child1 obj:", child1.value.obj);console.log("child1 fn:", child1.value.fn);console.log("----------");console.log("child2:", child2);console.log("child2 message:", child2.value.message);console.log("child2 obj:", child2.value.obj);console.log("child2 fn:", child2.value.fn);
}
</script>
setup() 子組件:
<template><div>setup() 子組件</div>
</template><script>
import { reactive, ref } from 'vue';
export default {setup() {const message = ref('Hello, Vue 3!');const obj = reactive({ name: '張三', age: 10 })const fn = () => {console.log("子組件方法");}return { message, obj }}
}
</script>
<script setup> 子組件:
<template><div><script setup> 子組件</div>
</template><script setup>
import { reactive, ref } from 'vue';
const message = ref('Hello, Vue 3!');
const obj = reactive({ name: '張三', age: 10 })
const fn = () => {console.log("子組件方法");
}
</script>
可以發現:setup() 會向父組件暴露所有 return 的屬性和方法,而<script setup>就不會,<script setup>語法糖只會對外暴露手動 defineExpose 的內容。
其他
其他的一些使用細節上的區別:
注冊組件:
setup():需要手動注冊
<script>
import Hello from '@/components/HelloWorld'
export default {components: {Hello}
}
</script>
<script setup>:不需要手動注冊
<script setup>
import Hello from '@/components/HelloWorld'
</script>
自定義指令:
setup():
<template><h1 v-onceClick>使用了setup函數</h1>
</template>
<script>export default {directives: {onceClick: {mounted (el, binding, vnode) {console.log(el)}}},
}
</script>
<script setup>:
不需要顯式注冊,但他們必須遵循 vNameOfDirective
這樣的命名規范。
<template><h1 v-once-Directive>使用了script setup</h1>
</template>
<script setup>
const vOnceDirective = {beforeMount: (el) => {console.log(el)}
}
</script>
setup 函數特點
4、setup 函數在 beforeCreate 鉤子函數之前執行
export default {setup() {console.log("setup");},beforeCreate() {console.log("beforeCreate");},created() {console.log("created");},mounted() {console.log("mounted");}
}// setup
// beforeCreate
// created
// mounted
setup(props, context) 詳細說明
執行順序
setup() 是組件中使用組合式 API 的入口點,它在組件實例創建之前執行,在 beforeCreate 和 created 生命周期鉤子之前調用。
注:有說法認為 Vue3 中沒有 beforeCreate 和 created 鉤子函數,這是不準確的。
-
組合式 API 中確實沒有 beforeCreate 和 created 鉤子函數,他們的功能被 setup 取代;
-
但是在選項式 API 中他們依然存在。
<template>
</template><script>
export default {beforeCreate() {console.log("beforeCreate");},created() {console.log("created");},setup() {console.log("setup");}
}
</script>
執行順序:setup——beforeCreate——created
props 參數
特性:
-
是響應式的,包含組件接收的所有 prop,當父組件更新 props 時會自動更新
-
不能使用 ES6 解構,否則會失去響應性
-
如果需要解構,可以使用 toRefs 或 toRef 保持響應性
特性 1 舉例:
<!--父組件-->
<template><div><Children name="張三" :age="10" /></div>
</template><script>
import Children from './Children.vue';
export default {components: { Children }
}
</script>
<!--子組件-->
<template><div><p>{{ name }}</p><p>{{ age }}</p></div>
</template><script>
export default {props: {name: String,// age: Number 沒有接收 age 屬性},setup(props) {console.log("props:", props);console.log("name:", props.name);console.log("age:", props.age);}
}
</script>
組件沒有接收 age 屬性,所以 props 參數中的 age 為 undefined。
特性 2、3 舉例:
<!--父組件-->
<template><div><Children :name="name" :age="age" @change="handleChange" /></div>
</template><script>
import { ref } from 'vue';
import Children from './Children.vue';
export default {components: { Children },setup() {const name = ref('張三');const age = ref(10);const handleChange = () => {age.value++}return { name, age, handleChange };}
}
</script>
<!--子組件-->
<template><div style="margin-left: 50px;"><p>{{ props.name }}</p><p>{{ props.age }}</p><p>{{ age }}</p><button @click="handleClick">按鈕</button></div>
</template><script>
import { toRefs } from 'vue';export default {props: {name: String,age: Number},setup(props, context) {const { age } = propsconst age1 = props.ageconst { age: age2 } = toRefs(props)const handleClick = () => {context.emit('change');setTimeout(() => {console.log("props.age:", props.age);console.log("age:", age);console.log("age1:", age1);console.log("age2.value:", age2.value);console.log("age2:", age2);})};return { props, age, handleClick }}
}
</script>
初始頁面:
點擊一次按鈕后:
可以看到:
const { age } = props
和 const age1 = props.age
丟失了響應性,
props.age
和 const { age: age2 } = toRefs(props)
保留了響應性。
拓展:
使用 toRefs(props) 創建的 age2 是一個 ref 對象,它包含了多個內部屬性:
屬性 | 類型 | 說明 |
---|---|---|
__v_isRef | boolean | 標識這是一個 ref 對象,值為 true |
_defaultValue | any | 默認值 |
_key | string | 對應的 props 鍵名,這里是 “age” |
_object | object | 指向原始的 props 響應式對象 |
_value | any | 當前存儲的 age 的值(與 value 相同) |
dep | Set<ReactiveEffect> | 存儲依賴該 ref 的副作用(effect) |
value | any | 訪問或修改 |
context 參數
context 是一個普通對象(非響應式),包含組件的三個屬性:
-
attrs
-
包含所有未在 props 中聲明的 attribute
-
相當于 Vue 2 中的 this.$attrs
-
非響應式
-
示例:
setup(props, { attrs }) {console.log(attrs.class) // 訪問 class 屬性 }
-
-
slots
-
包含所有插槽內容的對象
-
相當于 Vue 2 中的 this.$slots
-
非響應式
-
示例:
setup(props, { slots }) {const defaultSlot = slots.default() // 獲取默認插槽內容return () => h('div', defaultSlot) }
-
-
emit
-
用于觸發自定義事件的函數
-
相當于 Vue 2 中的 this.$emit
-
示例:
setup(props, { emit }) {const handleClick = () => {emit('change', 'new value')}return {handleClick} }
-
當然也可以不使用解構寫法:
setup(props, context) {console.log(context.attrs.class)const defaultSlot = context.slots.default()const handleClick = () => {context.emit('change', 'new value')}
}
返回值
setup() 可以返回一個對象,該對象的屬性/函數將被暴露給模板使用,也可以返回一個渲染函數:
返回對象:
import { ref } from 'vue'setup() {const count = ref(0)return {count,increment: () => count.value++}
}
返回渲染函數:
import { h, ref } from 'vue'setup() {const count = ref(0)return () => h('div', count.value)
}