Vue2基础入门

Vue概况

Vue概念

Vue 是一个用于 构建用户界面渐进式 JavaScript框架

  • 构建用户界面:基于数据渲染出用户看到的页面
  • 渐进式:根据需求循序渐进的学习和使用Vue
  • 框架:一套完整的项目解决方案,具有自己的语法规则(本质上还是使用JavaScript语法)

image-20230709125300352.png

Vue 的特点:

  • 采用组件化模式,提高代码复用率,且让代码更好维护
  • 声明式编码,让编程人员无序直接操作 DOM,提高开发效率
  • 使用虚拟 DOM + 优秀的 Diff 算法,尽量复用 DOM 节点

MVVM概念

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

image-20230626221542085.png

在 MVVM 概念中:

Model 表示当前页面渲染时所依赖的数据源

View 表示当前页面所渲染的 DOM 结构

ViewModel 表示 vue 的实例,它是 MVVM 的核心

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

image-20230626222109224.png

扫描二维码关注公众号,回复: 15716592 查看本文章

当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构

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

创建Vue实例

Snipaste_2023-05-14_20-49-31.png

国内CDN:BootCDN

国外CND:JSDELIVR

<!-- Vue所管理的范围 -->
<div id="app">
  <!-- 用于渲染的代码逻辑 -->
  <h1>{{ msg }}</h1>
  <a href="#">{{ count }}</a>
</div>
<!-- 引入的是开发版本包 - 包含完整的注释和警告 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
  // 一旦引入 VueJS核心包,在全局环境,就有了 Vue 构造函数
  new Vue({
    // 通过 el 配置选择器,指定 Vue 管理的是哪个容器,值通常为css选择器字符串
    el: '#app',
    // 通过 data 提供数据
    data: {
      msg: 'Hello World',
      count: 666
    }
  })
</script>

想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象

app 容器里面的代码依旧符合 html 规范,只不过混入了一些特殊的 Vue 语法。app 容器里面的代码被称为 Vue 模板

此外,Vue 还有先来先生效的原则:多个容器对应一个 Vue 实例,则 Vue 实例控制第一个容器;多个 Vue 实例对应一个容器,则只有第一个 Vue 实例才能控制该容器。总的来说,容器与 Vue 实例之间是一一对应的关系

el与data拓展

el的两种写法

<div id="app">
    <h1>{{msg}}</h1>
</div>
<script>
    const app = new Vue({
        el:'#app' //第一种写法,直接绑定
        data: {
        	msg:'hello world'
        }
    })
    app.$mount('#app') //第二种写法
</script>

data两种写法

<div id="app">
    <h1>{{msg}}</h1>
</div>
<script>
	const app = new Vue({
        el:'#app' 
        //第一种写法:对象式
        data: {
        	msg:'hello world'
        }
        //第二种写法:函数式                
        data:function(){
        	return {
                msg:'hello world'
            }
    	}
    	//由 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是 Vue 实例了
    })
</script>

插值表达式

  • 插值表达式是一种 Vue 的模板语法

  • 利用表达式进行插值,渲染到页面中

  • 表达式:是可以被求值的代码,JavaScript 引擎会将其计算出一个结果

  • 语法:

    {{表达式}}
    //小胡子语法
    

    使用的数据必须存在 data 中;支持的是表达式,而非语句不能在标签属性上使用 {{}} 插值

Vue响应式特性

  • 除了基本的模板渲染,Vue 背后还做了大量工作,最主要的就是实现了响应式特性
  • 响应式:数据变化,视图自动更新
  • data中的数据,最终会被添加到实例上
  • 访问数据: 实例.属性名
  • 修改数据: 实例.属性名 = 值

使用示例:

<div id="app">
  {{ msg }}
  {{ count }}
</div>
<script>
  const app = new Vue({
    el: '#app',
    // data中的数据,是会被添加到实例上
    data: {
      // 响应式数据 → 数据变化了,视图自动更新
      msg: 'Hello World',
      count: 666
    }
  })
</script>
  • 数据改变,视图会自动更新

image-20230514210801909.png

数据驱动视图,可以让程序员聚焦于数据,关注业务的核心逻辑,根据业务修改数据即可。此外我们需要注意的是,数据驱动视图是单向的数据绑定

Vue开发者工具

Vue Devtools

  • 通过谷歌应用商店安装(科学上网VPN)
  • 通过极简插件商店安装
  • 下载 → 开发者模式 → 拖拽安装 → 插件详情允许访问文件
  • 设置完成后,打开 Vue 运行的页面,调试工具中 Vue 栏,即可查看修改数据,进行调试

image-20230707004412385.png

Vue指令

Vue 指令是 Vue.js 框架中的特殊属性,用于向 HTML 元素添加交互式行为和动态功能。指令以 v- 前缀作为识别符,紧跟在指令名称后面的是其表达式或参数,Vue 会根据不同的指令,针对标签实现不同的功能

v-html

  • 作用:设置元素的 innerHTML
  • 语法:v-html = 表达式
  • v-html 后解析,因此标签内不能继续加其他内容,如插值表达式或文本,会被直接覆盖掉
  • 此外,v-text 为设置元素的 innerText,用于将数据渲染到元素的文本内容中,多数情况下使用 v-html

使用示例:

<div id="app">
    <div v-html="msg"></div>
</div>

<script>
   const app = new Vue({
     el: '#app',
     data: {
       msg: `<h1>Hello World!</h1>`
     }
   })
</script>

v-if与v-show

v-show

  • 作用: 控制元素显示隐藏
  • 语法: v-show = 表达式,表达式值 true 显示,false 隐藏
  • 原理: 切换display:none控制显示隐藏
  • 场景: 频繁切换显示隐藏的场景

