大家好,Capybara将继续与大家一起学习Vue框架。(主打的就是一个记录式、陪伴式、身临式学习!)
day03
生命周期
生命周期 & 生命周期四个阶段
思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来)
Vue生命周期:一个Vue实例从 创建 到 销毁 的整个过程。
生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁
Vue 生命周期函数(钩子函数)
Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码。
有八个钩子:
不同时期可执行特定的代码:
实操代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <h3>{{ title }}</h3> <div> <button @click="count--">-</button> <span>{{ count }}</span> <button @click="count++">+</button> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { count: 100, title: '计数器' }, // 1. 创建阶段(准备数据) beforeCreate () { console.log('beforeCreate 响应式数据准备好之前', this.count) }, created () { console.log('created 响应式数据准备好之后', this.count) // this.数据名 = 请求回来的数据 // 可以开始发送初始化渲染的请求了 }, // 2. 挂载阶段(渲染模板) beforeMount () { console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML) }, mounted () { console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML) // 可以开始操作dom了 }, // 3. 更新阶段(修改数据 → 更新视图) beforeUpdate () { console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML) }, updated () { console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML) }, // 4. 卸载阶段 beforeDestroy () { console.log('beforeDestroy, 卸载前') console.log('清除掉一些Vue以外的资源占用,定时器,延时器...') }, destroyed () { console.log('destroyed,卸载后') } }) </script> </body> </html>
效果:
没有任何点击操作时:
点击计数器(点击+号):
卸载Vue实例,Vue官方提供了方法 app.$destroy() :
页面中计数器还在,但点击加减号已经没有反应:
重点记住created和mounted。
生命周期两个例子-初始化渲染和获取焦点
新闻列表案例
在created钩子函数中发送请求:
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data
}
实操代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> * { margin: 0; padding: 0; list-style: none; } .news { display: flex; height: 120px; width: 600px; margin: 0 auto; padding: 20px 0; cursor: pointer; } .news .left { flex: 1; display: flex; flex-direction: column; justify-content: space-between; padding-right: 10px; } .news .left .title { font-size: 20px; } .news .left .info { color: #999999; } .news .left .info span { margin-right: 20px; } .news .right { width: 160px; height: 120px; } .news .right img { width: 100%; height: 100%; object-fit: cover; } </style> </head> <body> <div id="app"> <ul> <li v-for="(item, index) in list" :key="item.id" class="news"> <div class="left"> <div class="title">{{ item.title }}</div> <div class="info"> <span>{{ item.source }}</span> <span>{{ item.time }}</span> </div> </div> <div class="right"> <img :src="item.img" alt=""> </div> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> // 接口地址:http://hmajax.itheima.net/api/news // 请求方式:get const app = new Vue({ el: '#app', data: { list: [] }, async created () { // 1. 发送请求获取数据 const res = await axios.get('http://hmajax.itheima.net/api/news') // 2. 更新到 list 中,用于页面渲染 v-for this.list = res.data.data } }) </script> </body> </html>
效果(莫名其妙说图片违规,去掉部分信息):
输入框自动聚焦
以前学的autofocus属性没有效果
这部分HTML结构属于模板,Vue会根据数据动态渲染模板,原结构会被替换,即使写了autofocus,被替换后就没了。
// 核心思路:使用原生dom方法
// 1. 等input框渲染出来 mounted 钩子
// 2. 让input框获取焦点 inp.focus()
mounted () {
document.querySelector('#inp').focus()
}
实操代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>示例-获取焦点</title> <!-- 初始化样式 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset.css@2.0.2/reset.min.css"> <!-- 核心样式 --> <style> html, body { height: 100%; } .search-container { position: absolute; top: 30%; left: 50%; transform: translate(-50%, -50%); text-align: center; } .search-container .search-box { display: flex; } .search-container img { margin-bottom: 30px; } .search-container .search-box input { width: 512px; height: 16px; padding: 12px 16px; font-size: 16px; margin: 0; vertical-align: top; outline: 0; box-shadow: none; border-radius: 10px 0 0 10px; border: 2px solid #c4c7ce; background: #fff; color: #222; overflow: hidden; box-sizing: content-box; -webkit-tap-highlight-color: transparent; } .search-container .search-box button { cursor: pointer; width: 112px; height: 44px; line-height: 41px; line-height: 42px; background-color: #4e6ef2; border-radius: 0 10px 10px 0; font-size: 17px; box-shadow: none; font-weight: 400; border: 0; outline: 0; letter-spacing: normal; color: white; } body { background: no-repeat center /cover; background-color: #edf0f5; } </style> </head> <body> <div class="container" id="app"> <div class="search-container"> <img src="https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" alt=""> <div class="search-box"> <input type="text" v-model="words" id="inp"> <button>搜索一下</button> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { words: '' }, // 核心思路: // 1. 等input框渲染出来 mounted 钩子 // 2. 让input框获取焦点 inp.focus() mounted () { document.querySelector('#inp').focus() } }) </script> </body> </html>
效果(已自动聚焦):
综合案例:小黑记账清单
接口文档地址:https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
实现基本渲染
关键代码(发送请求得到数据,并进行渲染):
/** * 接口文档地址: * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058 * * 功能需求: * 1. 基本渲染 * (1) 立刻发送请求获取数据 created * (2) 拿到数据,存到data的响应式数据中 * (3) 结合数据,进行渲染 v-for * (4) 消费统计 => 计算属性 * 2. 添加功能 * 3. 删除功能 * 4. 饼图渲染 */ const app = new Vue({ el: '#app', data: { list: [] }, computed: { totalPrice () { return this.list.reduce((sum, item) => sum + item.price, 0) } }, async created () { const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: '小黑' } }) this.list = res.data.data } })
效果:
实现添加功能
关键代码:add方法
请求接口参数
(只有get、delete请求传参时需要写上params属性名)
methods: { async getList () { const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: '小黑' } }) this.list = res.data.data }, async add () { if (!this.name) { alert('请输入消费名称') return } if (typeof this.price !== 'number') { alert('请输入正确的消费价格') return } // 发送添加请求 const res = await axios.post('https://applet-base-api-t.itheima.net/bill', { creator: '小黑', name: this.name, price: this.price }) // 重新渲染一次 this.getList() this.name = '' this.price = '' } }
添加或删除后(后台数据发送变化),需要重新发送获取数据请求,将后台数据同步,故将其封装。
async getList () { const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: '小黑' } }) this.list = res.data.data }
效果(输入消费信息):
页面刷新:
实现删除功能
async del (id) { // 根据 id 发送删除请求 const res = await axios.delete(`https://applet-base-api- t.itheima.net/bill/${id}`) // 重新渲染 this.getList() }
饼图渲染
查看快速入门
核心代码:
需要的是饼图,到“所有示例”中找
需要根据数据实时更新饼图(快速入门下的数据处理):
在methods配置项中的getlist方法中需要再次setOption
async getList () { const res = await axios.get('https://applet-base-api-t.itheima.net/bill', { params: { creator: '小黑' } }) this.list = res.data.data // 更新图表 this.myChart.setOption({ // 数据项 series: [ { // data: [ // { value: 1048, name: '球鞋' }, // { value: 735, name: '防晒霜' } // ] data: this.list.map(item => ({ value: item.price, name: item.name})) } ] }) },
(因为需要跨mounted和methods配置项使用myChart对象,所以挂载到Vue实例中。)
完整mounted钩子函数:
4. 饼图渲染 * (1) 初始化一个饼图 echarts.init(dom) mounted钩子实现 * (2) 根据数据实时更新饼图 echarts.setOption({ ... }) */ mounted () { this.myChart = echarts.init(document.querySelector('#main')) this.myChart.setOption({ // 大标题 title: { text: '消费账单列表', left: 'center' }, // 提示框 tooltip: { trigger: 'item' }, // 图例 legend: { orient: 'vertical', left: 'left' }, // 数据项 series: [ { name: '消费账单', type: 'pie', radius: '50%', // 半径 data: [ // { value: 1048, name: '球鞋' }, // { value: 735, name: '防晒霜' } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }) },
总结:
工程化开发入门
工程化开发和脚手架
开发 Vue 的两种方式:
1. 核心包传统开发模式:基于 html / css / js 文件,直接引入核心包,开发 Vue。
2. 工程化开发模式:基于构建工具(例如:webpack ) 的环境中开发 Vue。
问题:
① webpack 配置不简单
② 雷同的基础配置
③ 缺乏统一标准
需要一个工具,生成标准化的配置
好处:
1. 开箱即用,零配置
2. 内置 babel 等工具
3. 标准化
使用步骤:
1. 全局安装 (一次) :yarn global add @vue/cli 或 npm i @vue/cli -g
2. 查看 Vue 版本:vue --version
3. 创建项目架子:vue create project-name(项目名-不能用中文)
4. 启动项目: yarn serve 或 npm run serve(找package.json)
项目目录介绍和运行流程
index.html
<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <!-- 兼容:给不支持js的浏览器一个提示 --> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <!-- Vue所管理的容器:将来创建结构动态渲染这个容器 --> <div id="app"> <!-- 工程化开发模式中:这里不再直接编写模板语法,通过 App.vue 提供结构渲染 --> </div> <!-- built files will be auto injected --> </body> </html>
main.js
// 文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html // 1. 导入 Vue 核心包 import Vue from 'vue' // 2. 导入 App.vue 根组件 import App from './App.vue' // 提示:当前处于什么环境 (生产环境 / 开发环境) Vue.config.productionTip = false // 3. Vue实例化,提供render方法 → 基于App.vue创建结构渲染index.html new Vue({ // el: '#app', 作用:和$mount('选择器')作用一致,用于指定Vue所管理容器 // render: h => h(App), render: (createElement) => { // 基于App创建元素结构 return createElement(App) } }).$mount('#app')
组件化开发 & 根组件
App.vue 文件(单文件组件)的三个组成部分
1. 语法高亮插件:
2. 三部分组成:
◆ template:结构 ( 有且只能一个根元素(vue2) )
◆ script: js逻辑
◆ style: 样式 (可支持less,需要装包)
3. 让组件支持 less
(1) style标签,lang="less" 开启less功能
(2) 装包: yarn add less less-loader
实操代码:
<template> <div class="App"> <div class="box" @click="fn"></div> </div> </template> <script> // 导出的是当前组件的配置项 // 里面可以提供 data(特殊) methods computed watch 生命周期八大钩子 export default { created () { console.log('我是created') }, methods: { fn () { alert('你好') } } } </script> <style lang="less"> /* 让style支持less 1. 给style加上 lang="less" 2. 安装依赖包 less less-loader yarn add less less-loader -D (开发依赖) */ .App { width: 400px; height: 400px; background-color: pink; .box { width: 100px; height: 100px; background-color: skyblue; } } </style>
总结:
普通组件的注册使用
组件注册的两种方式:
局部注册(组件)
1. 局部注册:只能在注册的组件内使用
① 创建 .vue 文件 (三个组成部分)
② 在使用的组件内导入并注册
(在components文件夹里新建.vue组件)
实操代码:
快速生成vue模板
HmFooter.vue
<template> <div class="hm-footer"> 我是hm-footer </div> </template> <script> export default { } </script> <style> .hm-footer { height: 100px; line-height: 100px; text-align: center; font-size: 30px; background-color: #4f81bd; color: white; } </style>
HmHeader.vue
<template> <div class="hm-header"> 我是hm-header </div> </template> <script> export default { } </script> <style> .hm-header { height: 100px; line-height: 100px; text-align: center; font-size: 30px; background-color: #8064a2; color: white; } </style>
HmMain.vue
<template> <div class="hm-main"> 我是hm-main </div> </template> <script> export default { } </script> <style> .hm-main { height: 400px; line-height: 400px; text-align: center; font-size: 30px; background-color: #f79646; color: white; margin: 20px 0; } </style>
<!-- 如果 HmFooter + tab 出不来 → 需要配置 vscode设置中搜索 trigger on tab → 勾上 -->
<template> <div class="App"> <!-- 头部组件 --> <HmHeader></HmHeader> <!-- 主体组件 --> <HmMain></HmMain> <!-- 底部组件 --> <HmFooter></HmFooter> <!-- 如果 HmFooter + tab 出不来 → 需要配置 vscode 设置中搜索 trigger on tab → 勾上 --> </div> </template> <script> import HmHeader from './components/HmHeader.vue' import HmMain from './components/HmMain.vue' import HmFooter from './components/HmFooter.vue' export default { components: { // '组件名': 组件对象 HmHeader: HmHeader, HmMain, HmFooter } } </script> <style> .App { width: 600px; height: 700px; background-color: #87ceeb; margin: 0 auto; padding: 20px; } </style>
全部注册(组件)
2. 全局注册:所有组件内都能使用
① 创建 .vue 文件 (三个组成部分)
② main.js 中进行全局注册
使用:
◆ 当成 html 标签使用 `<组件名></组件名>`
注意:
◆ 组件名规范 → 大驼峰命名法,如:HmHeader
实操代码:
main.js
// 编写导入的代码,往代码的顶部编写(规范)
import HmButton from './components/HmButton'
// 进行全局注册 → 在所有的组件范围内都能直接使用
// Vue.component(组件名,组件对象)
Vue.component('HmButton', HmButton)
// 文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html import Vue from 'vue' import App from './App.vue' // 编写导入的代码,往代码的顶部编写(规范) import HmButton from './components/HmButton' Vue.config.productionTip = false // 进行全局注册 → 在所有的组件范围内都能直接使用 // Vue.component(组件名,组件对象) Vue.component('HmButton', HmButton) // Vue实例化,提供render方法 → 基于App.vue创建结构渲染index.html new Vue({ // render: h => h(App), render: (createElement) => { // 基于App创建元素结构 return createElement(App) } }).$mount('#app')
综合案例:小兔鲜首页
小兔鲜首页 - 组件拆分
封装组件时,加一些前缀Xtx(整体框架),Base(基础组件)等,这样不容易出现重复命名的情况。
页面开发思路:
1. 分析页面,按模块拆分组件,搭架子 (局部或全局注册)
2. 根据设计图,编写组件 html 结构 css 样式 (已准备好)
小技巧(当CSS样式表内容过多时可折叠):
3. 拆分封装通用小组件 (局部或全局注册)
将来 → 通过 js 动态渲染,实现功能
本次Vue学习系列(二)结束,
欢迎大家在评论区友善留言、讨论。