当前位置: 首页 > news >正文

Vue 2脚手架从入门到实战核心知识点全解析(day6):从工程结构到高级通信(附代码讲解)

        作为前端开发的核心框架之一,Vue 的脚手架工具(Vue CLI)极大提升了开发效率。本文将从脚手架工程结构入手,系统梳理 Vue 核心配置、组件通信、存储方案等关键知识点,助力开发者夯实基础、高效开发。

一、Vue 脚手架工程结构详解

脚手架初始化后的项目结构清晰规范,各目录文件各司其职,理解其含义是高效开发的前提:

├── node_modules  # 项目依赖包,npm install生成
├── public        # 静态资源根目录,不会被webpack处理
│   ├── favicon.ico  # 浏览器标签页图标
│   └── index.html   # 应用入口HTML,Vue实例挂载的载体
├── src           # 源代码核心目录,开发核心区域
│   ├── assets    # 静态资源(图片、样式等),会被webpack处理
│   ├── component # 公共组件目录,可复用的UI单元
│   ├── App.vue   # 根组件,汇总所有业务组件的入口
│   └── main.js   # 程序入口文件,初始化Vue实例、配置全局资源
├── .gitignore    # Git版本控制忽略规则,指定无需提交的文件
├── babel.config.js # Babel配置文件,实现ES6+语法向下兼容
├── package.json  # 项目配置核心文件,包含依赖、脚本命令等信息
├── README.md     # 项目说明文档,记录开发规范、启动方式等
└── package-lock.json # 锁定依赖包版本,确保团队开发环境一致

二、Vue 核心版本差异与配置

2.1 核心文件版本区别

Vue 的核心文件分为完整版和运行时版,实际开发中需根据场景选择:

  • vue.js(完整版):包含核心功能 + 模板解析器,支持直接在组件中使用template配置项,开发调试更便捷。
  • vue.runtime.xxx.js(运行时版):仅包含核心功能,无模板解析器,体积更小。因缺少解析器,不能使用template,需通过render函数结合createElement方法构建 DOM 结构,适合生产环境。

2.2 脚手架配置技巧

脚手架默认配置可通过命令查看,个性化需求则通过配置文件实现:

  1. 查看默认配置:执行vue inspect > output.js,将默认配置输出到 output.js 文件
  2. 个性化配置:创建vue.config.js文件自定义配置,官方文档参考Vue CLI 配置,核心配置方向包括代理、入口文件、静态资源等。

三、Vue 组件核心配置

3.1 ref 属性:DOM 与组件的引用桥梁

ref 用于给元素或组件注册引用信息,替代传统 DOM 操作中的 id,使用简单高效:

  • 作用场景:获取 DOM 元素、调用子组件方法或访问子组件数据
  • 使用方式:
    1. 标记目标:<h1 ref="title">标题</h1> 或 <User ref="userComp"></User>
    2. 访问目标:this.$refs.title(获取 DOM 元素)、this.$refs.userComp(获取组件实例)

3.2 props 配置:父组件向子组件传值