v-if

  • 作用: 控制元素显示隐藏(条件渲染
  • 语法: v-if = 表达式,表达式值 true 显示, false 隐藏
  • 原理: 基于条件判断,是否创建移除元素节点
  • 场景: 要么显示,要么隐藏,不频繁切换的场景

使用示例:

<div id="app">
  <div v-show="flag" class="box">我是v-show控制的盒子</div>
  <div v-if="flag" class="box">我是v-if控制的盒子</div>
</div>

<script>
  const app = new Vue({
    el: '#app',
    data: {
      flag: false
    }
  })
</script>

v-else与v-else-if

  • 辅助 v-if 进行判断渲染
  • 语法:v-else v-else-if = "表达式"
  • 注意:需要紧挨着 v-if 一起使用

使用示例:

<div id="app">
    <p v-if="gender === 1">性别:男</p>
    <p v-else>性别:女</p>
    <hr>
    <p v-if="score >= 90">成绩评定A</p>
    <p v-else-if="score >= 80">成绩评定B</p>
    <p v-else-if="score >= 70">成绩评定C</p>
    <p v-else>成绩评定D</p>
</div>

<script>
  const app = new Vue({
    el: '#app',
    data: {
      gender: 2,
      score: 95
    }
  })
</script>

v-on

事件绑定

  • 注册事件 = 添加监听 + 提供处理逻辑

  • 语法1:v-on:事件名 = "内联语句"

    <button v-on:click='count++'>按钮</button>
    
  • 语法2:v-on:事件名 = "methods中的函数名"

    <button v-on:click='add'>按钮</button>
    
  • 语法简写:@事件名

    <button @click='add'>按钮</button>
    
  • 传参:

    <button @click='add(参数1, 参数2)'>按钮</button>
    

使用示例:

<div id="app">
    <p>num值为:{{ num }}</p>
    <button @click="add(5)">+5</button>  
</div>

<script>
  const app = new Vue({
    el: '#app',
    data: {
      num: 100
    },
    methods: {
      add(num) {
        this.num += num
        //此处的 this 指向的就是 app 实例对象
        //与 js 不相同,因为 Vue 内部已经把实例对象里面的 this 指向修改了,不会指向 Window
      }
    }
  })
</script>

事件参数对象

在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event。同理,在 v-on 指令所绑定的事件处理函数中,同样可以接收到事件参数对象 event。Vue 在出发事件回调时,会主动给我们传入 event 事件对象参数

事件传参有以下四种情况:

  • 不传参数:@click="show" ,show 方法会收到一个 event(事件对象)
  • 传一个参数:@click="show(6)" ,show 方法只会收到:6
  • 传多个参数:@click="show(6, 7, 8)" ,show 方法会收到:6、7、8
  • 传参 + 事件对象:@click="show($event, 6)",show 方法会收到:event、6

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

<!-- ... -->
<button v-on:click="addCount(1, $event)">+1</button>
<!-- ... -->
<script>
	const app = new Vue({ 
		//...
    	methods: {
      		addCount(step, e) { //接收事件参数对象 event
			    const nowBgColor = e.target.style.backgroundColor
                 e.target.style.backgroundColor = nowBgColor === 'red' ? '' : 'red'
                 this.count += step
	   	    }
        }
    })
</script>

$event有两种使用场景:

  1. 在 DOM 事件的回调函数中传入参数$event,可以获取到该事件的事件对象
  2. 在子组件中通过$emit注册事件,将数据作为参数传入,在父组件中通过$event接收(传过来的第一个参数)

v-bind

基本使用

  • 作用:动态的设置 html 的标签属性
  • 语法:v-bind:属性名="表达式"
  • 简写::属性名="表达式"

使用示例:

<div id="app">
    <img v-bind:src="imgUrl" v-bind:title="msg" alt="">
    <img :src="imgUrl" :title="msg" alt="">
</div>

<script>
  const app = new Vue({
    el: '#app',
    data: {
      imgUrl: './imgs/1.png',
      msg: 'hello'
    }
  })
</script>

class与style绑定

Vue 扩展了 v-bind 的语法,可以针对 class 类名 和 style 行内样式 进行控制

操作class

语法::class = "对象/数组"

  • 对象:键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类。适用于一个类名来回切换

    <div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
    
  • 数组:数组中所有的类,都会添加到盒子上,本质就是一个 class 列表。适用于批量添加或删除类

    <div class="box" :class="[ '类名1', '类名2', '类名3' ]"></div>
    
  • 在数组语法中也可以使用对象语法:

    <div :class="[{ 类名1: 布尔值 }, 类名2]"></div>
    

当在一个自定义组件上使用 class 属性时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖,语法与用在标签上类似

操作style

语法::style = "样式对象/数组"

  • v-bind:style 的对象语法十分直观,看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case) 来命名:

    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
    
  • v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

    <div :style="[baseStyles, overridingStyles]"></div>
    
  • 从 Vue2.3.0 起你可以为 style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

    <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
    

    这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

v-for

基于数据循环,多次渲染整个元素(数组、对象、字符串、数字)

遍历数组

语法:v-for = "(item, index) in 数组"

  • item每一项,index索引
  • index可省略:v-for = "item in 数组"

遍历对象

语法:v-for="(value, key, index) in 对象" :key="index"

  • value值,key键,index索引
  • keyindex可省略:v-for = "value in 数组"

遍历字符串

语法:v-for="(char, index) in 字符串"

  • char单个字符,index索引
  • index可省略:v-for="char in 字符串"

遍历指定次数

语法:v-for="(number, index) in 数字"

  • number一个数字,index索引
  • index可省略:v-for="(number, index) in 数字"
  • 遍历的number从1开始,index索引从0开始

v-for遍历中的key

在 Vue.js 中,当使用v-for指令进行循环渲染时,每个生成的元素都需要具有一个唯一的标识符,以便 Vue 可以跟踪每个元素的状态和重新排序。Vue通过比较新旧节点的key来最小化DOM操作,提高性能,这就是key属性的作用

key属性是 Vue 用来追踪列表渲染的特殊属性。它的主要作用有两个方面:

  1. 识别每个节点的身份:Vue 会根据每个节点的key属性来判断它们的身份,以便高效地复用和更新已有的元素,而不是销毁并重新创建。如果没有使用key或者key不唯一,Vue将无法正确追踪每个元素的状态变化,可能导致不必要的渲染错误。
  2. 提高性能:使用合适的key值可以帮助 Vue 优化列表渲染的性能。Vue 会通过比较新旧节点的key属性来确定是否需要重新渲染,从而减少不必要的 DOM 操作。

通常,key属性的值(只能是字符串或数字类型)应该是每个元素在循环中的唯一标识符,例如在使用数组进行循环时,可以使用数组项的唯一标识符作为key属性。此外,key的值

v-model

v-model官网详细讲解

v-model是Vue.js中用于双向数据绑定的指令,它可以在表单元素(如输入框、选择框等)和组件之间建立起数据的双向绑定关系。双向绑定意味着当绑定的数据发生变化时,表单元素或组件的值也会自动更新,反之亦然

v-model语法如下:

<!-- 在表单元素上 -->
<input v-model="dataProperty">

<!-- 在自定义组件上 -->
<custom-component v-model="dataProperty"></custom-component>

v-model 会忽略所有表单元素的 valuecheckedselected 属性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。常见的表单元素都可以用 v-model 绑定关联,用于快速获取设置表单元素的值,它会根据控件类型自动选取正确的方法来更新元素

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件
  • checkbox 和 radio 使用 checked property 和 change 事件
  • select 字段将 value 作为 property 并将 change 作为事件

使用示例:

<div id="app">
  <!-- 绑定的是 value 属性 -->
  姓名:
    <input v-model="username" type="text"> 
    <br><br>
 
  <!-- 绑定的是 checked 属性 -->
  <!-- <input :checked="toggle" @input="toggle = $event.target.checked" type="checkbox"> -->
  是否单身:
    <input v-model="isSingle" type="checkbox"> 
    <br><br>

  <!-- 复选框特殊点 -->
  <!-- 1.绑定的数据如果是数组类型, 会将 input 的 value 添加到数组中, 当数组中存在对应复选框的值时,对应的复选框 checked 属性为 true,否则为 false -->
  <!-- 2.绑定的数据如果是非数组型, 默认都当布尔值处理, 绑定的是 checked 属性 -->
  兴趣爱好:
    <input v-model="hobby" value="smoke" type="checkbox">抽烟
    <input v-model="hobby" value="drink" type="checkbox">喝酒
    <input v-model="hobby" value="hothead" type="checkbox">烫头
    <br><br>
 
  <!-- 1.name:  给单选框加上 name 属性可以分组,同一组互相会互斥 -->
  <!-- 2.value: 给单选框加上 value 属性,用于提交给后台的数据 -->
  性别: 
    <input v-model="gender" name="gender" value="male" type="radio"><input v-model="gender" name="gender" value="female" type="radio"><br><br>

  <!-- 1. option 需要设置 value 值,提交给后台 -->
  <!-- 2. select 的 value 值,关联了选中的 option 的 value 值 -->
  <!-- 注意: 下拉选择框使用 v-model 要绑定在 select 上 -->
  所在城市:
  <select v-model="city">
    <option value="beijing">北京</option>
    <option value="shanghai">上海</option>
  </select>

  自我描述:
  <textarea v-model="introduce"></textarea> 
  <button>立即注册</button>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.14/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      username: 'Seven',
      isSingle: true,
      hobby: [], 
      gender: 'female',
      city: 'beijing',
      introduce: ''
    }
  })
</script>

指令修饰符

指令修饰符是 Vue.js 中用于修改指令行为的特殊后缀,可以添加在指令后面,用以改变指令的行为方式。通过 . 指明一些指令后缀,不同后缀封装了不同的处理操作,这么做的好处是可以简化代码

按键修饰符

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

按键修饰符 说明
enter 回车
delete 删除 (捕获“删除”和“退格”键)
esc 退出
space 空格
up
down
left
right

Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

<input v-on:keyup.page-down="onPageDown">

Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

事件修饰符

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

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

使用示例:

<!-- 此处也可以不用添加点击事件 -->
<!-- 修饰符可以连续写 -->
<a href="https://www.baidu.com" @click.prevent.stop="onLinkClick">百度首页</a>

<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="console.log(1)">
	<div class="box2" @click="console.log(2)"></div>
</div>

<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>

<!-- 只有event.target是当前操作的元素时才触发事件 -->
<div class="demo1" @click.self="showInfo">
  <button>点我提示信息</button>
