vue 简介

什么是 vue

官方给出的概念:Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。
构建用户界面:将一系列数据转化为用户观看的界面
渐进式: Vue是自底向上逐层的应用

  • 简单应用:只需一个轻量小巧的核心库
  • 复杂应用:可以引入各式各样的Vue插件

MVVM

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

在 MVVM 概念中:

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

MVVM 的工作原理

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

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

vue的基本使用步骤:

  1. 导入 vue.js 的 script 脚本文件
  2. 在页面中声明一个将要被 vue 所控制的 DOM 区域
  3. 创建 vm 实例对象(vue 实例对象)
<body>
  <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
  <div id="app">{{ username }}</div>
  <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
  <script src="./lib/vue-2.6.12.js"></script>
  <!-- 2. 创建 Vue 的实例对象 -->
  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        username: 'zhangsan'
      }
    })
  </script>
</body>

vue 的调试工具

1. 安装 vue-devtools 调试工具

vue 官方提供的 vue-devtools 调试工具,能够方便开发者对 vue 项目进行调试与开发。

Chrome 浏览器在线安装 vue-devtools :

https://chrome.google/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

FireFox 浏览器在线安装 vue-devtools :

https://addons.mozilla/zh-CN/firefox/addon/vue-js-devtools/

2. 配置 Chrome 浏览器中的 vue-devtools

点击 Chrome 浏览器右上角的 按钮,选择更多工具 -> 扩展程序 -> Vue.js devtools 详细信息,并勾选两个选项:在所有网站上和允许访问文件网址。

3. 使用 vue-devtools 调试 vue 页面

在浏览器中访问一个使用了 vue 的页面,打开浏览器的开发者工具,切换到 Vue 面板,即可使用 vue-devtools 调试当前的页面。

vue 的指令与过滤器

指令的概念

指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。

vue 中的指令按照不同的用途可以分为如下 6 大类:

  1. 内容渲染指令
  2. 属性绑定指令
  3. 事件绑定指令
  4. 双向绑定指令
  5. 条件渲染指令
  6. 列表渲染指令

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

内容渲染指令

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

v-text

用法示例:

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

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

插值语法

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

<p>姓名:{{username}}</p>

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

v-html

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

<p v-html="discription"></p>

属性绑定指令

v-bind

如果需要为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令。用法示例如下:

<input type="text" v-bind:placeholder="inputValue" />

温馨提示:v-bind的简写形式:英文的:

使用 Javascript 表达式

在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算,例如:

{{number + 1}}
<div :id="'list-' + id"></div>

事件绑定指令

v-on

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

<h3>count 的值为:{{count}}</h3>
<button v-on:click="addCount">+1</button>

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

**温馨提示:**v-on的简写形式:@

通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明:

  <script>
    // 创建 Vue 的实例对象
    const vm = new Vue({
      // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
      el: '#app',
      // data 对象就是要渲染到页面上的数据
      data: {
        count: 0
      },
      // methods 的作用,就是定义事件的处理函数
      methods: {
        addCount() {
          this.count += 1
        }
      },
    })
  </script>

v-on 指令可以接收到事件参数对象 event,示例代码如下:

<h3>conut 的值为:{{count}}</h3>
<button v-on:click="addCount">+1</button>
//------分割线------
methods:{
	addCount(e){ //接收事件参数对象 event,简写为 e
		const nowBgColor = e.target.style.backgroundColor
		e.target.style.backgroundColor = nowBgColor === 'red' ? '' : 'red'
		this.count += 1
	}
}

绑定事件并传参

在使用 v-on 指令绑定事件时,可以使用 ( ) 进行传参,示例代码如下:

<h3>conut 的值为:{{count}}</h3>
<button @click="addNewCount(2)">+2</button>
//------分割线------
methods:{
    // 在形参处用 step 接收传递过来的参数值
	addNewCount(step){ 
		this.count += step
	}
}

$event

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

事件修饰符

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

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

语法格式如下:

<!-- 触发 click 点击事件时,阻止a链接的默认跳转行为-->
<a href="http://www.baidu" @click.prevent="onLinkClick">百度首页</a>

按键修饰符

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

<!-- 只有在 `key``Enter`时调用`vm.submit()`-->
<input @keyup.enter="submit">
<!-- 只有在 `key``Esc`时调用`vm.clearInput()`-->
<input @keyup.Esc="clearInput">    

双向绑定指令

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

  <div id="app">
    <p>用户的名字是:{{ username }}</p>
    <input type="text" v-model="username">
    <hr>
    <input type="text" :value="username">
    <hr>
    <select v-model="city">
      <option value="">请选择城市</option>
      <option value="1">北京</option>
      <option value="2">上海</option>
      <option value="3">广州</option>
    </select>
  </div>

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>

条件渲染指令

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

  <div id="app">
    <p v-if="flag">这是被 v-if 控制的元素</p>
    <p v-show="flag">这是被 v-show 控制的元素</p>
    <hr>
    <div v-if="type === 'A'">优秀</div>
    <div v-else-if="type === 'B'">良好</div>
    <div v-else-if="type === 'C'">一般</div>
    <div v-else></div>
  </div>

v-if 和 v-show 的区别

实现原理不同:

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

性能消耗不同:

v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此:

  • 如果需要非常频繁地切换,则使用 v-show 较好
  • 如果在运行时条件很少改变,则使用 v-if 较好

v-else

v-if 可以单独使用,或配合 v-else 指令一起使用:

<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-if 指令一起使用,否则它将不会被识别!

v-else-if

v-else-if 指令,顾名思义,充当 v-if 的“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-if 指令必须配合 v-if 指令一起使用,否则它将不会被识别!

列表渲染指令

v-for

vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for 指令需要使

item in items 形式的特殊语法,其中:

  • items 是待循环的数组
  • item 是被循环的每一项
data:{
	list:[//列表数据
		{id:1,name:'zs'}
         {id:2,name:'ls'}
	]
}
//------分割线------
<ul>
    <li v-for="item in list">姓名是:{{item.name}}</li>
</ul>

v-for 中的索引

v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items,示例代码如下:

data: {
	list: [
		{ id: 1, name: '张三' },
		{ id: 2, name: '李四' },
		{ id: 3, name: '王五' },
		{ id: 4, name: '张三' },
		]
}
//------分割线------
<ul>
    <li v-for="(item,index) in list">索引是:{{index}},姓名是:{{item.name}}</li>
</ul>

注意:v-for 指令中的 item 项和 index 索引都是形参,可以根据需要进行重命名。例如 (user, i) in userlist

使用 key 维护列表的状态

当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。

为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能。此时,需要为每项提供一个唯一的 key 属性:

<!-- 用户列表区域 -->
    <ul>
      <li v-for="(user, index) in userlist" :key="user.id">
        <input type="checkbox" />
        姓名:{{user.name}}
      </li>
    </ul>

key 的注意事项

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

过滤器

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定。

过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用,示例代码如下:

<p>{{ message |capitalize }}</p>
<div v-bind:id="rawId | formatId"></div>

定义过滤器

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

const vm = new Vue({
      el: '#app',
      data: {
        message: 'hello vue.js'
      },
      // 过滤器函数,必须被定义到 filters 节点之下
      // 过滤器本质上是函数
      filters: {
        // 注意:过滤器函数形参中的 val,永远都是“管道符”前面的那个值
        capi(val) {
          // 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来
          // val.charAt(0)
          const first = val.charAt(0).toUpperCase()
          // 字符串的 slice 方法,可以截取字符串,从指定索引往后截取
          const other = val.slice(1)
          // 强调:过滤器中,一定要有一个返回值
          return first + other
        }
      }
    })

私有过滤器和全局过滤器

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

    // 使用 Vue.filter() 定义全局过滤器
    Vue.filter('capi', function (str) {
      const first = str.charAt(0).toUpperCase()
      const other = str.slice(1)
      return first + other + '~~~'
    })

