一、快速上手:在不使用单文件组件的前提下创建应用实例
在 Vue 3 中,无需单文件组件也能构建完整的响应式应用。核心在于使用 Vue.createApp API 来创建应用实例,并通过根节点挂载到页面上。你可以选择 CDN 引入或现代打包工具,从而避免 SFC 的编译步骤。
无 SFC 的工作流强调将数据、模板与渲染逻辑放在一个简单的入口中,降低入门门槛,同时保持响应性和组件化思想的可维护性。
准备工作与环境
你需要一个可运行的浏览器环境,以及一个可用的入口文件。若使用 CDN,请在页面中引入 Vue 3 的全局对象;若使用打包工具,请确保安装了 @vue/runtime-dom,以便在浏览器中挂载与渲染。
核心目标是创建一个最小可用的应用实例,具备数据响应和模板渲染能力,而不需要任何 .vue 文件。
使用 CDN 构建最小示例
通过 CDN 使用 Vue 3 时,可以直接在全局对象 Vue 上调用 createApp,并用一个简单的模板字符串来渲染内容。
为了演示完整的工作流程,下面给出一个最小示例的要点:首先在 HTML 中定义根容器,然后在脚本中创建并挂载应用。
// CDN 引入后的最小示例(无 SFC)
const app = Vue.createApp({setup() {const count = Vue.ref(0)function inc() { count.value++ }return { count, inc }},template: `计数:{{ count }}`
})
app.mount('#app')
此外,需要一个根容器,例如在同一 HTML 文件中放置 <div id="app"></div>,Vue 将把模板渲染到这里。
在模块化项目中引入 Vue 进行创建
若你使用打包工具,如 Vite、Webpack,请从 npm 安装 Vue,并以模块化方式导入。核心仍然是 Vue.createApp,但你可以组合更复杂的逻辑。
示例场景包括使用组合式 API、使用渲染函数自行渲染内容,或者将模板写在普通的字符串中以保持无 SFC 的简单性。
// 使用 ESM 模块(非 SFC 的常用模式)
import { createApp, ref } from 'vue'const App = {setup() {const message = ref('欢迎使用 Vue 3 的无 SFC 模式')return { message }},template: `{{ message }}`
}
createApp(App).mount('#app')二、核心概念:响应式数据在无 SFC 情况下的实现
ref 与 reactive 的使用
在不使用 SFC 的前提下,ref 和 reactive 提供了核心的响应式能力。你可以把简单的数值绑定到模板,也可以把对象转为响应式数据以便在 UI 中直接使用。
ref 创建的值需要通过 .value 访问与修改;reactive 将对象包装为响应式代理,直接在模板中引用对象属性即可。
const app = Vue.createApp({setup() {const count = Vue.ref(0)const user = Vue.reactive({ name: 'Alice', role: '开发者' })function bump() { count.value++ }return { count, user, bump }},template: `用户: {{ user.name }},计数: {{ count }}`
})
app.mount('#app')计算属性与副作用
为了从现有数据派生新值,可以使用 Vue.computed;同时,可以通过副作用 API 观察数据变化,响应式系统会在相关值更新时重新渲染。
计算属性会根据依赖自动缓存,避免不必要的重复计算;watchEffect 则在依赖变化时执行副作用函数,便于实现简单的自动化响应。
const app = Vue.createApp({setup() {const count = Vue.ref(1)const double = Vue.computed(() => count.value * 2)Vue.watchEffect(() => {console.log('当前计数值:', count.value)})return { count, double }},template: `计数: {{ count }}, 双倍: {{ double }}`
})
app.mount('#app')生命周期与副作用清理
尽管没有 SFC,Vue 的组合式 API 仍然提供了生命周期钩子,用于在应用挂载、更新和卸载时执行逻辑。常用的有 onMounted、onUnmounted 等。
通过这些钩子,你可以在组件进入页面时执行初始化,在离开页面时进行资源清理,从而避免内存泄漏与多次订阅的问题。
const app = Vue.createApp({setup() {const started = Vue.ref(false)Vue.onMounted(() => {started.value = trueconsole.log('应用已挂载')})Vue.onUnmounted(() => {console.log('应用已卸载,清理资源')})return { started }},template: `已挂载: {{ started }}`
})
app.mount('#app')三、实战示例:逐步搭建一个可响应的计数器应用
HTML 结构与根实例
要在无 SFC 的前提下实现一个可响应的计数器,首先需要一个根容器来承载应用。这里的根容器与模板将直接绑定,避免额外的组件文件。
根容器是应用生命周期的锚点,通过 id 与挂载点实现绑定与渲染。
<div id="app"></div>
通过上面的根容器,可以在后续的脚本中对应用进行创建、绑定与交互。

脚本:创建与绑定应用实例
在这个阶段,我们通过 Vue.createApp 创建应用实例,使用 ref 维护计数状态,并通过模板来呈现交互控件。
要点在于明确数据源、事件处理与渲染模板的绑定,确保 UI 能够正确地响应用户操作。
const app = Vue.createApp({setup() {const count = Vue.ref(0)function increment() { count.value++ }return { count, increment }},template: `当前计数:{{ count }} `
})
app.mount('#app')优化与注意事项
在无 SFC 的场景下,注意保持模板简洁、将复杂逻辑拆分到可重用的函数中,并合理使用 ref、computed 与 watchEffect 来控制重新渲染的范围。
如果项目逐渐变大,考虑将公共逻辑提取为可复用的组合式函数,并适时引入组件化结构,但这并不一定要求走 SFC 路线。通过合理组织,仍然可以保持清晰的代码组织和良好的开发体验。
// 仅示意:将公共逻辑提取为组合式函数
function useCounter(start = 0) {const count = Vue.ref(start)function inc() { count.value++ }return { count, inc }
}const App = {setup() {const { count, inc } = useCounter(5)return { count, inc }},template: `计数: {{ count }} `
}
Vue.createApp(App).mount('#app') 