</div>

v-model修饰符

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

修饰符 作用 示例
.number 自动将用户的输入值转为数值类型 <input v-model.numbe r="age"/>
.trim 自动过滤用户输入的首尾空白字符 <input v-model.trim="msg"/>
.lazy 在“change”时而非“input”时更新 <input v-model.lazy="msg"/>

computed计算属性

概念

基于现有的数据,计算出来的新属性。 依赖的数据变化,自动重新计算

语法:

  • 声明在 computed 配置项中,一个计算属性对应一个函数,必须要有返回值

  • 使用起来和普通属性一样使用 {{ 计算属性名 }}

  • 计算属性可以将一段求值的代码进行封装

    computed: {
    	计算属性名 () {
    		//基于现有数据,编写求值逻辑
    		return 结果
        }
    },
    
  • 计算属性默认的简写,只能读取访问,不能修改,如果要修改,需要写计算属性的完整写法

    • 完整写法:计算属性是一个对象,对象中拥有两个方法(get / set),get 相当于以前的简写函数
    • 完整写法可以被赋值,当计算属性被赋值时,计算属性的 set 会执行
    computed: {
      计算属性名: {
            get() {
                //一段代码逻辑(计算逻辑)
                //提供值,不叫修改
          		return 结果
            },
            set(修改的值) {
                //一段代码逻辑(修改逻辑)
            }
      }
    }
    

computed与methods

computed计算属性

  • 作用:封装了一段对于数据的处理,求得一个结果

  • 语法:

    • 写在 computed 配置项中
    • 本质是函数,可作为属性,直接使用:this.计算属性、{{ 计算属性 }}

computed缓存特性:计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算并再次缓存

methods方法

  • 作用:给实例提供一个方法,调用以处理业务逻辑

  • 语法:

    • 写在 methods 配置项中
    • 作为方法,需要调用:this.方法名()、{{ 方法名() }}、@事件名="方法名"

两者的主要区别在于computed具有缓存特性、自动依赖追踪,适用于衍生数据的计算;而methods没有缓存和自动追踪依赖,适用于执行函数操作和处理事件

watch侦听器

watch 侦听器用于监视数据变化,执行一些业务逻辑异步操作

语法:

  • 简单写法:简单类型数据,直接监视

    • 监听器的名字需要和数据名完全相同,当数据变化时自动执行该函数
    • 如果数据是一个对象的属性,可以把函数名定义成:对象.属性
    watch: {
        //侦听器有两个参数, 参数1:新值, 参数2:旧值
    	数据属性名(newValue, oldValue) {
    		//一些业务逻辑 或 异步操作
    	},
    	'对象.属性名'(newValue, oldValue) {
    		//一些业务逻辑 或 异步操作
    	}
    }
    
  • 完整写法:添加额外配置项

    • deep: true 对复杂类型深度监视
    • immediate: true 初始化立刻执行一次handler方法
    watch: {// watch 完整写法
    	数据属性名: {
    	  deep: true, // 深度监视(针对复杂类型)
    	  immediate: true, // 是否立刻执行一次handler
    	  handler (newValue) {
    		console.log(newValue)
    	  }
       }
    }
    

生命周期

概念

Vue生命周期:一个Vue实例从创建销毁的整个过程

生命周期的四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁

image-20230628102332632.png

钩子函数

Vue生命周期过程中,会自动运行一些函数,被称为生命周期钩子让开发者可以在特定阶段运行自己的代码

这些生命周期钩子函数提供了在组件不同阶段执行代码的机会,我们可以根据需求在特定的生命周期函数中处理数据、发送请求、监听事件等操作。生命周期函数的执行顺序是固定的,遵循前后依赖关系

image-20230625113442497.png

Vue2 组件的生命周期分为8个阶段,分别是:

生命周期阶段 调用函数
将要创建(在实例创建之前,此时对象还未被初始化,无法访问data等属性) beforeCreate
创建完毕(实例创建完成,可以访问data等属性,但DOM节点还未被挂载) created
将要挂载(在Vue实例挂载到DOM节点之前调用,此时模板编译已完成) beforeMount
挂载完毕(Vue实例已经挂载到DOM节点上,可以进行DOM操作) mounted
将要更新(在数据更新之前调用,DOM还未更新) beforeUpdate
更新完毕(数据已经更新,DOM已经重新渲染) updated
将要摧毁(在实例销毁之前调用,此时实例仍可用) beforeDestroy
摧毁完毕(实例已经销毁,清理工作已完成) destroyed

vue2 生命周期各阶段详细过程如下:

生命周期.png

工程化开发

工程化开发是指使用各种现代化工具、技术和最佳实践来提高软件开发过程的效率、可靠性和可维护性的一种开发方法。它旨在解决传统开发中出现的一些问题,如复杂的依赖管理、低效的构建过程、缺乏一致性的代码质量等

通过工程化开发,开发团队可以更高效地协作、迭代和交付软件项目,减少人为错误,提高代码质量和项目的可维护性,同时使开发过程更加标准化和可重复。工程化开发是现代化软件开发的重要实践,被广泛应用于各种类型的项目和开发环境中

前端工程化方案包括构建工具、自动化测试、代码规范、模块化开发、版本控制等,下面主要介绍 Vue 构建工具 Vue CLI 以及组件化开发的概念

脚手架 Vue CLI

基本概念

Vue CLI 构建 Vue 项目的过程本质上就是通过 Webpack 进行构建,Vue CLI 是将 Webpack 集成在其中,简化了配置和操作的过程,提供了更高层次的抽象和命令工具

使用步骤:

  1. 全局安装:yarn global add @vue/clinpm i @vue/cli -g

  2. 查看 Vue 版本:vue --versionvue -V

  3. 创建项目架子:vue create project-name(项目名不能用中文,字母也不能大写)

  4. 启动项目: yarn servenpm run serve(找package.json)

image-20230625135034104.png

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

main.js 文件代码详解:

// 引入 Vue 构造函数
import Vue from 'vue'
// 引入 App.vue 组件
import App from './App.vue'
// 配置 Vue 生产版本的提示信息是否显示, false 表示不显示
Vue.config.productionTip = false

// 创建一个 Vue 实例
new Vue({
  el: '#app', // 指定挂载点, 告诉 Vue 在哪里渲染
  // render: h => h(App), // 挂载什么东西, 把什么东西渲染上去
  render(createElement) {
    // createElement 是一个函数, 它的作用是根据指定的组件创建「元素」
    return createElement(App)
  }
})
// $mount() 和 指定 el 效果和原理一样

易混淆点

在使用 Webpack(Vue CLI构建项目本质)构建 Vue.js 项目时,Webpack 的执行环境通常是 Node.js。Webpack 是一个用于打包、构建和管理前端资源的工具,它是基于 Node.js 构建的,其配置文件通常是使用 JavaScript 编写,并通过 Node.js 环境来解析和执行

当你在 Vue.js 项目中使用 Webpack 时,你会使用 Webpack 的命令行工具或者在配置文件中定义的脚本来运行 Webpack。例如,你可以使用webpack命令或者在package.json文件中配置一个脚本来执行 Webpack 构建。在构建过程中,Webpack 会根据配置文件的设置,解析入口文件及其依赖关系,处理各种类型的资源文件(如JavaScript、CSS、图片等),并生成最终的打包文件

需要注意的是,尽管 Webpack 的执行环境是 Node.js,Webpack 最终生成的打包文件是用于在浏览器环境中运行的。Webpack会根据配置文件的设置将各个模块合并、转换和压缩,最终生成浏览器可执行的JavaScript、CSS和其他静态资源文件

总的来说,尽管 Webpack 的执行环境是 Node.js,但 Webpack 的目标是生成适用于浏览器环境的资源文件,以供 Vue.js 应用程序在浏览器中运行。这点解释了:当你在 Vue 源码中看到 import 关键字时,它其实是在 Node.js 环境中执行的,因此,执行 import 就相当于执行了 require,会按照其算法来查找包路径

组件化开发

基本概念

组件:Vue.js 应用程序的可复用、自包含的一部分,用于封装一段特定功能的 HTML 模板、CSS 样式和 JavaScript 逻辑