连续调用多个过滤器

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

{{ message | filterA | filterB }}

示例代码如下:

<!-- 串联调用多个过滤器 -->
<p>{{text | capitalize | maxLength}}</p>
// 全局过滤器 - 首字母大写
Vue.filter('capitalize',(str) =>{
    return str.charAt(0).toUpperCase()+str.slice(1)+'~~'
})
// 全局过滤器 - 控制文本的最大长度
Vue.filter('maxLength',(str)=>{
    if(str.length<=10) 
        return str.slice(0,11)+'...'
})

过滤器传参

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

<p>{{message | filterA(arg1,arg2)}}</p>
Vue.filter('filterA',(msg,arg1,arg2)=>{
    // 过滤器的代码逻辑...
})

过滤器的兼容性

过滤器仅在 vue 2.x 和 1.x 中受支持,在 vue 3.x 的版本中剔除了过滤器相关的功能。

在企业级项目开发中:

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

具体的迁移指南,请参考 vue 3.x 的官方文档给出的说明:

https://v3.vuejs/guide/migration/filters.html#migration-strategy

watch 侦听器

什么是 watch 侦听器

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

const vm = new Vue({
      el: '#app',
      data: {
        username: 'admin'
      },
      // 所有的侦听器,都应该被定义到 watch 节点下
      watch: {
        // 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可
        // 新值在前,旧值在后
        username(newVal) {
          if (newVal === '') return
          // 1. 调用 jQuery 中的 Ajax 发起请求,判断 newVal 是否被占用!!!
          $.get('https://www.escook/api/finduser/' + newVal, function (result) {
            console.log(result)
          })
        }
      }
    })

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

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

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

immediate 选项

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

watch: {
	// 定义对象格式的侦听器
	username: {
		// 侦听器的处理函数
		handler(newVal, oldVal) {
			console.log(newVal, oldVal)
			},
// immediate 选项的默认值是 false
// immediate 的作用是:控制侦听器是否自动触发一次!
immediate: true
	}
}

deep 选项

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

