普通视图

发现新文章,点击刷新页面。
昨天以前首页

一个例子搞懂vite快再哪里

2026年1月6日 13:58

第一步:浏览器请求 /src/main.js

  • 你在浏览器中打开页面 → 浏览器加载 index.html
  • index.html 中有:html预览
<script type="module" src="/src/main.js"></script>
  • 浏览器向服务器发送请求:GET /src/main.js

💡注意:这是个 ESM 模块请求,必须用 type="module" 才能被浏览器支持。


第二步:Vite 即时编译并返回 main.js

  • Vite 接收到请求 /src/main.js
  • 它读取这个文件,发现它导入了 vueApp.vue
  • 由于 main.js 是纯 JS,无需复杂转换,Vite 直接返回:js编辑
import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');

注意:此时还没有处理 App.vue,因为浏览器还没请求它!


第三步: main.js import App.vue → 浏览器自动请求 /src/App.vue

  • 浏览器执行 main.js,遇到:js编辑
import App from './App.vue';
  • 浏览器根据 ESM 规范,自动发起新的 HTTP 请求bash编辑
GET /src/App.vue

这是关键!浏览器会自动按依赖关系加载模块,不需要 Vite 主动推送。


第四步:Vite 编译 App.vue 并返回 JS

  • Vite 收到 /src/App.vue 的请求
  • 它知道 .vue 文件需要解析模板、脚本、样式
  • Vite 使用内置的 Vue SFC 编译器App.vue 转换为纯 JavaScript:js编辑
// 编译后的内容(简化版)
const App = {
  setup() {
    return {};
  },
  render() {
    return h('div', [h(Header), h(Home)]);
  }
};
export default App;
  • 然后 Vite 返回这段 JS 给浏览器

我们来详细解释:


1. .vue 文件不是标准 JavaScript

<!-- App.vue -->
<template>
  <div><Header /><Home /></div>
</template>

<script>
  import Header from './Header.vue'
  import Home from './Home.vue'
  export default {
    components: { Header, Home }
  }
</script>
  • 浏览器无法直接理解这种语法。
  • 它需要被转换成浏览器能执行的 纯 JavaScript + 渲染函数

2. Vite(通过 @vitejs/plugin-vue)将 .vue 编译为 JS 模块

