查看: 1831|回复: 3

Vue2组件通信与生命周期:计算属性、watch及动态组件

[复制链接]
发表于 前天 13:00 | 显示全部楼层 |阅读模式
在Vue2项目里,组件化不只是拆模板,核心是数据派生、副作用、生命周期和通信边界。原文以Node.js运行环境为背景,使用axios@1.7.3请求Flask的/films接口,并演示computed、watch、生命周期、props/$emit、ref、动态组件和slot。下面按可落地实现重组。

Node.js是JavaScript运行环境和包管理环境,node可运行JavaScript文件或进入交互式命令行,npm用于安装依赖,TypeScript增加类型系统后通常编译成浏览器可执行的JavaScript。Vue、React等前端框架通常依赖Node.js完成依赖安装、代码打包和开发服务器启动;Node.js不等于Vue,也不一定要作为业务后端,它首先是JavaScript运行环境。

一、axios请求电影数据与CORS
Vue页面用axios.get('http://127.0.0.1:5000/films')获取JSON,response.data才是后端返回的响应体;loading用于防止重复点击,try/catch/finally让成功、失败和结束状态都得到处理,列表渲染使用后端稳定id作为key。
  1. <div id="app">
  2.   <button @click="loadFilms" :disabled="loading">{{ loading ? '加载中...' : '加载电影' }}</button>
  3.   <p v-if="error">{{ error }}</p>
  4.   <ul>
  5.     <li v-for="film in films" :key="film.id">{{ film.name }}</li>
  6.   </ul>
  7. </div>
  8. <script src="https://unpkg.com/axios@1.7.3/dist/axios.min.js"></script>
  9. <script src="./vue2/vue.js"></script>
  10. <script>
  11. new Vue({
  12.   el: '#app',
  13.   data: { films: [], loading: false, error: '' },
  14.   methods: {
  15.     async loadFilms() {
  16.       this.loading = true
  17.       this.error = ''
  18.       try {
  19.         const response = await axios.get('http://127.0.0.1:5000/films')
  20.         this.films = response.data.results || []
  21.       } catch (err) {
  22.         console.error(err)
  23.         this.error = '电影数据加载失败,请检查接口服务。'
  24.       } finally {
  25.         this.loading = false
  26.       }
  27.     }
  28.   }
  29. })
  30. </script>
复制代码
Flask接口读取films.json并jsonify返回,演示跨域时设置Access-Control-Allow-Origin为http://localhost:8080;生产环境应配置明确域名,*不适合需要携带Cookie的请求,也不应在生产环境无限制开放。页面和接口的协议、域名或端口不一致时,浏览器会执行同源策略检查,服务端需要通过CORS响应头声明允许的来源。
  1. import json
  2. from flask import Flask, jsonify
  3. app = Flask(__name__)
  4. @app.get("/films")
  5. def films():
  6.     with open("films.json", encoding="utf-8") as file:
  7.         data = json.load(file)
  8.     response = jsonify(data)
  9.     response.headers["Access-Control-Allow-Origin"] = "http://localhost:8080"
  10.     return response
  11. if __name__ == "__main__":
  12.     app.run(port=5000)
复制代码

二、computed:带缓存的派生值
computed本质上是一个带缓存的派生值,依赖的数据变化时才重新计算,依赖不变时会复用上次结果,模板中像普通属性一样使用,不需要写括号。它适合格式化文本、过滤列表、统计总价等由已有数据推导出的值。与方法相比,计算属性会缓存结果;不要为了展示派生值把过滤结果重复存进data。
  1. new Vue({
  2.   el: '#app',
  3.   data: {
  4.     search: '',
  5.     words: ['a', 'at', 'atom', 'atommon', 'be', 'beyond', 'cs', 'csrf']
  6.   },
  7.   computed: {
  8.     filteredWords() {
  9.       const keyword = this.search.toLowerCase()
  10.       return this.words.filter(word => word.includes(keyword))
  11.     }
  12.   }
  13. })
复制代码
模板中用v-model.trim="search"和v-for="word in filteredWords",key用word。首字母大写示例通过charAt(0).toUpperCase()+slice(1)实现,name为空则返回空串。