const vm = new Vue({
	el: '#app',
	data: {
		// 用户的信息对象
		info: {username: 'admin'
	},
// 所有的侦听器,都应该被定义到 watch 节点下
watch: {
	info: {
		handler(newVal) {
			console.log(newVal.username)
		},
		// 开启深度监听,只要对象中任何一个属性变化了,都会触发“对象的侦听器”
		deep: true
		}
	}
})

监听对象单个属性的变化

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

const vm = new Vue({
	el: '#app',
	data: {
		// 用户的信息对象
		info: {username: 'admin'
	},
// 所有的侦听器,都应该被定义到 watch 节点下
watch: {
	'info.username': {
		handler(newVal) {
			console.log(newVal.username)
			}
		}
	}
})

计算属性

什么是计算属性

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

var vm = new Vue({
   el: '#app',
   data: {
   		r: 0,g: 0,b: 0
   },
computed:{
    rgb(){
  		return `rgb(${this.r},${this.g},${this.b})`      
    }
},
methods: {
   show() {console.log(this.rgb)
   },
})

计算属性的特点

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

vue-cli

什么是 vue-cli

vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。

引用自 vue-cli 官网上的一句话:

程序员可以专注在撰写应用上,而不必花好几天去纠结 webpack 配置的问题。

**中文官网:**https://cli.vuejs/zh/

安装和使用

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

npm install -g @vue/cli

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

vue 项目的运行流程

在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。其中:

  1. App.vue 用来编写待渲染的模板结构
  2. index.html 中需要预留一个 el 区域
  3. main.js 把 App.vue 渲染到了 index.html 所预留的区域中

vue 组件

什么是组件化开发

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

vue 中的组件化开发

vue 是一个支持组件化开发的前端框架。

vue 中规定:组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。

vue 组件的三个组成部分

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

  • template -> 组件的模板结构
  • script -> 组件的 JavaScript 行为
  • style -> 组件的样式

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

template

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

<template>
	<!--当前组件的 DOM 结构,需要定义到 template 标签的内部-->
</template>

注意:

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

script

vue 规定:开发者可以在 < script > 节点中封装组件的 JavaScript 业务逻辑。

< script > 节点的基本结构如下:

<script>
//组件相关的data数据、methods方法等都要定义到export default所到出的对象中
export default {}
</script>

.vue 组件中的 data 必须是函数

vue 规定:.vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。因此在组件中定义 data 数据节点时,下面的方式是错误的:

data:{//组件中,data不能直接指向一个数据对象
	count:0
}

会导致多个组件实例共用同一份数据的问题。

style

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

<style>
h1{
	font-wight:normal;
}
</style>

让 style 中支持 less 语法

在 < style > 标签上添加 lang=“less” 属性,即可使用 less 语法编写组件的样式:

<style lang="less">
h1{
	font-wight:normal;
}
</style>

使用组件的三个步骤

//1.使用import语法导入需要的组件
import Left from '@/components/Left.vue'
//2.以标签的形式使用注册的组件
<div class="box">
	<Left></Left>
</div>
//3.使用components节点注册组件
export default{
	components:{
		Left
	}
}

注册全局组件

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

// 导入需要被全局注册的那个组件
import Count from '@/components/Count.vue'
// 参数1:字符串格式,表示组件的“注册名称”
// 参数2:需要被全局注册的那个组件
Vue.component('MyCount', Count)

组件的 props

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

export default {
	//组件的自定义属性
	props:['自定义属性A','自定义属性B','自定义属性C'...]
    //组件的私有数据
  	data() {
    	return { }
    }
}

1.props 是只读的

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

props:['init'],
data(){
	return{
		count:this.init //把 this.init 的值转存到count
	}
}

2.props 的 default 默认值

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

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

3.props 的 type 值类型

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

export default{
	props:{
		init:{
			//用default定义属性的默认值
			default:0
			//用type属性定义属性的值类型,
			type:Number
			}
	}
}

4.props 的 required 必填项

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

export default{
	props:{
		init:{
			//用type属性定义属性的值类型,
			type:Number
			//必填项效验
			required:true
			}
	}
}

组件之间的样式冲突问题

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

  • 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
  • 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

如何解决组件样式冲突的问题

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

<template>
  <div class="container" data-v-001>
		<h3 data-v-001>hello</h3>
  </div>
</template>

<style>
	.container[data-v-001]{
		border:1px solid red;
	}
</style>

style 节点的 scoped 属性

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

<template>
  <div class="container">
		<h3>hello</h3>
  </div>
</template>

<style scoped>
	.container{
		border:1px solid red;
	}
</style>

/deep/ 样式穿透

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

<style lang="less" scoped>
.title{
	color:blue;/*生成的选择器格式为:.title[data-v-052242de]*/
}
/deep/.title{
	color:blue;/*生成的选择器格式为:[data-v-052242de] .title*/
}
</style>

组件的生命周期

生命周期 & 生命周期函数

**生命周期(Life Cycle)**是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。

**生命周期函数:**是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。

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

组件生命周期函数的分类

组件创建阶段:

  • new Vue()
  • beforeCreate
  • created
  • beforeMount
  • mounted

组件运行阶段:

  • beforeUpdate
  • update

组件销毁阶段:

  • beforeDestory
  • destoryed

生命周期图示

可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:Vue 实例 — Vue.js (vuejs)

组件之间的数据共享

组件之间的关系

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

  • 父子关系
  • 兄弟关系

父子组件之间的数据共享

父子组件之间的数据共享又分为:

  • 父 -> 子共享数据
  • 子 -> 父共享数据

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

2.子组件向父组件共享数据

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

兄弟组件之间的数据共享

在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus。

EventBus 的使用步骤

  1. 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
  2. 在数据发送方,调用 bus.$emit(‘事件名称’, 要发送的数据) 方法触发自定义事件
  3. 在数据接收方,调用 bus.$on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件

ref 引用

什么是 ref 引用

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

<template>
  <div>
    <h3>MyRef</h3>
    <button @click="getRef">获取$refs引用</button>
  </div>
</template>
export default{
  methods: {
    getRef(){console.log(this)}//this是当前组件的实例对象,this.$refs默认指向空对象
  }
}

使用 ref 引用 DOM 元素

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

<!--使用ref属性,为对应的DOM添加引用名称-->
<h3>MyRef</h3>
<button @click="getRef">获取$refs引用</button>
methods: {
   getRef(){
       console.log(this)}//通过this.$refs引用的名称,可以获取到DOM元素的引用
       this.$refs.myh3.style.color = 'red'//操作DOM元素,把文本颜色改为红色
  },
}

使用 ref 引用组件实例

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

// 使用ref属性,为对应的“组件”添加引用名称
<my-counter ref="counterRef"></my-counter>
<button @click="getRef">获取$ref引用</button>
methods: {
   getRef(){
       console.log(this)}//通过this.$refs引用的名称,可以引用组件的实例
       this.$refs.counterRef.add()//引用组件的实例后,就可以调用组件上的methods方法
  },
}

控制文本框和按钮的按需切换

通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。示例代码如下:

<template>
<input type="text" v-if="inputVisible" ref="ipt">
<button v-else @click="showInput">展示input输入框</button>
</template>

<script>
export default{
  data() {
    return {
      //控制文本框和按钮的按需切换
      inputVisible:false,
    }
  },
  methods: {
    	showInput(){//切换布尔值,显示文本框
          	this.inputVisible = true  
        },
   },
}
</script>

让文本框自动获得焦点

当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的.focus() 方法即可。示例代码如下:

<input type="text" v-if="inputVisible" ref="ipt">
<button v-else @click="showInput">展示input输入框</button>

methods: {
  showInput(){
    this.inputVisible = true
    //获取文本框的DOM引用,并调用.focus()使其自动获取焦点
    this.$refs.ipt.focus()
  },
}

this.$nextTick(cb) 方法

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

<input type="text" v-if="inputVisible" ref="ipt">
<button v-else @click="showInput">展示input输入框</button>
methods: {
  showInput(){
    this.inputVisible = true
    //把对input文本框的操作,推迟到下次DOM更新之后,否则页面上根本不存在文本框元素
    this.$nextTick(() =>{
        this.$refs.ipt.focus()
    })
  },
}

动态组件

什么是动态组件

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

如何实现动态组件渲染

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

data(){
  //当前要渲染的组件名称
  return{comName:'Left'}
}
// 通过is属性,动态指定要渲染的组件
<component :is="comName"></component>
//点击按钮,动态切换组件的名称
<button @click="comName = 'Left'">显示Left组件</button>
<button @click="comName = 'Right'">显示Left组件</button>

使用 keep-alive 保持状态

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

<keep-alive>
    <component :is="comName"></component>
</keep-alive>

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

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

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

    export default {
      create(){console.log('组件被创建了')},
      destroyed() {
        console.log('组件被销毁了')
      },
      activated() {
        console.log('Left组件被激活了')
      },
      deactivated() {
        console.log('Left组件被缓存了')
      },
    }
    

keep-alive 的 include 属性

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

<keep-alive include="MyLeft,MyRight">
    <component :is="comName"></component>
</keep-alive>

插槽

什么是插槽

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

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

体验插槽的基础用法

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

<template>
  <p></p>
<!--为用户预留内容空间-->
  <slot></slot>
  <p></p>
</template>
<my-com-1>
    <!--使用MyCom1组件时,为插槽指定具体内容-->
    <p>~~~用户自定义内容</p>
</my-com-1>

没有预留插槽的内容会被丢弃

如果在封装组件时没有预留任何 <slot> 插槽,则用户提供的任何自定义内容都会被丢弃。示例代码如下:

<template>
  <p></p>
<!--没有为用户预留内容空间-->
  <p></p>
</template>
<my-com-1>
    <!--自定义具体内容被抛弃-->
    <p>~~~用户自定义内容</p>
</my-com-1>

后备内容

封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。示例代码如下:

<template>
  <p></p>
  <slot>后备内容</slot>
  <p></p>
</template>

具名插槽

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

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot name="main"></slot>
  </main>
</div>

注意:没有指定 name 名称的插槽,会有隐含的名称叫做 “default”。

1.为具名插槽提供内容

在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。示例代码如下:

<my-com-2>
  <template v-slot:header>
    <h1>hello</h1>
  </template>
  <template v-slot:default>
    <h1>hello</h1>
    <h2>hello</h2>
  </template>
</my-com-2>

2.具名插槽的简写形式

跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header可以被重写为 #header:

<my-com-2>
  <template #header>
    <h1>hello</h1>
  </template>
  <template #default>
    <h1>hello</h1>
    <h2>hello</h2>
  </template>
</my-com-2>

作用域插槽

在封装组件的过程中,可以为预留的 插槽绑定 props 数据,这种带有 props 数据的 叫做“作用域插槽”。示例代码如下:

<tbody>
  <slot v-for="item in list" :user="item"></slot>
</tbody>

1.使用作用域插槽

可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据。示例代码如下:

<my-com-3>
  <!--接收作用域插槽对外提供的数据-->
  <template v-slot:default="scope">
    <tr>
	<!--使用作用域插槽的数据-->
      <td>{{scoped}}</td>
    </tr>
  </template>
</my-com-3>

2.解构插槽 Prop

作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。示例代码如下:

<my-com-3>
  <template #default="{user}">
    <tr>
      <td>{{user.id}}</td>
	  <td>{{user.name}}</td>
      <td>{{user.state}}</td>
    </tr>
  </template>
</my-com-3>

自定义指令

什么是自定义指令

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

自定义指令的分类

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

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

私有自定义指令

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

export default router
directives:{
  color:{
    //为绑定到的HTML元素设置红色的文字
    bind(el){
      //形参中的el是绑定了此指令的,原生的DOM对象
      el.style.color = 'red'
    }
  }
}

使用自定义指令

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

<h1 v-color>App组件</h1>

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

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

data(){
    return{
        color:'red'//定义color颜色值
    }
}
<!--在使用指令时,动态为当前指令绑定参数值color-->
<h1 v-color="color">App组件</h1>

通过 binding 获取指令的参数值

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

directives:{
  color:{
    bind(el,binding){
      //通过binding对象的.value属性,获取动态的参数值
      el.style.color = binding.value
    }
  }
}

update 函数

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

directives:{
  color:{
    //当指令第一次被绑定到元素时被调用
    bind(el,binding){
      el.style.color = binding.value
    },
    //每次DOM更新时被调用
    update(el,binding){
        el.style.color = binding.value
    }
  }
}

函数简写

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

directives:{
  //在insert和update时,会触发相同的业务逻辑
  color(el,binding){
     el.style.color = binding.value
    }
}

全局自定义指令

全局共享的自定义指令需要通过“Vue.directive()”进行声明,示例代码如下:

//参数1:字符串,表示全局自定义指令的名字
//参数2:对象,用来接收指令的参数值
Vue.directive('color',function(el,binding){
    el.style.color = binding.value
})

路由

什么是路由

路由(英文:router)就是对应关系。

SPA 与前端路由

SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现。

结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!

前端路由通俗易懂的概念:Hash 地址与组件之间的对应关系。

前端路由的工作方式

  1. 用户点击了页面上的路由链接
  2. 导致了 URL 地址栏中的 Hash 值发生了变化
  3. 前端路由监听了到 Hash 地址的变化
  4. 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

结论:前端路由,指的是 Hash 地址与组件之间的对应关系!

实现简易的前端路由

步骤1:通过 < component > 标签,结合 comName 动态渲染组件。示例代码如下:

<!--通过 is 属性,指定要展示的组件名称-->
<component :is="conName"></component>

export default{
	name:'App',
	data(){
		return{
			//要展示的组件名称
			conName:'Home'
		}
	}
}

步骤2:在 App.vue 组件中,为 < a > 链接添加对应的 hash 值:

<a href="#/home">Home</a>&nbsp;
<a href="#/movie">Home</a>&nbsp;
<a href="#/about">Home</a>

步骤3:在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:

create(){
    window.onhashchange = () =>{
      switch(location.hash){
        case '#/home'://点击了“首页”的链接
          this.conName = 'Home'
          break
        case '#/Movie'://点击了“电影”的链接
          this.conName = 'Movie'
          break
        case '#/about'://点击了“关于”的链接
          this.conName = 'About'
          break
      }
    }
  }

vue-router 的基本用法

什么是 vue-router

vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。

vue-router 的官方文档地址:https://router.vuejs/zh/

vue-router 安装和配置的步骤

  • 安装 vue-router 包
  • 创建路由模块
  • 导入并挂载路由模块
  • 声明路由链接和占位符

在项目中安装 vue-router

在 vue2 的项目中,安装 vue-router 的命令如下:

npm i vue-router@3.5.2 -S

创建路由模块

在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码:

//1.导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'
//2.调用 Vue.use()函数,把VueRouter安装为Vue的插件
Vue.use(VueRouter)
//3.创建路由的实例对象
const router = new VueRouter()
//4.向外共享路由的实例对象
export default router
1.导入并挂载路由模块

src/main.js 入口文件中,导入并挂载路由模块。示例代码如下:

import Vue from 'vue'
import App from './App.vue'
// 导入路由模块
import router from '@/router'

new Vue({
  render: h => h(App),
  //2.挂载路由模块
  router
}).$mount('#app')
2.声明路由链接和占位符

src/App.vue 组件中,使用 vue-router 提供的 < router-link > 和 < router-view > 声明路由链接和占位符:

<template>
  <div class="app-container">
    <h1>App 组件</h1>
    <!-- 1.定义路由链接 -->
    <router-link to="/home">首页</router-link>
    <router-link to="/movie">电影</router-link>
    <router-link to="/about">关于</router-link>
    <hr/>
    <!-- 2.定义路由的占位符 -->
    <router-view></router-view>
  </div>
</template>
3.声明路由的匹配规则

src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。示例代码如下:

// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
//创建路由的实例对象
const router = new VueRouter({
  routes: [//声明路由的匹配规则
    //path:表示要匹配的hash地址,component:表示要展示的路由组件
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About }]
})