组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。这样做的好处是便于维护,利于复用 从而能够提升开发效率

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

组件分为普通组件和根组件,其中根组件是整个应用最上层的组件,包裹所有普通小组件

Snipaste_2023-06-25_14-03-09.png

组件组成

Vue 组件由三个主要部分组成:

  • template 结构 (只能有一个根节点)
  • style 样式 (支持 less 和 sass )
  • script 行为

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

image-20230625150505608.png

template

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

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

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

注意:

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

script

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

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

<script>
// 组件相关的 data 数据、methods 方法等都需要定义到 export default 所导出的对象中
export default {
  data() {
    return {
        //数据
    }
  },
  methods: {...}
}    
</script>

vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象,以保证每个组件实例维护独立的一份数据对象。每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象

style

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

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

<style>
h1{
    font-weight: nomal;
}
</style>

组件注册

关于组件名:

  • 单个单词组成:
    • 第一种写法:layout(首字母小写)
    • 第二种写法:Layout(首字母大写、推荐)
  • 多个单词组成:
    • 第一种写法:ProjectLayout(大)驼峰命名(Camel-case)多个单词首字母大写
    • 第二种写法:project-layout烤串命名(Kebab-case)多个单词之间用 - 连接

局部注册

  • 只能在注册的组件内使用

  • 当成 html 标签使用: <组件名></组件名>

    // 导入需要注册的组件
    // import 组件对象 from '.vue文件路径'
    import ProjectHeader from './components/ProjectHeader'
    
    export default {
    	// 局部注册
    	components: {
    	   //'组件名': 组件对象,
           ProjectHeader: ProjectHeader
        }
    }
    

全局注册

  • 所有组件内都能使用

  • 当成 html 标签使用:<组件名></组件名>

  • main.js 内导入,并全局注册 Vue.component(组件名, 组件对象)

  • 一般都用局部注册,如果发现确实是通用组件,再定义到全局

    // 在 main.js 中导入需要全局注册的组件
    import ProjectButton from './components/ProjectButton'
    // 调用 Vue.component 进行全局注册
    // Vue.component('组件名', 组件对象)
    Vue.component('ProjectButton', ProjectButton)
    

组件样式冲突

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

  • 全局样式:默认组件中的样式会作用到全局
  • 局部样式:可以给组件加上 scoped 属性,可以让样式只作用于当前组件

scoped原理:

  • 当前组件内标签都被添加 data-v-hash值 的自定义属性,且当前组件内的元素 hash 值都一样,但与其他组件的 hash 不同
  • Vue 在添加样式时,会自动使用属性交集选择器,都被添加 元素[data-v-hash值] 的属性选择器

最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到。此外,当使用scoped修饰符时,样式的选择器是动态生成的,因此在编译时无法直接访问这些选择器

需要注意的是,scoped样式修饰符仅在单文件组件中生效(当前组件的样式对其子组件是不生效的)。如果你在 Vue 的普通模板或外部样式表中使用scoped,它不会起到任何作用。如果想让某些样式对子组件生效,可以使用 /deep/ (less)或::v-deep(sass、less)深度选择器

/deep/ 为例:

<style lang="less" scoped>
.title {
    color: blue /* 不加/deep/时,生效的选择器格式为:.title[data-v-052342ad] 交集选择器 */
}
/deep/ .title {
    color: blue /* 加上/deep/时,生效的选择器格式为:[data-v-052342ad] .title 后代选择器*/
}
</style>

组件通信

概念

组件通信,就是指组件与组件之间的数据传递或消息的传递和交流的过程

  • 组件的数据是独立的,无法直接访问其他组件的数据,想用其他组件的数据就要用到组件通信
  • 组件关系分类如下:

image-20230625151610568.png

父子通信

  • 父组件通过属性传递数据给子组件,在子组件中使用props接收并使用这些数据。这是一种单向数据流,只能从父组件向子组件传递数据
  • 子组件可以通过 $emit触发自定义事件并向父组件发送消息。父组件在子组件上通过监听事件来接收消息,并作出响应

image-20230625151740943.png

父传子props:

①在父组件中给子组件标签添加自定义属性;②子组件使用 props 接收;③子组件使用数据,可以直接访问

image-20230630112015615.png

子传父$emit:

①子组件使用 $emit 发送消息;②父组件中给子组件绑定自定义事件,添加消息监听;③在父组件中的事件处理函数中获取数据

image-20230630111637735.png

非父子通信(不常用)

event bus 事件总线

非父子组件之间,进行简易消息传递(复杂场景 → Vuex)

  1. 创建一个都能访问到的事件总线 (空 Vue 实例) → utils/EventBus.js

    import Vue from 'vue'
    const Bus = new Vue()
    export default Bus
    
  2. A 组件(接收方),监听 Bus 实例的事件

    created () {
    	Bus.$on('sendMsg', (msg) => {
    		this.msg = msg
    	})
    }
    
  3. B 组件(发送方),触发 Bus 实例的事件

    Bus.$emit('sendMsg', '这是一个消息')
    

Snipaste_2023-06-25_15-38-38.png

provide & inject

provideinject是一对高级特性,可以在组件树中上层组件提供数据,然后在下层组件中注入并使用这些数据,用于跨层级共享数据

  1. 父组件 provide 提供数据

    export default {
      provide () {
        return {
    	  // 普通类型[非响应式]
          color: this.color, 
          // 复杂类型[响应式]
          userInfo: this.userInfo, 
        }
      }
    }
    
  2. 子/孙组件 inject 取值使用

    export default {
      inject: ['color','userInfo'],
      created () {
    	console.log(this.color, this.userInfo)
      }
    }
    

Prop

在Vue中,prop是一种用于父组件向子组件传递数据的机制。通过在子组件中定义props属性,可以声明需要从父组件接收的数据,并在子组件中使用这些数据

特点:

  • 可以传递任意数量的 prop
  • 可以传递任意类型的 prop

Props 校验

作用:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示,能够帮助开发者快速发现错误

语法:

  • 基础写法(类型校验)

    props: {
      校验的属性名: 类型 // Number String Boolean ...
    }
    
  • 完整写法(类型、非空校验、默认值、自定义校验)

    props: {
      校验的属性名: {
        type: 类型,  // Number String Boolean ...
        required: true, // 是否必填
        default: 默认值, // 默认值
        validator (value) {
          // 自定义校验逻辑,通过返回true,不通过返回false并报错
          return 是否通过校验
        }
      }
    }
    

Prop & data、单向数据流

共同点:都可以给组件提供数据

区别:

  • data 的数据是自己的可以随意更改
  • prop 的数据是外部的不能直接改,要遵循单向数据流,子组件通过 this.$emit 将数据传给父组件,让父组件来修改

单向数据流:父级 prop 的数据更新,会向下流动,影响子组件,这个数据流动是单向的

总之,prop用于接收来自父组件的数据,实现父子组件之间的数据传递;data用于存储组件内部的私有数据,并且具有响应式能力

Vue进阶语法

v-model 原理

原理

v-model 本质上是一个语法糖。例如应用在输入框上,就是 value属性input事件 的合写

作用: 提供数据的双向绑定

  • 数据变,视图跟着变 :value
  • 视图变,数据跟着变 @input

注意: $event 用于在模板中,获取事件的形参

<template>
  <div id="app" >
    <input v-model="msg" type="text">
    <input :value="msg" @input="msg = $event.target.value" type="text">
  </div>
</template>

v-model 除了可以使用在表单元素上,还可以使用在组件上,这也是需要理解 v-model 这个语法糖原理的根本原因

v-model 本质上是希望一个数据传过去,如果要修改就直接传回来,主要适用于数据双向绑定的场景

组件中的v-model

v-model指令用于自定义组件时,它实际上会将value属性和input事件与组件的对应属性和方法进行绑定。这样,在组件内部对属性进行赋值时,会触发input事件,从而更新外部的属性值;反过来,外部修改属性的值时,会通过value属性将新值传递给组件内部

需要注意的是,不同的表单元素和自定义组件可能对v-model的使用有所不同。对于大多数的表单元素,v-model会监听input事件和change事件来更新值。对于自定义组件,可以通过在组件内部使用$emit('input', newValue)来手动触发更新

