Vue 基础 (笔记)

vue 简介


1. 什么是 vue

官方给出的概念:Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的前端框架

① 构建用户界面:

  • 用 vue 往 html 页面中填充数据

② 框架:

  • 一套现成的解决方案,程序员必须遵循框架的规范去编写业务功能
  • 学习 vue,就是学习 vue 框架中规定的用法
  • vue 的指令、组件(对 UI 结构的复用)、路由、Vuex
  • 只有把以上内容掌握后才有开发 vue 项目的能力

2. vue 的特性

vue 框架的特性,主要体现在如下两方面:
🔔 数据驱动视图
🔔 双向数据绑定

2.1 数据驱动视图

在使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。示意如下:

01

☀️ 好处:当页面数据发生变化时,页面会自动重新渲染!
🔔 注意:数据驱动视图是单向的数据绑定。

2.2 双向数据绑定

在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。

在网页中,form 表单负责采集数据,Ajax 负责提交数据

示意图如下

02

☀️ 好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值

2.3 MVVM

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,
它把每个 HTML 页面都拆分成了这三个部分,如图所示:

在这里插入图片描述

在 MVVM 概念中:

  • Model 表示当前页面渲染时所依赖的数据源。
  • View 表示当前页面所渲染的 DOM 结构。
  • ViewModel 表示 vue 的实例,它是 MVVM 的核心。

2.4 MVVM 的工作原理

ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起

04

当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构
当表单元素的值发生变化时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中

3. vue 的版本

当前,vue 共有 3 个大版本,其中:

2.x 版本的 vue 是目前企业级项目开发中的主流版本
3.x 版本的 vue 于 2020-09-19 发布,生态还不完善,尚未在企业级项目开发中普及和推广
1.x 版本的 vue 几乎被淘汰,不再建议学习与使用

总结:

3.x 版本的 vue 是未来企业级项目开发的趋势;
2.x 版本的 vue 在未来(1 ~ 2 年内)会被逐渐淘汰;


vue 的基本使用


1. 基本使用步骤

① 导入 vue.js 的 script 脚本文件
② 在页面中声明一个将要被 vue 所控制的 DOM 区域
③ 创建 vm 实例对象(vue 实例对象)

<body>
  <!-- 2. 在页面中声明一个将要被 vue 控制的 DOM 区域 -->
  <div id="app">{{username}}</div>

  <!-- 1. 导入 vue.js 的 script脚本文件 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <script>
    // 3. 创建 vm 实例对象 (vue实例对象)
    const vm = new Vue({
      // 3.1 指定当前 vm 实例要控制页面哪个区域 (el为固定写法)
      el: '#app',
      // 3.2 指定 Model 数据源,要渲染到页面上的数据
      data: {
        username: 'zs',
      },
    })
  </script>
</body>

2. 基本代码与 MVVM 的对应关系

05


vue 的指令与过滤器


1. 指令的概念

指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。vue 中的指令按照不同的用途可以分为如下 6 大类:

① 内容渲染指令
② 属性绑定指令
③ 事件绑定指令
④ 双向绑定指令
⑤ 条件渲染指令
⑥ 列表渲染指令

🔔 注意:指令是 vue 开发中最基础、最常用、最简单的知识点。

1.1 内容渲染指令

内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:

  • v-text
  • {{ }}
  • v-html
① v-text

用法示例

<!-- 把 username 对应的值,渲染到第一个 p 标签中 -->
<p v-text="username"></p>

<!-- 把 gender 对应的值,渲染到第二个 p 标签中 -->
<!-- 注意:第二个 p 标签中,默认的文本"性别"会被 gender 的值覆盖掉 -->
<p v-text="gender">性别</p>

🔔 注意:v-text 指令会覆盖元素内默认的值

② {{ }} 语法

vue 提供的 {{ }} 语法,专门用来解决 v-text 会覆盖默认文本内容的问题。这种 {{ }} 语法的专业名称是插值表达式(英文名为:Mustache)

<!-- 使用 {{ }} 插值表达式,将对应的值渲染到元素的内容节点中,同时保留元素自身的默认值 -->
<p>姓名:{{username}}</p>
<p>性别:{{gender}}</p>

🔔 注意:相对于 v-text 指令来说,插值表达式在开发中最常用。因为它不会覆盖元素中默认的文本内容

③ v-html

v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素,则需要用到 v-html 这个指令:

<!-- <h4 style="color: red; font-weight: bold;">Vue.js</h4> -->
<p v-html="info"></p>

1.2 属性绑定指令 v-bind

🔔 插值表达式只能用在元素的内容节点中,不能用在元素的属性节点
如果需要为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令。用法示例如下:

<!-- 
data: {
  imput: '请输入内容',
  imgSrc: 'https://cn.vuejs.org/images/logo.svg'
} 
-->

<!-- 使用 v-bind 指令,为 input 的 placeholder 动态绑定属性值 -->
<input type="text" v-bind:placeholder="inputValue" />
<br />
<!-- 使用 v-bind 指令,为 img 的 src 动态绑定属性值 -->
<img v-bind:src="imgSrc" alt="" />
  • 由于 v-bind 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 : )。
<img :src="imgSrc" alt="" />
  • 在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算(不支持复杂的 JS 语句),例如:
{{ number + 1 }} {{ ok ? 'yes' : 'no' }} {{ message.split('').reverse().join('') }}

<div :id="'list-' + id"></div>
  • 在使用 v-bind 属性绑定期间,若绑定内容需要进行动态拼接,则字符串外面应该包裹单引号,例如
<div :title="'box' + index">这是一个div</div>

1.3 事件绑定指令 v-on

vue 提供了 v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听。语法格式如下:

<h3>count 的值为: {{ count }}</h3>
<!-- 语法格式为 v-on:事件名称="事件处理函数名称" -->
<button v-on:click="add">+1</button>
<!-- 若需传参,使用() -->
<button v-on:click="sub(1)">-1</button>

🔔 注意:原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件,替换为 vue 的事件绑定形式后,分别为:v-on:click、v-on:input、v-on:keyup

  • 通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明:
const vm = new Vue({
  el: '#app',
  data: {
    count: 0
  },
  // methods 作用,就是定义事件处理函数
  methods: {
    add() { // 等同于 add: function() {}
      this.count += 1
      // this === vm 实例对象
      // 通过 this 可以访问到 data 中的数据
    },
    // 若需传参
    sub(n) {
      this.count -= n
    }
  }
  • 由于 v-on 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 @ )
<button @click="add">+1</button>
① $event (不常用😅)

e v e n t 是 v u e 提 供 的 特 殊 变 量 , 用 来 表 示 ∗ ∗ 原 生 的 事 件 参 数 对 象 e v e n t ∗ ∗ 。 event 是 vue 提供的特殊变量,用来表示**原生的事件参数对象 event**。 eventvueeventevent 可以解决事件参数对象 event
被覆盖的问题。示例用法如下:

<!-- vue 提供了内置变量 名称为 $event,他就是原生 DOM 的事件对象 e -->
<button @click="add(1, $event)">+n</button>
methods: {
  // 在形参处使用 e 接收传递过来的原生事件参数对象 $event
  add(n, e) {
    this.count += n
    if (this.count % 2 === 0) {
      e.target.style.backgroundColor = 'skyblue'
    } else {
      e.target.style.backgroundColor = 'yellow'
    }
  }
}

🔔 若事件绑定函数不携带参数,则可以直接使用 e

② 事件修饰符 (又不常用😅)

在事件处理函数中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。因此,vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的 5 个事件修饰符如下

事件修饰符说明
.prevent阻止默认行为(例如阻止 a 链接跳转、表单提交等)
.stop阻止事件冒泡
.capture以捕获模式触发当前的事件处理函数
.once绑定的事件只触发 1 次
.self只有在 event.target 是当前元素自身时触发事件处理函数

语法格式如下

<a href="http://www.baidu.com" @click.prevent="show">百度</a>
③ 按键修饰符

在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符,例如:

<input @keyup.enter="submit" /> <input @keyup.esc="clearInput" />

1.4 双向绑定指令 v-model

vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据。

<div id="app">
  <p>用户名是:{{ username }}</p>
  <input type="text" v-model="username" />
  <!-- 使用 v-model 代替了 value -->
  <!-- 仅表单元素可以使用 v-model -->

  <select name="" id="" v-model="city">
    <option value="">请选择城市</option>
    <option value="1">北京</option>
    <option value="2">上海</option>
    <option value="3">广州</option>
  </select>
</div>

🔔 表单元素 input | texarea | select 可以使用
🔔 v-model 会自动判断绑定的表单类型并赋值给相应的属性

① v-model 指令的修饰符

为了方便对用户输入的内容进行处理,vue 为 v-model 指令提供了 3 个修饰符,分别是:

修饰符作用示例
.number自动将用户的输入值转换为数值类型
<input type="text" v-model.number="n1" /> + <input type="text" v-model.number="n2" /> = <span>{{ n1 + n2 }}</span>

1.5 条件渲染指令 v-if & v-show

条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:

  • v-if (无脑使用😅)
  • v-show
<div id="app">
  <p v-if="flag">这是被 v-if 控制的元素</p>
  <p v-show="flag">这是被 v-show 控制的元素</p>
</div>

🔔 v-if 和 v-show 的区别

实现原理不同:

  • v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
  • v-show 指令会动态为元素添加或移除 style=“display: none;” 样式,从而控制元素的显示与隐藏;

性能消耗不同:
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此:

  • 如果需要非常频繁地切换,则使用 v-show 较好
  • 如果在运行时条件很少改变;或者刚进入页面的时候,元素无需显示,且后期后期可能不需要被展示处理,则使用 v-if 较好
① v-else & v-else-if
<div v-if="type === 'A'">优秀</div>
<div v-else-if="type === 'B'">良好</div>
<div v-else-if="type === 'C'">将就</div>
<div v-else></div>

🔔 注意:v-else 以及 v-else-if 指令必须配合 v-if 指令一起使用,否则它将不会被识别!

1.6 列表渲染指令 v-for

vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for 指令需要使
用 item in items 形式的特殊语法,其中:

  • items 是待循环的数组
  • item 是被循环的每一项
data: {
  list: [
    { id: 1, name: '张三' },
    { id: 2, name: '李四' },
    { id: 3, name: '王五' }
  ]
<div id="app">
  <table class="table table-border table-hover table-striped">
    <thead>
      <th>索引</th>
      <th>ID</th>
      <th>姓名</th>
    </thead>
    <tbody>
      <tr v-for="(item, index) in list" :title="item.name">
        <!-- 可选参数 index -->
        <td>{{ index }}</td>
        <td>{{ item.id }}</td>
        <td>{{ item.name }}</td>
      </tr>
    </tbody>
  </table>
</div>
① index

v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items (示例如上)
🔔 注意:v-for 指令中的 item 项和 index 索引都是形参,可以根据需要进行重命名。例如 (user, i) in userlist

② key

当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。
为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能。此时,需要为每项提供一个唯一的 key 属性:

<tbody>
  <tr v-for="(item, index) in list" :key="item.id" :title="item.name">
    <td>{{ index }}</td>
    <td>{{ item.id }}</td>
    <td>{{ item.name }}</td>
  </tr>
</tbody>

🔔 key 的注意事项

  • key 的值只能是字符串或数字类型
  • key 的值必须具有唯一性(即:key 的值不能重复)
  • 建议把数据项 id 属性的值作为 key 的值,否则终端报错: Duplicate keys detected(因为 id 属性的值具有唯一性)
  • 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)
  • 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)
④ 改变循环列表数据的方法

若想改变列表数据,可以采用以下方法:

① 使用列表的遍历方法进行更改

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

② 替换数组

③ set 方法

通过 set 方法修改的数据可以在循环渲染列表中进行显示

// 对象
Vue.set(vm.userInfo, 'aaa', 'bbb')
vm.$set(vm.userInfo, 'ccc', 'ddd')
// 数组
Vue.set(vm.arr, 1, 5)
vm.$set(vm.arr, 2, 6)

2. 过滤器 filter (Vue3 已删除此功能😅)

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式v-bind 属性绑定。
过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用,示例代码如下:

<!-- 得到结果为过滤器函数的返回值 -->
<p>{{ message | capitalize }}</p>
<div v-bind:id="rawId | formatId"></div>

2.1 定义过滤器

在创建 vue 实例期间,可以在 filters 节点中定义过滤器,示例代码如下:

const vm = new Vue({
  el: '#app',
  data: {
    message: 'hello vue.js',
  },
  filters: {
    capi(val) {
      // charAt方法,接收索引值,返回字符串对应位置的字符
      const first = val.charAt(0).toUpperCase()
      // slice方法,截取字符串,从指定索引往后截取
      const other = val.slice(1)
      return first + other
    },
  },
})

🔔 过滤器注意点

  • 要定义到 filters 节点下,本质是一个函数
  • 在过滤器函数中,一定要有 return 值
  • 过滤器形参中,就可以获取到"管道符"前面待处理的值

2.2 私有过滤器和全局过滤器

在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。
如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:

// 全局过滤器 -- 独立于每个vm实例外
// 接收两个参数:
// 第一个参数:全局过滤器的名字
// 第二个参数:全局过滤器的"处理函数"
Vue.filter('capitalize', (str) => {
  return str.charAt(0).toUpperCase() + str.slice(1) + '~~'
})

🔔 全局过滤器更常用。若全局和私有过滤器名字冲突,按照就近原则,调用私有过滤器

2.3 连续调用多个过滤器 (不常用😅)

过滤器可以串联地进行调用,例如:

<!-- 依次处理 -->
{{ message | filterA | filterB }}

2.4 过滤器传参

过滤器的本质是 JavaScript 函数,因此可以接收参数,格式如下:

<p>{{ message | filterA(arg1, arg2) }}</p>
Vue.filter('filterA', (msg, arg1, arg2) => {...})

2.5 过滤器的兼容性

过滤器仅在 vue 2.x 和 1.x 中受支持,在 vue 3.x 的版本中剔除了过滤器相关的功能。
在企业级项目开发中:

  • 如果使用的是 2.x 版本的 vue,则依然可以使用过滤器相关的功能
  • 如果项目已经升级到了 3.x 版本的 vue,官方建议使用计算属性或方法代替被剔除的过滤器功能

