一、Vue框架概述
1. vue框架的介绍
渐进式javacript框架, 一套拥有自己规则的语法
Vue 是一套用于构建用户界面的渐进式框架. 与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。
Vue.js 是前端的主流框架之一,和 Angular.js、React.js 一起,并成为前端三大主流框架
官网地址: Vue.js - 渐进式 JavaScript 框架 | Vue.jsVue.js - 渐进式的 JavaScript 框架
讯享网https://cn.vuejs.org/ (作者: 尤雨溪)
2. vue的特点
- 渐进式
- 声明式渲染
- 数据驱动视图 (响应式)
- 极少的去写DOM操作相关代码
- 双向绑定
- 组件系统
- 不兼容IE8及以下浏览器
3. Vue的优点
1.体积小 2.更高的运行效率 3.双向数据绑定,简化 Dom 操作 通过 MVVM 思想实现数据的双向绑定 4 生态丰富、学习成本低 市场上拥有大量成熟、稳定的基于 vue.js 的 ui 框架、常用组件 来即用实现 快速开发 对初学者友好、入门容易、学习资料多.
讯享网
二、@vue/cli脚手架
1. @vue/cli 脚手架介绍
@vue/cli是Vue官方提供的一个全局模块包(得到vue命令), 此包用于创建脚手架项目
脚手架是为了保证各施工过程顺利进行而搭设的工作平台
@vue/cli的好处
开箱即用
0配置webpack
babel支持
css, less支持
开发服务器支持
2. @vue/cli安装
目标: 把@vue/cli模块包按到全局, 电脑拥有vue命令, 才能创建脚手架工程
-
(1)全局安装命令
讯享网yarn global add @vue/cli # OR npm install -g @vue/cli
![]()
注意: 如果半天没动静(95%都是网速问题), 可以ctrl c停止重新来,换一个网继续重来
-
(2)查看
vue脚手架版本vue -V

总结: 如果出现版本号就安装成功, 否则失败
3. @vue/cli 创建项目启动服务
目标: 使用vue命令, 创建脚手架项目
(1)创建项目
讯享网# vue和create是命令, vuecli-demo是文件夹名 vue create vuecli-demo
![]()
注意: 项目名不能带大写字母, 中文和特殊符号
(2)选择模板和包管理器, 等待脚手架项目创建完毕
a. 选择预置版本为vue2还是vue3(可以上下箭头选择, 弄错了ctrl+c重来)

b. 选择用什么方式下载脚手架项目需要的依赖包

c. 回车等待生成项目文件夹+文件+下载必须的第三方包们

d. cd进入项目下, 启动内置的webpack本地热更新开发服务器
cd vuecil-demo npm run serve # 或 yarn serve
讯享网只要看到绿色成功了(底层node+webpack热更新服务)

e. 如果未自动弹出浏览器, 手动打开浏览器输入上述地址

4. @vue/cli 目录和代码分析

vuecil-demo # 项目目录
├── node_modules # 项目依赖的第三方包
├── public # 静态文件目录
├── favicon.ico# 浏览器小图标
└── index.html # 单页面的html文件(网页浏览的是它)
├── src # 业务文件夹
├── assets # 静态资源
└── logo.png # vue的logo图片
├── components # 组件目录
└── HelloWorld.vue # 欢迎页面vue代码文件
├── App.vue # 整个应用的根组件
└── main.js # 入口js文件
├── .gitignore # git提交忽略配置
├── babel.config.js # babel配置
├── package.json # 依赖包列表
├── README.md # 项目说明
└── yarn.lock # 项目包版本锁定和缓存地址
主要文件及含义
讯享网node_modules下都是下载的第三方包
public/index.html – 浏览器运行的网页
src/main.js – webpack打包的入口文件
src/App.vue – vue项目入口页面
package.json – 依赖包列表文件
5. @vue/cli 项目架构了解


