st779779

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Vue 渐进式 javacript 框架

插件

可选安装 - 谷歌访问助手

这是一个谷歌浏览器上的插件

安装

  1. 必安插件(文件夹)下的 google-access-helper-2.3.0(文件夹) 复制到你想放的文件夹下(安装后不可以挪动位置)

建议D盘下, 弄一个专门按软件的文件夹

  1. 打开谷歌浏览器-扩展程序-开发者模式打开-把文件夹拖进来就安装完毕

    安装谷歌助手插件

功能如下:

谷歌浏览器上

  • 同步书签(需要注册和登录, 开启同步功能) - 可以暂不使用(因为有的手机号可能注册不了谷歌账号)
  • 谷歌商店(无需登录, 安装其他插件)

必安装 - vue-devtools

学习, 调试vue必备之利器 - 官方提供的呦

右上角-插件-谷歌访问助手-打开Chrome商店-搜索vue-devtools回车-然后添加至Chrome等待下载后自动安装-右上角显示已经添加即代表成功

如果实在打不开谷歌商店, 换个网 / 直接用备用文件夹里的vue-devtools插件包安装到浏览器扩展程序也一样用

不要图标上带橘黄色beta的

如果这个网址打不开, 就用预习资料里备用的本地版安装也可以, 安装过程和上个插件安装过程一致

安装vue_devtools插件到谷歌浏览器

vscode-插件补充

vue文件代码高亮插件-vscode中安装

image-20210212192713936

代码提示插件-vscode中安装

@vue/cli脚手架

@vue/cli安装

目标: 把@vue/cli模块包按到全局, 电脑拥有vue命令, 才能创建脚手架工程

  • 全局安装命令
yarn global add @vue/cli
# OR
npm install -g @vue/cli

注意: 如果半天没动静(95%都是网速问题), 可以ctrl c

  1. 停止重新来

  2. 换一个网继续重来

  • 查看vue脚手架版本
vue -V

总结: 如果出现版本号就安装成功, 否则失败

_@vue/cli 创建项目启动服务

目标: 使用vue命令, 创建脚手架项目

注意: 项目名不能带大写字母, 中文和特殊符号

  1. 创建项目
# vue和create是命令, vuecli-demo是文件夹名
vue create vuecli-demo
  1. 选择模板

    可以上下箭头选择, 弄错了ctrl+c重来

image-20210116230221236

​ 选择用什么方式下载脚手架项目需要的依赖包Snipaste_2021-03-26_15-24-14

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

image-20210212174314768

  1. 进入脚手架项目下, 启动内置的热更新本地服务器
cd vuecil-demo

npm run serve
# 或
yarn serve

只要看到绿色的 - 啊. 你成功了(底层node+webpack热更新服务)

image-20210116231815543

打开浏览器输入上述地址

image-20210116233035582

总结: vue命令创建工程目录, 项目内置webpack本地热更新服务器, 帮我们打包项目预览项目

@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 – 依赖包列表文件

@vue/cli 项目架构了解

目标: 知道项目入口, 以及代码执行顺序和引入关系

image-20210317201811310

@vue/cli 自定义配置

目标:项目中没有webpack.config.js文件,因为@vue/cli用的vue.config.js

src并列处新建vue.config.js

/* 覆盖webpack的配置 */
module.exports = {
  devServer: { // 自定义服务配置
    open: true, // 自动打开浏览器
    port: 3000
  }
}

eslint 代码检查工具

目标: 知道eslint的作用, 和如何暂时关闭, 它是一个代码检查工具

例子: 先在main.js 随便声明个变量, 但是不要使用

image-20210326165406694

观察发现, 终端和页面都报错了

记住以后见到这样子的错误, 证明你的代码不严谨

image-20210326165544865

image-20210326165606191

方式1: 手动解决掉错误, 以后项目中会讲如何自动解决

方式2: 暂时关闭eslint检查(因为现在主要精力在学习Vue语法上), 在vue.config.js中配置后重启服务

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
    // ...其他代码
  lintOnSave:false,
})

@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/cli main.js 文件

import Vue from 'vue' //引入vue对象 类似与 <script src="vue.js"></script>
import App from './App.vue'  // app.vue文件里的对象引入过来 (vue项目页面入口)
Vue.config.productionTip = false //在控制台有一句提示消息
let  a = 10
new Vue({  //开始实例化vue
  render: h => h(App), //准备渲染app页面
}).$mount('#app') //渲染到index.html文件里面id叫app的标签上

@vue/cli 欢迎界面清理

目标: 我们开始写我们自己的代码, 无需欢迎页面

  • src/App.vue默认有很多内容, 可以全部删除留下框
  • assets 和 components 文件夹下的一切都删除掉 (不要默认的欢迎页面)

Vue指令

vue基础-插值表达式

目的: 在dom标签中, 直接插入内容

又叫: 声明式渲染/文本插值

语法: {{ 表达式 }}

<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里声明

vue基础-MVVM设计模式

目的: 转变思维, 用数据驱动视图改变, 操作dom的事, vue源码内干了

设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。

演示: 在上个代码基础上, 在devtool工具改变M层的变量, 观察V层(视图的自动同步)

等下面学了v-model再观察V改变M的效果

1

  • 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操作, 挺高开发效率

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属性上, 影响标签显示效果

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标签绑定事件, 以及=右侧事件处理函数

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>

vue指令-v-on修饰符

目的: 在事件后面.修饰符名 - 给事件带来更强大的功能

  • 语法:
    • @事件名.修饰符="methods里函数"
      • .stop - 阻止事件冒泡
      • .prevent - 阻止默认行为
      • .once - 程序运行期间, 只触发一次事件处理函数
<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>

总结: 修饰符给事件扩展额外功能

vue指令-v-on按键修饰符

目标: 给键盘事件, 添加修饰符, 增强能力

  • 语法:
    • @keyup.enter - 监测回车按键
    • @keyup.esc - 监测返回按键

更多修饰符

<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>

总结: 多使用事件修饰符, 可以提高开发效率, 少去自己判断过程

课上练习-翻转世界

目标: 点击按钮 - 把文字取反显示 - 再点击取反显示(回来了)

提示: 把字符串取反赋予回去

效果演示:

练习1_翻转世界

正确代码:

<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>

总结: 记住方法特点, 多做需求, vue是数据变化视图自动更新, 减少操作DOM时间, 提高开发效率

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高级用法

vue指令 v-model修饰符

目标: 让v-model拥有更强大的功能

  • 语法:
    • v-model.修饰符="vue数据变量"
      • .number 以parseFloat转成数字类型
      • .trim 去除首尾空白字符
      • .lazy 在change时触发而非inupt时
<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修饰符, 可以对值进行预处理, 非常高效好用

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解析

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" >
          收起
        </span>
      </div>
      <div class="container">
        <p>寒雨连江夜入吴,</p>
        <p>平明送客楚山孤。</p>
        <p>洛阳亲友如相问,</p>
        <p>一片冰心在玉壶。</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      
    }
  }
}
</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>

正确答案:

<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>

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结构上循环生成

总结

    • 渐进式
    • 声明式渲染
    • 数据驱动视图 (响应式)
    • 极少的去写DOM操作相关代码
    • 双向绑定
    • 组件系统
    • 不兼容IE8及以下浏览器

面试题

1. Vue的最大优势是什么?

​ 简单易学, 轻量级整个源码js文件不大, 双向数据绑定, 数据驱动视图, 组件化, 数据和视图分离,

​ vue负责关联视图和数据, 作者中国人(尤雨溪), 文档都是中文的, 入门教程非常多, 上手简单.

​ 相比传统网页, vue是单页面可以只刷新某一部分

2. Vue和jQuery区别是什么?

​ jQuery应该算是一个插件, 里面封装了各种易用的方法, 方便你使用更少的代码来操作dom标签

​ Vue是一套框架, 有自己的规则和体系与语法, 特别是设计思想MVVM, 让数据和视频关联绑定, 省略了很多DOM操作. 然后指令还给标签注入了更多的功能

3. mvvm和mvc区别是什么?

​ MVC: 也是一种设计模式, 组织代码的结构, 是model数据模型, view视图, Controller控制器, 在控制器这层里编写js代码, 来控制数据和视图关联

​ MVVM: 即Model-View-ViewModel的简写。即模型-视图-视图模型, VM是这个设计模式的核心, 连接v和m的桥梁, 内部会监听DOM事件, 监听数据对象变化来影响对方. 我们称之为数据绑定

4. Vue常用修饰符有哪些?

​ .prevent: 提交事件不再重载页面;

​ .stop: 阻止单击事件冒泡;

​ .once: 只执行一次这个事件

5. Vue2.x兼容IE哪个版本以上

​ 不支持ie8及以下,部分兼容ie9 ,完全兼容10以上, 因为vue的响应式原理是基于es5的Object.defineProperty(),而这个方法不支持ie8及以下。

6. 对Vue渐进式的理解

​ 渐进式代表的含义是:主张最少, 自底向上, 增量开发, 组件集合, 便于复用

7. v-show和v-if的区别

​ v-show和v-if的区别? 分别说明其使用场景?

​ v-show 和v-if都是true的时候显示,false的时候隐藏

​ 但是:false的情况下,

​ v-show是采用的display:none

​ v-if采用惰性加载

​ 如果需要频繁切换显示隐藏需要使用v-show

8. 说出至少4个Vue指令及作用

​ v-for 根据数组的个数, 循环数组元素的同时还生成所在的标签

​ v-show 显示内容

​ v-if 显示与隐藏

​ v-else 必须和v-if连用 不能单独使用 否则报错

​ v-bind 动态绑定 作用: 及时对页面的数据进行更改, 可以简写成:分号

​ v-on 给标签绑定函数,可以缩写为@,例如绑定一个点击函数 函数必须写在methods里面

​ v-text 解析文本

​ v-html 解析html标签

9. 为什么避免v-for和v-if在一起使用

​ Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错好使, 但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.

vue基础

vue基础 v-for更新监测

目标: 当v-for遍历的目标结构改变, Vue触发v-for的更新

情况1: 数组翻转

情况2: 数组截取

情况3: 更新值

口诀:

数组变更方法, 就会导致v-for更新, 页面更新

数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set()

<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>

这些方法会触发数组改变, v-for会监测到并更新页面

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

这些方法不会触发v-for更新

  • slice()
  • filter()
  • concat()

注意: vue不能监测到数组里赋值的动作而更新, 如果需要请使用Vue.set() 或者this.$set(), 或者覆盖整个数组

总结: 改变原数组的方法才能让v-for更新

基础_v-for就地更新

v-for 的默认行为会尝试原地修改元素而不是移动它们。

详解v-for就地更新流程(可以看ppt动画)

image-20210414215302318

这种 虚拟DOM对比方式, 可以提高性能 - 但是还不够高

1.2_vue基础_虚拟dom

目标: 了解虚拟DOM的概念

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

  1. 内存中生成一样的虚拟DOM结构(本质是个JS对象)

    因为真实的DOM属性好几百个, 没办法快速的知道哪个属性改变了

    比如template里标签结构

    <template>
        <div id="box">
            <p class="my_p">123</p>
        </div>
    </template>
    

    对应的虚拟DOM结构

    const dom = {
        type: 'div',
        attributes: [{id: 'box'}],
        children: {
            type: 'p',
            attributes: [{class: 'my_p'}],
            text: '123'
        }
    }
    
  2. 以后vue数据更新

    • 生成新的虚拟DOM结构
    • 和旧的虚拟DOM结构对比
    • 利用diff算法, 找不不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁

好处1: 提高了更新DOM的性能(不用把页面全删除重新渲染)

好处2: 虚拟DOM只包含必要的属性(没有真实DOM上百个属性)

总结: 虚拟DOM保存在内存中, 只记录dom关键信息, 配合diff算法提高DOM更新的性能

在内存中比较差异, 然后给真实DOM打补丁更新上

image-20210414215426783

vue基础_diff算法

vue用diff算法, 新虚拟dom, 和旧的虚拟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: 根元素没变, 属性改变, 元素复用, 更新属性

旧虚拟DOM

<div id="box">
    <p class="my_p">123</p>
</div>

新虚拟DOM

<div id="myBox" title="标题">
    <p class="my_p">123</p>
</div>

基础_diff算法-key

情况3: 根元素没变, 子元素没变, 元素内容改变

无key - 就地更新

v-for不会移动DOM, 而是尝试复用, 就地更新,如果需要v-for移动DOM, 你需要用特殊 attribute 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, '新来的')
        }
    }
};

新_vfor更细_无key_就地更新

旧 - 虚拟DOM结构 和 新 - 虚拟DOM结构 对比过程

image-20210414215502653

性能不高, 从第二个li往后都更新了

有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, '新来的')
        }
    }
};

key为索引-图解过程 (又就地往后更新了)

新_vfor更细_无key_就地更新

image-20210414215525492

  1. v-for先循环产生新的DOM结构, key是连续的, 和数据对应

  2. 然后比较新旧DOM结构, 找到区别, 打补丁到页面上

    最后补一个li, 然后从第二个往后, 都要更新内容

口诀: key的值有id用id, 没id用索引

有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>

图解效果:

新_vfor更细_有key值为id_提高性能更新

image-20210414215546869

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

阶段小结

v-for什么时候会更新页面呢?

  • 数组采用更新方法, 才导致v-for更新页面

vue是如何提高更新性能的?

  • 采用虚拟DOM+diff算法提高更新性能

虚拟DOM是什么?

  • 本质是保存dom关键信息的JS对象

diff算法如何比较新旧虚拟DOM?

  • 根元素改变 – 删除当前DOM树重新建
  • 根元素未变, 属性改变 – 更新属性
  • 根元素未变, 子元素/内容改变
  • 无key – 就地更新 / 有key – 按key比较

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变量中赋予给标签

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的, 都用红色字体标记出来

细节:

​ ① 先铺设静态页面 --- 去.md文档里, 复制数据和标签模板

​ ② 此案例使用bootstrap, 需要下载, 并导入到工程main.js中

​ ③ 用v-for配合默认数据, 把数据默认铺设到表格上显示

​ ④ 直接在标签上, 大于100价格, 动态设置red类名

图示:

image-20210511120432874

  1. 因为案例使用了bootstrap, 工程化开发, 模块化用npm/yarn下载引入使用
yarn add bootstrap
  1. 在main.js - 引入bootstrap
import "bootstrap/dist/css/bootstrap.css" // 默认找文件夹下的index文件(但是这个不是所以需要写路径)
  1. 模板代码(在这个基础上写)
<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 >
            <td></td>
            <td></td>

            <!-- 如果价格超过100,就有red这个类 -->
            <td class="red"></td>
            <td></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="资产名称"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="价格"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <!-- 阻止表单提交 -->
        <button class="btn btn-primary">添加资产</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') }
      ],
    };
  },

};
</script>

<style >
.red{
  color: red;
}
</style>

正确代码, 不可复制

<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>

<script>
// 1. 明确需求
// 2. 标签+样式+默认数据
// 3. 下载bootstrap, main.js引入bootstrap.css
// 4. 把list数组 - 铺设表格
// 5. 修改价格颜色
</script>

案例-品牌管理(增)

目标: 数据新增

  • 需求1: 实现表单数据新增进表格功能

  • 需求2: 判断用户输入是否为空给提示

  • 分析

    ① 添加资产按钮 – 绑定点击事件

    ② 给表单v-model绑定vue变量收集用户输入内容

    ③ 添加数组到数组中

    ④ 判断用户内容是否符合规定

图示:

品牌管理_铺增删

在上个案例代码基础上接着写

正确代码,不可复制

<!-- 添加资产 -->
      <form class="form-inline">
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="资产名称"
              v-model="name"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="价格"
              v-model.number="price"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <!-- 4. 阻止表单提交(刷新网页数据又回去了) -->
        <button class="btn btn-primary" @click.prevent="addFn">添加资产</button>
      </form>

<script>
// 目标: 新增
// 1. 按钮 - 事件
// 2. 给表单v-model绑定vue变量
export default {
  // ...省略其他
  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()
      })
    }
  }
};
</script>

案例-品牌管理(删)

目标: 数据删除

  • 需求1: 点击删除的a标签, 删除数据

  • 需求2: 删除没数据了要提示暂无数据的tfoot

  • 分析

    ① a标签绑定点击事件

    ② 给事件方法传id

    ③ 通过id, 找到对应数据删除

    ④ 删除光了要让tfoot显示

    ⑤ 删除光了再新增, 有bug(id值问题)需要修复

图示:

品牌管理_铺增删

在上个案例代码基础上接着写

正确的代码(不可复制)

<td><a href="#" @click="delFn(obj.id)">删除</a></td>
          
<script>
// 目标: 删除功能
// 1. 删除a标签-点击事件
// 2. 对应方法名
// 3. 数据id到事件方法中
// 4. 通过id, 找到这条数据在数组中的下标
// 5. splice方法删除原数组里的对应元素
// 6. 设置tfoot, 无数据给出提示
// 7. 无数据再新增, id要判断一下
export default {
  // ...其他代码
  methods: {
    // ...其他代码
    delFn(id){
      // 通过id找到这条数据在数组中下标
      let index = this.list.findIndex(obj => obj.id === id)
      this.list.splice(index, 1)
    }
  }
};
</script>

vue过滤器

vue过滤器-定义使用

目的: 转换格式, 过滤器就是一个函数, 传入值返回处理后的值

过滤器只能用在, 插值表达式和v-bind表达式

Vue中的过滤器场景

  • 字母转大写, 输入"hello", 输出"HELLO"
  • 字符串翻转, "输入hello, world", 输出"dlrow ,olleh"

语法:

  • Vue.filter("过滤器名", (值) => {return "返回处理后的值"})

  • filters: {过滤器名字: (值) =>

例子:

  • 全局定义字母都大写的过滤器
  • 局部定义字符串翻转的过滤器
<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中注册, 一处注册到处使用

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>

总结: 过滤器可以传参, 还可以对某个过滤器结果, 后面在使用一个过滤器

案例-品牌管理(时间格式化)

目标: 复制上个案例, 在此基础上, 把表格里的时间用过滤器+moment模块, 格式化成YYYY-MM-DD 格式

图示:

image-20210215155844500

  1. 下载moment处理日期的第三方工具模块

    moment官网文档: http://momentjs.cn/docs/#/displaying/

    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计算属性

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相同

总结: 一个数据, 依赖另外一些数据计算而来的结果

vue计算属性-缓存

目标: 计算属性是基于它们的依赖项的值结果进行缓存的,只要依赖的变量不变, 都直接从缓存取结果

image-20210113232439893

<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>

总结: 计算属性根据依赖变量结果缓存, 依赖变化重新计算结果存入缓存, 比普通方法性能更高

案例-品牌管理(总价和均价)

目标: 基于之前的案例, 完成总价和均价的计算效果

image-20210215160140718

此处只修改了变化的代码

<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>

总结: 总价来源于所有数据计算而来的结果, 故采用计算属性

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方法

案例-小选影响全选

目标: 小选框都选中(手选), 全选自动选中

  • 需求: 小选框都选中(手选), 全选自动选中

分析:

① 先静态后动态, 从.md拿到静态标签和数据

② 循环生成复选框和文字, 对象的c属性和小选框的选中状态, 用v-model双向绑定

③ 定义isAll计算属性, 值通过小选框们统计c属性状态得来

图示:

image-20210511120432874

模板标签和数据(直接复制在这基础上写vue代码)

<template>
  <div>
    <span>全选:</span>
    <input type="checkbox"/>
    <button>反选</button>
    <ul>
      <li>
        <input type="checkbox"/>
        <span>任务名</span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          name: "猪八戒",
          c: false,
        },
        {
          name: "孙悟空",
          c: false,
        },
        {
          name: "唐僧",
          c: false,
        },
        {
          name: "白龙马",
          c: false,
        },
      ],
    };
  }
};
</script>

