
很多同學在從 Vue2 切換到 Vue3 的時候第一個繞不開的坎就是組合式 API 中的setup函數。而在setup里寫完一堆變量和方法之后到底要return什么、不return什么、為什么模板里拿不到數據這些問題在初學階段非常容易讓人懵。本文將圍繞setup函數的返回值這一核心知識點從概念、寫法、原理到實戰和排錯完整梳理一遍“返回值”在 Vue3 中的真實作用。無論你是剛開始學 Vue3 的新手還是準備面試時被問到“setup 返回對象和返回渲染函數有什么區別”的進階開發者這篇文章都值得收藏備用。1. 背景與核心概念setup 到底是什么1.1 從 Options API 到 Composition APIVue2 時代我們寫組件主要靠data、methods、computed、watch、生命周期鉤子這些選項組合出一個組件這種寫法被稱為 Options API選項式 API。它的優點是結構清晰、約定明確缺點也很明顯當一個組件邏輯變復雜時同一個功能的代碼往往被拆散在data、methods、watch等多個選項中維護起來非常割裂。比如一個搜索功能數據在data里方法在methods里監聽在watch里你要同時修改搜索邏輯就得來回跳轉。Vue3 引入了 Composition API組合式 API它的核心思想是把同一個邏輯關注點的代碼放在一起。而setup函數就是組合式 API 的入口。1.2 setup 函數的基本定位在 Vue3 組件中setup是一個特殊的選項它在組件創建之前執行。可以把setup理解為組件內部的一塊“邏輯組織區域”所有組合式 API 的能力比如響應式數據、計算屬性、監聽器、生命周期鉤子都可以在這個函數里使用。template div{{ message }}/div /template script import { ref } from vue export default { setup() { const message ref(Hello Vue3) return { message } } } /script這里最核心的一行就是return { message }。如果沒有這個 return模板里就完全拿不到message。1.3 為什么 setup 需要返回值setup函數本質上是組件實例內部的初始化邏輯而模板中要使用的數據和方法必須通過返回值暴露給模板渲染上下文。也就是說在setup內部定義的變量默認是“私有的”。只有通過return返回出去的內容模板才能訪問。返回的對象會被“展開”到模板渲染上下文中相當于 Vue2 中data和methods合并后的效果。這個設計也解釋了為什么很多新手第一次寫 Vue3 時會發現“數據定義了但頁面上什么都沒有”絕大多數情況都是忘記return了。2. 環境準備與版本說明2.1 開發環境要求在開始寫代碼之前建議先確認本地環境滿足基本要求工具版本建議說明Node.js18 或更高Vite 構建工具對 Node 版本有要求建議使用 LTS 版本npm / pnpm / yarn最新穩定版包管理器任選一種即可Vue CLI / ViteVite 推薦Vue3 官方腳手架create-vue底層就是 ViteVSCode最新版配合 Volar 插件獲得完整的模板類型提示注意版本需要根據你的項目實際情況調整本文示例以常見環境為例重點演示配置思路。2.2 創建 Vue3 項目推薦使用官方腳手架創建項目npm create vuelatest執行后按提示輸入項目名稱選擇需要的功能如 TypeScript、Router、Pinia等待依賴安裝完成即可。cd vue3-setup-demo npm install npm run dev如果你更習慣 Vite 直接創建npm create vitelatest vue3-setup-demo -- --template vue2.3 示例項目結構本文后面的代碼示例都基于下面這個精簡結構vue3-setup-demo/ ├── index.html ├── package.json ├── vite.config.js └── src/ ├── main.js ├── App.vue └── components/ └── UserCard.vue重點是components/UserCard.vue我們會一步步演示 setup 返回值的各種用法。3. setup 函數的返回值類型詳解3.1 返回對象最常用形態setup最常見的返回值類型是普通對象。對象的屬性會暴露給模板并且被模板自動解包。template div p{{ name }}/p p{{ age }}/p button clickhandleClick點擊/button /div /template script import { ref } from vue export default { setup() { const name ref(張三) const age ref(28) const handleClick () { age.value } return { name, age, handleClick } } } /script這里需要注意幾個點name、age是ref對象模板中會自動解包所以直接寫{{ name }}而不是{{ name.value }}。handleClick是普通函數直接返回即可。返回對象中的 key 名就是模板中的變量名。3.2 返回渲染函數非常規用法setup除了返回對象也可以返回一個渲染函數。這種寫法比較少見但了解它有助于理解 Vue3 內部渲染機制。script import { h } from vue export default { setup() { const msg 我是渲染函數輸出的內容 return () h(div, msg) } } /script當setup返回一個函數時這個函數會被當作組件的 render 函數使用組件模板內容會被完全忽略。也就是說如果在這個組件里同時寫了template和返回渲染函數最終渲染以渲染函數為準。3.3 返回值的響應式規則setup的返回值中只有響應式數據才能觸發視圖更新。如果你返回一個普通變量模板雖然能顯示初始值但后續修改不會更新頁面。template div{{ count }}/div /template script export default { setup() { // 普通變量不是響應式 let count 0 setTimeout(() { count 100 }, 1000) return { count } } } /script這段代碼運行后頁面顯示 01 秒后變量count雖然變成了 100但頁面不會重新渲染。因為count是普通變量Vue 無法追蹤它的變化。正確的做法是使用ref或reactive創建響應式數據。3.4 返回值與模板編譯的關系從 Vue3 編譯原理來看模板會被編譯成基于渲染上下文的函數。return出去的對象本質上成為渲染上下文的一部分。當組件狀態發生變化時Vue 會重新執行渲染函數并對比新舊虛擬 DOM最終更新視圖。因此“返回值”不只是給模板用的它還決定了組件的響應式更新鏈路是否完整。4. 完整實戰案例用戶信息卡片組件這一節我們通過一個完整的用戶信息卡片組件把 setup 返回值的常見場景全部串起來。4.1 組件需求實現一個用戶卡片展示用戶名、年齡、簡介支持點擊按鈕修改年齡同時展示“簡介字數”這一計算屬性。4.2 編寫 UserCard.vuetemplate div classuser-card h2{{ user.name }}/h2 p年齡{{ user.age }}/p p簡介{{ user.bio }}/p p簡介字數{{ bioLength }}/p button clickincreaseAge年齡 1/button button clickresetUser重置/button /div /template script import { reactive, ref, computed } from vue export default { setup() { // reactive 定義對象類型響應式數據 const user reactive({ name: 李四, age: 25, bio: 前端開發工程師喜歡 Vue3 和 TypeScript }) // 獨立 ref 數據 const count ref(0) // 計算屬性 const bioLength computed(() user.bio.length) // 方法 const increaseAge () { user.age } const resetUser () { user.age 25 count.value 0 } // 把所有需要在模板中使用的數據和方法返回出去 return { user, count, bioLength, increaseAge, resetUser } } } /script style scoped .user-card { border: 1px solid #ddd; border-radius: 8px; padding: 20px; max-width: 360px; } button { margin-right: 10px; padding: 6px 14px; cursor: pointer; } /style4.3 在 App.vue 中引入組件template div UserCard / /div /template script import UserCard from ./components/UserCard.vue export default { components: { UserCard } } /script4.4 運行與驗證執行npm run dev打開瀏覽器訪問本地地址你應該能看到用戶卡片展示李四的基本信息。點擊“年齡 1”按鈕年齡數字遞增頁面同步刷新。簡介字數由computed自動計算。點擊“重置”按鈕年齡恢復為 25。4.5 關鍵點總結這個示例展示了setup返回值的幾個核心類型返回內容類型模板中的使用方式userreactive 對象user.name、user.age直接訪問屬性countref 對象模板自動解包直接寫{{ count }}bioLengthcomputed 對象像普通變量一樣使用increaseAge函數綁定給事件如click5. setup 返回值與生命周期、this 的關系5.1 setup 的執行時機setup是在組件實例創建之前執行的早于beforeCreate鉤子。所以在setup內部無法通過this訪問組件實例。export default { setup() { // 這里拿不到 this console.log(this) // undefined } }這一點和 Vue2 的寫法差別很大新手很容易踩坑。5.2 在 setup 中使用生命周期鉤子Vue3 的組合式 API 提供了一套帶on前綴的生命周期函數比如onMounted、onUpdated、onUnmounted。它們需要在setup函數內同步調用注冊的生命周期邏輯會與組件實例關聯。template div{{ message }}/div /template script import { ref, onMounted, onUnmounted } from vue export default { setup() { const message ref() onMounted(() { message.value 組件掛載完成 console.log(mounted in setup) }) onUnmounted(() { console.log(組件卸載了) }) return { message } } } /script這里可以看到onMounted不是通過return暴露給模板的它只是注冊了一個回調因此不需要出現在返回值中。這也是理解“返回值”邊界的重點返回值只負責供模板渲染和事件綁定訪問內部邏輯和生命周期不需要返回。5.3 生命周期對比表Vue2 選項Vue3 組合式 API說明beforeCreate不需要寫setup本身就在實例創建前執行created不需要寫setup內直接寫邏輯beforeMountonBeforeMount掛載前mountedonMounted掛載后beforeDestroyonBeforeUnmount銷毀前destroyedonUnmounted銷毀后從使用角度你只需要記住Vue2 中寫在created里的邏輯現在直接寫在setup函數里其他生命周期需求用對應的onXxx函數注冊。6. 深入拆解setup 返回值的“內部機制”6.1 返回對象如何暴露給模板setup返回對象的屬性會被代理到渲染上下文。源碼層面Vue 會調用applyOptions處理組件選項把setup的結果合并到實例的setupState中。模板編譯后生成的渲染函數就是從這個上下文中取值的。用一句話概括return的對象是組件模板的“數據源”。6.2 模板解包機制要注意的邊界當setup返回的對象中嵌套了ref對象時模板訪問不需要加.value但在setup函數內部操作時必須明確使用.value。const count ref(0) // setup 內部修改 function add() { count.value // 必須寫 value }模板中則直接寫button clickadd{{ count }}/button6.3 返回值與 props 的關系setup的第一個參數是props它包含了父組件傳入的所有屬性。父組件傳入的 props 本身是響應式的但這些值不能直接通過return暴露給模板重新賦值。script export default { props: { title: { type: String, default: } }, setup(props) { console.log(props.title) return { // 不要把 props 的屬性重新賦值 // title: xxx 這種寫法不能修改父組件傳入的值 } } } /script如果確實需要基于 props 派生新值推薦用computedimport { computed } from vue setup(props) { const upperTitle computed(() props.title.toUpperCase()) return { upperTitle } }6.4 返回 context 中的內容setup的第二個參數是 context它包含attrs、slots、emit、expose等。這些內容通常不需要整體返回但emit可以通過返回的方法暴露給模板使用script export default { emits: [submit], setup(props, context) { const handleSubmit () { context.emit(submit, { ok: true }) } return { handleSubmit } } } /script7. 常見問題與排查思路在實際開發中setup返回值相關的報錯和異常非常多下面整理幾個高頻問題。問題現象常見原因解決思路模板中顯示 undefined 或空白忘記在setup中 return 對應變量檢查 return 對象是否包含該變量頁面渲染了但修改數據后不更新使用了普通變量而不是ref/reactive改用響應式 API 定義數據setup中打印this是 undefinedVue3 組合式 API 中不能訪問 this改用 props、context 或組合式 API返回渲染函數后模板不生效setup返回函數時會被當作 render 函數刪除返回的渲染函數或改為返回對象控制臺警告Property xxx was accessed during render but is not defined模板中使用了未返回的變量在 return 對象中補充該變量父組件拿不到子組件內部方法子組件沒有使用defineExpose暴露在script setup中顯式調用defineExposereactive對象被解構后失去響應性直接解構 reactive 對象會丟失代理使用toRefs或避免解構7.1 典型報錯案例忘了 returntemplate div{{ name }}/div /template script import { ref } from vue export default { setup() { const name ref(Hello) } } /script運行后模板里name不顯示控制臺報未定義。修復方法就是在setup最后加上return { name }7.2 典型報錯案例reactive 解構丟失響應性setup() { const user reactive({ name: 張三, age: 20 }) // 錯誤解構出來的是普通值 return { ...user } }這種寫法在模板中雖然能看到初始值但修改user.age不會更新解構后的變量。正確的寫法有兩種第一種直接返回整個對象return { user }第二種用toRefs保持響應性import { reactive, toRefs } from vue setup() { const user reactive({ name: 張三, age: 20 }) return { ...toRefs(user) } }8. 最佳實踐與工程建議8.1 約定 return 的命名規范在大型項目中setup返回的變量名就是模板中的變量名命名要盡量語義化。推薦使用數據變量用名詞userInfo、tableData、loading。方法用動詞短語handleClick、fetchList、submitForm。布爾值用is/has開頭isVisible、hasPermission。8.2 不要把所有邏輯都堆在 setup 里setup返回值設計得當確實能減少模板中的數據來源混亂但如果把所有邏輯都塞進一個setup組件依然會變得不可維護。推薦的做法是抽離組合式函數。// 文件路徑src/composables/useUser.js import { reactive, computed } from vue export function useUser() { const user reactive({ name: 王五, age: 30 }) const bioLength computed(() user.bio?.length || 0) const increaseAge () { user.age } return { user, bioLength, increaseAge } }然后在組件中直接使用script import { useUser } from ../composables/useUser export default { setup() { const { user, bioLength, increaseAge } useUser() return { user, bioLength, increaseAge } } } /script這樣setup函數本身變得非常簡潔邏輯也更容易在多個組件間復用。8.3 警惕返回值中混入過多非響應式數據對于模板中不需要變化的數據能不放setup返回值就不放。比如靜態配置、常量數組如果模板里只是展示一次可以直接寫在模板里或者用普通變量導出。8.4 配合script setup減少樣板代碼Vue 3.2 之后官方推薦使用script setup語法糖這種寫法最大的優勢就是頂部聲明的變量和函數默認可以在模板中使用無需手動 return。script setup import { ref } from vue const count ref(0) const increment () { count.value } /script template button clickincrement{{ count }}/button /template從編譯結果看script setup本質上還是把內容轉成setup函數的返回值只是幫我們省略了手動編寫return的步驟。對開發體驗的提升非常明顯。8.5 defineExpose需要主動暴露時的補丁雖然script setup默認對模板開放所有頂層綁定但父組件無法直接訪問子組件內部的方法和數據。如果確實需要父組件通過ref調用子組件內部方法必須使用defineExpose顯式暴露。!-- Child.vue -- script setup import { ref } from vue const count ref(0) const reset () { count.value 0 } defineExpose({ reset }) /script template div{{ count }}/div /template父組件中通過模板引用拿到子組件實例后調用resettemplate Child refchildRef / button clickresetChild重置子組件/button /template script setup import { ref } from vue import Child from ./Child.vue const childRef ref(null) const resetChild () { childRef.value.reset() } /script8.6 性能與可維護性注意點setup中的函數每次渲染都會被重新創建但如果依賴了響應式數據依賴變化會觸發重新執行渲染函數這是正常機制不需要刻意優化。如果return的對象體積非常大模板中又沒有使用會導致渲染上下文中包含多余屬性雖然瀏覽器性能開銷不明顯但從代碼整潔角度仍建議精簡返回值。使用markRaw跳過不需要轉換為響應式的對象可以減少不必要的代理開銷。9. 總結與學習路線本文圍繞setup函數的返回值從概念、類型、原理、實戰到排查完整拆解了 Vue3 組合式 API 的核心入口。你掌握了幾個關鍵點setup是 Composition API 的入口創建時機早于beforeCreate。setup可以返回對象也可以返回渲染函數實際開發中以返回對象為主。返回對象中的ref在模板中自動解包在setup內部必須使用.value。普通變量不會觸發視圖更新響應式數據必須用ref或reactive創建。script setup語法糖能省去手動return但在父組件訪問子組件內部方法時需要defineExpose。關于setup返回值的自查可以記一個很實用的口訣模板里要用的必須 return 出去不是響應式數據的不要放進來湊數需要暴露給父組件的記得到 defineExpose 里登記。下一步可以繼續學習ref與reactive的深層區別、provide/inject跨層級通信、watch與watchEffect的監聽策略以及script setup下的組件通信方式。把這幾個知識點串起來Vue3 組合式 API 的主干基本就通了。如果你在本地練習時遇到setup返回值相關的報錯歡迎按文中表格逐條排查也可以多改幾行代碼看看頁面的實時反饋這種“改代碼—看結果”的循環對理解 Vue3 響應式機制非常有幫助。