以表单类组件封装实现为例:

使用属性绑定和事件绑定的组合方法,并实现父与子的双向数据绑定

  • 父传子:数据是父组件 props 传递过来的,拆解 v-model 绑定数据
  • 子传父:监听输入,通过$emit传值给父组件进行修改

父组件:

<BaseSelect :cityId="selectId" @changeHandle="selecteId = $event" />

子组件:

<select :value="cityId" @change="handleChange">...</select>

<script>
  ...
  props: {
    cityId: String
  },
  methods: {
    handleChange (e) {
      this.$emit('changeHandle', e.target.value)
    }
  }
</script>

由于 v-model 本质上是一个:value="属性"@input="事件"的合写语法糖,于是我们可以把父组件中自己命名的属性名cityId改为value,并且把子组件传的事件名changeHandle改为input,这样就可以使用v-model直接替换:cityId="selectId" @changeHandle="selecteId = $event",达到代码简写的效果

简写代码示意如下:

<BaseSelect v-model="selectId"></BaseSelect>
<!-- 等同于 -->
<BaseSelect :value="selectId" @input="selecteId = $event" />

注:在自定义事件中,$event 是模板中事件触发时传入的第一个参数,而浏览器触发事件时传入的第一个参数正好是事件对象

.sync 修饰符

用于实现子组件父组件数据的双向绑定,简化代码。本质也是一个语法糖,是 :属性名@update:属性名 的合写

特点: prop 属性名,可以自定义,非固定为 value

以封装弹框类的基础组件为例:

  • visible 属性:true 显示、false 隐藏

父组件使用:

<!-- 简写 -->
<BaseDialog :visible.sync="isShow" />
<!-- 全写 -->
<BaseDialog :visible="isShow" @update:visible="isShow = $event" />

子组件使用:

props: {
  visible: Boolean
},
this.$emit('update:visible', false)

ref 和 $refs

ref是一种在模板中给元素或组件添加引用的方式,通过使用ref可以给元素或组件取一个名称,然后通过this.$refs来访问引用对象。ref用于在 Vue 实例中获取 DOM 元素的属性或方法,或调用组件实例的方法,其查找范围在当前组件内,更加精确与稳定

获取 DOM:

  • 目标标签添加 ref 属性

    <div ref="divRef">我是被标记的容器</div>
    
  • 恰当时机,通过 this.$refs.xxx 获取目标标签

    mounted () {
      console.log(this.$refs.divRef)
    }
    

获取组件:

  • 目标组件添加 ref 属性

    <BaseForm ref="baseForm"></BaseForm>
    
  • 恰当时机,通过 this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法

    this.$refs.baseForm.组件方法()
    

Vue异步更新、$nextTick

在 Vue 中,DOM 的更新是异步执行的,这是因为 Vue 实际上进行了优化以提高性能和效率

当你修改 Vue 实例的数据时,Vue 会触发一个 DOM 更新队列。而 Vue 为了尽可能地减少对真实 DOM 的直接操作,会先将需要更新的 DOM 操作进行缓存,并通过异步方式在合适的时机一次性进行批处理

例如实现编辑标题,编辑框自动聚焦:

  • 点击编辑,显示编辑框
  • 让编辑框,立刻获取焦点
this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点

此处,显示编辑框之后,立刻获取焦点是不能成功的。原因是因为 Vue 是异步更新 DOM 的(提升性能)。如果你需要确保在 DOM 更新完成后执行某些操作,可以使用$nextTick方法来处理

$nextTick:等 DOM 更新后,才会触发执行此方法里的函数体。在支持 Promise 的环境中,返回一个 Promise 对象

语法: this.$nextTick(函数体)

使用$nextTick既可实现点击编辑框后立刻获取焦点:

this.$nextTick(() => {
	this.$refs.inp.focus()
})

自定义指令

基本语法

除了核心功能默认内置的指令 (v-modelv-show),Vue 也允许注册自定义指令。当需要对普通 DOM 元素进行底层操作时,就会用到自定义指令(自己定义的指令, 可以封装一些 DOM 操作, 扩展额外功能)

全局注册:

Vue.directive('指令名', {
    inserted(el) {
        //可以对 el 标签拓展额外的功能
        el.focus()
	}
})

局部注册:

directives: {
	"指令名": {
        inserted(el) {
            //可以对 el 标签拓展额外的功能
            el.focus()
        }
    }
}

指令的值

在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值

<div v-color="color">我是内容</div>

通过 binding.value 可以拿到指令值,指令值修改会触发 update 函数

directives: {
  color: {
    // 参数1: el 指令绑定的元素对象
    // 参数2: binding 指令绑定的参数, 对象
    // 被绑定元素插入父节点时调用, 只执行一次
    inserted(el, binding) {
      el.style.color = binding.value
    },
    // 指令值修改会 触发 update 函数
    update(el, binding) {
      el.style.color = binding.value
    }
  }
}

插槽

默认插槽

默认插槽的主要作用是让组件内部的一些结构支持自定义

  • 组件内需要定制的结构部分,改用<slot></slot>占位
  • 使用组件时, <MyDialog></MyDialog>标签内部传入结构替换 slot

插槽后备内容(默认值):

  • 封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)
  • 语法:在 <slot> 标签内,放置内容, 作为默认显示内容
  • 外部使用组件时,不传内容,则 slot 会显示后备内容;外部使用组件时,传入内容,则 slot 整体会被替换

具名插槽

具名插槽,顾名思义就是具有名字的插槽,可以将一个组件中的多处结构进行定制

具名插槽语法:

  • 多个 slot 使用 name 属性区分名字(子组件中)

    <div class="dialog-header">
      <slot name="head"></slot>
    </div>
    <div class="dialog-content">
      <slot name="content"></slot>
    </div>
    <div class="dialog-footer">
      <slot name="footer"></slot>
    </div>
    
  • template 配合 v-slot:插槽名 来分发对应标签(父组件中)

    <MyDialog>
      <template v-slot:head>
        大标题
      </template>
      <template v-slot:content>
        内容文本
      </template>
      <template v-slot:footer>
        <button>按钮</button>
      </template>
    </MyDialog>
    

v-slot:插槽名可以简化成 #插槽名,此外插槽默认名为 default

作用域插槽

定义 slot 插槽的同时,是可以传值的。插槽上可以绑定数据,将来使用组件时可以使用

作用域插槽: 将数据从子组件通过插槽传递给父组件

  • 在子组件的slot上绑定数据
  • 在父组件中使用template配合v-slot取值

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素的一个属性绑定上去:

<span>
  <!-- 注意: :user 相当于往插槽上加了一个属性, 最终以对象包裹属性的形式传递过去{ user: user} -->
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

绑定在 <slot> 元素上的属性被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

<current-user>
  <!-- <template #default="slotProps"> -->
  <!-- 如果不用 # 也可以不指定名字, 直接写为 v-slot="变量名",不过使用多个插槽时不能这么做 -->
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,也可以使用任意命名。此外,插槽也是可以解构的

插槽新老版本写法

<template>
  <div id="app">
    <slotA>
      <!-- 具名插槽 -->
      <!-- vue2.6.0以下版本的书写方式 -->
      <div slot="header">
        <div>
          header头部
        </div>
      </div>
      <!-- vue2.6.0以上版本书写方式 -->
      <template v-slot:content>
        <div>
          content内容区位
        </div>
      </template>
      <!-- vue2.6.0以上版本的书写语法糖 -->
      <template #footer>
        <div>
          footer尾部
        </div>
      </template>

      <!-- 作用域插槽 -->
      <!-- vue2.6.0以下版本的书写方式 -->
      <template slot="params" slot-scope="{names}">
         姓名 {{names}}
      </template>
       <!-- vue2.6.0以上版本书写方式 -->
      <template v-slot:gender="{gender}">
          性别 {{gender}}
      </template>
       <!-- vue2.6.0以上版本的书写语法糖 -->
      <template #age="{age}">
          年龄 {{age}}
      </template>

    </slotA>
  </div>
</template>