正确代码,不可复制

<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 () {
         // 6. 统计小选框状态 ->  全选状态
        // every口诀: 查找数组里"不符合"条件, 直接原地返回false
        return this.arr.every(obj => obj.c === true)
    }
  }
};
</script>

案例-全选影响小选

目标: 全选影响小选

  • 需求1: 获取到全选状态 – 改装isAll计算属性
  • 需求2: 全选状态同步给所有小选框

分析:

①: isAll改成完整写法, set里获取到全选框, 勾选的状态值

②: 遍历数据数组, 赋给所有小选框v-model关联的属性

图示:

image-20210511120432874

正确代码,不可以复制

<script>
export default {
  // ...其他代码
  // 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)
      }
    }
  }
};
</script>

案例-反选

目标: 反选功能

  • 需求: 点击反选, 让所有小选框, 各自取相反勾选状态

分析:

①: 小选框的勾选状态, 在对象的c属性

②: 遍历所有对象, 把对象的c属性取相反值赋予回去即可

图示:

image-20210511120432874

正确代码,不可以复制

<button @click="btn">反选</button>

<script>
export default {
  // ...其他代码省略
  methods: {
    btn(){
      // 8. 让数组里对象的c属性取反再赋予回去
      this.arr.forEach(obj => obj.c = !obj.c)
    }
  }
};
</script>

vue侦听器

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

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固定方法触发

案例-品牌管理(数据缓存)

目标: 侦听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>

今日总结

面试题

1. Vue 中怎么自定义过滤器

​ Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和v-bind表达式

​ 全局的用Vue.filter()

​ 局部的用filters属性

2. Vue中:key作用, 为什么不能用索引

​ :key是给v-for循环生成标签颁发唯一标识的, 用于性能的优化

​ 因为v-for数据项的顺序改变,Vue 也不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素

​ :key如果是索引, 因为索引是连续的, 如果删除其中某一个, 会导致最后一个被删除

​ 当我们再删除的时候, :key再根据数据来把新旧的dom对比时, 删除:key不存在的对应的标签(添加也是一样的插入到指定位置, 别的都不会动)

3. 数组更新有的时候v-for不渲染

​ 因为vue内部只能监测到数组顺序/位置的改变/数量的改变, 但是值被重新赋予监测不到变更, 可以用 Vue.set() / vm.$set()

vue组件

为什么用组件

以前做过一个折叠面板

image-20210115092834016

需求: 现在想要多个收起展开的部分

方案1: 复制代码

  • 代码重复 冗余
  • 不利于维护
  1. 案例用less写的样式, 所以下载
yarn add less less-loader@5.0.0 -D
  1. 模板标签 - 在这个基础上, 把要复用的多复制几份(讲解不好的地方引出解决方案)
<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>

  1. 上面复制3份, 发现变化一起变化

    解决方案: 不同的部分, 用不同的isShow变量

<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>
      <div class="title">
        <h4>芙蓉楼送辛渐</h4>
        <span class="btn" @click="isShow1 = !isShow1">
          {{ isShow1 ? '收起' : '展开' }}
        </span>
      </div>
      <div class="container" v-show="isShow1">
        <p>寒雨连江夜入吴, </p>
        <p>平明送客楚山孤。</p>
        <p>洛阳亲友如相问,</p>
        <p>一片冰心在玉壶。</p>
      </div>
    </div>
    <div>
      <div class="title">
        <h4>芙蓉楼送辛渐</h4>
        <span class="btn" @click="isShow2 = !isShow2">
          {{ isShow2 ? '收起' : '展开' }}
        </span>
      </div>
      <div class="container" v-show="isShow2">
        <p>寒雨连江夜入吴, </p>
        <p>平明送客楚山孤。</p>
        <p>洛阳亲友如相问,</p>
        <p>一片冰心在玉壶。</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: false,
      isShow1: false,
      isShow2: 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>

总结: 代码非常的冗余和重复吧? 解决方案呢? 就是采用我们的组件化开发的方式, 往下看

组件_概念

组件是可复用的 Vue 实例, 封装标签, 样式和JS代码

组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护

一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)

image-20210216114452712

vue组件_基础使用

目标: 每个组件都是一个独立的个体, 代码里体现为一个独立的.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>
  1. 注册组件: 创建后需要注册后再使用

全局 - 注册使用

全局入口在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>

组件使用总结:

  1. (创建)封装html+css+vue到独立的.vue文件中
  2. (引入注册)组件文件 => 得到组件配置对象
  3. (使用)当前页面当做标签使用

vue组件-scoped作用

目的: 解决多个组件样式名相同, 冲突问题

需求: div标签名选择器, 设置背景色

问题: 发现组件里的div和外面的div都生效了

解决: 给Pannel.vue组件里style标签上加scoped属性即可

<style scoped>

在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v开头的属性

而且必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

image-20210216122749906

总结: style上加scoped, 组件内的样式只在当前vue组件生效

vue组件通信

因为每个组件的变量和值都是独立的

组件通信先暂时关注父传子, 子传父

父: 使用其他组件的vue文件

子: 被引入的组件(嵌入)

例如: App.vue(父) MyProduct.vue(子)

vue组件通信_父向子-props

目的: 从外面给组件内传值, 先学会语法, 练习中在看使用场景

需求: 封装一个商品组件MyProduct.vue - 外部传入具体要显示的数据, 如下图所示

image-20210305201956669

步骤:

  1. 创建组件components/MyProduct.vue - 复制下面标签

  2. 组件内在props定义变量, 用于接收外部传入的值

  3. App.vue中引入注册组件, 使用时, 传入具体数据给组件显示

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>

总结: 组件封装复用的标签和样式, 而具体数据要靠外面传入

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>

单向数据流

在vue中需要遵循单向数据流原则

1. 父组件的数据发生了改变,子组件会自动跟着变
2. 子组件不能直接修改父组件传递过来的props  props是只读的

父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,对象是引用类型, 互相更新

image-20210423161646951

总结: props的值不能重新赋值, 对象引用关系属性值改变, 互相影响

vue组件通信_单向数据流

目标: props变量本身是只读不能重新赋值

目标:从父到子的数据流向,叫单向数据流

原因: 子组件修改, 不通知父级, 造成数据不一致性

如果第一个MyProduct.vue内自己修改商品价格为5.5, 但是App.vue里原来还记着18.8 - 数据 不一致了

所以: Vue规定props里的变量, 本身是只读

image-20210511143218215

总结: 所以props变量本身是不能重新赋值的

问题: 那我怎么才能修改子组件接收到的值呢? - 其实要影响父亲, 然后数据响应式来影响儿子们

vue组件通信_子向父

目标: 从子组件把值传出来给外面使用

需求: 课上例子, 砍价功能, 子组件点击实现随机砍价-1功能

image-20210307134253897

语法:

  • 父: @自定义事件名="父methods函数"
  • 子: this.$emit("自定义事件名", 传值) - 执行父methods里函数代码

image-20210217102551882

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>

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>

总结: 父自定义事件和方法, 等待子组件触发事件给方法传值

阶段小结

目标: 总结父子组件关系-通信技术口诀

组件是什么?

  • 是一个vue实例, 封装标签, 样式和JS代码

组件好处?

  • 便于复用, 易于扩展

组件通信哪几种, 具体如何实现?

  • 父 -> 子

  • 父 <- 子

vue组件通信-EventBus

目标: 常用于跨组件通信时使用

两个组件的关系非常的复杂,通过父子组件通讯是非常麻烦的。这时候可以使用通用的组件通讯方案:事件总线(event-bus)

image-20210416122123301

核心语法

EventBus/index.js- 定义事件总线bus对象

import Vue from 'vue'
// 导出空白vue对象
export default new Vue()

List.vue注册事件 - 等待接收要砍价的值 (直接复制) - 准备兄弟页面

<template>
  <ul class="my-product">
      <li v-for="(item, index) in arr" :key="index">
          <span>{{ item.proname }}</span>
          <span>{{ item.proprice }}</span>
      </li>
  </ul>
</template>

<script>
export default {
  props: ['arr'],
}
</script>

<style>
.my-product {
  width: 400px;
  padding: 20px;
  border: 2px solid #000;
  border-radius: 5px;
  margin: 10px;
}
</style>

components/MyProduct_sub.vue(带学生主要写触发eventBus身上事件)

<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>

List.vue正确代码(EventBus接收方)

<template>
  <ul class="my-product">
    <li v-for="(item, index) in arr" :key="index">
      <span>{{ item.proname }}</span>
      <span>{{ item.proprice }}</span>
    </li>
  </ul>
</template>

<script>
// 目标: 跨组件传值
// 1. 引入空白vue对象(EventBus)
// 2. 接收方 - $on监听事件
import eventBus from "../EventBus";
export default {
  props: ["arr"],
  // 3. 组件创建完毕, 监听send事件
  created() {
    eventBus.$on("send", (index, price) => {
      this.arr[index].proprice > 1 &&
        (this.arr[index].proprice = (this.arr[index].proprice - price).toFixed(2));
    });
  },
};
</script>

<style>
.my-product {
  width: 400px;
  padding: 20px;
  border: 2px solid #000;
  border-radius: 5px;
  margin: 10px;
}
</style>

总结: 空的Vue对象, 只负责$on注册事件, $emit触发事件, 一定要确保$on先执行

今日总结

面试题

1. 请说下封装 vue 组件的过程

​ 首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。

  • 分析需求:确定业务需求,把页面中可以复用的结构,样式以及功能,单独抽离成一个组件,实现复用

  • 具体步骤:Vue.component 或者在new Vue配置项components中, 定义组件名, 可以在props中接受给组件传的参数和值,子组件修改好数据后,想把数据传递给父组件。可以采用$emit方法。