props 是父向子通信的核心方式,支持数据验证,确保数据规范性:

  • 基础用法:
    1. 父组件传值:<User name="张三" age="20"></User>
    2. 子组件接收:
      • 简单接收:props: ['name', 'age']
      • 类型限制:props: { name: String, age: Number }
      • 完整配置:包含类型、必要性、默认值
      props: {name: {type: String,    // 数据类型required: true,  // 是否必传default: '匿名'  // 默认值(required为false时生效)}
      }
      
  • 注意事项:props 是只读属性,若需修改,需复制到 data 中(data() { return { userName: this.name } })再操作。

3.3 mixin 混入:组件代码复用方案

当多个组件存在相同配置(如 data、methods)时,mixin 可实现代码抽离复用:

  • 定义混入对象:
    // mixin.js
    export const myMixin = {data() { return { count: 0 } },methods: { increment() { this.count++ } }
    }
    
  • 使用方式:
    • 局部混入(仅当前组件生效):mixins: [myMixin]
    • 全局混入(所有组件生效,谨慎使用):Vue.mixin(myMixin)

3.4 插件:Vue 功能增强利器

插件用于扩展 Vue 的核心能力,本质是包含install方法的对象:

  • 插件定义:
    export const myPlugin = {install(Vue, options) {// 全局过滤器Vue.filter('formatDate', (val) => new Date(val).toLocaleString())// 全局指令Vue.directive('focus', { inserted(el) { el.focus() } })// 实例方法Vue.prototype.$showMsg = (msg) => alert(msg)}
    }
    
  • 插件使用:在 main.js 中引入后调用Vue.use(myPlugin),即可全局使用插件功能。

3.5 scoped 样式:组件样式隔离

避免组件间样式冲突的核心方案,通过给样式添加作用域实现:

  • 用法:在 style 标签添加scoped属性,如<style scoped>
  • 原理:webpack 编译时会给组件 DOM 添加唯一属性,样式选择器自动关联该属性,确保仅作用于当前组件。

四、组件通信全方案

组件通信是 Vue 开发的核心场景,不同场景对应不同方案,需灵活选择:

4.1 自定义事件:子组件向父组件传值

最常用的子向父通信方式,回调函数定义在父组件,子组件触发执行:

  • 绑定方式:
    1. 模板绑定:<User @sendMsg="handleMsg"></User>
    2. 编程式绑定:通过 ref 获取组件后绑定this.$refs.user.$on('sendMsg', this.handleMsg)
  • 触发方式:子组件中通过this.$emit('sendMsg', 数据)触发事件并传值
  • 进阶技巧:添加once修饰符(<User @sendMsg.once="handleMsg">)实现事件仅触发一次;通过this.$off('sendMsg')解绑事件。

4.2 全局事件总线:任意组件间通信

基于 Vue 实例实现的全局通信方案,适用于跨层级、非关联组件通信:

  • 安装总线:在 main.js 的 Vue 实例创建前配置
    new Vue({beforeCreate() {Vue.prototype.$bus = this // 将Vue实例作为总线挂载到原型},render: h => h(App)
    }).$mount('#app')
    
  • 使用方式:
    1. 接收数据:在组件 mounted 中绑定事件this.$bus.$on('msg', (data) => {})
    2. 发送数据:在任意组件中触发this.$bus.$emit('msg', 数据)
    3. 销毁事件:在组件 beforeDestroy 中解绑this.$bus.$off('msg'),避免内存泄漏

步骤:

  • 安装全局事件总线:

     new Vue({......beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm},......}) 

  • 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的<span style="color:red">回调留在A组件自身。</span>

      methods(){demo(data){......}}......mounted() {this.$bus.$on('xxxx',this.demo)}

    2. 提供数据:this.$bus.$emit('xxxx',数据)

  • 最好在beforeDestroy钩子中,用$off去解绑<span style="color:red">当前组件所用到的</span>事件。

4.3 消息订阅与发布(pubsub):跨组件通信补充

第三方库实现的通信方案,适用于复杂组件结构,与全局总线功能类似:

  • 使用步骤:
    1. 安装依赖:npm i pubsub-js
    2. 引入库:import pubsub from 'pubsub-js'
    3. 订阅消息:this.pubId = pubsub.subscribe('msg', (msgName, data) => {})
    4. 发布消息:pubsub.publish('msg', 数据)
    5. 取消订阅:在 beforeDestroy 中执行pubsub.unsubscribe(this.pubId)

4.4 插槽:父组件向子组件传结构

特殊的通信方式,允许父组件向子组件指定位置插入 HTML 结构,分为三类:

  1. 默认插槽:无名称的基础插槽,父组件直接插入内容
    <!-- 父组件 -->
    <Card><div>卡片内容</div></Card>
    <!-- 子组件 -->
    <template><div><slot>默认内容</slot></div></template>
    
  2. 具名插槽:给插槽命名,实现多位置精准插入
    <!-- 父组件 -->
    <Card><template v-slot:header><h3>标题</h3></template><template #footer><button>按钮</button></template>
    </Card>
    <!-- 子组件 -->
    <template><div><slot name="header"></slot><slot name="footer"></slot></div>
    </template>
    
  3. 作用域插槽:子组件向父组件传数据,父组件决定渲染结构(数据在子,结构在父)
    <!-- 父组件 -->
    <GameList><template scope="scopeData"><ul><li v-for="g in scopeData.games" :key="g">{{g}}</li></ul></template>
    </GameList>
    <!-- 子组件 -->
    <template><div><slot :games="games"></slot> <!-- 向父组件传数据 --></div>
    </template>
    <script>
    export default {data() { return { games: ['王者荣耀', '原神'] } }
    }
    </script>
    

五、数据存储与网络请求配置

5.1 webStorage 本地存储

浏览器端本地存储方案,适用于存储少量非敏感数据,分为两类:

特性sessionStoragelocalStorage
存储周期浏览器窗口关闭后消失永久存储,需手动清除
作用域同一窗口共享同一域名下所有窗口共享
核心 API相同,均为 setItem、getItem、removeItem、clear
存储大小约 5MB约 5MB
  • 实用技巧:存储对象时需先 JSON 序列化(JSON.stringify(obj)),读取时反序列化(JSON.parse(str))。

5.2 脚手架配置代理

解决前端跨域问题的核心方案,通过配置 vue.config.js 实现请求转发:

  1. 简单代理(单目标):适合仅对接一个后端服务的场景

    module.exports = {devServer: {proxy: "http://localhost:5000" // 后端服务地址}
    }
    

    原理:请求前端不存在的资源时,自动转发到后端服务。

  2. 多代理配置(多目标):适合对接多个后端服务的场景

    module.exports = {devServer: {proxy: {'/api1': { // 匹配以/api1开头的请求target: 'http://localhost:5000', // 目标服务1changeOrigin: true, // 隐藏前端真实端口pathRewrite: {'^/api1': ''} // 移除请求前缀},'/api2': { // 匹配以/api2开头的请求target: 'http://localhost:5001', // 目标服务2changeOrigin: true,pathRewrite: {'^/api2': ''}}}}
    }
    

六、实用进阶技巧

6.1 nextTick:DOM 更新后的回调

当数据修改后需操作更新后的 DOM 时,使用 nextTick 确保操作时机正确:

  • 用法:this.$nextTick(() => { /* 操作DOM的代码 */ })
  • 原理:VueDOM 更新是异步的,nextTick 会在 DOM 更新完成后触发回调。

6.2 过渡与动画:提升用户体验

Vue 封装的过渡动画能力,通过<transition>标签实现元素插入 / 移除动画:

  • 基础用法:
    <template><transition name="fade"><div v-show="isShow">动画元素</div></transition>
    </template>
    <style>
    .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; }
    .fade-enter, .fade-leave-to { opacity: 0; }
    </style>
    
  • 多元素过渡:使用<transition-group>标签,需给子元素添加唯一 key。

七、TodoList 案例核心总结

案例代码:

1. 项目结构

src/
├── components/
│   ├── TodoHeader.vue   // 输入框组件:添加待办事项
│   ├── TodoList.vue     // 列表容器组件:展示待办列表
│   ├── TodoItem.vue     // 列表项组件:单个待办项的展示与操作
│   └── TodoFooter.vue   // 底部组件:全选、统计、清除已完成
├── App.vue              // 根组件:整合所有组件,管理核心数据
└── main.js              // 入口文件:创建Vue实例

2. 核心代码实现

main.js(入口文件)
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({render: h => h(App),
}).$mount('#app')
App.vue(根组件,数据管理核心)
<template><div id="app"><div class="todo-container"><div class="todo-wrap"><!-- 头部输入组件 --><TodoHeader @addTodo="addTodo" /><!-- 列表组件(传递待办数据和操作方法) --><TodoList :todos="todos" @deleteTodo="deleteTodo" @checkTodo="checkTodo"/><!-- 底部统计组件(传递数据和操作方法) --><TodoFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllDone="clearAllDone"/></div></div></div>
</template><script>
import TodoHeader from './components/TodoHeader'
import TodoList from './components/TodoList'
import TodoFooter from './components/TodoFooter'export default {name: 'App',components: { TodoHeader, TodoList, TodoFooter },data() {return {// 从本地存储读取数据(初始化)todos: JSON.parse(localStorage.getItem('todos')) || []}},methods: {// 添加待办事项addTodo(todoName) {if (!todoName.trim()) return  // 空内容不添加this.todos.unshift({id: Date.now(),  // 用时间戳作为唯一IDtitle: todoName,done: false      // 默认未完成})},// 删除待办事项deleteTodo(id) {this.todos = this.todos.filter(todo => todo.id !== id)},// 切换待办事项状态(完成/未完成)checkTodo(id) {this.todos.forEach(todo => {if (todo.id === id) todo.done = !todo.done})},// 全选/取消全选checkAllTodo(isCheckAll) {this.todos.forEach(todo => {todo.done = isCheckAll})},// 清除所有已完成事项clearAllDone() {this.todos = this.todos.filter(todo => !todo.done)}},// 监听todos变化,同步到本地存储(实现数据持久化)watch: {todos: {deep: true,  // 深度监听(对象内部属性变化)handler(value) {localStorage.setItem('todos', JSON.stringify(value))}}}
}
</script><style>
/* 基础样式 */
body {background: #fff;
}
.todo-container {width: 600px;margin: 0 auto;padding: 20px;box-shadow: 0 0 5px #ccc;
}
.todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}
</style>
TodoHeader.vue(添加待办组件)
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车确认"@keyup.enter="handleKeyUp"v-model="todoName"></div>
</template><script>
export default {name: 'TodoHeader',data() {return {todoName: ''  // 绑定输入框内容}},methods: {// 回车添加待办handleKeyUp() {// 通过自定义事件将输入内容传递给父组件this.$emit('addTodo', this.todoName)// 清空输入框this.todoName = ''}}
}
</script><style scoped>
.todo-header input {width: 100%;height: 40px;padding: 0 10px;font-size: 16px;border: 1px solid #ddd;border-radius: 4px;box-sizing: border-box;
}
.todo-header input:focus {outline: none;border-color: #66afe9;box-shadow: 0 0 8px rgba(102, 175, 233, 0.6);
}
</style>
TodoList.vue(列表容器组件)
<template><ul class="todo-list"><!-- 遍历待办数据,渲染列表项 --><TodoItem v-for="todo in todos" :key="todo.id" :todo="todo"  // 传递单个待办项数据@deleteTodo="deleteTodo"  // 传递删除事件@checkTodo="checkTodo"    // 传递状态切换事件/></ul>
</template><script>
import TodoItem from './TodoItem'export default {name: 'TodoList',components: { TodoItem },props: {todos: {  // 接收父组件传递的待办列表type: Array,required: true}},methods: {// 转发删除事件给父组件deleteTodo(id) {this.$emit('deleteTodo', id)},// 转发状态切换事件给父组件checkTodo(id) {this.$emit('checkTodo', id)}}
}
</script><style scoped>
.todo-list {margin: 0;padding: 0;list-style: none;
}
</style>
TodoItem.vue(列表项组件)
<template><li><label><!-- 复选框绑定待办状态 --><input type="checkbox" :checked="todo.done"@change="handleCheck"><!-- 已完成项添加删除线样式 --><span :class="{ done: todo.done }">{{ todo.title }}</span></label><!-- 删除按钮 --><button class="btn-del" @click="handleDelete">×</button></li>
</template><script>
export default {name: 'TodoItem',props: {todo: {  // 接收单个待办项数据type: Object,required: true}},methods: {// 切换待办状态handleCheck() {this.$emit('checkTodo', this.todo.id)},// 删除当前待办项handleDelete() {if (confirm('确定要删除吗?')) {this.$emit('deleteTodo', this.todo.id)}}}
}
</script><style scoped>
li {padding: 10px;border-bottom: 1px solid #eee;display: flex;align-items: center;justify-content: space-between;
}
li:hover {background-color: #f5f5f5;
}
li label {flex: 1;cursor: pointer;
}
li label input {margin-right: 8px;
}
/* 已完成样式 */
.done {color: #999;text-decoration: line-through;
}
/* 删除按钮样式 */
.btn-del {width: 30px;height: 30px;border: none;background: #f44336;color: white;border-radius: 50%;cursor: pointer;font-size: 16px;display: flex;align-items: center;justify-content: center;
}
.btn-del:hover {background: #d32f2f;
}
</style>
TodoFooter.vue(底部统计组件)
<template><div class="todo-footer"><label><!-- 全选复选框 --><input type="checkbox" :checked="isCheckAll"@change="handleCheckAll"></label><span><!-- 统计未完成数量 -->剩余 <span class="todo-count">{{ remainingCount }}</span> 项未完成</span><!-- 清除已完成按钮(有已完成项才显示) --><button class="btn-clear" @click="handleClearAll"v-show="hasDone">清除已完成</button></div>
</template><script>
export default {name: 'TodoFooter',props: {todos: {type: Array,required: true}},computed: {// 未完成数量remainingCount() {return this.todos.filter(todo => !todo.done).length},// 是否有已完成项hasDone() {return this.todos.some(todo => todo.done)},// 是否全选(所有项都完成时为true)isCheckAll() {return this.todos.length > 0 && this.todos.every(todo => todo.done)}},methods: {// 全选/取消全选handleCheckAll(e) {this.$emit('checkAllTodo', e.target.checked)},// 清除所有已完成项handleClearAll() {this.$emit('clearAllDone')}}
}
</script><style scoped>
.todo-footer {padding: 10px;border-top: 1px solid #ddd;display: flex;align-items: center;justify-content: space-between;
}
.todo-count {color: #e74c3c;font-weight: bold;
}
.btn-clear {padding: 5px 10px;border: none;background: #3498db;color: white;border-radius: 4px;cursor: pointer;
}
.btn-clear:hover {background: #2980b9;
}
</style>

3. 功能说明

  1. 添加功能:在输入框输入内容,按回车添加到待办列表
  2. 状态切换:点击复选框可切换单个待办项的完成状态
  3. 全选功能:底部复选框可一键全选 / 取消全选所有待办项
  4. 删除功能:点击单个待办项后的 "×" 可删除该项
  5. 清除功能:点击 "清除已完成" 可删除所有已完成的待办项
  6. 统计功能:实时显示未完成的待办项数量
  7. 数据持久化:通过 localStorage 保存数据,页面刷新后数据不丢失

4. 核心技术点

  • 组件化拆分与通信(props 传递数据,自定义事件传递操作)
  • 数据持久化(localStorage + watch 深度监听)
  • 列表渲染与 key 的使用(v-for 遍历,时间戳作为唯一 ID)
  • 计算属性(computed)处理派生数据(未完成数量、全选状态等)
  • 事件委派与事件转发(子组件事件通过父组件中转)

通过 TodoList 案例可串联组件开发全流程,核心思路如下:

  1. 组件拆分:按功能拆分为 TodoHeader(输入)、TodoList(列表)、TodoItem(项)、TodoFooter(统计)
  2. 数据管理:采用状态提升,将待办数据存放在根组件,子组件通过 props 接收或通过自定义事件修改
  3. 核心原则
    • v-model 绑定的值不能是 props 直接传递的内容
    • 父子通信优先用 props + 自定义事件
    • 跨组件通信可采用全局事件总线
  4. 本地存储:结合 localStorage 实现待办数据持久化,页面刷新后数据不丢失

结语

        Vue 脚手架的核心知识点围绕工程结构、组件配置、通信方案三大维度展开。实际开发中,需根据项目规模和业务场景灵活选择技术方案:小型项目可简化配置,聚焦组件通信;大型项目则需规范工程结构,合理使用混入、插件提升代码复用性。掌握本文梳理的知识点,可轻松应对多数 Vue 开发场景,为后续学习 Vue3 打下坚实基础。

http://www.dtcms.com/a/609651.html

相关文章:

  • 2025年AI面试防作弊指南:技术笔试如何识别异常行为
  • (十)嵌入式面试题收集:15道
  • 标准解读|即将实施的三份汽车安全强制性标准
  • 手机网站建设的流程2024房价即将暴涨十大城市
  • 根系扫描仪实战解析:如何精准获取根长、根表面积与拓扑结构?
  • Mock技术文档
  • 【OpenCV + VS】用addWeighted实现图像线性融合
  • Ubuntu系统创建Conda环境并安装Pytorch(保姆教程)
  • 腾讯KaLM-Embedding开源,登顶全球第一
  • 从零开始学习tensort模型部署(二):从文件加载引擎的完整指南
  • Muon 优化器代码实现详解
  • 老河口网站设计保定网站搜索引擎优化
  • 基于TRAESOLO与cpolar的AI远程开发环境搭建教程
  • 一个公司可以做几个网站吗icp许可证
  • 引入日志系统设计:基于UDP协议的 回声系统 服务器-客户端通信实现
  • Shell 文件查找与复制
  • 网站服务器免费申请北京市住房和城乡建设官网
  • wordpress全站模板学校网站对学校建设的重要性
  • 小波自适应去噪在脑电信号处理MATLAB仿真实现
  • conda安装Django+pg运行环境
  • 【淘店CRM电商管理分享】以我开源的安心转支付宝批量转账工具为例,浅谈程序员软件产品变现的路径,剖析一款成熟软件产品的运营策略
  • Vue浅响应式如何解决深层响应式的性能问题?适用场景有哪些?
  • 如何使用Metasploit进行暴力破解的详细步骤
  • 力扣刷题251114
  • 63-65 使用工厂方法创建对象,构造函数,构造函数修改
  • swift中VNDetectBarcodesRequest VNImageRequestHandler 是什么?有什么作用?VN是什么意思
  • 二十一、循环神经网络及其变体
  • 添加网站图标浙江杭州
  • 点胶机 东莞网站建设wordpress 分类伪静态
  • 深度搜索 ≠ RAG:厘清两种“智能检索”技术的本质差异与协同可能