<script>
import slotA from './components/slot/slotA.vue'
export default {
  components: { slotA }
}
</script>

了解新老版本写法,有助于理解 element ui 组件库中的部分插槽写法,element ui 使用的是旧版 vue 语法~

路由

SPA

SPA (Single Page Application) 单页面应用程序:所有功能在 一个 HTML 页面上实现

  • 优点:按需更新性能高,开发效率高,用户体验好
  • 缺点:学习成本高,首屏加载慢,不利于SEO
开发分类 实现方式 页面性能 开发效率 用户体验 学习成本 首屏加载 SEO
单页 一个HTML页面 按需更新、性能高 非常好
多页 多个HTML页面 整页更新、性能低 中等 一般 中等

单页面应用程序,之所以开发效率高,性能高,用户体验好,最大的原因就是按需更新

路由与VueRouter

路由是一种映射关系,在 Vue 中,路由是路径组件的映射关系。根据路由就能知道不同的路径应该匹配渲染哪个组件

Vue Router 是 Vue 官方提供的路由管理器,它作为 Vue 的插件(第三方包),可以使你构建单页应用程序(SPA)时实现页面之间的导航和路由控制

Vue Router 允许你通过定义路由配置来映射 URL 路径到 Vue 组件,根据 URL 的变化来动态地加载和渲染相应的组件。使用Vue Router,你可以实现跳转到不同路由,而不需要刷新页面,从而实现更流畅和更快速的用户体验

VueRouter 基本使用步骤:

  • 下载 VueRouter 模块到当前工程

    npm i vue-router@3
    
  • 引入

    import VueRouter from 'vue-router'
    
  • 安装注册

    Vue.use(VueRouter)
    
  • 创建路由对象

    const router = new VueRouter()
    
  • 注入,将路由对象注入到 new Vue 实例中,建立关联

    new Vue({
        render: h => h(App),
        router
    }).$mount('#app')
    

VueRouter 核心使用步骤:

  • 创建需要的组件 (views目录),配置路由规则

    import Find from './views/Find.vue'
    import My from './views/My.vue'
    import Friend from './views/Friend.vue'
    
    const router = new VueRouter({
      routes: [
        { path: '/find', component: Find },
        { path: '/my', component: My },
        { path: '/friend', component: Friend },
      ]
    })
    
  • 配置导航,配置路由出口 (路径匹配的组件显示的位置)

    <div class="footer_wrap">
      <!-- a 标签的 href 要以 # 开头, 不然就会变成页面跳转了! #为锚点-->
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/friend">朋友</a>
    </div>
    <div class="top">
       <!-- 配置路由出口 -->
      <router-view></router-view>
    </div>
    

组件分类与路由抽离

Vue 组件分为两类:

  • 页面组件:

    也称视图组件(View Components),用于表示应用程序中的不同页面或视图。这些组件通常与路由关联,根据路由路径呈现不同的页面内容。页面组件通常具有页面级别的功能和布局,并且可能包含其他复用组件

  • 复用组件:

    也称功能组件(Functional Components)或UI组件(UI Components),是可以在应用程序中多次使用的独立组件。这些组件通常是独立的、可组合的,用于表示应用程序中的特定功能或UI元素。复用组件是构建应用程序的基本构建块,它们在整个应用程序中可以重复使用,以实现代码重用和模块化

将组件分类更易维护:

  • src/views文件夹或scr/pages文件夹:页面组件,用于页面展示,配合路由用
  • src/components文件夹:复用组件,用于展示数据,常用于组件复用

将路由模块抽离出来,利于维护:

src 中新建 router 文件夹,文件夹内部新建 index.js 文件,将 main.js 中的路由相关代码移入 index.js 文件中,随后将路由对象导出并在 main.js 文件中引入即可

  • router 文件夹中的 index.js 文件

    import VueRouter from 'vue-router'
    import Vue from 'vue'
    // Vue 的脚手架为我们配置了路径别名: @ 表示 src 的绝对路径
    import Find from '@/views/Find.vue'
    import My from '@/views/My.vue'
    import Friend from '@/views/Friend.vue'
    Vue.use(VueRouter) // 帮我们自动注册两个组件: router-view   router-link
    
    const router = new VueRouter({
      routes: [
        // 一个对象表示一条路由规则
        // 路由规则就是路径和组件的映射关系
        { path: '/find', component: Find },
        { path: '/my', component: My },
        { path: '/friend', component: Friend },
      ]
    })
    
    export default router
    
  • main.js

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      router //挂载路由
    }).$mount('#app')
    

路由懒加载

路由懒加载是一种优化技术,用于延迟加载网页中的路由组件。在 Vue 中,当应用初始化时,会将所有的路由组件一起打包到一个单独的 JavaScript 文件中。这意味着,无论用户实际需要访问哪个路由,都会在初始加载时一次性下载所有的路由组件,导致初始页面加载时间变长

而路由懒加载则可以使得页面初始加载时只下载当前所需的路由组件,而不是一次性下载全部的路由组件。当用户访问某个路由时,才会异步加载对应的路由组件。这样可以减少初始加载时间,提升网页性能和用户体验

在 Vue 中,可以通过使用动态导入(dynamic import)来实现路由懒加载。在路由配置中,使用import()函数来导入路由组件,将其作为路由的component选项,例如:

// import Home from '@/views/Home.vue'
// 替换成:() => import('@/views/Home.vue') 直接作为component选项
const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
  },
];

const router = new VueRouter({
  routes
});

在上述示例中,每个路由的component选项使用了import()函数,它会返回一个 Promise,用于异步加载对应的路由组件。这样,在初始加载页面时,只会下载首页所需的Home.vue组件,而不会加载其他路由组件。当用户访问 About 页面时,才会异步加载并渲染About.vue组件

声明式导航

导航链接

vue-router 提供了一个全局组件 router-link (取代 a 标签)

  • 能跳转,配置 to 属性指定路径(必须),本质还是 a 标签 ,to 无需 #

    <router-link  to="/路径值" ></router-link>
    
  • 能高亮,默认就会提供高亮类名,可以直接设置高亮样式

    • router-link-active 模糊匹配 (用的多)

      to="/my" 可以匹配 /my/my/a/my/b ...

    • router-link-exact-active 精确匹配

      to="/my" 仅可以匹配 /my

  • 此外,类名还可以自定义 (在 VueRouter 配置对象中设置)

    const router = new VueRouter({
      routes: [...],
      linkActiveClass: "类名1",
      linkExactActiveClass: "类名2"
    })
    

跳转传参

查询参数(适合传多个参数)

  • 跳转:/path?参数名1=参数值1&参数名2=参数值2

    <router-link to="/search?words=参数">传参</router-link>
    
  • 接收:$route.query.参数名

动态路径参数(适合传单个参数)

  • 定义参数:在路由中配置(必须) /path/:参数名

    const router = new VueRouter({
      routes: [
        { path: '/home', component: () => import('@/views/Home.vue') },
        // 谁接受参数谁配置
        { path: '/search/:words', component: () => import('@/views/search.vue') }
      ]
    })
    
  • 跳转:/path/参数值

    <router-link to="/search/参数">传参</router-link>
    
  • 接收:$route.params.参数名

  • 动态路由参数可选符:如果不传参数也希望匹配,可以加个可选符 ?

Vue路由重定向

重定向:匹配 path 后,强制跳转 path路径

语法: { path: 匹配路径, redirect: 重定向到的路径 }

示例如下:

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    redirect: '/home' // 将根路径重定向到/home
  },
  {
    path: '/home',
    component: () => import('@/components/Home.vue')
  },
  {
    path: '/about',
    component: () => import('@/components/About.vue')
  }
];

const router = new VueRouter({
  mode: 'history',
  routes
});
export default router;

Vue路由404

当路径找不到匹配时,给个提示页面,配在路由最后

语法:path: '*' (任意路径) (前面不匹配就命中最后这个)

示例如下:

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      component: () => import('@/components/Homepage'),
    },
    {
      path:'*',
      component: () => import('@/views/404.vue')
    }
  ]
})

Vue路由模式设置

路由的路径带有 # ,看起来不自然,可以设置 history 路由模式,让 URL 看起来更像 URL