2. Vue组件如何进行传值的

父向子 -> props定义变量 -> 父在使用组件用属性给props变量传值

子向父 -> $emit触发父的事件 -> 父在使用组件用@自定义事件名=父的方法 (子把值带出来)

3. Vue 组件 data 为什么必须是函数

每个组件都是 Vue 的实例, 为了独立作用域, 不让变量污染别人的变量

4. 讲一下组件的命名规范

​ 给组件命名有两种方式(在Vue.Component/components时),一种是使用链式命名"my-component",一种是使用大驼峰命名"MyComponent",

​ 因为要遵循W3C规范中的自定义组件名 (字母全小写且必须包含一个连字符),避免和当前以及未来的 HTML 元素相冲突

vue生命周期

人的-生命周期

一组件从 创建 到 销毁 的整个过程就是生命周期

image-20210111193143574

Vue_生命周期

image-20210511152835915

钩子函数

目标: Vue 框架内置函数,随着组件的生命周期阶段,自动执行

作用: 特定的时间点,执行特定的操作

场景: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据

分类: 4大阶段8个方法

  • 初始化
  • 挂载
  • 更新
  • 销毁
阶段 方法名 方法名
初始化 beforeCreate created
挂载 beforeMount mounted
更新 beforeUpdate updated
销毁 beforeDestroy destroyed

官网文档

下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

Day03

初始化阶段

目标: 掌握初始化阶段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选项

image-20210511153050932

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>

挂载阶段

目标: 掌握挂载阶段2个钩子函数作用和执行时机

含义讲解:

1.template选项检查

​ 有 - 编译template返回render渲染函数

​ 无 – 编译el选项对应标签作为template(要渲染的模板)

2.虚拟DOM挂载成真实DOM之前

3.beforeMount – 生命周期钩子函数被执行

4.Create … – 把虚拟DOM和渲染的数据一并挂到真实DOM上

5.真实DOM挂载完毕

6.mounted – 生命周期钩子函数被执行

image-20210511153649298

components/Life.vue - 创建一个文件

<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>

更新阶段

目标: 掌握更新阶段2个钩子函数作用和执行时机

含义讲解:

1.当data里数据改变, 更新DOM之前

2.beforeUpdate – 生命周期钩子函数被执行

3.Virtual DOM…… – 虚拟DOM重新渲染, 打补丁到真实DOM

4.updated – 生命周期钩子函数被执行

5.当有data数据改变 – 重复这个循环

image-20210511154016777

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>

销毁阶段

目标: 掌握销毁阶段2个钩子函数作用和执行时机

含义讲解:

1.当$destroy()被调用 – 比如组件DOM被移除(例v-if)

2.beforeDestroy – 生命周期钩子函数被执行

3.拆卸数据监视器、子组件和事件侦听器

4.实例销毁后, 最后触发一个钩子函数

5.destroyed – 生命周期钩子函数被执行

image-20210511154330252

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>

axios

axios基本使用

axios文档

特点

  • 支持客户端发送Ajax请求
  • 支持服务端Node.js发送请求
  • 支持Promise相关用法
  • 支持请求和响应的拦截器功能
  • 自动转换JSON数据
  • axios 底层还是原生js实现, 内部通过Promise封装的

axios的基本使用

axios({
  method: '请求方式', // get post
  url: '请求地址',
  data: {    // 拼接到请求体的参数,  post请求的参数
    xxx: xxx,
  },
  params: {  // 拼接到请求行的参数, get请求的参数
   	xxx: xxx 
  }
}).then(res => {
  console.log(res.data) // 后台返回的结果
}).catch(err => {
  console.log(err) // 后台报错返回
})

axios基本使用-获取数据

目标: 调用文档最后_获取所有图书信息接口

功能: 点击调用后台接口, 拿到所有数据 – 打印到控制台

接口: 参考预习资料.md – 接口文档

引入: 下载axios, 引入后才能使用

效果:

image-20210511154911824

例子如下:

components/UseAxios.vue

<template>
  <div>
    <p>1. 获取所有图书信息</p>
    <button @click="getAllFn">点击-查看控制台</button>
  </div>
</template>

<script>
// 目标1: 获取所有图书信息
// 1. 下载axios
// 2. 引入axios
// 3. 发起axios请求
import axios from "axios";
export default {
  methods: {
    getAllFn() {
      axios({
        url: "http://123.57.109.30:3006/api/getbooks",
        method: "GET", // 默认就是GET方式请求, 可以省略不写
      }).then((res) => {
        console.log(res);
      });
      // axios()-原地得到Promise对象
    },
  }
};
</script>

axios基本使用-传参

目标: 调用接口-获取某本书籍信息

功能: 点击调用后台接口, 查询用户想要的书籍信息 – 打印到控制台

接口: 参考预习资料.md – 接口文档

效果:

image-20210511160538891

例子如下:

components/UseAxios.vue

<template>
  <div>
    <p>2. 查询某本书籍信息</p>
    <input type="text" placeholder="请输入要查询 的书名" v-model="bName" />
    <button @click="findFn">查询</button>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      bName: ""
    };
  },
  methods: {
    // ...省略了查询所有的代码
    findFn() {
      axios({
        url: "/api/getbooks",
        method: "GET",
        params: { // 都会axios最终拼接到url?后面
            bookname: this.bName
        }
      }).then(res => {
          console.log(res);
      })
    }
  },
};
</script>

axios基本使用-发布书籍

目标: 完成发布书籍功能

功能: 点击新增按钮, 把用户输入的书籍信息, 传递给后台 – 把结果打印在控制台

接口: 参考预习资料.md – 接口文档

效果:

image-20210511161239034

例子如下:

components/UseAxios.vue

<template>
  <div>
    <p>3. 新增图书信息</p>
    <div>
        <input type="text" placeholder="书名" v-model="bookObj.bookname">
    </div>
    <div>
        <input type="text" placeholder="作者" v-model="bookObj.author">
    </div>
    <div>
        <input type="text" placeholder="出版社" v-model="bookObj.publisher">
    </div>
    <button @click="sendFn">发布</button>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      bName: "",
      bookObj: { // 参数名提前和后台的参数名对上-发送请求就不用再次对接了
          bookname: "",
          author: "",
          publisher: ""
      }
    };
  },
  methods: {
    // ...省略了其他代码
    sendFn(){
       axios({
           url: "/api/addbook",
           method: "POST",
           data: {
               appkey: "7250d3eb-18e1-41bc-8bb2-11483665535a",
               ...this.bookObj
            // 等同于下面
            // bookname: this.bookObj.bookname,
            // author: this.bookObj.author,
            // publisher: this.bookObj.publisher
           }
       }) 
    }
  },
};
</script>

axios基本使用-全局配置

目标: 避免前缀基地址, 暴露在逻辑页面里, 统一设置

axios.defaults.baseURL = "http://123.57.109.30:3006"

// 所有请求的url前置可以去掉, 请求时, axios会自动拼接baseURL的地址在前面
getAllFn() {
    axios({
        url: "/api/getbooks",
        method: "GET", // 默认就是GET方式请求, 可以省略不写
    }).then((res) => {
        console.log(res);
    });
    // axios()-原地得到Promise对象
},

$nextTick和$refs知识

$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标签

$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.值 来获取组件对象, 就能继续调用组件内的变量

$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更新是异步的

$nextTick使用场景

目标: 点击搜索按钮, 弹出聚焦的输入框, 按钮消失

$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>

组件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>

今日总结

vue的生命周期哪4个阶段, 哪8个方法

axios是什么, 底层是什么, 具体如何使用

axios返回的是什么, 如何接收结果

知道ref和$refs使用和作用以及场景

知道$nextTick的作用

跟着老师的视频完成购物车案例

面试题

1、Vue 的 nextTick 的原理是什么? (高薪常问)

​ \1. 为什么需要 nextTick ,Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改--刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)这个 api 了。

​ \2. 理解原理前的准备 首先需要知道事件循环中宏任务和微任务这两个概念,常见的宏任务有 script, setTimeout, setInterval, setImmediate, I/O, UI rendering 常见的微任务有 process.nextTick(Nodejs),Promise.then(), MutationObserver;

​ \3. 理解 nextTick 的原理正是 vue 通过异步队列控制 DOM 更新和 nextTick 回调函数先后执行的方式。如果大家看过这部分的源码,会发现其中做了很多 isNative()的判断,因为这里还存在兼容性优雅降级的问题。可见 Vue 开发团队的深思熟虑,对性能的良苦用心。

2、vue生命周期总共分为几个阶段?(必会)

Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

1****)beforeCreate

​ 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

2****)created

​ 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer), 属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

3****)beforeMount

​ 在挂载开始之前被调用:相关的 render 函数首次被调用。

4****)mounted

​ el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。

5****)beforeUpdate

​ 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。

6****)updated

​ 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

7****)activated

​ keep-alive 组件激活时调用。该钩子在服务器端渲染期间不被调用。

8****)deactivated

​ keep-alive 组件停用时调用。该钩子在服务器端渲染期间不被调用。

9****)beforeDestroy

​ 实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。

10****)destroyed

​ Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

11****)errorCaptured(2.5.0+ 新增)

​ 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

3、第一次加载页面会触发哪几个钩子函数?(必会)

当页面第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子函数

组件进阶

组件进阶 - 动态组件

目标: 多个组件使用同一个挂载点,并动态切换,这就是动态组件

需求: 完成一个注册功能页面, 2个按钮切换, 一个填写注册信息, 一个填写用户简介信息

效果如下:

动态组件

  1. 准备被切换的 - UserName.vue / UserInfo.vue 2个组件

  2. 引入到UseDynamic.vue注册

  3. 准备变量来承载要显示的"组件名"

  4. 设置挂载点, 使用is属性来设置要显示哪个组件

  5. 点击按钮 – 修改comName变量里的"组件名"