6. @vue/cli 自定义配置
目标:项目中没有webpack.config.js文件,因为@vue/cli用的vue.config.js
src并列处新建vue.config.js
/* 覆盖webpack的配置 */ module.exports = { devServer: { // 自定义服务配置 open: true, // 自动打开浏览器 port: 3000 } }
7. eslint了解
目标: 知道eslint的作用, 和如何暂时关闭, 它是一个代码检查工具
(1)如果写代码违反了eslint的规则-报错
演示: 在main.js中随便定义变量 – 不使用 – 观察eslint报错

(2)处理eslint代码检查
方式1: 手动解决掉错误, 以后项目中会讲如何自动解决
方式2: 暂时关闭eslint检查(因为现在主要精力在学习Vue语法上), 在vue.config.js中配置后重启服务

8. @vue/cli 单vue文件讲解
目标: 单vue文件好处, 独立作用域互不影响
讯享网Vue推荐采用.vue文件来开发项目
template里只能有一个根标签
vue文件-独立模块-作用域互不影响
style配合scoped属性, 保证样式只针对当前template内标签生效
vue文件配合webpack, 把他们打包起来插入到index.html
<!-- template必须, 只能有一个根标签, 影响渲染到页面的标签结构 --> <template> <div>欢迎使用vue</div> </template> <!-- js相关 --> <script> export default { name: 'App' } </script> <!-- 当前组件的样式, 设置scoped, 可以保证样式只对当前页面有效 --> <style scoped> </style>
最终: Vue文件配合webpack, 把他们打包起来插入到index.html, 然后在浏览器运行
讯享网单vue文件好处? 独立作用域, 不再担心变量重名问题
9. @vue/cli 欢迎界面清理


三、vue基础-MVVM设计模式
目的: 转变思维, 用数据驱动视图改变, 操作dom的事, vue源码内操作了
设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
- MVVM,一种软件架构模式,决定了写代码的思想和层次
- M: model数据模型 (data里定义)
- V: view视图 (html页面)
- VM: ViewModel视图模型 (vue.js源码)
- MVVM通过
数据双向绑定让数据自动地双向同步 不再需要操作DOM- V(修改视图) -> M(数据自动同步)
- M(修改数据) -> V(视图自动同步)

1. 在vue中,不推荐直接手动操作DOM
2. 在vue中,通过数据驱动视图,不再需操作DOM,而是如何操作数据

总结: vue源码内采用MVVM设计模式思想, 大大减少了DOM操作, 挺高开发效率
mvvm和mvc区别是什么? MVC: 也是一种设计模式, 组织代码的结构, 是model数据模型, view视图, Controller控制器, 在控制器这层里编写js代码, 来控制数据和视图关联 MVVM: 即Model-View-ViewModel的简写。即模型-视图-视图模型, VM是这个设计模式的核心, 连接v和m的桥梁, 内部会监听DOM事件, 监听数据对象变化来影响对方. 我们称之为数据绑定
四、vue基础
1. 插值表达式
目的: 在dom标签中, 直接插入内容
又叫: 声明式渲染/文本插值
语法: { { 表达式 }}
讯享网msg和obj是vue数据变量 要在js中data函数里声明

<template> <div> <h1>{
{ msg }}</h1> <h2>{
{ obj.name }}</h2> <h3>{
{ obj.age > 18 ? '成年' : '未成年' }}</h3> </div> </template> <script> export default { data() { // 格式固定, 定义vue数据之处 return { // key相当于变量名 msg: "hello, vue", obj: { name: "小vue", age: 5 } } } } </script> <style> </style>
总结: dom中插值表达式赋值, vue的变量必须在data里声明

2. vue指令-v-bind
目标: 给标签属性设置vue变量的值
vue指令, 实质上就是特殊的 html 标签属性, 特点: v- 开头
每个指令, 都有独立的作用

- 语法:
v-bind:属性名="vue变量" - 简写:
:属性名="vue变量"
讯享网<!-- vue指令-v-bind属性动态赋值 --> <a v-bind:href="url">我是a标签</a> <img :src="imgSrc">
总结: 把vue变量的值, 赋予给dom属性上, 影响标签显示效果
3. vue指令-v-on
目标: 给标签绑定事件
- 语法
- v-on:事件名="要执行的==少量代码=="
- v-on:事件名="methods中的函数"
- v-on:事件名="methods中的函数(实参)"
- 简写: @事件名="methods中的函数"
<!-- vue指令: v-on事件绑定--> <p>你要买商品的数量: {
{count}}</p> <button v-on:click="count = count + 1">增加1</button> <button v-on:click="addFn">增加1个</button> <button v-on:click="addCountFn(5)">一次加5件</button> <button @click="subFn">减少</button> <script> export default { // ...其他省略 methods: { addFn(){ // this代表export default后面的组件对象(下属有data里return出来的属性) this.count++ }, addCountFn(num){ this.count += num }, subFn(){ this.count-- } } } </script>
总结: 常用@事件名, 给dom标签绑定事件, 以及=右侧事件处理函数
练习-翻转世界

目标: 点击按钮 - 把文字取反显示 - 再点击取反显示(回来了)
提示: 把字符串取反赋予回去
讯享网<template> <div> <h1>{
{ message }}</h1> <button @click="btn">逆转世界</button> </div> </template> <script> export default { data() { return { message: "HELLO, WORLD", }; }, methods: { btn(){ this.message = this.message.split("").reverse().join("") } } }; </script>
4. vue指令-v-on事件对象
目标: vue事件处理函数中, 拿到事件对象
- 语法:
- 无传参, 通过形参直接接收
- 传参, 通过$event指代事件对象传给事件处理函数
<template> <div> <a @click="one" href="http://www.baidu.com">阻止百度</a> <hr> <a @click="two(10, $event)" href="http://www.baidu.com">阻止去百度</a> </div> </template> <script> export default { methods: { one(e){ e.preventDefault() }, two(num, e){ e.preventDefault() } } } </script>
5. vue指令-v-on修饰符
目的: 在事件后面.修饰符名 - 给事件带来更强大的功能
- 语法:
- @事件名.修饰符="methods里函数"
- .stop - 阻止事件冒泡
- .prevent - 阻止默认行为
- .once - 程序运行期间, 只触发一次事件处理函数
- @事件名.修饰符="methods里函数"
讯享网<template> <div @click="fatherFn"> <!-- vue对事件进行了修饰符设置, 在事件后面.修饰符名即可使用更多的功能 --> <button @click.stop="btn">.stop阻止事件冒泡</button> <a href="http://www.baidu.com" @click.prevent="btn">.prevent阻止默认行为</a> <button @click.once="btn">.once程序运行期间, 只触发一次事件处理函数</button> </div> </template> <script> export default { methods: { fatherFn(){ console.log("father被触发"); }, btn(){ console.log(1); } } } </script>
总结: 修饰符给事件扩展额外功能
6. vue指令-v-on按键修饰符
目标: 给键盘事件, 添加修饰符, 增强能力
- 语法:
- @keyup.enter - 监测回车按键
- @keyup.esc - 监测返回按键
更多修饰符
https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6
<template> <div> <input type="text" @keydown.enter="enterFn"> <hr> <input type="text" @keydown.esc="escFn"> </div> </template> <script> export default { methods: { enterFn(){ console.log("enter回车按键了"); }, escFn(){ console.log("esc按键了"); } } } </script>
总结: 多使用事件修饰符, 可以提高开发效率, 少去自己判断过程
7. vue指令 v-model
目标: 把value属性和vue数据变量, 双向绑定到一起
- 语法: v-model="vue数据变量"
- 双向数据绑定
- 数据变化 -> 视图自动同步
- 视图变化 -> 数据自动同步

- 演示: 用户名绑定 - vue内部是MVVM设计模式
讯享网<template> <div> <!-- v-model:是实现vuejs变量和表单标签value属性, 双向绑定的指令 --> <div> <span>用户名:</span> <input type="text" v-model="username" /> </div> <div> <span>密码:</span> <input type="password" v-model="pass" /> </div> <div> <span>来自于: </span> <!-- 下拉菜单要绑定在select上 --> <select v-model="from"> <option value="北京市">北京</option> <option value="南京市">南京</option> <option value="天津市">天津</option> </select> </div> <div> <!-- (重要) 遇到复选框, v-model的变量值 非数组 - 关联的是复选框的checked属性 数组 - 关联的是复选框的value属性 --> <span>爱好: </span> <input type="checkbox" v-model="hobby" value="抽烟">抽烟 <input type="checkbox" v-model="hobby" value="喝酒">喝酒 <input type="checkbox" v-model="hobby" value="写代码">写代码 </div> <div> <span>性别: </span> <input type="radio" value="男" name="sex" v-model="gender">男 <input type="radio" value="女" name="sex" v-model="gender">女 </div> <div> <span>自我介绍</span> <textarea v-model="intro"></textarea> </div> </div> </template> <script> export default { data() { return { username: "", pass: "", from: "", hobby: [], sex: "", intro: "", }; // 总结: // 特别注意: v-model, 在input[checkbox]的多选框状态 // 变量为非数组, 则绑定的是checked的属性(true/false) - 常用于: 单个绑定使用 // 变量为数组, 则绑定的是他们的value属性里的值 - 常用于: 收集勾选了哪些值 } }; </script>
总结: 本阶段v-model只能用在表单元素上, 以后学组件后讲v-model高级用法
8. vue指令 v-model修饰符
目标: 让v-model拥有更强大的功能
- 语法:
- v-model.修饰符="vue数据变量"
- .number 以parseFloat转成数字类型
- .trim 去除首尾空白字符
- .lazy 在change时触发而非inupt时
- v-model.修饰符="vue数据变量"

<template> <div> <div> <span>年龄:</span> <input type="text" v-model.number="age"> </div> <div> <span>人生格言:</span> <input type="text" v-model.trim="motto"> </div> <div> <span>自我介绍:</span> <textarea v-model.lazy="intro"></textarea> </div> </div> </template> <script> export default { data() { return { age: "", motto: "", intro: "" } } } </script>
总结: v-model修饰符, 可以对值进行预处理, 非常高效好用
9. vue指令 v-text和v-html
目的: 更新DOM对象的innerText/innerHTML
- 语法:
- v-text="vue数据变量"
- v-html="vue数据变量"

- 注意: 会覆盖插值表达式
讯享网<template>
<div>
<p v-text="str"></p>
<p v-html="str"></p>
</div>
</template>
<script>
export default {
data() {
return {
str: "<span>我是一个span标签</span>"
}
}
}
</script>
总结: v-text把值当成普通字符串显示, v-html把值当做html解析
10. vue指令 v-show和v-if
目标: 控制标签的隐藏或出现
- 语法:
- v-show="vue变量"
- v-if="vue变量"

- 原理
- v-show 用的display:none隐藏 (频繁切换使用)
- v-if 直接从DOM树上移除
- 高级
- v-else使用
<template> <div> <h1 v-show="isOk">v-show的盒子</h1> <h1 v-if="isOk">v-if的盒子</h1> <div> <p v-if="age > 18">我成年了</p> <p v-else>还得多吃饭</p> </div> </div> </template> <script> export default { data() { return { isOk: true, age: 15 } } } </script>
总结: 使用v-show和v-if以及v-else指令, 方便通过变量控制一套标签出现/隐藏
案例-折叠面板

目标: 点击展开或收起时,把内容区域显示或者隐藏
此案例使用了less语法, 项目中下载模块
讯享网yarn add less@3.0.4 less-loader@5.0.0 -D
<template> <div id="app"> <h3>案例:折叠面板</h3> <div> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {
{ isShow ? '收起' : '展开' }} </span> </div> <div class="container" v-show="isShow"> <p>寒雨连江夜入吴, </p> <p>平明送客楚山孤。</p> <p>洛阳亲友如相问,</p> <p>一片冰心在玉壶。</p> </div> </div> </div> </template> <script> export default { data() { return { isShow: false } } } </script> <style lang="less"> body { background-color: #ccc; #app { width: 400px; margin: 20px auto; background-color: #fff; border: 4px solid blueviolet; border-radius: 1em; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5); padding: 1em 2em 2em; h3 { text-align: center; } .title { display: flex; justify-content: space-between; align-items: center; border: 1px solid #ccc; padding: 0 1em; } .title h4 { line-height: 2; margin: 0; } .container { border: 1px solid #ccc; padding: 0 1em; } .btn { /* 鼠标改成手的形状 */ cursor: pointer; } } } </style>
11. vue指令-v-for
目标: 列表渲染, 所在标签结构, 按照数据数量, 循环生成
- 语法
- v-for="(值, 索引) in 目标结构"
- v-for="值 in 目标结构"
- 目标结构:
- 可以遍历数组 / 对象 / 数字 / 字符串 (可遍历结构)
- 注意:
v-for的临时变量名不能用到v-for范围外
讯享网<template> <div id="app"> <div id="app"> <!-- v-for 把一组数据, 渲染成一组DOM --> <!-- 口诀: 让谁循环生成, v-for就写谁身上 --> <p>学生姓名</p> <ul> <li v-for="(item, index) in arr" :key="item"> {
{ index }} - {
{ item }} </li> </ul> <p>学生详细信息</p> <ul> <li v-for="obj in stuArr" :key="obj.id"> <span>{
{ obj.name }}</span> <span>{
{ obj.sex }}</span> <span>{
{ obj.hobby }}</span> </li> </ul> <!-- v-for遍历对象(了解) --> <p>老师信息</p> <div v-for="(value, key) in tObj" :key="value"> {
{ key }} -- {
{ value }} </div> <!-- v-for遍历整数(了解) - 从1开始 --> <p>序号</p> <div v-for="i in count" :key="i">{
{ i }}</div> </div> </div> </template> <script> export default { data() { return { arr: ["小明", "小欢欢", "大黄"], stuArr: [ { id: 1001, name: "孙悟空", sex: "男", hobby: "吃桃子", }, { id: 1002, name: "猪八戒", sex: "男", hobby: "背媳妇", }, ], tObj: { name: "小黑", age: 18, class: "1期", }, count: 10, }; }, }; </script>
总结: vue最常用指令, 铺设页面利器, 快速把数据赋予到相同的dom结构上循环生成
为什么避免v-for和v-if在一起使用 Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错, 但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.
练习-购物车

目标: 完成商品浏览和删除功能, 当无数据给用户提示
- 需求1: 根据给的初始数据, 把购物车页面铺设出来
- 需求2: 点击对应删除按钮, 删除对应数据
- 需求3: 当数据没有了, 显示一条提示消息
讯享网
<template> <div id="app"> <table class="tb"> <tr> <th>编号</th> <th>品牌名称</th> <th>创立时间</th> <th>操作</th> </tr> <!-- 循环渲染的元素tr --> <tr v-for="(item,index) in list" :key="item.id"> <td>{ {item.id}}</td> <td>{ {item.name}}</td> <td>{ {item.time}}</td> <td> <button @click="del(index)">删除</button> </td> </tr> <tr v-if="list.length === 0"> <td colspan="4">没有数据咯~</td> </tr> </table> </div> </template> <script> export default { data() { return { list: [ { id: 1, name: "奔驰", time: "2020-08-01" }, { id: 2, name: "宝马", time: "2020-08-02" }, { id: 3, name: "奥迪", time: "2020-08-03" }, ], }; }, methods: { del(index) { // 删除按钮 - 得到索引, 删除数组里元素 this.list.splice(index, 1); }, }, }; </script> <style> #app { width: 600px; margin: 10px auto; } .tb { border-collapse: collapse; width: 100%; } .tb th { background-color: #0094ff; color: white; } .tb td, .tb th { padding: 5px; border: 1px solid black; text-align: center; } .add { padding: 5px; border: 1px solid black; margin-bottom: 10px; } </style>12. vue基础 v-for更新监测
目标: 当v-for遍历的目标结构改变, Vue触发v-for的更新
情况1: 数组翻转
情况2: 数组截取
情况3: 更新值
口诀: 数组变更方法, 就会导致v-for更新, 页面更新 数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set()
这些方法会触发数组改变, v-for会监测到并更新页面
讯享网push() pop() shift() unshift() splice() sort() reverse()
这些方法不会触发v-for更新
slice() filter() concat()
讯享网<template> <div> <ul> <li v-for="(val, index) in arr" :key="index"> {
{ val }} </li> </ul> <button @click="revBtn">数组翻转</button> <button @click="sliceBtn">截取前3个</button> <button @click="updateBtn">更新第一个元素值</button> </div> </template> <script> export default { data(){ return { arr: [5, 3, 9, 2, 1] } }, methods: { revBtn(){ // 1. 数组翻转可以让v-for更新 this.arr.reverse() }, sliceBtn(){ // 2. 数组slice方法不会造成v-for更新 // slice不会改变原始数组 // this.arr.slice(0, 3) // 解决v-for更新 - 覆盖原始数组 let newArr = this.arr.slice(0, 3) this.arr = newArr }, updateBtn(){ // 3. 更新某个值的时候, v-for是监测不到的 // this.arr[0] = 1000; // 解决-this.$set() // 参数1: 更新目标结构 // 参数2: 更新位置 // 参数3: 更新值 this.$set(this.arr, 0, 1000) } } } </script> <style> </style>
注意: vue不能监测到数组里赋值的动作而更新, 如果需要请使用Vue.set() 或者this.$set(), 或者覆盖整个数组
总结: 改变原数组的方法才能让v-for更新
13. vue基础 v-for就地更新
v-for 的默认行为会尝试原地修改元素而不是移动它们。
详解v-for就地更新流程

这种虚拟DOM对比方式(同级对比,标签与标签对比,相同则复用), 可以提高性能 - 但是还不够高
14. vue基础_虚拟dom
(1)真实DOM:在document对象上, 渲染到浏览器上显示的标签


(2)虚拟DOM:本质是保存节点信息, 属性和内容的一个JS对象

a. .vue文件中的template里写的标签, 都是模板, 都要被vue处理成虚拟DOM对象, 才会渲染显示到真实DOM页面上

b. 内存中生成一样的虚拟DOM结构(本质是个JS对象)
(3)vue数据更新
- 生成新的虚拟DOM结构 - 和旧的虚拟DOM结构对比 - 利用diff算法, 找不不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁
好处1: 提高了更新DOM的性能(不用把页面全删除重新渲染)
好处2: 虚拟DOM只包含必要的属性(没有真实DOM上百个属性)
总结: 虚拟DOM保存在内存中, 只记录dom关键信息, 配合diff算法提高DOM更新的性能
在内存中比较差异, 然后给真实DOM打补丁更新上

15. vue基础_diff算法
vue用diff算法, 新虚拟dom和旧的虚拟dom比较
(1)同级比较-根元素变化-整个dom树删除重建

情况1: 根元素变了, 删除重建
旧虚拟DOM
讯享网<div id="box"> <p class="my_p">123</p> </div>
新虚拟DOM
<ul id="box"> <li class="my_p">123</li> </ul>
(2)同级比较-根元素不变-属性改变更新属性

情况2: 根元素没变, 属性改变, 元素复用, 更新属性
旧虚拟DOM
讯享网<div id="box"> <p class="my_p">123</p> </div>
新虚拟DOM
<div id="myBox" title="标题"> <p class="my_p">123</p> </div>
16. vue基础_diff算法-key
情况3: 根元素没变, 子元素没变, 元素内容改变
(1)无key - 就地更新(最大限度尝试就地修改/复用相同类型元素)



讯享网<ul id="myUL"> <li v-for="str in arr"> {
{ str }} <input type="text"> </li> </ul> <button @click="addFn">下标为1的位置新增一个</button>
export default { data(){ return { arr: ["老大", "新来的", "老二", "老三"] } }, methods: { addFn(){ this.arr.splice(1, 0, '新来的') } } };
旧虚拟DOM结构 和 新虚拟DOM结构 对比过程

性能不高, 从第二个li往后都更新了
(2)有key - 值为索引(还是就地更新 )
有key属性, 基于key的来比较新旧虚拟DOM, 移除key不存在元素
先产生新旧虚拟DOM, 根据key比较, 因为新旧虚拟DOM对比, key存在就复用此标签更新内容, 如果不存在就直接建立一个新的,因此还是就地更新
讯享网<ul id="myUL"> <li v-for="(str, index) in arr" :key="index"> {
{ str }} <input type="text"> </li> </ul> <button @click="addFn">下标为1的位置新增一个</button>
export default { data(){ return { arr: ["老大", "新来的", "老二", "老三"] } }, methods: { addFn(){ this.arr.splice(1, 0, '新来的') } } };

讯享网 v-for先循环产生新的DOM结构, key是连续的, 和数据对应 然后比较新旧DOM结构, 找到区别, 打补丁到页面上 最后补一个li, 然后从第二个往后, 都要更新内容
(3)有key - 值为id
key的值只能是唯一不重复的, 字符串或数值 v-for不会移动DOM, 而是尝试复用, 就地更新,如果需要v-for移动DOM, 需要用特殊 attribute key 来提供一个排序提示 新DOM里数据的key存在, 去旧的虚拟DOM结构里找到key标记的标签, 复用标签 新DOM里数据的key存在, 去旧的虚拟DOM结构里没有找到key标签的标签, 创建 旧DOM结构的key, 在新的DOM结构里没有了, 则==移除key所在的标签
讯享网<template> <div> <ul> <li v-for="obj in arr" :key="obj.id"> {
{ obj.name }} <input type="text"> </li> </ul> <button @click="btn">下标1位置插入新来的</button> </div> </template> <script> export default { data() { return { arr: [ { name: '老大', id: 50 }, { name: '老二', id: 31 }, { name: '老三', id: 10 } ], }; }, methods: { btn(){ this.arr.splice(1, 0, { id: 19, name: '新来的' }) } } }; </script> <style> </style>
先产生新旧虚拟DOM, 根据key比较

总结: 不用key也不影响功能(就地更新), 添加key可以提高更新的性能
(4)有key和无key小结


17. vue基础_动态class
目标: 用v-bind给标签class设置动态的值
- 语法:
- :class="{类名: 布尔值}"

<template> <div> <!-- 语法: :class="{类名: 布尔值}" 使用场景: vue变量控制标签是否应该有类名 --> <p :class="{red_str: bool}">动态class</p> </div> </template> <script> export default { data(){ return { bool: true } } } </script> <style scoped> .red_str{ color: red; } </style>
总结: 就是把类名保存在vue变量中赋予给标签
18. vue基础-动态style
目标: 给标签动态设置style的值
- 语法
- :style="{css属性: 值}"
讯享网<template> <div> <!-- 动态style语法 :style="{css属性名: 值}" --> <p :style="{backgroundColor: colorStr}">动态style</p> </div> </template> <script> export default { data(){ return { colorStr: 'red' } } } </script> <style> </style>
总结: 动态style的key都是css属性名

案例-品牌管理

- 需求1: 把默认数据显示到表格上
- 需求2: 注意资产超过100的, 都用红色字体标记出来
- 实现数据增删
细节:
① 先铺设静态页面
② 此案例使用bootstrap, 需要下载, 并导入到工程main.js中
bootstrap, 工程化开发, 模块化用npm/yarn下载引入使用
arn add bootstr
在main.js - 引入bootstrap
讯享网import "bootstrap/dist/css/bootstrap.css" // 默认找文件夹下的index文件(但是这个不是所以需要写路径)
③ 用v-for配合默认数据, 把数据默认铺设到表格上显示
④ 直接在标签上, 大于100价格, 动态设置red类名
<template> <div id="app"> <div class="container"> <!-- 顶部框模块 --> <div class="form-group"> <div class="input-group"> <h4>品牌管理</h4> </div> </div> <!-- 数据表格 --> <table class="table table-bordered table-hover mt-2"> <thead> <tr> <th>编号</th> <th>资产名称</th> <th>价格</th> <th>创建时间</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="obj in list" :key="obj.id"> <td>{
{ obj.id }}</td> <td>{
{ obj.name }}</td> <!-- 如果价格超过100,就有red这个类 --> <td :class="{red: obj.price > 100}">{
{ obj.price }}</td> <td>{
{ obj.time }}</td> <td><a href="#" >删除</a></td> </tr> </tbody> <!-- <tfoot > <tr> <td colspan="5" style="text-align: center">暂无数据</td> </tr> </tfoot> --> </table> <!-- 添加资产 --> <form class="form-inline"> <div class="form-group"> <div class="input-group"> <input type="text" class="form-control" placeholder="资产名称" v-model="name" /> </div> </div> <div class="form-group"> <div class="input-group"> <input type="text" class="form-control" placeholder="价格" v-model.number="price" /> </div> </div> <!-- 4. 阻止表单提交(刷新网页数据又回去了) --> <button class="btn btn-primary" @click.prevent="addFn">添加资产</button> </form> </div> </div> </template> <script> export default { data() { return { name: "", // 名称 price: 0, // 价格 list: [ { id: 100, name: "外套", price: 199, time: new Date('2010-08-12')}, { id: 101, name: "裤子", price: 34, time: new Date('2013-09-01') }, { id: 102, name: "鞋", price: 25.4, time: new Date('2018-11-22') }, { id: 103, name: "头发", price: 19900, time: new Date('2020-12-12') } ], }; }, methods: { addFn(){ // 5. 判断是否为空 if (this.name.trim().length === 0 || this.price === 0) { alert("不能为空") return } // 3. 把值以对象形式-插入list this.list.push({ // 当前数组最后一个对象的id+1作为新对象id值 id: this.list[this.list.length - 1].id + 1, name: this.name, price: this.price, time: new Date() }) } // ...其他代码 delFn(id){ // 通过id找到这条数据在数组中下标 let index = this.list.findIndex(obj => obj.id === id) this.list.splice(index, 1) } } }; </script> <style > .red{ color: red; } </style>
五、vue过滤器
1. vue过滤器-定义使用
目的: 转换格式, 过滤器就是一个函数, 传入值返回处理后的值

例子:
- 全局定义字母都大写的过滤器

- 局部定义字符串翻转的过滤器

讯享网<template> <div> <p>原来的样子: {
{ msg }}</p> <!-- 2. 过滤器使用 语法: {
{ 值 | 过滤器名字 }} --> <p>使用翻转过滤器: {
{ msg | reverse }}</p> <p :title="msg | toUp">鼠标长停</p> </div> </template> <script> export default { data(){ return { msg: 'Hello, Vue' } }, // 方式2: 局部 - 过滤器 // 只能在当前vue文件内使用 /* 语法: filters: { 过滤器名字 (val) { return 处理后的值 } } */ filters: { toUp (val) { return val.toUpperCase() } } } </script> <style> </style>
总结: 把值转成另一种形式, 使用过滤器, Vue3用函数替代了过滤器.
全局注册最好在main.js中注册, 一处注册到处使用
2. vue过滤器-传参和多过滤器
目标: 可同时使用多个过滤器, 或者给过滤器传参
- 语法:
- 过滤器传参: vue变量 | 过滤器(实参)
- 多个过滤器: vue变量 | 过滤器1 | 过滤器2

<template> <div> <p>原来的样子: {
{ msg }}</p> <!-- 1. 给过滤器传值 语法: vue变量 | 过滤器名(值) --> <p>使用翻转过滤器: {
{ msg | reverse('|') }}</p> <!-- 2. 多个过滤利使用 语法: vue变量 | 过滤器1 | 过滤器2 --> <p :title="msg | toUp | reverse('|')">鼠标长停</p> </div> </template> <script> export default { data(){ return { msg: 'Hello, Vue' } }, filters: { toUp (val) { return val.toUpperCase() } } } </script> <style> </style>
总结: 过滤器可以传参, 还可以对某个过滤器结果, 后面在使用一个过滤器
3. 案例-品牌管理(时间格式化)
(1)下载moment处理日期的第三方工具模块
moment官网文档: Moment.js 中文网 | 开发文档
讯享网yarn add moment
(2)定义过滤器, 把时间用moment模块格式化, 返回我们想要的格式
// 目标: 处理时间 // 1. 下载moment模块 import moment from 'moment' // 2. 定义过滤器, 编写内部代码 filters: { formatDate (val){ return moment(val).format('YYYY-MM-DD') } } <!-- 3. 使用过滤器 --> <td>{
{ obj.time | formatDate }}</td>
六、vue计算属性
1. vue计算属性-computed
目标: 一个数据, 依赖另外一些数据计算而来的结果
语法:
-
讯享网computed: { "计算属性名" () { return "值" } }

需求:
- 需求: 求2个数的和显示到页面上
<template> <div> <p>{
{ num }}</p> </div> </template> <script> export default { data(){ return { a: 10, b: 20 } }, // 计算属性: // 场景: 一个变量的值, 需要用另外变量计算而得来 /* 语法: computed: { 计算属性名 () { return 值 } } */ // 注意: 计算属性和data属性都是变量-不能重名 // 注意2: 函数内变量变化, 会自动重新计算结果返回 computed: { num(){ return this.a + this.b } } } </script> <style> </style>
注意: 计算属性也是vue数据变量, 所以不要和data里重名, 用法和data相同
总结: 一个数据, 依赖另外一些数据计算而来的结果
2. vue计算属性-缓存
目标: 计算属性是基于它们的依赖项的值结果进行缓存的,只要依赖的变量不变, 都直接从缓存取结果

讯享网<template> <div> <p>{
{ reverseMessage }}</p> <p>{
{ reverseMessage }}</p> <p>{
{ reverseMessage }}</p> <p>{
{ getMessage() }}</p> <p>{
{ getMessage() }}</p> <p>{
{ getMessage() }}</p> </div> </template> <script> export default { data(){ return { msg: "Hello, Vue" } }, // 计算属性优势: // 带缓存 // 计算属性对应函数执行后, 会把return值缓存起来 // 依赖项不变, 多次调用都是从缓存取值 // 依赖项值-变化, 函数会"自动"重新执行-并缓存新的值 computed: { reverseMessage(){ console.log("计算属性执行了"); return this.msg.split("").reverse().join("") } }, methods: { getMessage(){ console.log("函数执行了"); return this.msg.split("").reverse().join("") } } } </script> <style> </style>
总结: 计算属性根据依赖变量结果缓存, 依赖变化重新计算结果存入缓存, 比普通方法性能更高
3. vue计算属性-完整写法
目标: 计算属性也是变量, 如果想要直接赋值, 需要使用完整写法
语法:
computed: { "属性名": { set(值){ }, get() { return "值" } } }
需求:
- 计算属性给v-model使用
页面准备输入框
讯享网<template> <div> <div> <span>姓名:</span> <input type="text" v-model="full"> </div> </div> </template> <script> // 问题: 给计算属性赋值 - 需要setter // 解决: /* 完整语法: computed: { "计算属性名" (){}, "计算属性名": { set(值){ }, get(){ return 值 } } } */ export default { computed: { full: { // 给full赋值触发set方法 set(val){ console.log(val) }, // 使用full的值触发get方法 get(){ return "无名氏" } } } } </script> <style> </style>
总结: 想要给计算属性赋值, 需要使用set方法

4. 案例-品牌管理(总价和均价)
目标: 基于之前的案例, 完成总价和均价的计算效果
此处只修改了变化的代码
<tr style="background-color: #EEE"> <td>统计:</td> <td colspan="2">总价钱为: {
{ allPrice }}</td> <td colspan="2">平均价: {
{ svgPrice }}</td> </tr> <script> // 目标: 总价和均价显示 // 1. 末尾补tr - 显示总价和均价 export default { // ...源代码省略 // 2. 计算属性 computed: { allPrice(){ // 3. 求总价 return this.list.reduce((sum, obj) => sum += obj.price, 0) }, avgPrice(){ // 4. 求均价 - 保留2位小数 return (this.allPrice / this.list.length).toFixed(2) } } } </script>
总结: 总价来源于所有数据计算而来的结果, 故采用计算属性
5. 案例-全选反选

讯享网<template> <div> <span>全选:</span> <!-- 4. v-model 关联全选 - 选中状态 --> <input type="checkbox" v-model="isAll"/> <button>反选</button> <ul> <li v-for="(obj, index) in arr" :key="index"> <!-- 3. 对象.c - 关联 选中状态 --> <input type="checkbox" v-model="obj.c"/> <span>{
{ obj.name }}</span> </li> </ul> </div> </template> <script> // 目标: 小选框 -> 全选 // 1. 标签+样式+js准备好 // 2. 把数据循环展示到页面上 export default { data() { return { arr: [ { name: "猪八戒", c: false, }, { name: "孙悟空", c: false, }, { name: "唐僧", c: false, }, { name: "白龙马", c: false, }, ], }; }, // 5. 计算属性-isAll computed: { isAll: { set(val){ // 7. 全选框 - 选中状态(true/false) this.arr.forEach(obj => obj.c = val) }, get(){ // 6. 统计小选框状态 -> 全选状态 // every口诀: 查找数组里"不符合"条件, 直接原地返回false return this.arr.every(obj => obj.c === true) } } } methods: { btn(){ // 8. 让数组里对象的c属性取反再赋予回去 this.arr.forEach(obj => obj.c = !obj.c) } } }; </script>
七、 vue侦听器
1. vue侦听器-watch
目标: 可以侦听data/computed属性值改变
语法:
watch: { "被侦听的属性名" (newVal, oldVal){ } }
完整例子代码:
讯享网<template> <div> <input type="text" v-model="name"> </div> </template> <script> export default { data(){ return { name: "" } }, // 目标: 侦听到name值的改变 /* 语法: watch: { 变量名 (newVal, oldVal){ // 变量名对应值改变这里自动触发 } } */ watch: { // newVal: 当前最新值 // oldVal: 上一刻值 name(newVal, oldVal){ console.log(newVal, oldVal); } } } </script> <style> </style>
总结: 想要侦听一个属性变化, 可使用侦听属性watch
2. vue侦听器-深度侦听和立即执行
目标: 侦听复杂类型, 或者立即执行侦听函数
- 语法:
watch: { "要侦听的属性名": { immediate: true, // 立即执行 deep: true, // 深度侦听复杂类型内变化 handler (newVal, oldVal) { } } }
完整例子代码:
讯享网<template> <div> <input type="text" v-model="user.name"> <input type="text" v-model="user.age"> </div> </template> <script> export default { data(){ return { user: { name: "", age: 0 } } }, // 目标: 侦听对象 /* 语法: watch: { 变量名 (newVal, oldVal){ // 变量名对应值改变这里自动触发 }, 变量名: { handler(newVal, oldVal){ }, deep: true, // 深度侦听(对象里面层的值改变) immediate: true // 立即侦听(网页打开handler执行一次) } } */ watch: { user: { handler(newVal, oldVal){ // user里的对象 console.log(newVal, oldVal); }, deep: true, immediate: true } } } </script> <style> </style>
总结: immediate立即侦听, deep深度侦听, handler固定方法触发
3. 案例-品牌管理(数据缓存)
目标: 侦听list变化, 同步到浏览器本地
- 需求: 把品牌管理的数据实时同步到本地缓存
分析:
① 在watch侦听list变化的时候, 把最新的数组list转成JSON字符串存入到localStorage本地
② data里默认把list变量从本地取值, 如果取不到给个默认的空数组
效果:
新增/删除 – 刷新页面 – 数据还在
<script> import moment from "moment"; export default { data() { return { name: "", // 名称 price: 0, // 价格 // 3. 本地取出缓存list list: JSON.parse(localStorage.getItem('pList')) || [], }; }, // ...其他代码省略 watch: { list: { handler(){ // 2. 存入本地 localStorage.setItem('pList', JSON.stringify(this.list)) }, deep: true } } }; </script>
八、vue组件
1. vue组件_概念
组件是可复用的 Vue 实例, 封装标签, 样式和JS代码
组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护
一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)

2. vue组件_基础使用
讯享网口诀: 哪部分标签复用, 就把哪部分封装到组件内 (重要): 组件内template只能有一个根标签 (重要): 组件内data必须是一个函数, 独立作用域
步骤:
(1)创建组件 components/Pannel.vue
封装标签+样式+js - 组件都是独立的, 为了复用
<template> <div> <div class="title"> <h4>芙蓉楼送辛渐</h4> <span class="btn" @click="isShow = !isShow"> {
{ isShow ? "收起" : "展开" }} </span> </div> <div class="container" v-show="isShow"> <p>寒雨连江夜入吴,</p> <p>平明送客楚山孤。</p> <p>洛阳亲友如相问,</p> <p>一片冰心在玉壶。</p> </div> </div> </template> <script> export default { data() { return { isShow: false, }; }, }; </script> <style scoped> .title { display: flex; justify-content: space-between; align-items: center; border: 1px solid #ccc; padding: 0 1em; } .title h4 { line-height: 2; margin: 0; } .container { border: 1px solid #ccc; padding: 0 1em; } .btn { /* 鼠标改成手的形状 */ cursor: pointer; } </style>
(2)注册组件: 创建后需要注册后再使用
全局 - 注册使用
全局入口在main.js, 在new Vue之上注册
语法:
讯享网import Vue from 'vue' import 组件对象 from 'vue文件路径' Vue.component("组件名", 组件对象)
main.js
// 目标: 全局注册 (一处定义到处使用) // 1. 创建组件 - 文件名.vue // 2. 引入组件 import Pannel from './components/Pannel' // 3. 全局 - 注册组件 /* 语法: Vue.component("组件名", 组件对象) */ Vue.component("PannelG", Pannel)
全局注册PannelG组件名后, 就可以当做标签在任意Vue文件中template里用
单双标签都可以或者小写加-形式, 运行后, 会把这个自定义标签当做组件解析, 使用组件里封装的标签替换到这个位置
讯享网<PannelG></PannelG> <PannelG/> <pannel-g></pannel-g>
局部 - 注册使用
语法:
import 组件对象 from 'vue文件路径' export default { components: { "组件名": 组件对象 } }
任意vue文件中中引入, 注册, 使用
讯享网<template> <div id="app"> <h3>案例:折叠面板</h3> <!-- 4. 组件名当做标签使用 --> <!-- <组件名></组件名> --> <PannelG></PannelG> <PannelL></PannelL> </div> </template> <script> // 目标: 局部注册 (用的多) // 1. 创建组件 - 文件名.vue // 2. 引入组件 import Pannel from './components/Pannel_1' export default { // 3. 局部 - 注册组件 /* 语法: components: { "组件名": 组件对象 } */ components: { PannelL: Pannel } } </script>
(3)组件使用总结
(1)(创建)封装html+css+vue到独立的.vue文件中
(2)(引入注册)组件文件 => 得到组件配置对象
(3)(使用)当前页面当做标签使用
4. vue组件-scoped作用
目的: 解决多个组件样式名相同, 冲突问题
需求: div标签名选择器, 设置背景色
问题: 发现组件里的div和外面的div都生效了
解决: 给Pannel.vue组件里style标签上加scoped属性即可
讯享网<style scoped>
在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v开头的属性
而且必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

总结: style上加scoped, 组件内的样式只在当前vue组件生效
九、vue组件通信
因为每个组件的变量和值都是独立的 组件通信先暂时关注父传子, 子传父 父: 使用其他组件的vue文件 子: 被引入的组件(嵌入) 例如: App.vue(父) MyProduct.vue(子)
1. vue组件通信_父向子-props
目的: 从外面给组件内传值
(1)父组件 -> 子组件 传值
讯享网步骤: (1)创建组件components(父)/MyProduct.vue (子) (2)子组件内在props定义变量, 用于接收外部传入的值 (3)父组件中引入注册组件, 使用时, 传入具体数据给组件显示
components/MyProduct.vue - 准备标签(子组件内, 定义变量, 准备接收, 然后使用变量)
<template> <div class="my-product"> <h3>标题: {
{ title }}</h3> <p>价格: {
{ price }}元</p> <p>{
{ intro }}</p> </div> </template> <script> export default { props: ['title', 'price', 'intro'] } </script> <style> .my-product { width: 400px; padding: 20px; border: 2px solid #000; border-radius: 5px; margin: 10px; } </style>
App.vue中使用并传入数据(引入组件, 注册组件, 使用组件, 传值进去)
讯享网<template> <div> <!-- 目标: 父(App.vue) -> 子(MyProduct.vue) 分别传值进入 需求: 每次组件显示不同的数据信息 步骤(口诀): 1. 子组件 - props - 变量 (准备接收) 2. 父组件 - 传值进去 --> <Product title="好吃的口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product> <Product title="好可爱的可爱多" price="20" intro="老板不在家, 全场1折"></Product> <Product title="好贵的北京烤鸭" price="290" :intro="str"></Product> </div> </template> <script> // 1. 创建组件 (.vue文件) // 2. 引入组件 import Product from './components/MyProduct' export default { data(){ return { str: "好贵啊, 快来啊, 好吃" } }, // 3. 注册组件 components: { // Product: Product // key和value变量名同名 - 简写 Product } } </script> <style> </style>
总结: 组件封装复用的标签和样式, 而具体数据要靠外面传入
(2)vue组件通信_父向子-配合循环
目的: 把数据循环分别传入给组件内显示
数据
list: [ { id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: '开业大酬宾, 全场8折' }, { id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: '好吃不腻, 快来买啊' }, { id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: '炎热的夏天, 来个冰激凌了' }, ],
讯享网<template> <div> <MyProduct v-for="obj in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info" ></MyProduct> </div> </template> <script> // 目标: 循环使用组件-分别传入数据 // 1. 创建组件 // 2. 引入组件 import MyProduct from './components/MyProduct' export default { data() { return { list: [ { id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: "开业大酬宾, 全场8折", }, { id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: "好吃不腻, 快来买啊", }, { id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: "炎热的夏天, 来个冰激凌了", }, ], }; }, // 3. 注册组件 components: { // MyProduct: MyProduct MyProduct } }; </script> <style> </style>
2. 单向数据流
从父到子的数据流向,叫单向数据流
在vue中需要遵循单向数据流原则
父组件的数据发生了改变,子组件会自动跟着变 子组件不能直接修改父组件传递过来的props, props变量本身是只读不能重新赋值 注意: 父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的, 对象是引用类型, 互相更新

原因: 子组件修改, 不通知父级, 造成数据不一致性
第一个MyProduct.vue内自己修改商品价格为5.5, 但是App.vue里原来还记着18.8 - 数据 不一致了
总结: props变量本身是不能重新赋值的
3. vue组件通信_子向父
目标: 从子组件把值传出来给外面使用
语法:
- 父: @自定义事件名="父methods函数"
- 子: this.$emit("自定义事件名", 传值) - 执行父methods里函数代码

(1)父组件内, 绑定自定义事件和事件处理函数
@自定义事件名="父methods里函数名"


App.vue
讯享网<template> <div> <!-- 目标: 子传父 --> <!-- 1. 父组件, @自定义事件名="父methods函数" --> <MyProduct v-for="(obj, ind) in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info" :index="ind" @subprice="fn" ></MyProduct> </div> </template> <script> import MyProduct from './components/MyProduct_sub' export default { data() { return { list: [ { id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: "开业大酬宾, 全场8折", }, { id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: "好吃不腻, 快来买啊", }, { id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: "炎热的夏天, 来个冰激凌了", }, ], }; }, components: { MyProduct }, methods: { fn(inde, price){ // 逻辑代码 this.list[inde].proprice > 1 && (this.list[inde].proprice = (this.list[inde].proprice - price).toFixed(2)) } } }; </script> <style> </style>
(2)子组件内, 恰当的时机, 触发父给我绑的自定义事件, 导致父methods里事件处理函数执行

components/MyProduct_sub.vue
<template> <div class="my-product"> <h3>标题: {
{ title }}</h3> <p>价格: {
{ price }}元</p> <p>{
{ intro }}</p> <button @click="subFn">宝刀-砍1元</button> </div> </template> <script> import eventBus from '../EventBus' export default { props: ['index', 'title', 'price', 'intro'], methods: { subFn(){ this.$emit('subprice', this.index, 1) // 子向父 eventBus.$emit("send", this.index, 1) // 跨组件 } } } </script> <style> .my-product { width: 400px; padding: 20px; border: 2px solid #000; border-radius: 5px; margin: 10px; } </style>
总结: 父自定义事件和方法, 等待子组件触发事件给方法传值
3. vue组件通信-EventBus
目标: 常用于跨组件通信时使用
两个没有任何引入关系的组件, 要如何互相通信呢?
两个组件的关系非常的复杂,通过父子组件通讯是非常麻烦的。
这时候可以使用通用的组件通讯方案:事件总线(event-bus)



总结: 空的Vue对象, 只负责$on注册事件, $emit触发事件, 一定要确保$on先执行
十、vue的生命周期
一组件从 创建 到 销毁 的整个过程就是生命周期
Vue_生命周期

1. 钩子函数
目标: Vue 框架内置函数,随着组件的生命周期阶段,自动执行
作用: 特定的时间点,执行特定的操作
场景: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据
分类: 4大阶段8个方法
- 初始化
- 挂载
- 更新
- 销毁
| 阶段 | 方法名 | 方法名 |
|---|---|---|
| 初始化 | beforeCreate | created |
| 挂载 | beforeMount | mounted |
| 更新 | beforeUpdate | updated |
| 销毁 | beforeDestroy | destroyed |
官网文档
https://cn.vuejs.org/v2/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA
下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

2. 初始化阶段
目标: 掌握初始化阶段2个钩子函数作用和执行时机
讯享网含义讲解: 1.new Vue() – Vue实例化(组件也是一个小的Vue实例) 2.Init Events & Lifecycle – 初始化事件和生命周期函数 3.beforeCreate – 生命周期钩子函数被执行 4.Init injections&reactivity – Vue内部添加data和methods等 5.created – 生命周期钩子函数被执行, 实例创建 6.接下来是编译模板阶段 –开始分析 7.Has el option? – 是否有el选项 – 检查要挂到哪里 没有. 调用$mount()方法 有, 继续检查template选项

components/Life.vue - 创建一个文件
<script> export default { data(){ return { msg: "hello, Vue" } }, // 一. 初始化 // new Vue()以后, vue内部给实例对象添加了一些属性和方法, data和methods初始化"之前" beforeCreate(){ console.log("beforeCreate -- 执行"); console.log(this.msg); // undefined }, // data和methods初始化以后 // 场景: 网络请求, 注册全局事件 created(){ console.log("created -- 执行"); console.log(this.msg); // hello, Vue this.timer = setInterval(() => { console.log("哈哈哈"); }, 1000) } } </script>
App.vue - 引入使用
讯享网<template> <div> <h1>1. 生命周期</h1> <Life></Life> </div> </template> <script> import Life from './components/Life' export default { components: { Life } } </script>
3. 挂载阶段
目标: 掌握挂载阶段2个钩子函数作用和执行时机
含义讲解: 1.template选项检查 有 - 编译template返回render渲染函数 无 – 编译el选项对应标签作为template(要渲染的模板) 2.虚拟DOM挂载成真实DOM之前 3.beforeMount – 生命周期钩子函数被执行 4.Create … – 把虚拟DOM和渲染的数据一并挂到真实DOM上 5.真实DOM挂载完毕 6.mounted – 生命周期钩子函数被执行

讯享网<template> <div> <p>学习生命周期 - 看控制台打印</p> <p id="myP">{
{ msg }}</p> </div> </template> <script> export default { // ...省略其他代码 // 二. 挂载 // 真实DOM挂载之前 // 场景: 预处理data, 不会触发updated钩子函数 beforeMount(){ console.log("beforeMount -- 执行"); console.log(document.getElementById("myP")); // null this.msg = "重新值" }, // 真实DOM挂载以后 // 场景: 挂载后真实DOM mounted(){ console.log("mounted -- 执行"); console.log(document.getElementById("myP")); // p } } </script>
4. 更新阶段
目标: 掌握更新阶段2个钩子函数作用和执行时机
含义讲解: 1.当data里数据改变, 更新DOM之前 2.beforeUpdate – 生命周期钩子函数被执行 3.Virtual DOM…… – 虚拟DOM重新渲染, 打补丁到真实DOM 4.updated – 生命周期钩子函数被执行 5.当有data数据改变 – 重复这个循环

components/Life.vue - 创建一个文件
准备ul+li循环, 按钮添加元素, 触发data改变->导致更新周期开始
讯享网<template> <div> <p>学习生命周期 - 看控制台打印</p> <p id="myP">{
{ msg }}</p> <ul id="myUL"> <li v-for="(val, index) in arr" :key="index"> {
{ val }} </li> </ul> <button @click="arr.push(1000)">点击末尾加值</button> </div> </template> <script> export default { data(){ return { msg: "hello, Vue", arr: [5, 8, 2, 1] } }, // ...省略其他代码 // 三. 更新 // 前提: data数据改变才执行 // 更新之前 beforeUpdate(){ console.log("beforeUpdate -- 执行"); console.log(document.querySelectorAll("#myUL>li")[4]); // undefined }, // 更新之后 // 场景: 获取更新后的真实DOM updated(){ console.log("updated -- 执行"); console.log(document.querySelectorAll("#myUL>li")[4]); // li } } </script>
5. 销毁阶段
目标: 掌握销毁阶段2个钩子函数作用和执行时机
含义讲解: 1.当$destroy()被调用 – 比如组件DOM被移除(例v-if) 2.beforeDestroy – 生命周期钩子函数被执行 3.拆卸数据监视器、子组件和事件侦听器 4.实例销毁后, 最后触发一个钩子函数 5.destroyed – 生命周期钩子函数被执行

components/Life.vue - 准备生命周期方法(Life组件即将要被删除)
讯享网<script> export default { // ...省略其他代码 // 四. 销毁 // 前提: v-if="false" 销毁Vue实例 // 场景: 移除全局事件, 移除当前组件, 计时器, 定时器, eventBus移除事件$off方法 beforeDestroy(){ // console.log('beforeDestroy -- 执行'); clearInterval(this.timer) }, destroyed(){ // console.log("destroyed -- 执行"); } } </script>
主要: App.vue - 点击按钮让Life组件从DOM上移除 -> 导致Life组件进入销毁阶段
<Life v-if="show"></Life> <button @click="show = false">销毁组件</button> <script> data(){ return { show: true } }, </script>
十一、$refs和$nextTick
1. $refs-获取DOM
目标: 利用 ref 和 $refs 可以用于获取 dom 元素
components/More.vue
讯享网<template> <div> <p>1. 获取原生DOM元素</p> <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1> </div> </template> <script> // 目标: 获取组件对象 // 1. 创建组件/引入组件/注册组件/使用组件 // 2. 组件起别名ref // 3. 恰当时机, 获取组件对象 export default { mounted(){ console.log(document.getElementById("h")); // h1 console.log(this.$refs.myH); // h1 } } </script> <style> </style>
总结: 通过id / ref, 都可以获取原生DOM标签
2. $refs-获取组件对象
目标: 获取组件对象, 调用组件里方法
components/Child/Demo.vue
<template> <div> <p>我是Demo组件</p> </div> </template> <script> export default { methods: { fn(){ console.log("demo组件内的方法被调用了"); } } } </script>
More.vue - 获取组件对象 - 调用组件方法
讯享网<template> <div> <p>1. 获取原生DOM元素</p> <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1> <p>2. 获取组件对象 - 可调用组件内一切</p> <Demo ref="de"></Demo> </div> </template> <script> // 目标: 获取组件对象 // 1. 创建组件/引入组件/注册组件/使用组件 // 2. 组件起别名ref // 3. 恰当时机, 获取组件对象 import Demo from './Child/Demo' export default { mounted(){ console.log(document.getElementById("h")); // h1 console.log(this.$refs.myH); // h1 let demoObj = this.$refs.de; demoObj.fn() }, components: { Demo } } </script>
总结: ref定义值, 通过$refs.值 来获取组件对象, 就能继续调用组件内的变量
3. $nextTick使用
Vue更新DOM-异步的
目标: 点击count++, 马上通过"原生DOM"拿标签内容, 无法拿到新值
components/Move.vue - 继续新增第三套代码
<template> <div> <p>1. 获取原生DOM元素</p> <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1> <p>2. 获取组件对象 - 可调用组件内一切</p> <Demo ref="de"></Demo> <p>3. vue更新DOM是异步的</p> <p ref="myP">{
{ count }}</p> <button @click="btn">点击count+1, 马上提取p标签内容</button> </div> </template> <script> // 目标: 获取组件对象 // 1. 创建组件/引入组件/注册组件/使用组件 // 2. 组件起别名ref // 3. 恰当时机, 获取组件对象 import Demo from './Child/Demo' export default { mounted(){ console.log(document.getElementById("h")); // h1 console.log(this.$refs.myH); // h1 let demoObj = this.$refs.de; demoObj.fn() }, components: { Demo }, data(){ return { count: 0 } }, methods: { btn(){ this.count++; // vue监测数据更新, 开启一个DOM更新队列(异步任务) console.log(this.$refs.myP.innerHTML); // 0 // 原因: Vue更新DOM异步 // 解决: this.$nextTick() // 过程: DOM更新完会挨个触发$nextTick里的函数体 this.$nextTick(() => { console.log(this.$refs.myP.innerHTML); // 1 }) } } } </script>
总结: 因为DOM更新是异步的
4. $nextTick使用场景

目标: 点击搜索按钮, 弹出聚焦的输入框, 按钮消失
components/Tick.vue
讯享网<template> <div> <input ref="myInp" type="text" placeholder="这是一个输入框" v-if="isShow"> <button v-else @click="btn">点击我进行搜索</button> </div> </template> <script> // 目标: 点按钮(消失) - 输入框出现并聚焦 // 1. 获取到输入框 // 2. 输入框调用事件方法focus()达到聚焦行为 export default { data(){ return { isShow: false } }, methods: { async btn(){ this.isShow = true; // this.$refs.myInp.focus() // 原因: data变化更新DOM是异步的 // 输入框还没有挂载到真实DOM上 // 解决: // this.$nextTick(() => { // this.$refs.myInp.focus() // }) // 扩展: await取代回调函数 // $nextTick()原地返回Promise对象 await this.$nextTick() this.$refs.myInp.focus() } } } </script>
5. 组件name属性使用
目标: 可以用组件的name属性值, 来注册组件名字
问题: 组件名不是可以随便写的?
答案: 我们封装的组件-可以自己定义name属性组件名-让使用者有个统一的前缀风格
components/Com.vue
<template> <div> <p>我是一个Com组件</p> </div> </template> <script> export default { name: "ComNameHaHa" // 注册时可以定义自己的名字 } </script>
App.vue - 注册和使用
讯享网<template> <div> <h1>1. 生命周期</h1> <Life v-if="show"></Life> <button @click="show = false">销毁组件</button> <hr> <h1>2. axios使用</h1> <UseAxios></UseAxios> <hr> <h1>3. $refs的使用</h1> <More></More> <hr> <h1>4. $nextTick使用场景</h1> <Tick></Tick> <hr> <h1>5. 组件对象里name属性</h1> <ComNameHaHa></ComNameHaHa> </div> </template> <script> import Life from './components/Life' import UseAxios from './components/UseAxios' import More from './components/More' import Tick from './components/Tick' import Com from './components/Com' export default { data(){ return { show: true } }, components: { Life, UseAxios, More, Tick, [Com.name]: Com // 对象里的key是变量的话[]属性名表达式 // "ComNameHaHa": Com } } </script>


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/41449.html