vue-router 的常见用法

路由重定向

路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

const router = new VueRouter({
  routes: [
    //当用户访问/的时候,通过redirect属性跳转到/home对应的路由规则
    { path: '/', redirect: '/login' },
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About }]
})

嵌套路由

通过路由实现组件的嵌套展示,叫做嵌套路由

1.声明子路由链接和子路由占位符

在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。示例代码如下:

<template>
  <div>
    <h3>About 组件</h3>
    <!-- 在关于页面中,声明两个子路由链接 -->
    <router-link to="/about/tab1">tab1</router-link>
    <router-link to="/about/tab2">tab2</router-link>
    <hr/>
    <!-- 占位符 -->
    <router-view></router-view>
  </div>  
</template>
2.通过 children 属性声明子路由规则

src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则:

import Tab1 from '@/components/tabs/Tab1'
import Tab2 from '@/components/tabs/Tab2'
const router = new VueRouter({
  routes: [
    { //home页面的路由规则(父级路由规则)
      path: '/home',
      component: Home,
      children: [//通过children属性,嵌套声明子级路由规则
        { path: 'tab1', component: tab1 },//访问/home/tab1时,展示Tab1组件
        { path: 'tab2', component: tab2 },//访问/home/tab2时,展示Tab2组件
      ]
    }
  ]
})