<template>
  <div>
      <button @click="comName = 'UserName'">账号密码填写</button>
      <button @click="comName = 'UserInfo'">个人信息填写</button>

      <p>下面显示注册组件-动态切换:</p>
      <div style="border: 1px solid red;">
          <component :is="comName"></component>
      </div>
  </div>
</template>

<script>
// 目标: 动态组件 - 切换组件显示
// 场景: 同一个挂载点要切换 不同组件 显示
// 1. 创建要被切换的组件 - 标签+样式
// 2. 引入到要展示的vue文件内, 注册
// 3. 变量-承载要显示的组件名
// 4. 设置挂载点<component :is="变量"></component>
// 5. 点击按钮-切换comName的值为要显示的组件名

import UserName from '../components/01/UserName'
import UserInfo from '../components/01/UserInfo'
export default {
    data(){
        return {
            comName: "UserName"
        }
    },
    components: {
        UserName,
        UserInfo
    }
}
</script>

在App.vue - 引入01_UseDynamic.vue并使用显示

总结: vue内置component组件, 配合is属性, 设置要显示的组件名字

组件进阶 - 组件缓存

目标: 组件切换会导致组件被频繁销毁和重新创建, 性能不高

使用Vue内置的keep-alive组件, 可以让包裹的组件保存在内存中不被销毁

演示1: 可以先给UserName.vue和UserInfo.vue 注册created和destroyed生命周期事件, 观察创建和销毁过程

演示2: 使用keep-alive内置的vue组件, 让动态组件缓存而不是销毁

语法:

​ Vue内置的keep-alive组件 包起来要频繁切换的组件

02_UseDynamic.vue

<div style="border: 1px solid red;">
    <!-- Vue内置keep-alive组件, 把包起来的组件缓存起来 -->
    <keep-alive>
        <component :is="comName"></component>
    </keep-alive>
</div>

补充生命周期:

  • activated - 激活
  • deactivated - 失去激活状态

总结: keep-alive可以提高组件的性能, 内部包裹的标签不会被销毁和重新创建, 触发激活和非激活的生命周期方法

组件进阶 - 激活和非激活

目标: 被缓存的组件不再创建和销毁, 而是激活和非激活

补充2个钩子方法名:

​ activated – 激活时触发

​ deactivated – 失去激活状态触发

组件进阶 - 组件插槽

目标: 用于实现组件的内容分发, 通过 slot 标签, 可以接收到写在组件标签内的内容

vue提供组件插槽能力, 允许开发者在封装组件时,把不确定的部分定义为插槽

插槽例子:

1ddad96d-f925-452b-8c40-85288fc2cbc4

需求: 以前折叠面板案例, 想要实现不同内容显示, 我们把折叠面板里的Pannel组件, 添加组件插槽方式

image-20210307110014556

语法口诀:

  1. 组件内用占位
  2. 使用组件时夹着的地方, 传入标签替换slot

03/Pannel.vue - 组件(直接复制)

<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>
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;
}

img {
  width: 50%;
}
</style>

views/03_UserSlot.vue - 使用组件(直接复制)

框: 在这个基础重复使用组件

<template>
  <div id="container">
    <div id="app">
      <h3>案例:折叠面板</h3>
    </div>
  </div>
</template>

<script>
export default {
};
</script>

<style>
#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;
}
</style>

views/03_UseSlot.vue - 组件插槽使用

<template>
  <div id="container">
    <div id="app">
      <h3>案例:折叠面板</h3>
      <Pannel>
          <img src="../assets/mm.gif" alt="">
          <span>我是内容</span>
      </Pannel>
      <Pannel>
          <p>寒雨连江夜入吴,</p>
          <p>平明送客楚山孤。</p>
          <p>洛阳亲友如相问,</p>
          <p>一片冰心在玉壶。</p>
      </Pannel>
      <Pannel></Pannel>
    </div>
  </div>
</template>

<script>
import Pannel from "../components/03/Pannel";
export default {
  components: {
    Pannel,
  },
};
</script>

总结: 组件内容分发技术, slot占位, 使用组件时传入替换slot位置的标签

组件进阶 - 插槽默认内容

目标: 如果外面不给传, 想给个默认显示内容

口诀: 夹着内容默认显示内容, 如果不给插槽slot传东西, 则使用夹着的内容在原地显示

<slot>默认内容</slot>

组件进阶 - 具名插槽

目标: 当一个组件内有2处以上需要外部传入标签的地方

传入的标签可以分别派发给不同的slot位置

要求: v-slot一般用跟template标签使用 (template是html5新出标签内容模板元素, 不会渲染到页面上, 一般被vue解析内部标签)

components/04/Pannel.vue - 留下具名slot

<template>
  <div>
    <!-- 按钮标题 -->
    <div class="title">
      <slot name="title"></slot>
      <span class="btn" @click="isShow = !isShow">
        {{ isShow ? "收起" : "展开" }}
      </span>
    </div>
    <!-- 下拉内容 -->
    <div class="container" v-show="isShow">
     <slot name="content"></slot>
    </div>
  </div>
</template>

views/04_UseSlot.vue使用

<template>
  <div id="container">
    <div id="app">
      <h3>案例:折叠面板</h3>
      <Pannel>
        <template v-slot:title>
          <h4>芙蓉楼送辛渐</h4>
        </template>
        <template v-slot:content>
          <img src="../assets/mm.gif" alt="">
          <span>我是内容</span>
        </template>
      </Pannel>
      <Pannel>
        <template #title>
          <span style="color: red;">我是标题</span>
        </template>
        <template #content>
          <p>寒雨连江夜入吴,</p>
          <p>平明送客楚山孤。</p>
          <p>洛阳亲友如相问,</p>
          <p>一片冰心在玉壶。</p>
        </template>
      </Pannel>
    </div>
  </div>
</template>

<script>
import Pannel from "../components/04/Pannel";
export default {
  components: {
    Pannel,
  },
};
</script>

v-slot可以简化成#使用

v-bind可以省略成: v-on: 可以省略成@ 那么v-slot: 可以简化成#

总结: slot的name属性起插槽名, 使用组件时, template配合#插槽名传入具体标签

组件进阶 - 作用域插槽

目标: 子组件里值, 在给插槽赋值时在父组件环境下使用

复习: 插槽内slot中显示默认内容

例子: 默认内容在子组件中, 但是父亲在给插槽传值, 想要改变插槽显示的默认内容

口诀:

  1. 子组件, 在slot上绑定属性和子组件内的值
  2. 使用组件, 传入自定义标签, 用template和v-slot="自定义变量名"
  3. scope变量名自动绑定slot上所有属性和值

components/05/Pannel.vue - 定义组件, 和具名插槽, 给slot绑定属性和值

<template>
  <div>
    <!-- 按钮标题 -->
    <div class="title">
      <h4>芙蓉楼送辛渐</h4>
      <span class="btn" @click="isShow = !isShow">
        {{ isShow ? "收起" : "展开" }}
      </span>
    </div>
    <!-- 下拉内容 -->
    <div class="container" v-show="isShow">
     <slot :row="defaultObj">{{ defaultObj.defaultOne }}</slot>
    </div>
  </div>
</template>

<script>
// 目标: 作用域插槽
// 场景: 使用插槽, 使用组件内的变量
// 1. slot标签, 自定义属性和内变量关联
// 2. 使用组件, template配合v-slot="变量名"
// 变量名会收集slot身上属性和值形成对象
export default {
  data() {
    return {
      isShow: false,
      defaultObj: {
        defaultOne: "无名氏",
        defaultTwo: "小传同学"
      }
    };
  },
};
</script>

views/05_UseSlot.vue

<template>
  <div id="container">
    <div id="app">
      <h3>案例:折叠面板</h3>
      <Pannel>
        <!-- 需求: 插槽时, 使用组件内变量 -->
        <!-- scope变量: {row: defaultObj} -->
        <template v-slot="scope">
          <p>{{ scope.row.defaultTwo }}</p>
        </template>
      </Pannel>
    </div>
  </div>
</template>

<script>
import Pannel from "../components/05/Pannel";
export default {
  components: {
    Pannel,
  },
};
</script>

总结: 组件内变量绑定在slot上, 然后使用组件v-slot="变量" 变量上就会绑定slot身上属性和值

组件进阶 - 作用域插槽使用场景

目标: 了解作用域插槽使用场景, 自定义组件内标签+内容

案例: 封装一个表格组件, 在表格组件内循环产生单元格

准备MyTable.vue组件 – 内置表格, 传入数组循环铺设页面, 把对象每个内容显示在单元格里

准备UseTable.vue – 准备数据传入给MyTable.vue使用

components/06/MyTable.vue - 模板(直接复制)

<template>
  <div>
      <table border="1">
          <thead>
              <tr>
                  <th>序号</th>
                  <th>姓名</th>
                  <th>年龄</th>
                  <th>头像</th>
              </tr>
          </thead>
          <thead>
              <tr>
                  <td></td>
                  <td></td>
                  <td></td>
                  <td></td>
              </tr>
          </thead>
      </table>
  </div>
</template>

<script>
export default {

}
</script>

views/06_UseTable.vue - 准备数据, 传入给MyTable.vue组件里循环使用

list: [
    {
        name: "小传同学",
        age: 18,
        headImgUrl:
        "http://yun.itheima.com/Upload/./images/20210303/603f2d2153241.jpg",
    },
    {
        name: "小黑同学",
        age: 25,
        headImgUrl:
        "http://yun.itheima.com/Upload/./images/20210304/6040b101a18ef.jpg",
    },
    {
        name: "智慧同学",
        age: 21,
        headImgUrl:
        "http://yun.itheima.com/Upload/./images/20210302/603e0142e535f.jpg",
    },
],

例子: 我想要给td内显示图片, 需要传入自定义的img标签

image-20210511170436114

正确做法:

​ 在MyTable.vue的td中准备占位, 但是外面需要把图片地址赋予给src属性,所以在slot上把obj数据绑定

components/06/MyTable.vue - 正确代码

<template>
  <div>
      <table border="1">
          <thead>
              <tr>
                  <th>序号</th>
                  <th>姓名</th>
                  <th>年龄</th>
                  <th>头像</th>
              </tr>
          </thead>
          <tbody>
              <tr v-for="(obj, index) in arr" :key="index">
                  <td>{{ index + 1 }}</td>
                  <td>{{ obj.name }}</td>
                  <td>{{ obj.age }}</td>
                  <td>
                      <slot :row="obj">
                          <!-- 默认值给上,如果使用组件不自定义标签显示默认文字 -->
                          {{ obj.headImgUrl}}
                      </slot>
                  </td>
              </tr>
          </tbody>
      </table>
  </div>
</template>

<script>
export default {
    props: {
        arr: Array
    }
}
</script>

​ 在UseTable使用MyTable的时候, template上v-slot绑定变量, 传入img组件设置图片地址

<template>
  <div>
    <MyTable :arr="list"></MyTable>
    <MyTable :arr="list">
        <!-- scope: {row: obj} -->
       <template v-slot="scope">
            <a :href="scope.row.headImgUrl">{{ scope.row.headImgUrl }}</a>
       </template>
    </MyTable>
    <MyTable :arr="list">
       <template v-slot="scope">
            <img style="width: 100px;" :src="scope.row.headImgUrl" alt="">
       </template>
    </MyTable>
  </div>
</template>

<script>
import MyTable from "../components/06/MyTable";
export default {
  components: {
    MyTable,
  },
  data() {
    return {
      list: [
        {
          name: "小传同学",
          age: 18,
          headImgUrl:
            "http://yun.itheima.com/Upload/./Vue前端框架.assets/20210303/603f2d2153241.jpg",
        },
        {
          name: "小黑同学",
          age: 25,
          headImgUrl:
            "http://yun.itheima.com/Upload/./Vue前端框架.assets/20210304/6040b101a18ef.jpg",
        },
        {
          name: "智慧同学",
          age: 21,
          headImgUrl:
            "http://yun.itheima.com/Upload/./Vue前端框架.assets/20210302/603e0142e535f.jpg",
        },
      ],
    };
  },
};
</script>

<style>
</style>

总结: 插槽可以自定义标签, 作用域插槽可以把组件内的值取出来自定义内容

自定义指令

自定义指令文档

除了核心功能默认内置的指令 (v-modelv-show),Vue 也允许注册自定义指令。 v-xxx

html+css的复用的主要形式是组件

你需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令

自定义指令-注册

目标: 获取标签, 扩展额外的功能

局部注册和使用

07_UseDirective.vue - 只能在当前组件.vue文件中使用

<template>
  <div>
      <!-- <input type="text" v-gfocus> -->
      <input type="text" v-focus>
      
  </div>
</template>

<script>
// 目标: 创建 "自定义指令", 让输入框自动聚焦
// 1. 创建自定义指令
// 全局 / 局部
// 2. 在标签上使用自定义指令  v-指令名
// 注意:
// inserted方法 - 指令所在标签, 被插入到网页上触发(一次)
// update方法 - 指令对应数据/标签更新时, 此方法执行
export default {
    data(){
        return {
            colorStr: 'red'
        }
    },
    directives: {
        focus: {
            inserted(el){
                el.focus()
            }
        }
    }
}
</script>

<style>

</style>

全局注册

在main.js用 Vue.directive()方法来进行注册, 以后随便哪个.vue文件里都可以直接用v-fofo指令

// 全局指令 - 到处"直接"使用
Vue.directive("gfocus", {
  inserted(el) {
    el.focus() // 触发标签的事件方法
  }
})

总结: 全局注册自定义指令, 哪里都能用, 局部注册, 只能在当前vue文件里用

自定义指令-传值

目标: 使用自定义指令, 传入一个值

需求: 定义color指令-传入一个颜色, 给标签设置文字颜色

main.js定义处修改一下

// 目标: 自定义指令传值
Vue.directive('color', {
  inserted(el, binding) {
    el.style.color = binding.value
  },
  update(el, binding) {
    el.style.color = binding.value
  }
})

Direct.vue处更改一下

<p v-color="colorStr" @click="changeColor">修改文字颜色</p>

<script>
  data() {
    return {
      theColor: "red",
    };
  },
  methods: {
    changeColor() {
      this.theColor = 'blue';
    },
  },
</script>

总结: v-xxx, 自定义指令, 获取原生DOM, 自定义操作

今日总结

  1. 动态组件的使用步骤
  2. 组件缓存使用步骤和作用
  3. 组件插槽默认使用
  4. 插槽默认显示的内容
  5. 多个插槽时, 具名插槽如何使用
  6. 作用域插槽如何使用以及目的
  7. 自定义命令如何使用
  8. 跟随视频完成tabbar案例

面试题

1. vue中solt的使用方式,以及solt作用域插槽的用法

使用方式:当组件当做标签进行使用的时候,用slot可以用来接受组件标签包裹的内容,当给solt标签添加name属性的 时候,可以调换响应的位置
(高级用法) 插槽作用域: 当传递的不是单一的标签, 例如需要循环时, 把要循环的标签传入, 组件内使用v-for在slot标签上, 内部可以v-bind:把值传出来, 再外面把值赋予进去, 看示例

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

// current-user组件, user属性和值, 绑定给slotProps上
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

扩展阅读: https://cn.vuejs.org/v2/guide/components-slots.html (了解即可, 一般用不上)

2. 跟keep-alive有关的生命周期是哪些?(必会)

1****)前言:在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染,在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染

2****)生命周期函数:在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated。

1****、activated钩子:在在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。

2****、Activated钩子调用时机: 第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用,并且给因为组件被缓存了,再次进入缓存路由、组件时,不会触发这些钩子函数,beforeCreate created beforeMount mounted 都不会触发

1****、deactivated钩子:组件被停用(离开路由)时调用。

2****、deactivated钩子调用时机:使用keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了,这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,可以放在这个钩子里,组件内的离开当前路由钩子beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)

3. 自定义指令(v-check、v-focus)的方法有哪些?它有哪些钩子函数?还有哪些钩子函数参数?(必会)

​ 全局定义指令:在vue对象的directive方法里面有两个参数,一个是指令名称,另外一个是函数。组件内定义指令:directives

​ 钩子函数:bind(绑定事件触发)、inserted(节点插入的时候触发)、update(组件内相关更新)

​ 钩子函数参数:el、binding

4. is这个特性你有用过吗?主要用在哪些方面?(高薪常问)

1 动态组件

, componentName可以是在本页面已经注册的局部组件名和全局组件名,也可以是一个组件的选项对象。 当控制componentName改变时就可以动态切换选择组件。

2 is的用法


​    有些HTML元素,诸如 <ul>、<ol>、<table>和<select>,对于哪些元素可以出现在其内部是有严格限制的。

​    而有些HTML元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。

​    <ul>

​      <card-list></card-list>

​    </ul>

​    所以上面<card-list></card-list>会被作为无效的内容提升到外部,并导致最终渲染结果出错。应该这么写:

​    <ul>

​      <li is="cardList"></li>

​     </ul>

vue路由简介和基础使用

什么是路由

目标: 设备和ip的映射关系

image-20210512095742920

目标: 接口和服务的映射关系

image-20210512095805578

目标: 路径和组件的映射关系

image-20210512095825389

为什么使用路由

目标: 在一个页面里, 切换业务场景

具体使用示例: 网易云音乐 https://music.163.com/

单页面应用(SPA): 所有功能在一个html页面上实现

前端路由作用: 实现业务场景切换

优点:

  • 整体不刷新页面,用户体验更好

  • 数据传递容易, 开发效率高

缺点:

  • 开发成本高(需要学习专门知识)

  • 首次加载会比较慢一点。不利于seo

vue-router介绍

目标: 如何在Vue项目中集成路由

官网: https://router.vuejs.org/zh/

vue-router模块包

它和 Vue.js 深度集成

可以定义 - 视图表(映射规则)

模块化的

提供2个内置全局组件

声明式导航自动激活的 CSS class 的链接

……

路由 - 组件分类

目标: .vue文件分2类, 一个是页面组件, 一个是复用组件

.vue文件本质无区别, 方便大家学习和理解, 总结的一个经验

src/views(或pages) 文件夹 和 src/components文件夹

  • 页面组件 - 页面展示 - 配合路由用
  • 复用组件 - 展示数据/常用于复用

image-20210424215930981

总结: views下的页面组件, 配合路由切换, components下的一般引入到views下的vue中复用展示数据

vue-router使用

目标: 学会vue官方提供的vue-router路由系统功能模块使用

App.vue - 页面标签和样式准备(可复制继续写)

<template>
  <div>
    <div class="footer_wrap">
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/part">朋友</a>
    </div>
    <div class="top">
      
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.footer_wrap {
  position: fixed;
  left: 0;
  top: 0;
  display: flex;
  width: 100%;
  text-align: center;
  background-color: #333;
  color: #ccc;
}
.footer_wrap a {
  flex: 1;
  text-decoration: none;
  padding: 20px 0;
  line-height: 20px;
  background-color: #333;
  color: #ccc;
  border: 1px solid black;
}
.footer_wrap a:hover {
  background-color: #555;
}
.top {
  padding-top: 62px;
}
</style>

vue-router文档

  • 安装
yarn add vue-router
  • 导入路由
import VueRouter from 'vue-router'
  • 使用路由插件
// 在vue中,使用使用vue的插件,都需要调用Vue.use()
Vue.use(VueRouter)
  • 创建路由规则数组
const routes = [
  {
    path: "/find",
    component: Find
  },
  {
    path: "/my",
    component: My
  },
  {
    path: "/part",
    component: Part
  }
]
  • 创建路由对象 - 传入规则
