Vue 组件化进阶:计算属性、生命周期、组件通信与动态组件

📅 2026/8/23 8:45:39
Vue 组件化进阶:计算属性、生命周期、组件通信与动态组件
一、内容回顾前面学习了 Vue 的响应式数据、模板指令、列表和表单以及用 axios 请求接口。本篇进一步学习 Node.js 运行环境、计算属性、监听属性、生命周期、组件和组件通信。二、Node.js 与前端工程JavaScript 最初主要运行在浏览器中浏览器还提供了 DOM 和 BOM 等 API。Node.js 把 Chrome V8 引擎带到了操作系统环境使 JavaScript 可以处理文件、网络和进程也可以作为前端工程的运行时和包管理环境。node运行 JavaScript 文件或进入交互式命令行。npmNode.js 的包管理工具作用类似 Python 的 pip。TypeScriptJavaScript 的超集增加类型系统通常编译成浏览器可以执行的 JavaScript。Vue、React 等前端框架通常依赖 Node.js 完成依赖安装、代码打包和开发服务器启动。Node.js 不等于 Vue也不一定要作为业务后端它首先是 JavaScript 的运行环境。三、axios 获取后端数据前端页面通常不直接写死全部业务数据而是通过 HTTP 请求获取后端返回的 JSON。axios 是基于XMLHttpRequest封装的客户端库提供统一的请求方法、JSON 处理、拦截器和错误处理。1. Vue 页面请求电影数据dividapph1电影列表/h1buttonclickloadFilms:disabledloading{{ loading ? 加载中... : 加载电影 }}/buttonpv-iferrorclasserror{{ error }}/pulliv-forfilm in films:keyfilm.idh2{{ film.name }}/h2img:srcfilm.poster:altfilm.namewidth200p{{ film.synopsis }}/p/li/ul/divscriptsrchttps://unpkg.com/axios1.7.3/dist/axios.min.js/scriptscriptsrc./vue2/vue.js/scriptscriptnewVue({el:#app,data:{films:[],loading:false,error:},methods:{asyncloadFilms(){this.loadingtruethis.errortry{constresponseawaitaxios.get(http://127.0.0.1:5000/films)// response.data 才是后端返回的响应体。this.filmsresponse.data.results||[]}catch(err){console.error(err)this.error电影数据加载失败请检查接口服务。}finally{this.loadingfalse}}}})/scriptloading可以防止用户重复点击try/catch/finally让成功、失败和结束状态都得到处理。列表渲染时使用后端稳定的id作为key不要使用随机数。2. Flask 接口示例importjsonfromflaskimportFlask,jsonify appFlask(__name__)app.get(/films)deffilms():withopen(films.json,encodingutf-8)asfile:datajson.load(file)responsejsonify(data)# 演示跨域时允许前端开发服务器生产环境应配置明确域名。response.headers[Access-Control-Allow-Origin]http://localhost:8080returnresponseif__name____main__:app.run(port5000)当页面和接口的协议、域名或端口不一致时浏览器会执行同源策略检查。服务端需要通过 CORS 响应头声明允许的来源*不适合需要携带 Cookie 的请求也不应在生产环境无限制开放。四、计算属性计算属性本质上是一个带缓存的派生值。它依赖的数据变化时才重新计算依赖不变时会复用上次结果。适合用于格式化文本、过滤列表、统计总价等“由已有数据推导出的值”。1. 基本用法dividappinputv-modelnameplaceholder输入英文名p首字母大写{{ capitalizedName }}/p/divscriptnewVue({el:#app,data:{name:},computed:{capitalizedName(){if(!this.name)returnreturnthis.name.charAt(0).toUpperCase()this.name.slice(1)}}})/script计算属性在模板中像普通属性一样使用不需要写括号。与方法相比计算属性会缓存结果如果页面其他地方更新但name没变它不会重复执行。2. 用计算属性实现过滤dividappinputv-model.trimsearchplaceholder搜索单词ulliv-forword in filteredWords:keyword{{ word }}/li/ul/divscriptnewVue({el:#app,data:{search:,words:[a,at,atom,atommon,be,beyond,cs,csrf]},computed:{filteredWords(){constkeywordthis.search.toLowerCase()returnthis.words.filter(wordword.includes(keyword))}}})/script不要在模板中堆积复杂表达式也不要为了展示一个派生值把过滤结果重复存进data。只要原始数据和关键词在data中过滤结果就适合放入computed。五、监听属性 watchwatch用来监听数据变化并执行副作用例如发送请求、保存草稿、记录日志或根据一个字段修改另一个字段。它与计算属性的区别是计算属性返回结果监听属性执行动作。dividappbuttonclickcategory 国产国产/buttonbuttonclickcategory 日韩日韩/buttonbuttonclickcategory 欧美欧美/buttonp当前分类{{ category }}/p/divscriptnewVue({el:#app,data:{category:国产},watch:{category(newValue,oldValue){console.log(${oldValue}-${newValue})// 这里可以根据新分类请求后端数据。}}})/script监听对象或数组内部变化时Vue 2 需要使用deep: true初始化时就希望执行一次可以使用immediate: true。深度监听成本更高应只监听确实需要的对象。六、Vue 生命周期组件从创建、挂载、更新到销毁会经历一系列生命周期钩子。生命周期钩子适合处理与组件存在时间相关的逻辑例如初始化请求、注册事件和清理定时器。阶段钩子常见用途创建前/后beforeCreate、created初始化前后created中可请求数据挂载前/后beforeMount、mountedDOM 挂载前后需要 DOM 时使用mounted更新前/后beforeUpdate、updated数据改变导致 DOM 更新前后销毁前/后beforeDestroy、destroyed清理定时器、事件监听和资源dividappbuttonclickshow !show显示 / 销毁子组件/buttonchild-panelv-ifshow/child-panel/divscriptVue.component(child-panel,{template:section h2{{ name }}/h2 button clickname 新名字修改名字/button /section,data(){return{name:子组件,timerId:null}},created(){console.log(created可以在这里发送初始化请求)this.timerIdsetInterval((){console.log(定时任务)},3000)},mounted(){console.log(mountedDOM 已插入页面)},beforeDestroy(){// 组件销毁前清理定时器否则会继续执行。clearInterval(this.timerId)this.timerIdnull}})newVue({el:#app,data:{show:false}})/scriptcreated中还不能可靠访问已经挂载的 DOM需要操作 DOM 尺寸、焦点等内容时使用mounted。定时器、WebSocket、全局事件监听都要在销毁钩子中释放。七、组件与组件通信组件是包含模板、逻辑和样式的可复用单元。组件的data必须是返回对象的函数保证每个组件实例拥有独立的数据。1. 全局组件和局部组件dividappglobal-card/global-cardlocal-card/local-card/divscript// 注册后所有 Vue 实例和子组件都可以使用。Vue.component(global-card,{template:div classcard全局组件/div,data(){return{}}})newVue({el:#app,components:{// 只能在当前实例的模板中使用。local-card:{template:div classcard局部组件/div,data(){return{}}}}})/script实际.vue单文件组件项目更常使用import/export管理组件而不是大量全局注册。全局组件虽然方便但会增加命名冲突和维护成本。2. 父传子props父组件通过自定义属性传值子组件使用props声明接收。Props 是单向数据流子组件不应直接修改父组件传入的值。dividappchild-card:titlepageTitleuser-namelqz/child-card/divscriptVue.component(child-card,{props:{title:{type:String,required:true},userName:{type:String,default:}},template:section h2{{ title }}/h2 p用户{{ userName }}/p /section})newVue({el:#app,data:{pageTitle:父组件传来的标题}})/scriptHTML 属性通常使用短横线写法user-name在组件的 JavaScript 中对应驼峰形式userName。:title表示绑定变量title固定文本则表示传入字符串常量。3. 子传父自定义事件子组件通过$emit触发事件并传递数据父组件使用事件名监听。这样可以保持数据流向清晰。dividappp父组件收到{{ message }}/pchild-editorsubmit-messagereceiveMessage/child-editor/divscriptVue.component(child-editor,{data(){return{text:}},template:div input v-modeltext button click$emit(submit-message, text)发送/button /div})newVue({el:#app,data:{message:},methods:{receiveMessage(value){this.messagevalue}}})/script$emit的事件名要与父组件监听的名称一致。复杂项目中可以进一步使用状态管理工具避免组件层级过深时逐层传递。八、ref 属性ref用于在父组件中获取 DOM 元素或子组件实例放在普通标签上得到 DOM 对象放在组件上得到组件实例。它适合聚焦输入框、调用子组件暴露的方法等少量命令式操作不应替代正常的 props 和事件通信。dividappinputrefsearchInputchild-boxrefchild/child-boxbuttonclickfocusAndCall操作 ref/button/divscriptVue.component(child-box,{template:p{{ text }}/p,data(){return{text:初始文本}},methods:{changeText(value){this.textvalue}}})newVue({el:#app,methods:{focusAndCall(){this.$refs.searchInput.focus()this.$refs.child.changeText(父组件调用了子组件方法)}}})/script$refs只有在组件挂载后才可用不能在created中依赖它。使用$refs直接修改子组件内部数据会提高耦合度优先考虑 props 和$emit。九、动态组件动态组件通过component :is组件名或组件对象切换要渲染的组件适合选项卡、步骤表单和不同状态页面。dividappbuttonclickcurrent home-panel首页/buttonbuttonclickcurrent profile-panel个人资料/buttoncomponent:iscurrent/component/divscriptVue.component(home-panel,{template:p首页内容/p})Vue.component(profile-panel,{template:p个人资料内容/p})newVue({el:#app,data:{current:home-panel}})/script动态切换时旧组件可能被销毁。如果希望保留组件内部的输入内容和滚动状态可以使用keep-alive包裹动态组件并结合activated、deactivated钩子处理缓存状态。十、插槽 slot插槽让父组件向子组件提供模板内容子组件负责定义布局。它适合封装卡片、弹窗、布局容器等“结构固定但内容可变”的组件。dividappbase-cardh2文章标题/h2p这是由父组件传入的内容。/p/base-card/divscriptVue.component(base-card,{template:section classcard headerslot nameheader默认标题/slot/header mainslot默认内容/slot/main /section})newVue({el:#app})/script默认插槽使用slot具名插槽通过name区分。组件化时可以先把稳定的布局放入子组件再把需要变化的内容交给插槽避免复制大量 HTML。十一、总结与练习计算属性用于生成带缓存的派生值监听属性用于执行请求、日志等副作用。生命周期中常在created请求数据在mounted操作 DOM在销毁钩子中清理定时器和监听器。父子组件通信优先使用 props 和$emitref适合少量命令式操作。动态组件用于切换组件插槽用于复用布局并允许父组件注入内容。建议完成电影列表、选项卡、父子消息传递和可复用卡片四个练习并观察组件创建与销毁时的生命周期日志。