最常见的路由模式有两种:

只需要在VueRouter构造函数中设置 mode 属性即可:

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

编程式导航

基本跳转

编程式导航顾名思义就是使用 JS 代码来进行跳转

两种跳转方式:

  • path 路径跳转

    //方式一
    this.$router.push('路由路径')
    //方式二
    this.$router.push({
      path: '路由路径'
    })
    
  • name 命名路由跳转

    this.$router.push({
      name: '路由名'
    })
    //routes配置中:
    routes: [
        ...
        { name: '路由名', path: '/path/xxx', component: XXX },
    ]
    

跳转传参

两种传参方式:查询参数传参 + 动态路由传参

两种跳转方式,对于两种传参方式都支持:

  • path 路径跳转传参 (query传参)

    //方式一
    this.$router.push('/路径?参数名1=参数值1&参数名2=参数值2')
    //方式二
    this.$router.push({
      path: '/路径',
      query: {
        参数名1: '参数值1',
        参数名2: '参数值2'
      }
    })
    
  • path 路径跳转传参 (动态路由传参)

    //方式一
    this.$router.push('/路径/参数值')
    //方式二
    this.$router.push({
      path: '/路径/参数值'
    })
    
  • name 命名路由跳转传参 (query传参)

    this.$router.push({
      name: '路由名字',
      query: {
        参数名1: '参数值1',
        参数名2: '参数值2'
      }
    })
    
  • name 命名路由跳转传参 (动态路由传参)

    this.$router.push({
      name: '路由名字',
      params: {
        参数名: '参数值',
      }
    })
    

路由嵌套

使用 children属性 即可实现路由嵌套,示例如下:

const router = new VueRouter({
  routes: [
    { 
      path: '/', 
      component: Layout,
      redirect: '/article',
      children: [
        { path: 'article', component: Article },
        { path: 'collect', component: Collect },
        { path: 'like', component: Like },
        { path: 'user', component: User },
      ]
    },
    { path: '/detail/:id?', component: ArticleDetail},
  ],
})

不带 /

  • 子路由不带 / ,是以相对路径进行访问的
  • 例如嵌套路由中的父级路由路径是 path:'/questions',子路由的路径是 path:'new',那么进行访问的时候,路由地址会拼接上父级路由的路径:http://localhost:8080/#/questions/new

带 /

  • 默认以绝对路径进行访问
  • 如果是子路由前面加/ ,就不会拼接上父级路由的path路径,地址则为:http://localhost:8080/#/new

组件缓存

<keep-alive> 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。<keep-alive> 是一个抽象组件,它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中

此外, <keep-alive> 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部 / 全局注册

<keep-alive>优点:

在组件切换过程中把切换出去的组件保留在内存中,防止重复渲染 DOM,减少加载时间及性能消耗,提高用户体验性

<keep-alive>的三个属性:

  • include:组件名数组,只有匹配的组件会被缓存

  • exclude:组件名数组,任何匹配的组件都不会被缓存,二者都可以用逗号分隔的字符串、正则表达式或一个数组来表示

    <!-- 逗号分隔字符串 -->
    <keep-alive include="a,b">
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 正则表达式 (使用 v-bind) -->
    <keep-alive :include="/a|b/">
      <component :is="view"></component>
    </keep-alive>
    
    <!-- 数组 (使用 v-bind) -->
    <keep-alive :include="['a', 'b']">
      <component :is="view"></component>
    </keep-alive>
    
  • max:数字,最多可以缓存多少组件实例

    <keep-alive :max="10">
      <component :is="view"></component>
    </keep-alive>
    

<keep-alive>的使用会触发两个生命周期函数

  • activated:当组件被激活(使用)的时候触发(进入这个页面的时候触发)

    activated () {
      console.log('actived 激活 → 进入页面');
    }, 
    
  • deactivated:当组件不被使用的时候触发(离开这个页面的时候触发)

    deactivated() {
      console.log('deactived 失活 → 离开页面');
    }
    

组件缓存后就不会执行组件的 created、mounted、destroyed 等钩子了。所以其提供了 actived 和 deactived 钩子,帮我们实现业务需求

导航守卫(路由守卫)

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的

路由守卫有三种:

  • 全局前置守卫: beforeEach、全局后置钩子:afterEach
  • 独享守卫: beforeEnter、 beforeLeave
  • 组件内守卫:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave

全局前置守卫

使用 router.beforeEach 注册一个全局前置守卫:

const router = createRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
  // 返回 false 以取消导航
  return false
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中

守卫有三个参数:

  • to 往哪里去, 到哪去的路由信息对象
  • from 从哪里来, 从哪来的路由信息对象
  • next()是否放行(可选)
    • 如果next()调用,就是放行
    • next(路径) 拦截到某个路径页面

可以返回的值如下:

  • false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址
  • 一个路由地址:通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: truename: 'home' 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样

如果遇到了意料之外的情况,可能会抛出一个 Error。这会取消导航并且调用 router.onError() 注册过的回调

如果什么都没有,undefined 或返回 true则导航是有效的,并调用下一个导航守卫

全局后置钩子

全局后置钩子和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用

独享守卫

单路由独享守卫,独享守卫只有前置没有后置

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from, next) => {
      // reject the navigation
      return false
    },
  },
]

组件内守卫

你可以为路由组件添加以下配置:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
const UserDetails = {
  template: ...,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

Vuex

概念

Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。它通过提供一个集中存储管理应用的所有组件的状态,并提供了一些操作和方法来修改和访问这些状态。通过使用 Vuex,可以更好地组织和管理 Vue.js 应用程序的状态,提高代码的可维护性和可测试性。它适用于中大型的应用程序或需要共享状态的多个组件之间进行通信的场景

一般情况下,只有多个组件均需要共享的数据 ,才有必要存储在 Vuex 中,对于某个组件中的私有数据,依旧存储在组件自身的 data 中

image-20230708195252560.png

使用 Vuex 优势主要有三点:

  1. 共同维护一份数据,数据集中化管理
  2. 响应式变化
  3. 操作简洁 (Vuex 提供了一些辅助函数)

Vuex使用

安装 Vuex 插件,初始化一个空仓库

  • 安装 vuex,与 vue-router 类似,vuex 是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。

    npm i vuex@3
    
  • 新建 store/index.js 专门存放 vuex。为了维护项目目录的整洁,在 src 目录下新建一个 store 目录,其下放置一个index.js文件 (和 router/index.js 类似)

image-20230709235833899.png

  • 创建仓库 store/index.js

    // 导入 vue
    import Vue from 'vue'
    // 导入 vuex
    import Vuex from 'vuex'
    // vuex 也是 vue 的插件, 需要 use 一下, 进行插件的安装初始化
    Vue.use(Vuex)
    
    // 创建仓库 store
    const store = new Vuex.Store()
    // 导出仓库
    export default store
    
  • main.js 中导入挂载到 Vue 实例上

    import Vue from 'vue'
    import App from './App.vue'
    import store from './store'
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      store
    }).$mount('#app')
    

    此刻起, 就成功创建了一个 空仓库!!

Vuex 的核心概念

Vuex 的核心概念包括:State(状态)、Mutation(变化)、Action(动作)、Getter(获取器)、Module(模块)

State

State(状态): Vuex 使用单一状态树来管理应用的状态,也就是一个包含了所有状态的对象。通过将状态放在一个集中的地方,可以方便地跟踪和管理应用的状态

State 提供唯一公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储。在 state 对象中可以添加我们要共享的数据:

// 创建仓库 store
const store = new Vuex.Store({
  // state 状态, 即数据, 类似于 vue 组件中的 data,
  // 区别在于 data 是组件自己的数据, 而 state 中的数据整个 vue 项目的组件都能访问到
  state: {
    count: 10
  }
})

State数据访问

通过仓库直接访问

  • 组件中可以使用 this.$store 获取到 vuex 中的 store 对象实例,可通过 state 属性属性获取 count

    <h1>state的数据---{{ $store.state.count }}</h1>
    
  • 或者将 state 属性定义在计算属性中:

    // 把 state 中数据,定义在组件内的计算属性中
    computed: {
      count () {
        return this.$store.state.count
      }
    }
    

    使用计算属性简化了代码,但是每次都需要依次提供计算属性,比较繁琐,vuex 辅助函数帮我们解决了这个问题