侦听器 watch


1. 什么是 watch 侦听器

watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。所有侦听器都应定义在 watch 节点
语法格式如下:

const vm = new Vue({
  el: '#app',
  data: {
    username: '',
  },
  watch: {
    // 监听 username 值的变化,就将该值当做侦听器的函数名
    // newVal 是"变化后的新值",oldVal 是"变化之前的旧值",新前旧后
    username(newVal, oldVal) {
      console.log(newVal, oldVal)
    },
  },
})

2. 使用 watch 检测用户名是否可用

监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:

watch: {
  // 监听 username 值的变化
  async username(newVal) {
    if (newVal === '') return
    // 使用 axios 发起请求,判断用户名是否可用
    const { data: res } = await axios.get('https://www.escook.cn/api/finduser/' + newVal)
    console.log(res)
  }
}

3. immediate 选项

🔔 补充:侦听器的格式

① 方法格式的侦听器 – (一般情况使用)
❄️ 缺点 1:无法在刚进入页面的时候自动触发
❄️ 缺点 2:若侦听的是对象,对象中的属性发生变化,不会触发侦听器

② 对象格式的侦听器 – (有特殊需求时使用)
☀️ 好处 1:可以通过 immediate 选项,让侦听器自动触发
☀️ 好处 2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使
用 immediate 选项。示例代码如下

watch: {
  // 定义对象格式的侦听器
  username: {
    // handler 侦听器的处理函数(固定写法)
    handler(newVal, oldVal) {
      console.log(newVal, oldVal);
    },
    // 默认值 false
    // 控制侦听器是否在页面渲染好之后自动触发当前 watch 侦听器
    immediate: true
  }
}

4. deep 选项

如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项,代码示例如下:

const vm = new Vue({
  el: '#app',
  data: {
    // 用户的信息对象
    info: {
      username: 'admin',
    },
  },
  watch: {
    info: {
      handler(newVal) {
        console.log(newVal)
      },
      // 开启深度监听,只要对象中任一属性变化就会触发侦听器
      deep: true,
    },
  },
})

5. 监听对象单个属性的变化

如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:

const vm = new Vue({
  el: '#app',
  data: {
    info: {
      username: 'admin',
    },
  },
  watch: {
    // 若需要侦听的是对象的子属性变化,则必须包裹一层单引号
    'info.username'(newVal) {
      console.log(newVal)
    },
  },
})

计算属性 cumputed


1. 什么是计算属性

计算属性指的是通过一系列运算之后,最终得到一个属性值(Vue 实例对象的属性)。
这个动态计算出来的属性值可以被模板结构或 methods 方法使用。示例代码如下:

var vm = new Vue({
  el: '#app',
  data: {
    r: 0,
    g: 0,
    b: 0,
  },
  methods: {
    // 点击按钮,在终端显示最新的颜色
    show() {
      console.log(this.rgb)
    },
  },
  // 所有计算属性都要定义到 computed 节点下
  // 计算属性在定义的时候要定义成方法格式
  computed: {
    // rgb 作为一个计算属性,被定义成了方法格式
    // 最终,在这个方法中,要返回一个生成好的 rgb(x, x, x) 的字符串
    rgb: function () {
      return `rgb(${this.r}, ${this.g}, ${this.b})`
    },
  },
})

2. 计算属性的特点

① 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性
② 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算

☀️ 好处:

  • 实现了代码的复用
  • 只要计算属性依赖的数据变化时,计算属性会重新求值

3. 计算属性的 getter 和 setter

computed: {
  fullname: {
    // 取用该计算属性时会执行 get
    get: function() {
      return this.firstName + " " + this.lastName
    },
    // 设置该计算属性时会执行 set
    set: function(value) {
      var arr = value.split(" ")
      this.firstName = arr[0]
      this.lastName = arr[1]
      console.log(value)
    }
  },
},

axios


axios 是一个专注于网络请求的库

1. axios 的基本使用