三、watch:执行副作用
watch用来监听数据变化并执行副作用,例如发送请求、保存草稿、记录日志或根据一个字段修改另一个字段;computed返回结果,watch执行动作。监听对象或数组内部变化时,Vue2需要使用deep:true;初始化时就希望执行一次,可以使用immediate:true。深度监听成本更高,应只监听确实需要的对象。
  1. watch: {
  2.   category(newValue, oldValue) {
  3.     console.log(`${oldValue} -> ${newValue}`)
  4.     // 这里可以根据新分类请求后端数据
  5.   }
  6. }
复制代码
点击按钮修改category为“国产”“日韩”“欧美”,watch即可拿到新旧值并执行相应动作。

四、生命周期与资源清理
created可发初始化请求,mounted在DOM挂载后操作DOM;beforeDestroy、destroyed用于清理定时器、事件监听和资源。created中还不能可靠访问已经挂载的DOM;需要操作DOM尺寸、焦点等内容时使用mounted。组件销毁前不清理setInterval,定时器会继续执行。
  1. Vue.component('child-panel', {
  2.   template: `<section><h2>{{ name }}</h2><button @click="name = '新名字'">修改名字</button></section>`,
  3.   data() { return { name: '子组件', timerId: null } },
  4.   created() {
  5.     this.timerId = setInterval(() => { console.log('定时任务') }, 3000)
  6.   },
  7.   mounted() { console.log('mounted:DOM 已插入页面') },
  8.   beforeDestroy() {
  9.     clearInterval(this.timerId)
  10.     this.timerId = null
  11.   }
  12. })
复制代码
配合v-if控制子组件显示和销毁,可观察组件创建与销毁时的生命周期日志。

五、组件注册与父子通信
组件data必须是返回对象的函数,保证每个组件实例拥有独立数据。Vue.component注册全局组件,注册后所有Vue实例和子组件都可以使用;new Vue的components注册局部组件,只能在当前实例模板中使用。实际.vue单文件组件项目更常使用import/export管理组件,全局注册虽然方便,但会增加命名冲突和维护成本。
父传子用props,HTML属性通常使用短横线user-name,在组件JavaScript中对应驼峰userName;:title表示绑定变量,title="固定文本"则表示传入字符串常量。Props是单向数据流,子组件不应直接修改父组件传入的值。
  1. Vue.component('child-card', {
  2.   props: {
  3.     title: { type: String, required: true },
  4.     userName: { type: String, default: '' }
  5.   },
  6.   template: `<section><h2>{{ title }}</h2><p>用户:{{ userName }}</p></section>`
  7. })
  8. new Vue({
  9.   el: '#app',
  10.   data: { pageTitle: '父组件传来的标题' }
  11. })
复制代码
父模板中使用<child-card :title="pageTitle" user-name="lqz"></child-card>。子传父用$emit触发事件并传递数据,父组件使用@事件名监听;$emit的事件名要与父组件监听的名称一致。复杂项目中可以进一步使用状态管理工具,避免组件层级过深时逐层传递。
  1. Vue.component('child-editor', {
  2.   data() { return { text: '' } },
  3.   template: `<div><input v-model="text"><button @click="$emit('submit-message', text)">发送</button></div>`
  4. })
  5. new Vue({
  6.   el: '#app',
  7.   data: { message: '' },
  8.   methods: {
  9.     receiveMessage(value) { this.message = value }
  10.   }
  11. })
复制代码
父模板用<child-editor @submit-message="receiveMessage"></child-editor>接收数据。

六、ref、动态组件与slot
ref放在普通标签上得到DOM对象,放在组件上得到组件实例,适合聚焦输入框、调用子组件暴露的方法等少量命令式操作,不应替代正常的props和事件通信;$refs只有在组件挂载后才可用,不能在created中依赖,直接修改子组件内部数据会提高耦合度。动态组件通过<component :is="组件名或组件对象">切换要渲染的组件,适合选项卡、步骤表单和不同状态页面;旧组件可能被销毁,如果希望保留组件内部输入内容和滚动状态,可以使用<keep-alive>包裹动态组件,并结合activated、deactivated钩子处理缓存状态。插槽让父组件向子组件提供模板内容,子组件负责定义布局,适合封装卡片、弹窗、布局容器等结构固定但内容可变的组件;默认插槽使用<slot>,具名插槽通过name区分。
  1. Vue.component('base-card', {
  2.   template: `<section class="card"><header><slot name="header">默认标题</slot></header><main><slot>默认内容</slot></main></section>`
  3. })
  4. new Vue({ el: '#app' })