使用辅助函数 mapState

mapState 是辅助函数,帮助我们把 store 中的数据映射到组件的计算属性中

使用步骤:

  • 导入 mapState ( mapState 是 vuex 中的一个函数 )

    import { mapState } from 'vuex'
    
  • 采用数组形式引入 state 属性

    mapState(['count']) 
    
  • 利用展开运算符将导出的状态映射给计算属性

    computed: {
      ...mapState(['count'])
    }
    

mutations

Mutation(变化): 变化是修改状态的唯一方式。它类似于事件,每个变化都有一个类型和一个处理函数。通过提交一个变化,可以触发状态的修改

事实上确实是可以直接修改 store 中的数据,无需通过 mutation,并且不会报错。但直接修改可能会造成很严重的后果, Vue 官方也不希望我们这么做,如若担心自己可能无意间直接修改了 store 中的数据,可以开启严格模式 strict: true ,让 Vue 来监督我们:

const store  = new Vuex.Store({
  strict: true
  state: {
    count: 0
  },
  // 定义mutations
  mutations: {
  }
  // ...
})

访问mutations

state 数据的修改只能通过 mutations,并且 mutations 必须是同步的。mutations 是一个对象,对象中存放修改 state 的方法

mutations: {
  // 第一个参数是当前 store 的 state 属性
  // payload载荷 运输参数,调用mutaiions时,可以传递参数,传递载荷
  addCount (state) {
    state.count += 1
  }
}

组件中提交 mutations(其实就是执行对应函数)

this.$store.commit('addCount')

带参数的mutations

提交 mutations 是可以传递参数的 this.$store.commit('xxx', 参数)

  • 提供 mutations 函数

    mutations: {
      ...
      addCount (state, count) {
        state.count = count
      }
    },
    
  • 提交 mutations

    handle ( ) {
      this.$store.commit('addCount', 10)
    }
    

提交的参数只能是一个, 如果有多个参数要传, 可以传递一个对象

辅助函数

mapMutations 和 mapState 很像,它把位于 mutations 中的方法提取了出来

import  { mapMutations } from 'vuex'
methods: {
    ...mapMutations(['addCount'])
}

此时,就可以直接通过 this.addCount()调用

<button @click="addCount">+1</button>

mutations 中不能写异步代码,如果有异步的 ajax 请求,应该放置在 actions 中

actions

Action(动作): action 类似于变化,它可以包含任意异步操作。它们是通过提交一个 action 来触发的,然后在 actions 中可以执行异步操作并通过提交变化来修改状态

state 用于存放数据,mutations 用于同步更新数据(便于监测数据的变化,更新视图等,方便于调试工具查看变化),actions 则负责进行异步操作

定义actions

  • actions 的函数第一个参数是 context (上下文对象,参数名可修改),可以理解为是一个简化版本的 store 对象,第二个参数同 mutations 一样,只能接受一个参数,多个参数用对象包裹后传递

    actions: {
      setAsyncCount (context, num) {
        // 一秒后, 给一个数, 去修改 num
        setTimeout(() => {
          context.commit('changeCount', num)
        }, 1000)
      }
    },
    
  • 原始调用$store (支持传参)

    setAsyncCount () {
      this.$store.dispatch('setAsyncCount', 666)
    }
    

辅助函数

actions 也有辅助函数(mapActions),可以将 actions 导入到组件中

import { mapActions } from 'vuex'
methods: {
    ...mapActions(['setAsyncCount'])
}

直接通过 this.方法就可以调用

<button @click="setAsyncCount(200)">异步修改数值</button>

getters

Getter(获取器): 获取器用于从状态中派生出新的状态。它可以将现有的状态进行计算和转换,并提供一个类似于计算属性的方式来访问这些派生状态

  • 例如,state 中定义了 list 数组,而在组件中,需要显示所有大于5的数据,正常的方式,是需要 list 在组件中进行再一步的处理,但是 getters 可以帮助我们实现它:

    ...
    state: {
        list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    }
    getters: {
      // getters函数的第一个参数是 state
      // 必须要有返回值
       filterList:  state =>  state.list.filter(item => item > 5)
    }
    
  • 原始方式$store

    <div>{{ $store.getters.filterList }}</div>
    

Getter 辅助函数与上文类似,均是用于用于获取对象属性值的函数,简化了代码

  • mapGetters

    computed: {
        ...mapGetters(['filterList'])
    }
    
  • 使用

    <div>{{ filterList }}</div>
    

modules

概念

Module(模块): 模块可以将整个状态树分割成更小的模块,每个模块都有自己的状态(state)、变化(mutation)、动作(action) 和获取器(getter)。这样可以将复杂的应用程序拆分成可维护和可测试的模块。通过模块化的方式,可以更好地组织和维护大型的状态管理

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿,于是就有了 Vuex 的模块化。模块化是使用 Vuex 的一种重要的模式,开发中基本上都是基于模块化思想来开发

image-20230708212838412.png

理解 Vuex 中的模块,可以从以下几个方面来思考:

  1. 分割状态:一个大型的应用程序可能包含多个功能模块,每个模块都有自己的一组相关状态(state),例如用户信息、商品列表、购物车等。通过将这些状态划分成不同的模块,可以更好地组织和管理状态,使代码更清晰和可维护
  2. 调用命名空间:每个模块可以通过命名空间(namespace)来调用自己的状态、mutations、actions 和 getters,避免了全局命名冲突的问题。调用模块中的状态和操作时,需要在相应的模块名前加上模块的命名空间
  3. 可嵌套的模块:在 Vuex 中,模块可以嵌套定义,形成树状结构。这种嵌套关系可以反映实际应用程序中的结构关系,使代码更符合应用程序的逻辑结构
  4. 共享状态和模块间通信:通过模块,可以实现在不同模块间共享状态和进行模块间的通信。例如,一个模块可以通过getters 获取另一个模块的状态,或者通过 actions 调用其他模块的 mutations 来修改状态
  5. 代码重用和组合性:模块化的设计让我们可以更好地重用和组合代码。一个模块可以独立开发和测试,然后可以在其他地方多次使用,减少了代码的冗余和重复

综上所述,模块是 Vuex 中用于组织和管理状态的一种机制,通过将状态、mutations、actions 和 getters 划分为不同的模块,可以更好地组织和维护代码,提高代码的可维护性和扩展性

模块化示例

定义模块 user

user 中管理用户的信息状态:userInfo,位于modules/user.js

const state = {
  userInfo: {
    name: 'zs',
    age: 18
  }
}
const mutations = {}
const actions = {}
const getters = {}
export default {
  state,
  mutations,
  actions,
  getters
}

使用模块中的数据, 可以直接通过模块名访问 $store.state.模块名.xxx,也可以通过 mapState 映射

命名空间 namespaced

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的。这意味着,刚才的 user 模块,它的 action、mutation 和 getter 其实并没有区分,都可以直接通过全局的方式调用,如下图所示:

image-20201029163627229.png

如果我们想保证内部模块的高封闭性,我们可以通过 namespaced 来设置

modules/user.js

const state = {
  userInfo: {
    name: 'zs',
    age: 18
  },
  myMsg: '我的数据'
}
const mutations = {
  updateMsg (state, msg) {
    state.myMsg = msg
  }
}
const actions = {}
const getters = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}

提交模块中的 mutation

//全局的 
this.$store.commit('mutation函数名', 参数)
//模块中的
this.$store.commit('模块名/mutation函数名', 参数)

namespaced: true 后要添加映射,可以加上模块名,找对应模块的 state/mutations/actions/getters

computed: {
  // 全局的
  ...mapState(['count']),
  // 模块中的
  ...mapState('user', ['myMsg']),
},
methods: {
  // 全局的
  ...mapMutations(['addCount'])
  // 模块中的
  ...mapMutations('user', ['updateMsg'])
}

关系图

5d58131e0001f33010861004.png

猜你喜欢

转载自juejin.im/post/7255955134131093564