当你在浏览器中请求 /src/App.vue 时,Vite 内部使用 Vue 官方的 SFC 编译器( @vue/compiler-sfc 将其转换为一个标准的 ES 模块(ESM) ,内容大致如下(简化版):

// 这是一个合法的 JavaScript 模块!
import { defineComponent as _defineComponent } from 'vue';
import Header from './Header.vue';
import Home from './Home.vue';

const App = /*#__PURE__*/_defineComponent({
  __name: 'App',
  setup(__props, { expose }) {
    expose();
    return {};
  },
  render() {
    const _component_Header = Header;
    const _component_Home = Home;
    return (_openBlock(), _createElementBlock("div", null, [
      _createVNode(_component_Header),
      _createVNode(_component_Home)
    ]));
  }
});

export default App;

🔍 注意:

  • 它有 importexport → 是标准 ESM
  • 它导出一个对象(或函数),符合 Vue 组件规范
  • 它可以被其他模块(如 main.js)正常导入

3. 浏览器如何处理这个“JS 文件”?

当 Vite 返回上述代码时,对浏览器来说:

  • 它收到的是一个 .js 类型的响应(即使 URL 是 /src/App.vue
  • 因为请求是通过 <script type="module"> 触发的,浏览器会:
    1. 解析这段 JS
    2. 执行其中的 import(自动发起新请求加载 Header.vue 等)
    3. App 组件注册到 Vue 应用中

从网络面板看:
请求 URL 是 App.vue,但 Content-Type 是 application/javascript,说明服务器返回的是 JS。


4. 为什么说它是“一个 JS 文件”?

虽然物理上你写的是 .vue 文件,但在开发服务器运行时:

角色 看到的内容
开发者 .vue单文件组件(模板 + 脚本 + 样式)
Vite 服务器 接收 .vue请求 → 编译 → 返回 JS
浏览器 收到一段可执行的 JavaScript 模块

所以,在运行时, .vue 文件被“虚拟地”转换成了一个 JS 模块。你可以把它理解为:

💡 “每个 .vue 文件在 Vite 中都会动态生成一个对应的 JS 模块”


5. 验证方法:打开浏览器 DevTools

  1. 启动 Vite 项目
  2. 打开 Chrome DevTools → Network(网络)选项卡
  3. 刷新页面
  4. 找到 App.vue 的请求
  5. 点击它,查看 Preview 或 Response

你会看到:返回的确实是 JavaScript 代码,而不是原始的 <template> 结构!


总结

是的, App.vue ****在 Vite 开发服务器中会被编译成一个标准的 JavaScript ES 模块,并以 JS 形式返回给浏览器。

虽然文件扩展名是 .vue,但传输和执行的内容是纯 JS,因此对浏览器来说,它就是一个普通的 JS 模块。

这就是 Vite(以及现代前端工具链)能够无缝支持 .vue.svelte.jsx 等非标准文件的关键机制:在请求时即时编译为浏览器可执行的 JavaScript


第五步: App.vue import Header.vue Home.vue → 继续按需加载

  • 浏览器执行 App.vue 的 JS,发现:js编辑
import Header from './components/Header.vue';
import Home from './components/Home.vue';
  • 浏览器再次自动发起两个请求:bash编辑
GET /src/components/Header.vue
GET /src/components/Home.vue
  • Vite 分别编译这两个文件,返回对应的 JS

第六步:最终渲染页面

  • 所有模块都加载完毕
  • 浏览器执行所有代码,渲染出:html预览
<div>
  <header>My App</header>
  <main>Welcome to Home</main>
</div>

全流程总结(时间线)

时间 事件
t=0 浏览器请求 /src/main.js
t=50ms Vite 返回 main.js
t=100ms 浏览器执行 main.js,请求 /src/App.vue
t=150ms Vite 编译并返回 App.vue
t=200ms 浏览器执行 App.vue,请求 Header.vueHome.vue
t=250ms Vite 编译并返回两个组件
t=300ms 页面渲染完成

总耗时:< 500ms,且只处理了实际使用的模块!


对比:如果用 Webpack 会怎样?

Webpack 会在启动时:

  1. 扫描 main.jsApp.vueHeader.vueHome.vue
  2. 把所有这些文件全量编译成一个 bundle(如 app.js
  3. 再返回给浏览器

即使你只是想看首页,也得等整个项目打包完!


关键结论

Vite 的“快”,来自于“懒”
它不提前做任何事,只在你真正需要某个模块时,才去编译它。

这就像:

  • Webpack:先煮一锅汤,再分碗吃
  • Vite:你点什么菜,我现炒什么,立刻上桌

这就是为什么 Vite 启动速度能秒杀传统打包工具的原因。

vue3中使用defineModel

2026年1月4日 16:41

是的!从 Vue 3.4 开始,Vue 官方引入了 defineModel() 编译宏(macro),极大简化了组件中实现 v-model 双向绑定的写法,无需手动声明 propsemit,也无需处理 modelValue / update:modelValue 的样板代码。

下面系统讲解 defineModel() 的使用方式、原理、优势和注意事项。


一、基础用法:替代 modelValue + emit

传统写法(Vue 3.0 ~ 3.3)

<!-- Child.vue -->
<script setup>
  const props = defineProps(['modelValue'])
  const emit = defineEmits(['update:modelValue'])

  function update() {
    emit('update:modelValue', props.modelValue + 1)
  }
</script>

<template>
  <div>{{ props.modelValue }}</div>
  <button @click="update">+</button>
</template>

Vue 3.4+ 新写法: defineModel()

<!-- Child.vue -->
<script setup>
  const model = defineModel() // 返回一个 ref

  function update() {
    model.value++ // 直接修改,自动同步到父组件
  }
</script>

<template>
  <div>Parent bound v-model is: {{ model }}</div>
  <button @click="update">Increment</button>
</template>

父组件完全不变:

<template>
  <Child v-model="count" />
</template>

<script setup>
  import { ref } from 'vue'
  const count = ref(0)
</script>

model 是一个 双向绑定的 ref

  • 读取 model.value → 获取父组件传入的值
  • 修改 model.value → 自动触发 update:modelValue,更新父组件数据

二、支持带参数的 v-model (多模型绑定)

Vue 支持多个 v-model,例如:

<Parent>
  <Child v-model:name="name" v-model:age="age" />
</Parent>

使用 defineModel 实现:

<!-- Child.vue -->
<script setup>
  const name = defineModel('name')
  const age = defineModel('age')

  // 或者用对象形式(可选)
  // const { name, age } = defineModels({ name: String, age: Number })
</script>

<template>
  <input v-model="name" />
  <input v-model.number="age" />
</template>

注意:defineModel('propName') 会自动对应 v-model:propName


三、类型与默认值(TypeScript / 运行时校验)

1. 指定类型(TypeScript)

const model = defineModel<string>()
// model.value 类型为 string | undefined

2. 设置默认值

const model = defineModel({ default: 'hello' })

3. 运行时校验 + 默认值

const model = defineModel({
  type: String,
  required: false,
  default: 'default text'
})

💡 这些选项会自动转换为等效的 ****props ****声明,由 Vue 编译器处理。


四、与 useAttrs() 协同工作

虽然 defineModel() 自动处理了 modelValue,但其他属性仍需透传:

<script setup>
  const model = defineModel()
  // 如果有多个根节点,或想控制透传位置,才需要 useAttrs
</script>

<template>
  <!-- 单根节点:自动透传 attrs(包括 class/style/@focus 等) -->
  <input v-model="model" />
</template>

如果组件有多个根节点,必须手动使用 v-bind="$attrs",否则 Vue 会警告。


五、总结:为什么推荐 defineModel()

对比项 传统方式 defineModel()
代码量 多(props + emits) 极简(一行)
易错性 容易拼错 update:modelValue 零错误
可读性 逻辑分散 聚焦数据流
封装效率 高(尤其包装原生元素)
TypeScript 支持 需手动标注 自动推导

一句话
defineModel() ****让组件的双向绑定回归“直觉”——就像操作本地状态一样简单,却能自动同步到父组件。


❌
❌