动态路由

1.动态路由的概念

动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。

在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下:

{path:'/movie/:id',component:Movie}
//id:动态参数名称,将以下3个路由规则合并成一个,提高路由规则的复杂性
{path:'/movie/:1',component:Movie}
{path:'/movie/:2',component:Movie}
{path:'/movie/:3',component:Movie}
2.$route.params 参数对象

在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值。

<template>
  <div class="movie-container">
    <!-- this.$route 是路由的参数对象 -->
    <h3>Movie 组件 -- {{this.$route.params.id}}</h3>
  </div>
</template>
<script>
export default {
  name: 'Movie' 
}
</script>
3.使用 props 接收路由参数

为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参。示例代码如下:

{path:'/movie/:id',component:Movie,props:true}
<template>
    <h3>Movie 组件 -- {{id}}</h3>
</template>
<script>
export default {
  props:['id']
}
</script>

声明式导航 & 编程式导航

在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:

  • 普通网页中点击 < a > 链接、vue 项目中点击 < router-link > 都属于声明式导航

在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如:

  • 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航

1.vue-router 中的编程式导航 API

vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:

① this.$router.push(‘hash 地址’)

  • 跳转到指定 hash 地址,并增加一条历史记录

② this.$router.replace(‘hash 地址’)

  • 跳转到指定的 hash 地址,并替换掉当前的历史记录