const router = new VueRouter({
  routes
})
  • 关联到vue实例
new Vue({
  router
})
  • components换成router-view
<router-view></router-view>

总结: 下载路由模块, 编写对应规则注入到vue实例上, 使用router-view挂载点显示切换的路由

总结2: 一切都围绕着hash值变化为准

vue路由 - 声明式导航

声明式导航 - 基础使用

目标: 可用全局组件router-link来替代a标签

  1. vue-router提供了一个全局组件 router-link
  2. router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
  3. router-link提供了声明式导航高亮的功能(自带类名)
<template>
  <div>
    <div class="footer_wrap">
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/part">朋友</router-link>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
/* 省略了 其他样式 */
.footer_wrap .router-link-active{
  color: white;
  background: black;
}
</style>

总结: 链接导航, 用router-link配合to, 实现点击切换路由

声明式导航 - 跳转传参

目标: 在跳转路由时, 可以给路由对应的组件内传值

在router-link上的to属性传值, 语法格式如下

  • /path?参数名=值

  • /path/值 – 需要路由对象提前配置 path: “/path/参数名”

对应页面组件接收传递过来的值

  • $route.query.参数名

  • $route.params.参数名

  1. 创建components/Part.vue - 准备接收路由上传递的参数和值

    <template>
      <div>
          <p>关注明星</p>
          <p>发现精彩</p>
          <p>寻找伙伴</p>
          <p>加入我们</p>
          <p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p>
      </div>
    </template>
    
  2. 路由定义

    {
        path: "/part",
        component: Part
      },
      {
        path: "/part/:username", // 有:的路径代表要接收具体的值
        component: Part
      },
    
  3. 导航跳转, 传值给MyGoods.vue组件

    <router-link to="/part?name=小传">朋友-小传</router-link>
    <router-link to="/part/小智">朋友-小智</router-link>
    

总结:

?key=value 用$route.query.key 取值

/值 提前在路由规则/path/:key 用$route.params.key 取值

vue路由 - 重定向和模式

路由 - 重定向

目标: 匹配path后, 强制切换到目标path上

  • 网页打开url默认hash值是/路径
  • redirect是设置要重定向到哪个路由路径

例如: 网页默认打开, 匹配路由"/", 强制切换到"/find"上

const routes = [
  {
    path: "/", // 默认hash值路径
    redirect: "/find" // 重定向到/find
    // 浏览器url中#后的路径被改变成/find-重新匹配数组规则
  }
]

总结: 强制重定向后, 还会重新来数组里匹配一次规则

路由 - 404页面

目标: 如果路由hash值, 没有和数组里规则匹配

默认给一个404页面

语法: 路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应组件页面

  1. 创建NotFound页面

    <template>
      <img src="../assets/404.png" alt="">
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style scoped>
        img{
            width: 100%;
        }
    </style>
    
  2. 在main.js - 修改路由配置

    import NotFound from '@/views/NotFound'
    
    const routes = [
      // ...省略了其他配置
      // 404在最后(规则是从前往后逐个比较path)
      {
        path: "*",
        component: NotFound
      }
    ]
    

总结: 如果路由未命中任何规则, 给出一个兜底的404页面

路由 - 模式设置

目标: 修改路由在地址栏的模式

hash路由例如: http://localhost:8080/#/home

history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)

模式文档

router/index.js

const router = new VueRouter({
  routes,
  mode: "history" // 打包上线后需要后台支持, 模式是hash
})

vue路由 - 编程式导航

用JS代码跳转, 声明式导航用a标签

编程式导航 - 基础使用

目标: 用JS代码来进行跳转

语法:

this.$router.push({
    path: "路由路径", // 都去 router/index.js定义
    name: "路由名"
})
  1. main.js - 路由数组里, 给路由起名字
{
    path: "/find",
    name: "Find",
    component: Find
},
{
    path: "/my",
    name: "My",
    component: My
},
{
    path: "/part",
    name: "Part",
    component: Part
},
  1. App.vue - 换成span 配合js的编程式导航跳转
<template>
  <div>
    <div class="footer_wrap">
      <span @click="btn('/find', 'Find')">发现音乐</span>
      <span @click="btn('/my', 'My')">我的音乐</span>
      <span @click="btn('/part', 'Part')">朋友</span>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
  methods: {
    btn(targetPath, targetName){
      // 方式1: path跳转
      this.$router.push({
        // path: targetPath,
        name: targetName
      })
    }
  }
};
</script>

编程式导航 - 跳转传参

目标: JS跳转路由, 传参

语法 query / params 任选 一个

this.$router.push({
    path: "路由路径"
    name: "路由名",
    query: {
    	"参数名": 值
    }
    params: {
		"参数名": 值
    }
})

// 对应路由接收   $route.params.参数名   取值
// 对应路由接收   $route.query.参数名    取值

格外注意: 使用path会自动忽略params

App.vue

<template>
  <div>
    <div class="footer_wrap">
      <span @click="btn('/find', 'Find')">发现音乐</span>
      <span @click="btn('/my', 'My')">我的音乐</span>
      <span @click="oneBtn">朋友-小传</span>
      <span @click="twoBtn">朋友-小智</span>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name+query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
  methods: {
    btn(targetPath, targetName){
      // 方式1: path跳转
      this.$router.push({
        // path: targetPath,
        name: targetName
      })
    },
    oneBtn(){
      this.$router.push({
        name: 'Part',
        params: {
          username: '小传'
        }
      })
    },
    twoBtn(){
      this.$router.push({
        name: 'Part',
        query: {
          name: '小智'
        }
      })
    }
  }
};
</script>

总结: 传参2种方式

query方式

params方式

vue路由 - 嵌套和守卫

vue路由 - 路由嵌套

目标: 在现有的一级路由下, 再嵌套二级路由

二级路由示例-网易云音乐-发现音乐下

router-view嵌套架构图

  1. 创建需要用的所有组件

    src/views/Find.vue -- 发现音乐页

    src/views/My.vue -- 我的音乐页

    src/views/Second/Recommend.vue -- 发现音乐页 / 推荐页面

    src/views/Second/Ranking.vue -- 发现音乐页 / 排行榜页面

    src/views/Second/SongList.vue -- 发现音乐页 / 歌单页面

  2. main.js– 继续配置2级路由

    一级路由path从/开始定义

    二级路由往后path直接写名字, 无需/开头

    嵌套路由在上级路由的children数组里编写路由信息对象

  3. 说明:

    App.vue的router-view负责发现音乐和我的音乐页面, 切换

    Find.vue的的router-view负责发现音乐下的, 三个页面, 切换

image-20210116162013983

  1. 配置二级导航和样式(可直接复制) - 在Find.vue中
<template>
  <div>
    <!-- <p>推荐</p>
    <p>排行榜</p>
    <p>歌单</p> -->
    <div class="nav_main">
      <router-link to="/find/recommend">推荐</router-link>
      <router-link to="/find/ranking">排行榜</router-link>
      <router-link to="/find/songlist">歌单</router-link>
    </div>

    <div style="1px solid red;">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.nav_main {
  background-color: red;
  color: white;
  padding: 10px 0;
}
.nav_main a {
  text-align: center;
  text-decoration: none;
  color: white;
  font-size: 12px;
  margin: 7px 17px 0;
  padding: 0px 15px 2px 15px;
  height: 20px;
  display: inline-block;
  line-height: 20px;
  border-radius: 20px;
}
.nav_main a:hover {
  background-color: brown;
}
.nav_main .router-link-active{
  background-color: brown;
}
</style>
  1. 配置路由规则-二级路由展示
const routes = [
  // ...省略其他
  {
    path: "/find",
    name: "Find",
    component: Find,
    children: [
      {
        path: "recommend",
        component: Recommend
      },
      {
        path: "ranking",
        component: Ranking
      },
      {
        path: "songlist",
        component: SongList
      }
    ]
  }
  // ...省略其他
]
  1. 说明:
  • App.vue, 外层的router-view负责发现音乐和我的音乐页面切换

  • Find.vue 内层的router-view负责发现音乐下的子tab对应的组件切换

  1. 运行 - 点击导航观察嵌套路由在哪里展示

总结: 嵌套路由, 找准在哪个页面里写router-view和对应规则里写children

声明导航 - 类名区别

目标: router-link自带的2个类名的区别是什么

观察路由嵌套导航的样式

  • router-link-exact-active (精确匹配) url中hash值路径, 与href属性值完全相同, 设置此类名

  • router-link-active (模糊匹配) url中hash值, 包含href属性值这个路径

image-20210512102829250

全局前置守卫

目标: 路由跳转之前, 先执行一次前置守卫函数, 判断是否可以正常跳转

使用例子: 在跳转路由前, 判断用户登陆了才能去<我的音乐>页面, 未登录弹窗提示回到发现音乐页面

  1. 在路由对象上使用固定方法beforeEach
// 目标: 路由守卫
// 场景: 当你要对路由权限判断时
// 语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})
// 参数1: 要跳转到的路由 (路由对象信息)    目标
// 参数2: 从哪里跳转的路由 (路由对象信息)  来源
// 参数3: 函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上")
// 注意: 如果不调用next, 页面留在原地

// 例子: 判断用户是否登录, 是否决定去"我的音乐"/my
const isLogin = true; // 登录状态(未登录)
router.beforeEach((to, from, next) => {
  if (to.path === "/my" && isLogin === false) {
    alert("请登录")
    next(false) // 阻止路由跳转
  } else {
    next() // 正常放行
  }
})

总结: next()放行, next(false)留在原地不跳转路由, next(path路径)强制换成对应path路径跳转

补充知识

vant组件库

vant组件库-介绍

目标: vant是一个轻量、可靠的移动端 Vue 组件库, 开箱即用

vant官网

特点:

  • 提供 60 多个高质量组件,覆盖移动端各类场景
  • 性能极佳,组件平均体积不到 1kb
  • 完善的中英文文档和示例
  • 支持 Vue 2 & Vue 3
  • 支持按需引入和主题定制

