watch侦听器(监视器)
1.监视数据变化,执行一些业务逻辑或异步操作
2.语法:
(1) watch同样声明在跟data同级的配置项中
(2) 简单写法: 简单类型数据直接监视
(3) 完整写法:添加额外配置项
data: {
words: '苹果',
obj: {
words: '苹果'
}
},
watch: {
//直接监视
// 该方法会在数据变化时调用执行
//newValue新值, oldValue老值(一般不用)
//words (newValue){
//console.log('变化了',newValue)
//}
//对象里面的子属性 通过访问路径的方式
'obj.words' (newValue){
console.log('变化了',newValue)
'对象.属性名' (newValue, oldValue) {
}
}
})
防抖:延迟执行
axios?
背景介绍:Ajax(Asynchronous JavaScript and XML):
异步网络请求。Ajax能够让页面无刷新的请求数据。
实现ajax的方式有多种,如jQuery封装的ajax,原生的XMLHttpRequest,以及axios。但各种方式都有利弊:
-
原生的XMLHttpRequest的配置和调用方式都很繁琐,实现异步请求十分麻烦
-
jQuery的ajax相对于原生的ajax是非常好用的,但是没有必要因为要用ajax异步网络请求而引用jQuery框架
Axios(ajax i/o system): 这不是一种新技术,本质上还是对原生XMLHttpRequest的封装,可用于浏览器和nodejs的HTTP客户端,只不过它是基于Promise的,符合最新的ES规范。具备以下特点:
- 在浏览器中创建XMLHttpRequest请求
- 在node.js中发送http请求
- 支持Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消要求
- 自动转换JSON数据
- 客户端支持防止CSRF/XSRF(跨域请求伪造)
watch 完整写法 — 添加额外的配置项
- deep:true 对复杂类型深度监视
- immediate:true 初始化立刻执行一次handler方法
watch: {// watch 完整写法
数据属性名: {
deep: true, // 深度监视(针对复杂类型)
immediate: true, // 是否立刻执行一次handler
handler (newValue) {
console.log(newValue)
}
}
}
Vue生命周期
Vue生命周期:一个Vue实例从创建到销毁的整个过程
生命周期四个阶段:创建—挂裁—更新—销毁
1.创建阶段:创建响应式数据
2.挂载阶段:渲染模板
3.更新阶段:修改数据,更新视图
4.销毁阶段:销毁Vue实例
创建与挂裁阶段只会执行一次,更新阶段则会执行多次
Vue 生命周期函数(钩子函数)
Vue生命周期过程中,会 自动运行一些函数 ,被称为【生命周期钩子】→ 让开发者可以在 特定阶段 运行自己的代码
- beforeUpdate:数据修改了,视图还未更新
- updated:数据修改了,视图已经更新
mounted 应用
mounted:模板渲染完成,可以开始操作DOM了
需求:已进入页面刷新之后,输入框可以获取焦点
核心思路:
- 等输入框渲染出来
- 让输入框获取焦点
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: #ad2a27;
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;
}
<div class="container" id="app">
<div class="search-container">
<img src="https://www.itheima.com/images/logo.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: ''
},
mounted(){
document.querySelector('#inp').focus()
}
})
</script>
在mounted阶段之后再获取DOM
案例:小黑记账清单
1.基本渲染
- 立刻发送请求获取数据 created
- 拿到数据,存到data的响应式数据中
- 结合数据,进行渲染 v-for
- 消费统计 —> 计算属性
2.添加功能
- 收集表单数据 v-model,使用指令修饰符处理数据
- 给添加按钮注册点击事件,对输入的内容做非空判断,发送请求
- 请求成功后,对文本框内容进行清空
- 重新渲染列表
3.删除功能
- 注册点击事件,获取当前行的id
- 根据id发送删除请求
- 需要重新渲染
.red {
color: red!important;
}
.search {
width: 300px;
margin: 20px 0;
}
.my-form {
display: flex;
margin: 20px 0;
}
.my-form input {
flex: 1;
margin-right: 20px;
}
.table > :not(:first-child) {
border-top: none;
}
.contain {
display: flex;
padding: 10px;
}
.list-box {
flex: 1;
padding: 0 30px;
}
.list-box a {
text-decoration: none;
}
.echarts-box {
width: 600px;
height: 400px;
padding: 30px;
margin: 0 auto;
border: 1px solid #ccc;
}
tfoot {
font-weight: bold;
}
@media screen and (max-width: 1000px) {
.contain {
flex-wrap: wrap;
}
.list-box {
width: 100%;
}
.echarts-box {
margin-top: 30px;
}
}
<div id="app">
<div class="contain">
<!-- 左侧列表 -->
<div class="list-box">
<!-- 添加资产 -->
<form class="my-form">
<input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
<input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
<button @click="add" type="button" class="btn btn-primary">添加账单</button>
</form>
<table class="table table-hover">
<thead>
<tr>
<th>编号</th>
<th>消费名称</th>
<th>消费价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
<td><a @click="del(item.id)" href="javascript:;">删除</a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
</tr>
</tfoot>
</table>
</div>
<!-- 右侧图表 -->
<div class="echarts-box" id="main"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<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>
const app = new Vue({
el: '#app',
data: {
list: [],
name: '',
price: ''
},
computed: {
totalPrice () {
return this.list.reduce((sum, item) => sum + item.price, 0)
}
},
created () {
// const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
// params: {
// creator: '小李'
// }
// })
// this.list = res.data.data
this.getList()
},
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 del (id) {
// 根据 id 发送删除请求
const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
// 重新渲染
this.getList()
}
}
})
</script>
效果图如下:
当点击删除时,删除按钮所在的一行可以删除
4.饼图渲染
- 初始化一个饼图 echarts.init(dom) mounted钩子中渲染
- 根据数据试试更新饼图 echarts.setOptions({...})
思路:
- 在绘图前需要为ECharts准备一个定义了高宽的DOM容器
- 通过echarts.init方法初始化一个echarts实例 并通过setOption方法生成一个简单的柱状图
<div id="app">
<div class="contain">
<!-- 左侧列表 -->
<div class="list-box">
<!-- 添加资产 -->
<form class="my-form">
<input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
<input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
<button @click="add" type="button" class="btn btn-primary">添加账单</button>
</form>
<table class="table table-hover">
<thead>
<tr>
<th>编号</th>
<th>消费名称</th>
<th>消费价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
<td><a @click="del(item.id)" href="javascript:;">删除</a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
</tr>
</tfoot>
</table>
</div>
<!-- 右侧图表 -->
<div class="echarts-box" id="main"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<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>
const app = new Vue({
el: '#app',
data: {
list: [],
name: '',
price: ''
},
computed: {
totalPrice () {
return this.list.reduce((sum, item) => sum + item.price, 0)
}
},
created () {
// const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
// params: {
// creator: '小黑'
// }
// })
// this.list = res.data.data
this.getList()
},
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)'
}
}
}
]
})
},
methods: {
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}))
}
]
})
},
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 del (id) {
// 根据 id 发送删除请求
const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
// 重新渲染
this.getList()
}
}
})
</script>
效果图如下:
工程化开发和脚手架
1.开发Vue的两种方式
-
核心包传统开发模式:基于html / css / js 文件,直接引入核心包,开发 Vue。
-
工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue。
基本介绍:
- Vue CLI 是 Vue官方提供的一个全局命令工具
- 可以帮助我们快速创建一个开发Vue项目的标准化基础架子【集成了webpack配置】
使用步骤:
- 全局安装(一次):yarn global add@vue/cli 或 npm i @vue/cli -g
- 查看Vue版本:vue --version
- 创建项目架子:vue create project-name(项目名不能用中文)
- 启动项目:yarn serve 或npm run serve(找package.json)
脚本架目录文件介绍 & 项目运行流程
- main.js 入口文件
- App.vue App根组件
- index.html 模板文件
main.js 文件核心作用:
- 导入app.vue,基于app.vue创建结构 渲染 index.html
组件化开发 & 根组件
- 组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
-
好处:便于维护,利于复用——提升开发效率
-
组件分类:普通组件、跟组件
- 根组件:整个应用最上层的组件,包括所有普通小组件
App.vue 文件(单文件组件)的三个组成部分
- 语法高亮插件
- 三部分组成
- template:结构 (有且只能一个根元素)
- script: js逻辑
- style: 样式 (可支持less,需要装包)
- 让style支持less:
(1) 给style加上 lang="less"
(2)安装依赖包 less less-loader yarn add less less-loader -D(开发依赖)
<style lang ="less">
.App{
width:400px;
height:400px;
background-color:pink;
.box{
width:100px;
height:100px;
background-color:lightblue;
}
}
普通组件的注册使用(局部注册)
组件注册的两种方式:
- 局部注册:只能在注册的组件内使用
- 创建.vue文件(三个部分组成)
- 在使用的组件内导入并局部注册
components('组件名',组件对象) - 当组件名与组件对象相同时,组件名可省略
- 全局注册:所有组件内都能使用
- 使用:
当成html标签使用
<组件名></组件名>
注意:
组件名规范—大驼峰命名法 如:HmHeader
补充:
Q:何为大驼峰命名法?
A:每一个单词的首字母都采用大写字母,例如:FirstName、LastName、CamelCase,也被称为 Pascal 命名法。
Q:什么是小驼峰命名法?
A: 第一个单词以小写字母开始,第二个单词的首字母大写 例如:firstName、lastName。
// 导入需要注册的组件
import 组件对象 from '.vue文件路径'
import HmHeader from './components/HmHeader'
export default { // 局部注册
components: {
'组件名': 组件对象,
HmHeader:HmHeaer,
HmMain,
HmFooter
}
}
示例:
//头部
<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>
//主体
<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>
//底部
<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>
效果图如下:
普通组件的注册(全局注册)
- 创建.vue文件(/三个组成部分)
- main.js 中进行全局注册
- 编写导入代码,往代码顶部编写
- 进入全局注册—在所有的组件范围内都能直接使用
- vue.component('组件名',组件对象)
注:编写导入代码,往代码顶部编写
综合案例:小兔鲜首页
- 组件拆分
2.页面开发思路
(1)分析页面,按模块拆分组件,搭架子(局部或全局注册) 通常为局部注册 若一个模块多次使用也可全局注册
(2)根据设计图,编写组件html结构 css样式
(3)拆分封装通用小组将(局部或全局注册)
将来 ——通过js 动态渲染,实现功能
当内容太多时可使用快捷键:
- 将所有都折叠 ctrl+k,ctrl+ 0
- 将所有都展开 ctrl+k,ctrl+ j
- ctrl +滚轮向下拖动:可复制需要的内容
组件多次渲染:v-for
<div class="bd">
<ul>
<BaseBrandItem v-for="item in 5":key="item"> <BaseBrandItem>
</ul>
</div>