③ this.$router.go(数值 n)

  • 实现导航历史前进、后退

2.$router.push

调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。示例代码如下:

<template>
  <div>
    <h3>Home 组件</h3>
    <button @click="gotoMovie">跳转到Movie页面</button>
  </div>
</template>
<script>
export default {
  methods: {
    gotoMovie(){this.$router.push('/movie/1')}//后退到之前的组件界面
  }
}
</script>

3.$router.replace

调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

push 和 replace 的区别:

  • push 会增加一条历史记录
  • replace 不会增加历史记录,而是替换掉当前的历史记录

4.$router.go

调用 this.$router.go() 方法,可以在浏览历史中前进和后退。示例代码如下:

<template>
      <h3>{{id}}</h3>
      <button @click="goBack">后退</button>
</template>
<script>
export default {
  props:['id'],
  methods: {
    goBack(){this.$router.go(-1)}//后退到之前的组件界面
  },
}
</script>

$router.go 的简化用法

在实际开发中,一般只会前进和后退一层页面。因此 vue-router 提供了如下两个便捷方法:

① $router.back()

  • 在历史记录中,后退到上一个页面

② $router.forward()

  • 在历史记录中,前进到下一个页面

导航守卫

导航守卫可以控制路由的访问权限。示意图如下:

全局前置守卫