全部引入

目标: 看官网文档, 下载, 引入vant组件库

全部引入, 快速开始:https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart

1.全部引入, 快速开始: https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart

2.下载Vant组件库到当前项目中

3.在main.js中全局导入所有组件,

4.使用按钮组件 – 作为示范的例子

image-20210512103112221

  1. 下载vant组件库到当前项目中

    yarn add vant -D
    
  2. 导入所有组件, 在main.js中

    import Vue from 'vue';
    import Vant from 'vant';
    import 'vant/lib/index.css';
    
    Vue.use(Vant);
    
  3. 使用按钮组件

    https://vant-contrib.gitee.io/vant/#/zh-CN/button

    <van-button type="primary">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    <van-button type="default">默认按钮</van-button>
    <van-button type="warning">警告按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
    

手动按需引入

目标: 只引入使用的组件

image-20210512103145759

1.手动单独引入, 快速开始: https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart

// 方式2: 手动 按需引入
// import Button from 'vant/lib/button'; // button组件
// import 'vant/lib/button/style'; // button样式
  1. 注册
// components: { // 手动注册组件名
//   // VanButton: Button
//   // 等价的
//   [Button.name]: Button
// }
  1. 使用
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>

自动按需引入

目标: 按需加载组件

babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。

  1. 安装插件

    yarn add babel-plugin-import -D
    
  2. 在babel配置文件里 (babel.config.js)

    module.exports = {
      plugins: [
        ['import', {
          libraryName: 'vant',
          libraryDirectory: 'es',
          style: true
        }, 'vant']
      ]
    };
    
  3. 全局注册 - 会自动按需引入

    // 方式1: 全局 - 自动按需引入vant组件
    // (1): 下载 babel-plugin-import
    // (2): babel.config.js - 添加官网说的配置 (一定要重启服务器)
    // (3): main.js 按需引入某个组件, Vue.use全局注册 - 某个.vue文件中直接使用vant组件
    import { Button } from 'vant';
    Vue.use(Button) // Button组件全局注册, 真正注册的组件名VanButton
    

弹出框使用

目标: 使用弹出框组件

image-20210512103419096

https://vant-contrib.gitee.io/vant/#/zh-CN/dialog

<template>
  <div>
    <van-button type="primary" @click="btn">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    <van-button type="default">默认按钮</van-button>
    <van-button type="warning">警告按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
  </div>
</template>

<script>
// 方式2: 手动 按需引入
// import Button from 'vant/lib/button'; // button组件
// import 'vant/lib/button/style'; // button样式

// 目标: 使用弹出框
// 1. 找到vant文档
// 2. 引入
// 3. 在恰当时机, 调用此函数 (还可以用组件的用法)
import { Dialog } from "vant";
export default {
  // components: { // 手动注册组件名
  //   // VanButton: Button
  //   // 等价的
  //   [Button.name]: Button
  // }
  methods: {
    btn() {
      Dialog({ message: "提示", showCancelButton: true }); // 调用执行时, 页面就会出弹出框
    },
  },
};
</script>

表单使用

目标: 使用vant组件里的表单组件

https://vant-contrib.gitee.io/vant/#/zh-CN/form

image-20210512103438024

表单验证规则:

image-20210321220047052

vant适配

目标: 切换不同机型, 刷新后看看标签大小适配吗

  • postcss – 配合webpack翻译css代码
  • postcss-pxtorem – 配合webpack, 自动把px转成rem
  • 新建postcss.config.js – 设置相关配置
  • 重启服务器, 再次观察Vant组件是否适配
  1. 下载postcss和postcss-pxtorem@5.1.1

    postcss作用: 是对css代码做降级处理

    postcss-pxtorem: 自动把所有代码里的css样式的px, 自动转rem

  2. src/新建postcss.config.js

module.exports = {
  plugins: {
    'postcss-pxtorem': {
      // 能够把所有元素的px单位转成Rem
      // rootValue: 转换px的基准值。
      // 例如一个元素宽是75px,则换成rem之后就是2rem。
      rootValue: 37.5,
      propList: ['*']
    }
  }
}

以iphone6为基准, 37.5px为基准值换算rem

今日总结

  • 跳转方法 传参位置 路由规则 接收
    /path?key=value 无特殊 $route.query.key
    /path/值 /path/:key $route.params.key
    this.$router.push({path: "/path", query: {key: value}}) query的对象 无特殊 $route.query.key
    this.$router.push({name: "com", params: {key: value}) params的对象 路由规则需要name属性 $route.params.key(注意,这种在内存中保存)

    无论哪种格式, 声明式和编程式都是通用的, 保证路径和参数格式正确就ok

    • 在路由规则里找到一级路由, 写children: [] 注意: 除了第一层一级路由path写/, 子的开头都无需/
    • 跳转时, 要去的路由路径从一级开始写
    • 心中要做到 浏览器url 路由值 和路由规则里的path 对上即可

面试题

1. 路由之间是怎么跳转的?有哪些方式

1、

2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面

3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面

4、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数

2. vue-router怎么配置路由

在vue中配置路由分为5个步骤,分别是:

  1. 引入vue-router.js
  2. 配置路由path和组件, 和生成路由对象
  3. 把路由对象配置到new Vue中router选项下
  4. 页面使用 承载路由
  5. 设置路由导航(声明式导航方式/编程式跳转)

3. vue-router的钩子函数都有哪些

关于vue-router中的钩子函数主要分为3类

全局钩子函数要包含beforeEach

beforeEach函数有三个参数,分别是:

​ to:router即将进入的路由对象
​ from:当前导航即将离开的路由
​ next:function,进行管道中的一个钩子,如果执行完了,则导航的状态就是 confirmed (确认的)否则为false,终止导航。

单独路由独享组件

​ beforeEnter,

组件内钩子

beforeRouterEnter,
beforeRouterUpdate,
beforeRouterLeave

4. 路由传值的方式有哪几种

Vue-router传参可以分为两大类,分别是编程式的导航 router.push和声明式的导航

router.push

字符串:直接传递路由地址,但是不能传递参数

​ this.$router.push("home")

​ 对象:

​ 命名路由 这种方式传递参数,目标页面刷新会报错 - name+params

​ this.$router.push({name:"news",params:{userId:123})

​ 查询参数 和path配对的是query

​ this.$router.push({path:"/news',query:{uersId:123})

​ 接收参数 this.$route.query

声明式导航

​ 字符串 <router-link to:"news">

​ 命名路由 <router-link :to:"{name:'news',params:{userid:1111}}">

​ 还可以to="/path/值" - 需要提前在路由 规则里值 /path/:key

​ 查询参数

​ 还可以to="/path?key=value

5. 怎么定义vue-router的动态路由?怎么获取传过来的动态参数?

动态路由指的就是path路径上传智, 前提需要路由规则了提前配置/path/:key名, 可以写多个用/隔开, 获取使用$route.params.key名来提取对应用路径传过来的值

6. Vue的路由实现模式:hash模式和history模式(必会)

hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用 window.location.hash 读取。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。

history模式:history采用HTML5的新特性;且提供了两个新方法: pushState(), replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更

7. 请说出路由配置项常用的属性及作用(必会)

​ 路由配置参数:

​ path : 跳转路径
​ component : 路径相对于的组件
​ name:命名路由
​ children:子路由的配置参数(路由嵌套)
​ props:路由解耦
​ redirect : 重定向路由

8. 编程式导航使用的方法以及常用的方法(必会)

​ 路由跳转 : this.$router.push()
​ 路由替换 : this.$router.replace()
​ 后退: this.$router.back()
​ 前进 :this.$router.forward()

9. Vue如何去除URL中的#(必会)

​ vue-router 默认使用 hash 模式,所以在路由加载的时候,项目中的 URL 会自带 “#”。如果不想使用 “#”, 可以使用 vue-router 的另一种模式 history:new Router ({ mode : 'history', routes: [ ]})

​ 需要注意的是,当我们启用 history 模式的时候,由于我们的项目是一个单页面应用,所以在路由跳转的时候,就会出现访问不到静态资源而出现 “404” 的情况,这时候就需要服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 “index.html” 页面。

10. 说一下你在vue中踩过的坑(必会)

​ 1、第一个是给对象添加属性的时候,直接通过给data里面的对象添加属性然后赋值,新添加的属性不是响应式的

​ 【解决办法】通过Vue.set(对象,属性,值)这种方式就可以达到,对象新添加的属性是响应式的

2、 在created操作dom的时候,是报错的,获取不到dom,这个时候实例vue实例没有挂载

​ 【解决办法】通过:Vue.nextTick(回调函数进行获取)

11. $route和$router的区别?

$route是路由信息对象,包括‘path,hash,query,fullPath,matched,name’等路由信息参数;
$router是路由实例对象,包括了路由的跳转方法,实例对象等

-vant适配

目标: 切换不同机型, 刷新后看看标签大小适配吗

  • postcss – 配合webpack翻译css代码
  • postcss-pxtorem – 配合webpack, 自动把px转成rem
  • 新建postcss.config.js – 设置相关配置
  • 重启服务器, 再次观察Vant组件是否适配
  1. 下载postcss和postcss-pxtorem@5.1.1

    postcss作用: 是对css代码做降级处理

    postcss-pxtorem: 自动把所有代码里的css样式的px, 自动转rem

  2. src/新建postcss.config.js

module.exports = {
  plugins: {
    'postcss-pxtorem': {
      // 能够把所有元素的px单位转成Rem
      // rootValue: 转换px的基准值。
      // 例如一个元素宽是75px,则换成rem之后就是2rem。
      rootValue: 37.5,
      propList: ['*']
    }
  }
}

以iphone6为基准, 37.5px为基准值换算rem

posted on 2023-10-20 19:58  xirang熙攘  阅读(69)  评论(0编辑  收藏  举报