axios({
  method: '请求类型',
  url: '',
  // URL 中的查询参数 (GET)
  params: {},
  // 请求体参数 (POST)
  data: {}
}).then((result) => {
  // .then 用来指定请求成功之后的回调函数
  // 形参中的 result 是请求成功后的结果
}
// axios 在请求到数据之后,在真实数据外套了一层壳
{
  config: {},
  data: { 真实数据 },
  headers: {},
  request: {},
  status: xxx,
  statusText: ''
}

🔔 注意:调用 axios 方法得到的返回值是 Promise 对象

2. axios 发起 POST 请求

// 1. 使用 .then() 方法
document.querySelector('#btnPost').addEventListener('click', function () {
  axios({
    method: 'post',
    url: 'http://www.liulongbin.top:3006/api/post',
    data: {
      name: 'zs',
      age: 20
    }
  }).then(function (result) {
    console.log(result);
  })
})

// 2. 使用 async / await 方法 -- 简化
document.querySelector('#btnPost').addEventListener('click', async function () {
  // 若调用某方法的返回值是 Promise 实例,可以在前面添加 await
  // await 只能用在被 async 修饰的方法中
  const result = await axios({
    method: 'post',
    url: 'http://www.liulongbin.top:3006/api/post',
    data: {
      name: 'zs',
      age: 20
    }
  })
  console.log(result);
})
// 3. 解构赋值
// ① 使用解构赋值从 axios 封装的对象中直接获取真实返回结果
// ② 解构赋值并使用 : 进行重命名
const { data:res } = await axios({...})

🔔 axios 还包含:axios.get() | .post() | .delete() | .put() 方法

3. axios 直接发起 get 和 post 请求

// 1. GET
axios.get('url地址', {
  // GET参数
  params: {},
})
// 2. POST
axios.post('url地址', { POST参数 })

vue-cli


1. 什么是单页面应用程序

单页面应用程序(英文名:Single Page Application)简称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。

2. 什么是 vue-cli

vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。
引用自 vue-cli 官网上的一句话:
程序员可以专注在撰写应用上,而不必花好几天去纠结 webpack 配置的问题。
中文官网:https://cli.vuejs.org/zh/

3. 安装和使用

vue-cli 是 npm 上的一个全局包,使用 npm install 命令,即可方便的把它安装到自己的电脑上:

npm install -g @vue/cli

基于 vue-cli 快速生成工程化的 Vue 项目:

vue create 项目的名称(英文)

① 选择预设

? Please pick a preset
  Default([Vue 2] babel, eslint)
  Default (Vue 3) ([Vue 3] babel, eslint)
> mannually select features   ---  手动设置 (建议)

② 选择需要安装的功能

? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Choose Vue version
 (*) Babel -- 解决兼容性
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 ( ) Router
 ( ) Vuex
 (*) CSS Pre-processors
 ( ) Linter / Formatter -- 约束团队代码风格
 ( ) Unit Testing
 ( ) E2E Testing

③ 选择需要安装的 vue 版本

? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
> 2.x
  3.x

④ 选择 CSS 预处理器

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
  Sass/SCSS (with dart-sass)
  Sass/SCSS (with node-sass)
> Less
  Stylus

⑤ 第三方插件的配置文件是独立放置还是统一在 package.json 中

? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
  In package.json

⑥ 是否存入预设

? Save this as a preset for future projects? (y/N)
? Save preset as:

创建完毕

 $ cd demo-first
 $ npm run serve

4. Vue 项目中 src 目录构成

文件夹/文件存放内容/作用
assets 文件夹存放项目中用到的静态资源,例如:css 样式表、图片资源
components 文件夹存放程序员封装的、可复用的组件
main.js 文件项目的入口文件,整个项目的运行需要先执行该文件
App.vue 文件项目的根组件 (结构显示于页面上)

5. Vue 项目的运行流程

在工程化的项目中,vue 要做的事情很单纯:通过 main.jsApp.vue 渲染到 index.html 的指定区域中。
其中:
① App.vue 用来编写待渲染的模板结构
② index.html 中需要预留一个 el 区域
③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中

main.js 文件:

// 导入 vue 这个包,得到 Vue 构造函数
import Vue from 'vue'
// 导入 App.vue 根组件,将来要把 Test.vue 中的模板结构,渲染到 HTML 页面中
import Test from './Test.vue'

Vue.config.productionTip = false

// 创建 Vue 的实例对象
new Vue({
  // 把 render 函数指定的组件,渲染到 HTML 页面中
  // render函数渲染的组件就是根组件
  render: (h) => h(Test),
}).$mount('#app')

🔔 .$mount(’#app’) 等同于指定 el: ‘#app’


vue 组件


1. 什么是组件化开发

组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。

2. vue 中的组件化开发

vue 是一个支持组件化开发的前端框架。
vue 中规定:组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。

3. vue 组件的三个组成部分

每个 .vue 组件都由 3 部分构成,分别是:

  • template -> 组件的模板结构
  • script -> 组件的 JavaScript 行为
  • style -> 组件的样式
    其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。

3.1 template

vue 规定:每个组件对应的模板结构,需要定义到 节点中。

<template>
  <div class="test-box">
    <h3>这是用户自定义的 Test.vue --- {{ username }}</h3>
  </div>
</template>

🔔 注意:

  • template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素
  • template 中只能包含唯一的根节点

3.2 script

vue 规定:开发者可以在 script 节点中封装组件的 JavaScript 业务逻辑。
script 节点的基本结构如下:

<script>
  // 默认导出 固定写法
  export default {
    // 注意: .vue组件里的 data 不能像之前一样指向对象
    // 组件中的 data 必须是函数
    data() {
      // return 出去的 {} 中,可以定义数据
      return {
        username: 'admin',
      }
    },
    methods: {
      changeName() {
        // 组建中 this 就表示当前组件的实例对象
        this.username = '娃哈哈'
      },
      // 当前组件中的侦听器
      watch: {},
      // 当前组件中的计算属性
      computed: {},
      // 当前组件中的过滤器
      filters: {},
    },
  }
</script>

3.3 style

vue 规定:组件内的 style 节点是可选的,开发者可以在 style 节点中编写样式美化当前组件的 UI 结构。
style 节点的基本结构如下:

<style>
  .test-box {
    background-color: orange;
  }
</style>
  • 让 style 中支持 less 语法
    在 < style > 标签上添加 lang=“less” 属性,即可使用 less 语法编写组件的样式:
<style lang="less">
  h1 {
    color: red;
    span {
      color: orange;
    }
  }
</style>

4. 组件之间的父子关系

06

4.1 使用组件的三个步骤

  • 步骤 1:使用 import 语法导入需要的组件 (建议大写开头)
import Left from '@/components/Left.vue'
  • 步骤 2:使用 components 节点注册组件
export default {
  // 2. 注册组件
  components: {
    // 'Left': Left,
    Left,
  },
}
  • 步骤 3:以标签形式使用刚才注册的组件
<div class="box">
  <!-- 以标签形式使用注册好的组件 -->
  <Left></Left>
</div>

🔔 小插件 Path Autocomplete – 配置 settings.json

4.2 通过 components 注册的是私有子组件

例如:
在组件 A 的 components 节点下,注册了组件 F。则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
❄️ 缺点:若一个组件会频繁的被使用,私有子组件就比较麻烦

4.3 注册全局组件

在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。示例代码如下:

// 导入需要全局注册的组件
import Count from '@/components/Count.vue'

// 参数1:字符串格式,表示组件的"注册名称",尽量使用大写
// 参数2:需要被注册的组件
Vue.component('MyCount', Count)

5. 组件的 props

props 是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!
它的语法格式如下:

<script>
  export default {
    // props 是自定义属性,允许使用者通过自定义属性为当前组件指定初始值
    // 自定义属性的名字是封装者自定义的(只有名称合法)
    // props 中的数据,可以直接在模板结构中被使用
    props: ['init'],
    data() {
      return {
        count: 0,
      }
    },
  }
</script>

调用组件

<!-- 加上冒号,自定义属性传入的内容为js格式,故此处为数值 -->
<MyCount :init="9"></MyCount>

5.1 props 是只读的

vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错。
要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!

props: ["init"], // init 的值可以作为属性直接访问
data() {
  return {
    count: this.init // 将 this.init 的值转存到 count
  };
},

5.2 props 的 default 默认值

在声明自定义属性时,可以通过 default 来定义属性的默认值。示例代码如下:

exports default {
  props: {
    init: {
      // 用 default 属性定义属性的默认值
      default: 0
    }
  }
}

🔔 此时 props 不能使用数组格式而需使用对象格式。

5.3 props 的 type 值类型

在声明自定义属性时,可以通过 type 来定义属性的值类型。示例代码如下:

exports default {
  props: {
    init: {
      // 用 type 属性定义属性的值类型
      // 若不符合,则会报错
      // 可选值:Number Boolean String Array Object ...
      type: Number
    }
  }
}

5.4 props 的 required 必填项

在声明自定义属性时,可以通过 required 选项,将属性设置为必填项,强制用户必须传递属性的值。示例代
码如下:

exports default {
  props: {
    init: {
      type: Number,
      // 必填项校验
      required: true
    }
  }
}

5.5 props 的 validator 自定义验证函数

validator: function (value) {
  // 这个值必须匹配下列字符串中的一个
  return ['success', 'warning', 'danger'].indexOf(value) !== -1
}

6. 组件之间的样式冲突问题

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。

导致组件之间样式冲突的根本原因是:
① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

6.1 解决方法 (麻烦)

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:

<template>
  <div class="left-container" data-v-001>
    <h3 data-v-001>Left 组件</h3>

    <hr data-v-001 />

    <MyCount :init="9" data-v-001></MyCount>
  </div>
</template>
h3[data-v-001] {
  color: red;
}

6.2 style 节点的 scoped 属性

为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题

<style lang="less" scoped>
/* style 节点的 scoped 属性,用来自动为每个组件分配唯一的"自定义属性",并自动为当前组件的 DOM 标签和 style 样式应用这个自定义属性,防止组件的样式冲突问题 */

6.3 /deep/ 样式穿透

如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样
式对子组件生效,可以使用 /deep/ 深度选择器。

<style lang="less" scoped>
  /* 若不加 /deep/ 生成的选择器格式为 h5[data-v-xxx] */
  /* 若加 /deep/ 生成的选择器格式为 [data-v-xxx] h5 */
  /deep/ h5 {
    color: yellow;
  }
</style>

🔔 当使用第三方组件库时,若有修改第三方组件默认样式的需求,需要用到 /deep/

7. 组件细节点补充

7.1 is 属性
<tbody>
  <!-- row 为自定义组件名 -->
  <tr is="row"></tr>
  <tr is="row"></tr>
  <tr is="row"></tr>
  <!-- 该处若直接使用 <row> 不满足 tbody 下必须使用 tr 的规范 -->
</tbody>
<!-- 类似的还有: ul > li; select > option -->
Vue.component('row', {
  template: '<tr><td>this is a row</td></tr>',
})

组件的生命周期


1. 生命周期 & 生命周期函数

生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。

🔔 注意:生命周期强调的是时间段,生命周期函数强调的是时间点

2. 组件生命周期函数的分类

07

3. 生命周期图示

可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:
https://cn.vuejs.org/v2/guide/instance.html#生命周期图示

lifecircle

① 组件创建阶段

  • beforeCreate:组件的 props/data/methods 尚未被创建,都处于不可用状态

  • created:组件的 props/data/methods 已创建并可用,但是组件的模板结构尚未生成
    🔔 该生命周期函数非常常用,常在此处调用 methods 中的方法,请求服务器的数据(Ajax),并把请求到的数据转存至 data 中,供 template 模板渲染时使用

  • beforeMount:将要把内存中编译好的 HTML 结构渲染到浏览器中。此时浏览器中还没有当前组件的 DOM 结构。

  • mounted:已经把内存中的 HTML 结构成功地渲染到了浏览器中,此时浏览器包含了当前组件的 DOM 结构
    🔔 最早可以操作组件 DOM 的阶段

② 组件运行阶段

  • beforeUpdate:将要根据变化过后、最新的数据重新渲染组件的模板结构。此时拿到的数据是更新前的。

  • updated:已根据最新的数据,完成了组件 DOM 结构的重新渲染。拿到的数据为更新后的。
    🔔 数据变化后,为了能操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中。

③ 组件销毁阶段

  • beforeDestroy:将要销毁此组件,此时尚未销毁,组件尚处于正常工作的状态。

  • destroyed:组件已经被销毁,此组件在浏览器中对应的 DOM 结构已被完全移除。

🔔 生命周期函数定义需要与 methods 平级,且在到达事件节点时自动触发


组件之间的数据共享 😱


1. 组件之间的关系

在项目开发中,组件之间的最常见的关系分为如下两种:
① 父子关系
② 兄弟关系

2. 父子组件之间的数据共享

父子组件之间的数据共享又分为:
① 父 -> 子共享数据
② 子 -> 父共享数据

2.1 父组件向子组件共享数据

父组件向子组件共享数据需要使用自定义属性。示例代码如下

  • 父组件
<Son :msg="message" :user="userinfo"></Son>
data() {
  return {
    message: 'Hello vue.js',
    userinfo: { name: 'zs', age: 20 }
  }
}
  • 子组件
<template>
  <div>
    <h5>Son 组件</h5>
    <p>父组件传递的 msg 值为:{{ msg }}</p>
    <p>父组件传递的 user 值为:{{ user }}</p>
  </div>
</template>
props: ['msg', 'user']

🔔 子属性中利用 props 自定义属性并接收数据;父组件仅提供数据并传递给子组件
🔔 不要修改 porps 的值

2.2 子组件向父组件共享数据

子组件向父组件共享数据使用自定义事件。示例代码如下:

  • 子组件
export default {
  data() {
    return {
      // 子组件自己的数据,将来希望把 count 值传给父组件
      count: 0,
    }
  },
  methods: {
    add() {
      // 让子组件的 count 值自增 1
      this.count++
      // 修改数据时,通过 $emit() 触发自定义事件,将自增结果传给父组件
      this.$emit('numchange', this.count)
    },
  },
}
  • 父组件
<Right @numchange="getNewCount"></Right>
export default {
  data() {
    return {
      // 定义 countFromSon 来接收子组件传递过来的数据
      countFromSon: 0,
    }
  },
  components: {
    Left,
    Right,
  },
  methods: {
    // 获取子组件传递过来的信息
    getNewCount(val) {
      this.countFromSon = val
    },
  },
}

2.3 兄弟组件之间的数据共享

在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus (现成解决方案)。

  • 兄弟组件 1 – 数据发送方
import bus from './eventBus.js'

export default {
  data() {
    return {
      msg: 'hello vue.js',
    }
  },
  methods: {
    senMsg() {
      bus.$emit('share', this.msg)
    },
  },
}
  • eventBus.js
import Vue from 'vue'

// 向外共享 Vue 的实例对象
export default new Vue()
  • 兄弟组件 2 – 数据接收方
import bus from './eventBus.js'

export default {
  data() {
    return {
      msgFromLeft: '',
    }
  },
  created() {
    but.$on('share', (val) => {
      this.msgFromLeft = val
    })
  },
}

EventBus 的使用步骤
① 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
② 在数据发送方,调用 bus. e m i t ( ′ 事 件 名 称 ′ , 要 发 送 的 数 据 ) 方 法 触 发 自 定 义 事 件 ③ 在 数 据 接 收 方 , 调 用 b u s . emit('事件名称', 要发送的数据) 方法触发自定义事件 ③ 在数据接收方,调用 bus. emit(,)bus.on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件

🔔 补充 – 实例方法
① **vm. o n ( e v e n t , c a l l b a c k ) ∗ ∗ 监 听 当 前 实 例 上 的 自 定 义 事 件 。 事 件 可 以 由 v m . on( event, callback )** 监听当前实例上的自定义事件。事件可以由 vm. on(event,callback)vm.emit 触发。回调函数会接收所有传入事件触发函数的额外参数。
② **vm.$emit( eventName, […args] )**
触发当前实例上的事件。附加参数都会传给监听器回调。

  • 需绑定到同一个实例上(EventBus)

ref 引用


  • vue 优势:MVVM。在 vue 中,程序员不需要操作 DOM ,程序员只需要把数据维护好即可
  • 结论:vue 项目中,不建议大家安装和操作 jQuery
  • 假设:在 vue 项目中需要操作 DOM ,需要拿到页面上某个 DOM 元素的引用

1. 什么是 ref 引用

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

2. 使用 ref 引用 DOM 元素

如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作

<h1 ref="myh1">App 根组件</h1>
<button @click="showThis">按钮</button>
export default {
  methods: {
    showThis() {
      // console.log(this.$refs.myh1);
      // 通过 this.$refs.引用的名称 可以获取到 DOM 元素的引用
      this.$refs.myh1.style.color = 'red'
    },
  },
}

3. 使用 ref 引用组件实例

如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:

<Left ref="comLeft"></Left> <button @click="onReset">重置 Left 的 count 值为 0</button>
onReset() {
  // 引用组件实例并调用方法
  this.$refs.comLeft.resetCount();
  // this.$refs.comLeft.count = 0;
},

🔔 建议取名以 ref 结尾

4. this.$nextTick(cb) 方法

组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。
通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素

showInput() {
  // 点击按钮 展示输入框
  this.inputVisible = true;
  // 让展示出来的文本框自动获得焦点
  this.$nextTick(() => {
    this.$refs.iptRef.focus();
  });
},

5. 循环知识点复习 😄

5.1 some

const arr = ['red', 'blue', 'yellow', 'orange']
// forEach 循环一旦开始不会停止
// arr.forEach((item, index) => {
//   console.log('ok');
//   if (item === 'yellow')
//     console.log(index);
// })
arr.some((item, index) => {
  console.log('ok')
  if (item === 'yellow') {
    console.log(index)
    // 在找到对应的项后,可以通过 return true 固定语法,终止 some 循环
    return true
  }
})

5.2 every

// every 每一项都满足才返回 true 否则 false
const arr = [
  { id: 1, name: 'a', stats: true },
  { id: 2, name: 'b', stats: true },
  { id: 3, name: 'c', stats: true },
]
// 判断数组中是否被全选
const result = arr.every((item) => item.stats) // 判断条件
console.log(result)
// 返回布尔值

5.3 reduce

const arr = [
  { id: 1, name: 'a', state: true, price: 10, count: 1 },
  { id: 2, name: 'b', state: false, price: 20, count: 2 },
  { id: 3, name: 'c', state: true, price: 30, count: 3 },
]
// 把数组中已勾选的总价累加
let amt = 0 // 总价
arr
  .filter((item) => item.state)
  .forEach((item) => {
    amt += item.price * item.count
  })
console.log(amt)
// reduce((累加的结果, 当前的循环项)=>{}, 初始值)
const result2 = arr
  .filter((item) => item.state)
  .reduce((amt, item) => {
    return (amt += item.price * item.count)
  }, 0)
console.log(result2)

购物车案例


1. Header 区域

1.1 导入、注册并使用 Header 的组件

2. 请求数据

2.1 安装并在 APP 中导入 axios
2.2 在 methods 方法中,定义 initCartList 函数(运用 async/await,解构赋值)请求列表数据,若成功,将请求回来的数据转存至 data (https://www.escook.cn/api/cart)
2.3 在 created 生命周期函数中,调用封装的 initCartList 函数
🔔 只要请求回来的数据,在页面渲染期间要用到,必须转存到 data 中

3. Goods 区域

3.1 导入、注册 Goods 组件
3.2 v-for 循环渲染 Goods
3.3 将循环得到的数据中的 title, img, price, id, state 传递到子组件 Goods 并渲染页面 – 父传子(自定义属性)
3.4 修改商品的勾选状态,在 Goods 组件中监听复选框的变化,在处理函数 statechange 中获取状态 newState,触发自定义事件 state-change,并将 id 和 状态作为参数传递给父组件,父组件通过处理函数 getNewState 接收参数,遍历数组并将状态传递给商品的属性 – 子传父(自定义事件)
3.5 修改 label 的 for 属性及 input 的 id ,保证选取到指定对象

4. Footer 区域

4.1 导入、注册 Footer 组件
4.2 通过计算属性 fullState 利用 every 得到全选按钮的值
🔔 一个数据的结果需要依赖于其他数据的变化来动态计算时,使用计算属性
4.3 通过列表是否全选决定 isfull 全选按钮的结果 – 父传子(自定义属性)
4.4 通过全选按钮 full-change 使用自定义事件将全选框的值传递给父组件。父组件通过自定义事件处理函数 getFullState 遍历数组将结果传递给每个商品的属性 – 子传父(自定义事件)

5. 计算总价格

5.1 通过计算属性 amt 在父组件计算选取商品的总价格,先过滤再累加。在子组件通过自定义属性 amount 接收 – 父传子(自定义属性)

6. Goods 内 Counter 区域

6.1 导入、注册并使用 Counter 组件
6.2 将 App 组件的 count 和 id 传递给 Goods 组件,再传递给 Counter 组件。渲染到页面

7. 加减数量

7.1 获取数量及 id
7.1 绑定点击事件 add 并生成传递的对象(包含 id 和数量)
7.3 通过 EventBus 把 obj 对象通过 share 事件发送给 App 组件
7.4 在 App 组件通过 created 中 EventBus 接收,并将结果赋值给对应属性
🔔 减操作时设置最小值 1

8. Footer 总数量

7.1 计算属性 total 筛选并计算总数量
7.2 数据 all 渲染到页面 – 父传子(自定义属性)

🔔 先指令 后绑定’:’ 最后事件’@’
🔔 计算属性必须 return


动态组件


1. 什么是动态组件

动态组件指的是动态切换组件的显示与隐藏。

2. 如何实现动态组件渲染

vue 提供了一个内置的 组件,专门用来实现动态组件的渲染。示例代码如下:

<!-- is 属性指定展示的组件名 -->
<component :is="comName"></component>
data() {
  return {
    // comName 表示要展示的组件的名字
    comName: "Left",
  };
},
  • component 标签是 vue 内置的,作用:组件的占位符
  • is 属性的值,表示要渲染的组件的名字
  • 切换组件时会销毁原组件

3. 使用 keep-alive 保持状态

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 组件保持动态组件的状态。示例代码如下:

<keep-alive>
  <component :is="comName"></component>
</keep-alive>
  • keep-alive 可以把内部的组件进行缓存,而不是销毁组件

4. keep-alive 对应的生命周期函数

当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
当组件被激活时,会自动触发组件的 activated 生命周期函数。

created() {
  console.log("组件被创建了");
},
destroyed() {
  console.log("组件被销毁了");
},
activated() {
  console.log("组件被激活了,activated");
},
deactivated() {
  console.log("组件被缓存了,deactivated");
},
  • 当组件第一次被创建时,既会执行 created 生命周期,也会执行 activated 生命周期 ( 先 created 再 activated )
  • 当组件被激活时,只执行 activated 生命周期。因为组件没有被重新创建

5. keep-alive 的 include 属性

include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:

<keep-alive include="Left,Right">
  <component :is="comName"></component>
</keep-alive>
  • 相反的还有 exclude 属性 (但是 include exclude 二者不要同时使用)

6. name

如果在声明组件的时候,没有为组件指定 name 名称,则组件名称默认就是"注册时候的名称"
当提供了 name 属性之后,组件名称就以该名称为准

export default {
  name: 'MyLeft',
}
  • 组件的 “注册名称” 的主要应用常见是:以标签的形式,把注册好的组件,渲染和使用到页面结构中
  • 组件声明时候的 “name” 名称的主要应用常见:结合 标签实现组件缓存功能,以及在调试工具中看到组件的 name

插槽


1. 什么是插槽

插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。

08

可以把插槽认为是组件封装期间,为用户预留的内容的占位符。

2. 体验插槽的基础用法

在封装组件时,可以通过 元素定义插槽,从而为用户预留内容占位符。示例代码如下:

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 声明一个插槽 -->
    <!-- vue 官方规定:每一个 slot 插槽都要有一个 name 名称,默认为 default -->
    <slot name="default"></slot>
  </div>
</template>
<Left>
  <!-- 默认情况下,使用组件是提供的内容会填充到名字为 default 的插槽 -->
  <p>在使用 Left 标签时,用户自定义的内容</p>
</Left>
  • 若需要指定插槽
<Left>
  <template v-slot:default>
    <p>这是在 Left 组件的内容区域,声明的 p 标签</p>
  </template>
</Left>

🔔 注意

  1. 指定插槽时,自定义内容必须放在 template 标签内 且设置 v-slot:插槽名
  2. v-slot 指令不能直接用在元素上,必须在 template 标签上或者组件上
  3. template 标签是一个虚拟的标签,只起到包裹性质的作用,不会被渲染为任何实际性的 html 元素
  4. v-slot: 简写为 # ,例如 <template #default>
  5. 若在声明插槽时想要提供默认内容(后备内容),直接在插槽的两个标签间填写即可

3. 具名插槽

如果在封装组件时需要预留多个插槽节点,则需要为每个 插槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。

<template>
  <div class="article-container">
    <!-- 文章的标题 -->
    <div class="header-box">
      <slot name="title"></slot>
    </div>
    <!-- 文章的内容 -->
    <div class="content-box">
      <slot name="content"></slot>
    </div>
    <!-- 文章的作者 -->
    <div class="footer-box">
      <slot name="author"></slot>
    </div>
  </div>
</template>

<script>
  export default {
    // 首字母大写
    name: 'Article',
  }
</script>
  • 为具名插槽提供内容
    在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称(缩写为 # )。示例代码如下:
<article>
  <template #title>
    <h1>标题</h1>
  </template>

  <template #content>
    <p>内容</p>
  </template>

  <template #author>
    <div>作者</div>
  </template>
</article>

4. 作用域插槽

在封装组件时,为预留的 提供属性对应的值,这种用法叫做 “作用域插槽”

<slot name="content" msg="hello vue.js" :user="userinfo"></slot>
<!-- -------------------------------------- -->
<template #content="scope">
  <p>{{ scope }}</p>
</template>
  • 解构插槽
<template #content="{ msg, user}">
  <p>{{ msg }}</p>
  <p>{{ user.name }}</p>
</template>

5. 基于插槽改造作用域案例

  1. 在 Goods 组件中预留 slot 插槽
  2. App 在使用 Goods 组件时,通过自定义插槽内容传入 Counter 组件,并导入、注册 Counter 组件
  3. 通过自定义属性将 goods_count 直接传给 Counter 组件
  4. 通过自定义事件,将 “点击数量加一” 后的数量直接传递给 App 组件
  5. App 组件通过自定义事件的处理函数 getNewNum 将数量赋值给该对象的商品数量

自定义指令


1. 什么是自定义指令

vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。

2. 自定义指令的分类

vue 中的自定义指令分为两类,分别是:

  • 私有自定义指令
  • 全局自定义指令

3. 私有自定义指令

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。示例代码如下:

directives: {
  color: {
    // 为绑定的 HTML 元素设置红色字体
    bind(el) { // 当指令第一次被绑定到元素上的时候,会立即触发 bind 函数
      // 形参的 el 是绑定了此指令的、原生的 DOM 对象
      el.style.color = 'red'
    }
  }
}

4. 使用自定义指令

在使用自定义指令时,需要加上 v- 前缀。示例代码如下:

<h1 v-color>App</h1>

5. 为自定义指令动态绑定参数值

在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值:

data() {
  return {
    color: 'red'
  }
}
<h1 v-color="color">App</h1>

6. 通过 binding 获取指令的参数值

在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值:

directives: {
  // 定义名为 color 的自定义指令 指向一个配置对象
  color: {
    bind(el, binding) {
      el.style.color = binding.value;
      // 通过 binding 对象的 .value 属性,获取动态的参数值
    },
  },
},

7. update 函数

bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函数会在每次 DOM 更新时被调用。示例代码如下:

update(el, binding) {
  el.style.color = binding.value;
},

8. 函数简写

如果 bind 和 update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:

directives: {
  // 在 bind 和 update 时,会触发相同的业务逻辑
  color(el, binding) {
    el.style.color = binding.value
  }
}
  1. 全局自定义指令
    全局共享的自定义指令需要通过“Vue.directive()”进行声明,示例代码如下:
// 参数1:字符串,表示全局自定义指令的名字
// 参数2:对象,用来接收指令的参数值
Vue.directive('color', function (el, binding) {
  el.style.color = binding.value
})

ESlint 基本使用


1. no-console 及 no-debugger

在配置文件 .eslintrc.js 中

rules: {
  'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
  • no-console 用在生产阶段,提醒去除所有的 console 命令
  • debugger 使用代码的形式打断点,此处表示在生产阶段提醒去除 debugger 命令

2. 遇到错误

复制问题内容到官网 “规则” 页面通过 ctrl+f 查询

3. 问题类型

问题类型描述
no-trailing-spaces禁用行尾空格
quotes强制使用一致的反勾号、双引号或单引号
key-spacing强制在对象字面量的属性中键和值之间使用一致的间距
comma-dangle要求或禁止末尾逗号
no-multiple-empty-lines禁止出现多行空行
no-trailing-spaces禁用行尾空格
eol-last要求或禁止文件末尾存在空行
spaced-comment强制在注释中 // 或 /* 使用一致的空格
indent强制使用一致的缩进
import/firstimport 导入模块的语句必须声明在文件的顶部
space-before-function-paren强制在 function 的左括号之前使用一致的空格

4. 修改 ESlint 配置

查看官方文档,在 .eslintrc.js 中添加规则进行修改

5. 配置插件

5.1 ESlint

"editor.codeActionsOnSave": {
  "source.fixAll": true,
}

5.2 Prettier - Code formatter

"prettier.configPath": "C:\\Users\\xxxx\\.prettierrc",

"eslint.alwaysShowStatus": true,
"prettier.trailingComma": "none",
"prettier.semi": false,
// 每行文字个数超出此限制将会被迫换行
"prettier.printWidth": 300,
// 使用单引号替换双引号
"prettier.singleQuote": true,
"prettier.arrowParens": "avoid",
// 设置 .vue 文件中,HTML代码的格式化插件
"vetur.format.defaultFormatter.html": "js-beautify-html",
"vetur.ignoreProjectWarning": true,
"vetur.format.defaultFormatterOptions": {
    "js-beautify-html": {
        "wrap_attributes": false
    },
    "prettier": {
        "printWidth": 300,
        "trailingComma": "none",
        "semi": false,
        "singleQuote": true,
        "arrowParens": "avoid"
    }
},
  • 新建 .prettierrc 文件并写入
{semi: false, singleQuote: true, printWidth: 300}
  • 右键,设置格式化方法 - prettier

axios


1. 使用步骤

  • 在 main.js 文件中
import axios from 'axios'
// 把 axios 挂载到 Vue.prototype 上,供每个 .vue 组件的实例直接调用
Vue.prototype.$http = axios

❄️ 缺点:把 axios 挂载到 Vue 原型上缺点是无法实现 API 接口复用

  • 使用(组件中)
export default {
  methods: {
    async getInfo() {
      const { data: res } = await this.axios.get('http://www.liulongbin.top:3006/api/get')
      console.log(res)
    },
  },
}

2. 配置请求根路径

  • 在 main.js 文件中
axios.defaults.baseURL = '请求根路径'

Vue 中的样式绑定


1. Class 绑定

  • ① 原始方法 – class 的对象绑定
<style>
  .activated {
    color: red;
  }
</style>
<div @click="handleDivClick" :class="{activated: isActivated}">Hello World</div>
data: {
  isActivated: false
},
methods: {
  handleDivClick: function() {
    this.isActivated = !this.isActivated // 取反
  }
}
  • ② 现代方法
<style>
  .activated {
    color: red;
  }
</style>
<div @click="handleDivClick" :class="[activated, activatedOne]">Hello World</div>
data: {
  activated: "",
  activatedOne: "activated-one"
},
methods: {
  handleDivClick: function() {
    this.activated = this.activated === "activated" ? "" : "activated"
  }
}

2. Style 绑定

  • ① 方法 1
<div :style="styleObj" @click="handleDivClick">Hello World</div>
data: {
  styleObj: {
    color: "black"
  }
},
methods: {
  handleDivClick: function() {
    this.styleObj.color = this.styleObj.color === "black" ? "red" : "black"
  }
}
  • ② 方法 2
<div :style="[styleObj, {fontSize: '20px'}]" @click="handleDivClick">Hello World</div>
data: {
  styleObj: {
    color: "black"
  }
},
methods: {
  handleDivClick: function() {
    this.styleObj.color = this.styleObj.color === "black" ? "red" : "black"
  }
}

Vue 过渡 & 动画


1. 单元素/组件的过渡

过渡的类名

  • v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

  • v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

  • v-enter-to:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。

  • v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

  • v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

  • v-leave-to:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

🔔 注意 “v” 根据标签的 name 属性决定

.fade-enter,
.fade-leave-to {
  opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 2s;
}
<transition name="fade">
  <div v-if="show">Hello World</div>
</transition>

enter

leave

  • 原理图

transition

2. CSS 动画

  • example
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
.v-enter-active {
  transform-origin: left center;
  animation: bounce-in 1s;
}
.v-leave-active {
  transform-origin: left center;
  animation: bounce-in 1s reverse;
}
  • 自定义类名

enter-class
enter-active-class
enter-to-class
leave-class
leave-active-class
leave-to-class

<transition name="fade" enter-active-class="active" leave-active-class="leave">
  <div v-if="show">Hello World</div>
</transition>
  • 使用 animate.css

① 引入 css 文件

② 按照说明添加类进行使用

<transition name="fade" enter-active-class="animated swing" leave-active-class="animated shake">
  <div v-if="show">Hello World</div>
</transition>

3. Vue 中同时使用过渡和动画

<transition type="transition" name="fade" appear enter-active-class="animated swing fade-enter-active" leave-active-class="animated shake fade-leave-active" appear-active-class="animated swing">
  <div v-if="show">Hello World</div>
</transition>
  • 使用 type 并设置 animation 或 transition 来明确需要 Vue 监听的类型

  • 设置动画持续时间
    :duration="10000"
    :duration="{enter: 5000, leave: 10000}" – 定制进入和移出的持续时间

4. js 动画

事件:

v-on:before-enter=“beforeEnter”
v-on:enter=“enter”
v-on:after-enter=“afterEnter”
v-on:enter-cancelled=“enterCancelled”
v-on:before-leave=“beforeLeave”
v-on:leave=“leave”
v-on:after-leave=“afterLeave”
v-on:leave-cancelled=“leaveCancelled”

<transition name="fade" @before-enter="handleBeforeEnter" @enter="handleEnter" @after-enter="handleAfter
  <div v-if="show">Hello World</div>
</transition>
handleBeforeEnter(el) {
  el.style.color = "red"
},
handleEnter(el, done) {
  setTimeout(() => {
    el.style.color = "yellow"
  }, 2000);
  setTimeout(() => {
    done()
  }, 4000);
},
handleAfterEnter(el) {
  el.style.color = "blue"
}

🔔 注意

  • 当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。

  • 推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css=“false”,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。

  • Velocity 的使用

methods: {
  handleClick() {
    this.show = !this.show
  },
  handleBeforeEnter(el) {
    el.style.opacity = 0
  },
  handleEnter(el, done) {
    Velocity(el, {opacity: 1}, {duration: 1000, complete: done})
  },
  handleAfterEnter(el) {
    console.log('finish');
  }
},

5. 初始渲染的过渡

可以通过 appear attribute 设置节点在初始渲染的过渡

<transition appear>
  <!-- ... -->
</transition>

自定义 CSS 类名
appear
appear-class=“custom-appear-class”
appear-to-class=“custom-appear-to-class” (2.1.8+)
appear-active-class=“custom-appear-active-class”
自定义 JS 事件
appear
v-on:before-appear=“customBeforeAppearHook”
v-on:appear=“customAppearHook”
v-on:after-appear=“customAfterAppearHook”
v-on:appear-cancelled=“customAppearCancelledHook”

6. 多个元素或组件的过渡

① 多个元素的过渡

<transition mode="in-out">
  <div v-if="show" key="hello">Hello World</div>
  <div v-else key="bye">Bye World</div>
</transition>
  • in-out 先显示再隐藏
  • out-in 先隐藏再显示

② 多个组件的过渡
运用自定义组件的方法

<div id="root">
  <transition mode="out-in">
    <component :is="type"></component>
  </transition>
  <button @click="handleClick">切换</button>
</div>
<script>
  Vue.component('child', {
    template: '<div>child</div>',
  })
  Vue.component('child1', {
    template: '<div>child1</div>',
  })
  var vm = new Vue({
    el: '#root',
    data() {
      return {
        type: 'child',
      }
    },
    methods: {
      handleClick() {
        this.type = this.type === 'child' ? 'child1' : 'child'
      },
    },
  })
</script>

7. 列表过渡

<div id="root">
  <transition-group>
    <div v-for="item of list" :key="item.id">{{ item.title }}</div>
  </transition-group>
  <button @click="handleBtnClick">Add</button>
</div>
<script>
  var count = 0
  var vm = new Vue({
    el: '#root',
    data() {
      return {
        list: [],
      }
    },
    methods: {
      handleBtnClick() {
        this.list.push({
          id: count++,
          title: 'hello world',
        })
      },
    },
  })
</script>

8. 动画封装

  • 封装
Vue.component('fade', {
  props: ['show'],
  template: `
    <transition @before-enter="handleBeforeEnter" @enter="handleEnter">
      <slot v-if="show"></slot>
    </transition>
  `,
  methods: {
    handleBeforeEnter(el) {
      el.style.color = 'red'
    },
    handleEnter(el, done) {
      setTimeout(() => {
        el.style.color = 'green'
        done()
      }, 2000)
    },
  },
})
  • 使用
<fade :show="show">
  <div>Hello World</div>
</fade>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值