每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制:

//创建路由实例对象
const rounter = new VueRouter{{...}}
//调用路由实例对象的beforeEach方法,即可声明“全局前置守卫”
//每次发生路由导航跳转的时候,都会自动触发这个“回调函数”
router.beforeEach(fn)

守卫方法的 3 个形参

全局前置守卫的回调函数中接收 3 个形参,格式为:

//创建路由实例对象
const rounter = new VueRouter{{...}}
router.beforeEach((to,from,next) => {
	//to:将要访问的路由的信息对象
	//from:将要离开的路由的信息对象
	//next:函数,被调用时表示,允许这次路由导航
})

next 函数的 3 种调用方式

参考示意图,分析 next 函数的 3 种调用方式最终导致的结果:

  • 当前用户拥有后台主页的访问权限,直接放行:next()
  • 当前用户没有后台主页的访问权限,强制其跳转到登录页面:next(‘/login’)
  • 当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)

控制后台主页的访问权限

router.beforeEach((to,from,next) => {
  if(to.path === '/main'){
    const token = localStorage.getItem('token')
    if(token){
      next()//访问的是后台主页,且有token 的值
    }else{
      next('/login')//访问的是后台主页,但是没有token的值
    }
  }else{
    next()//访问的不是后台的主页,直接放行
  }
})

更多推荐

vue2.0 基础知识