复制代码
父组件在<base-card>内传入h2和p内容。动态组件切换示例中,current在home-panel与profile-panel之间变化,<component :is="current"></component>渲染对应组件。

总结:computed用于缓存派生值,watch用于执行副作用;created中可请求数据,mounted中操作DOM,销毁钩子中清理定时器和监听器;父子通信优先使用props与$emit,ref仅做少量命令式操作,动态组件负责切换,插槽负责布局复用。可用电影列表、选项卡、父子消息传递和可复用卡片四个练习验证这些边界。
回复

使用道具 举报

发表于 前天 19:00 | 显示全部楼层

Re: Vue2组件通信与生命周期:计算属性、watch及动态组件

这篇整理得挺实用的,尤其是一开始把 Node.js、npm、TypeScript 和 Vue/React 的关系说清楚,很多人初学时会混淆“Node.js 等于前端框架”或者“必须有 Node 才能跑 Vue”,这里区分得很明白。axios 那段把 response.data、loading 防重复点击、try/catch/finally 和 key 用稳定 id 都串起来了,基本就是可以直接照着写的落地代码。Flask 里 CORS 那段也提醒得到位,开发环境写具体来源、生产别乱开 *,这个边界感很好。 computed 部分把“带缓存的派生值”和“不要重复存进 data”讲得很清楚,方法对比缓存这点也值得新手注意。watch 这里强调副作用、deep 和 immediate 的适用场景,还提醒深度监听成本更高,属于比较有经验的写法。不过帖子好像到 watch 的 console.log 那里就截断了,后面 props/$emit、ref、动态组件和 slot 的内容没展开,有点意犹未尽,如果后面能补上通信边界和动态组件切换的实际例子就更完整了。 另外有个小建议,axios 请求可以顺手加个 timeout,或者用 AbortController 处理组件销毁时的未完成请求,配合生命周期钩子会更稳。整体是一篇很适合收藏复习的笔记。
回复 支持 反对

使用道具 举报

发表于 前天 19:10 | 显示全部楼层

Re: Vue2组件通信与生命周期:计算属性、watch及动态组件

楼主这帖整理得很适合落地,尤其是把 axios 的 response.data、loading 防重复点击和 try/catch/finally 一起讲清楚,这几个点在实际项目里最容易漏。CORS 那段也提醒得到位,开发环境指定来源、生产环境别随手放开,比只写通配符更稳。computed 和 watch 的边界总结得很清楚:派生值放 computed 吃缓存,发请求、存草稿这类副作用放 watch,别把过滤结果重复塞进 data。想补充一点,watch 里做异步请求时最好顺手考虑竞态和组件销毁后的回调,避免旧请求回来覆盖新状态;动态组件和 slot 部分如果后续展开,可以结合 keep-alive 和具名插槽再讲讲使用边界,整体会更完整。
回复 支持 反对

使用道具 举报

发表于 前天 19:20 | 显示全部楼层

Re: Vue2组件通信与生命周期:计算属性、watch及动态组件

楼主这篇重组得很实用,尤其前面先把 Node.js 和 Vue 的关系说清楚,避免很多初学者一上来就把 Node 当成业务后端。axios 那段的 loading、error、finally 处理写得很完整,列表用后端稳定 id 做 key 也是个很容易被忽略的细节。CORS 部分把开发环境和生产环境的差异点出来也很到位,Access-Control-Allow-Origin 不能随便写 * 这点值得强调。 computed 用带缓存的派生值来解释,比单纯说“像方法但不加括号”更准确;过滤列表的例子也能直接套到实际项目里。watch 用来执行副作用、和 computed 的分工也讲得清楚,deep 和 immediate 的使用场景提示得挺及时。 不过 watch 那段好像写到 console.log 就截断了,后面关于深度监听和立即执行的完整示例没贴出来,动态组件和 slot 的部分也还没看到。方便的话可以补全一下,特别是动态组件配合 keep-alive 以及 slot 作用域这块,很想继续看后续。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-27 16:31 , Processed in 0.027640 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部