admin 管理员组

文章数量: 887019

⭐️Vue

vue3基础入门参考文章!必看

Vue 2

是一套用于构建用户界面的框架

Vue 的特性

  • 数据驱动视图

  • 双向数据绑定

  • MVVM

    • MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分
      • Model 表示当前页面渲染时所依赖的数据源。
      • View 表示当前页面所渲染的 DOM 结构。
      • ViewModel 表示 vue 的实例,它是 MVVM 的核心。
  • 数据代理

    通过vm对象来代理data对象中属性的操作(读/写)

    • 更加方便的操作data中的数据

    • 基本原理

      • 通过Object.defineProperty()把data对象中所有属性添加到vm上。

      • 为每一个添加到vm上的属性,都指定一个getter/setter。

      • 在getter/setter内部去操作(读/写)data中对应的属性。

Vue监视数据的原理:

    1. vue会监视data中所有层次的数据。

    2. 如何监测对象中的数据?
	通过setter实现监视,且要在new Vue时就传入要监测的数据。
        (1).对象中后追加的属性,Vue默认不做响应式处理
        (2).如需给后添加的属性做响应式,请使用如下API:
        Vue.set(target,propertyName/index,value) 或 
        vm.$set(target,propertyName/index,value)

	3. 如何监测数组中的数据?
		通过包裹数组更新元素的方法实现,本质就是做了两件事:
            (1).调用原生对应的方法对数组进行更新。
            (2).重新解析模板,进而更新页面。

	4.在Vue修改数组中的某个元素一定要用如下方法:
		1 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
		2 Vue.set() 或 vm.$set()
				
		特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

Vue 的基本使用

  • 使用步骤

    • 1 导入 vue.js 的script 脚本文件
    • 2 在页面中声明一个将被 Vue 所控制的 DOM 区域
    • 3 创建 vm 实例对象
    <!-- 希望 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>
    

指令

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

按照不同用途可分为:

1 内容渲染指令
  • v-text

    • 示例:<p v-text="gender">性别:</p>
    • ❗️v-text 指令会覆盖元素内默认的值
  • ⭐️{{ }}

    • <p>性别:{{ gender }}</p>
    • 不会覆盖元素内默认的值
  • v-html

    • 把包含 HTML 标签的字符串渲染为页面的 HTML 元素

    • <div v-html="info"></div>

    • ❗️v-html有安全性问题

      • (1).在网站上动态渲染任意HTML是非常危险的,容易导致 XSS 攻击。

      • (2).一定要在可信的内容上使用v-html,不要用在用户提交的内容上!(和eval()有点像)

  • v-cloak

    • 1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
    • 2.使用css(display:'none')配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
  • v-once

    • 1.v-once所在节点在初次动态渲染后,就视为静态内容了。
    • 2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
  • v-pre

    • 1.跳过其所在节点的编译过程。
    • 2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
2 属性绑定
  • v-bind:

    • v-bind: 指令可以简写为 :

    • <input type="text" :placeholder="tips">

    • <img :src="photo" alt="">

    • <div title="'box'+index"></div>

    • 动态绑定 class / style / checked

      • :checked="isChecked"

        • isChekced是 Boolean值
      • 字符串:

        • 适用于:类名不确定,要动态获取。
        • :class="classes"
          • classes是一个计算属性
      • 对象语法:

        • 要绑定多个样式,个数确定,名字也确定,但不确定用不用

        • :class="{active:isActive}"

        • :class="{active:isActive,line:isLine}"

        • class="title":class="{active:isActive,line:isLine}"

      • 数组语法:

        • 要绑定多个样式,个数不确定,名字也不确定。
        • :style='[styleobj,overridingStyles]'

    使用 JavaScript 表达式 的运算

3 事件绑定
  • v-on :简写形式:@

    • eg: @click=‘addCount’ @keyup=‘count += 1’
  • 需要在 methods 节点中进行声明

  • 事件参数对象

    • $(event) 指原生的事件参数对象 event

    • 绑定事件并传参

      <button @click="add($event, 1)">+N</button>

  • 事件修饰符

    事件修饰符说明
    .prevent阻止默认行为(eg:阻止 a 链接的跳转、阻止表单的提交等)
    .stop阻止事件冒泡
    .capture以捕获模式触发当前的事件处理函数
    .once绑定的事件只触发一次
    .self只有在 event.target 是当前元素自身时触发事件处理函数
    • <a href="http://www.baidu" @click.prevent="show">跳转到百度首页</a>
  • 按键修饰符

    • <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
    • .enter
    • .delete
    • .esc
    • .space
    • .up .down .left .right
    • tab 换行 (必须配合 keydown去使用)
  • 可以执行少量代码 count++

  • 可以写函数传参(可以获取事件对象,没写参数默认有,写参数要 (a,$event)

    • 给调用函数+个()eg:@change="handelClick()"
    • 如果只有一个 e 不加 ()
    • e.target.value/checkded
4 双向绑定
  • v-model

    • 实现表单数据的双向绑定

    • 用于获取表单(输入类)的数据

    • 场景

      • 表单(value / checked)、全选(状态在 computed )反选(状态在 data )
    • 收集表单数据:
          若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
      
          若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
      
          若:<input type="checkbox"/>
          1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
          
          2.配置input的value属性:
            (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
            
            (2)v-model的初始值是数组,那么收集的的就是value组成的数组(多选)
      
      
    • v-model 修饰符

      修饰符作用示例
      .number自动把用户输入的值转为数值类型<input type="number" v-model.number="age">
      .trim自动过滤用户输入 的首尾空白字符<input v-model.number="msg">
      .lazy在“change”时而非“input”时更新<input v-model.number="msg">
5 条件渲染
  • 条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏

    • v-if

      • <p v-if="flag">这是被 v-if 控制的元素</p>

      • 会动态地创建或移除 DOM 元素

        • v-elsev-else-if=“ ”
        • 必须相邻
      • 只能在与 v-if 平级的时候使用

        优秀 良好 一般 差
      • 移除时 dom 不存在

    • v-show

    • <p v-show="flag">这是被 v-show 控制的元素</p>

      • 会动态为元素添加或移除 style=“display: none;” 样式
      • 移除时 dom 存在
    v-ifv-show
    动态地创建或移除 DOM 元素动态为元素添加或移除 style="display: none;"样式
    支持多条件显示不支持多条件显示
    有更高的开销有更高的初始渲染开销
    使用场景:切换频率较低、判断条件较多的场景使用场景:非常频繁地切换
6 列表渲染
  • v-for

    • 基于一个数组来循环渲染一个列表结构
    • 需要使用 item in items 形式的特殊语法
    <tr v-for="(item, index) in list" :key="item.id">
      <td>{{ index }}</td>
      <td>{{ item.id }}</td>
      <td>{{ item.name }}</td>
    </tr>
    
    • 1 用于 循环 数组 (item,index) in array
    • 2 用于循环 对象 (item,key) in obj
    • 3 用于循环 数字 item in num
    • 使用 key 维护列表的状态
      • Vue 复用已存在的DOM元素提升渲染性能 但导致有状态的列表无法被正确更新
      • key 注意事项
        • key 的值只能是字符串或数字类型
        • key 的值必须 具有唯一性
        • 建议把数据项的 id 属性值作为 key 的值
        • 使用 index 的值当做 key 的值没有意义( index 不具唯一性)
        • 建议使用 v-for 指令时要指定 key 的值(既提升性能,又防止列表状态紊乱)
    • label 的 for 属性
      • :for="'cb' + item.id" input里面 :id="'cb' + item.id"

过滤器

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。本质是 js 函数

  • 过滤器放在 js 表达式的尾部 由“管道符”进行调用

    • 插值表达式
      • <p>message 的值是:{{ message | capi }}</p>
      • 不能给属性用,直接“ ”
    • v-bind 属性绑定
      • <div v-bind:id="rawId | formatId"></div>
  • 定义过滤器

    • 在创建 vue 实例期间,可以在 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.filter() 定义全局过滤器
          Vue.filter('capi', function (str) {
            const first = str.charAt(0).toUpperCase()
            const other = str.slice(1)
            return first + other + '~~~'
          })
      
  • 连续调用多个过滤器

    • <p>message 的值是:{{ message | capi | maxLength }}</p>
  • 过滤器传参

    • 本质是 js 函数

      <p>{{ message | filterA(arg1,arg2)}</p>
      
      Vue.filter('filterA',(mesg,arg1,arg2)=>{})
      
  • 兼容性

    • 仅在 Vue1、2中受支持
    • vue3 不支持
      • 官方建议使用 计算属性方法 代替过滤器功能
      • 参考

侦听器

watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。

当被监视的属性变化时, 回调函数自动调用, 进行相关操作

  • 1 在 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)
      })
    }
  }
})
  • 2 使用 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)
    	} 
    }
    
  • 3 immediate选项

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

    watch: {
       // 让被监听的对象指向一个 配置对象
    username: {
    // handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数
        handler: async function (newVal) {
            if (newVal === '') return
            const { data: res } = await axios.get('https://www.escook/api/finduser/' + newVal)
            console.log(res)
    },
    // 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器
    immediate: true
    } }
    
  • 4 deep 选项

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

    • 在上面的基础上加一个 deep:true
  • 5 监听对象单个属性的变化

    watch:{
        info: {
          handler(newVal) {
            console.log(newVal)
          },
          deep:true,
        }
        // 如果要侦听的是 子属性 的变换,则必须包裹一层单引号
        'info.username'(newVal) {
          console.log(newVal)
        }
        // 配置对象
        'info.username':{
           async handler(newVal){
              const { data:res } = await axios.get('https://www.escook/api/finduser/' + newVal.username)
              console.log(res)
            }
        }
    }
    

监视的属性必须存在,才能进行监视!!

  • 应用场景

    • 本地存储

      subjectList() { // 要侦听的属性
          localStorage.setItem('scoreMsg', JSON.stringify(this.subjectList))
          }
      
    • 当监听 对象数组 时,数组的长度变化时,不用 deep 也可以,但是监听不到对象内部的 变化

    • 数据变化时,发起 ajax 请求

计算属性

计算属性指的是通过一系列运算之后,最终得到一个属性值

  • 个动态计算出来的属性值可以被模板结构或 methods 方法使用。

    <div id="app">
        <!-- 专门用户呈现颜色的 div 盒子 -->
        <!-- 在属性身上,: 代表  v-bind: 属性绑定 -->
        <!-- :style 代表动态绑定一个样式对象,它的值是一个 {  } 样式对象 -->
        <!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 -->
        <div class="box" :style="{ backgroundColor: rgb }">
          {{ rgb }}
        </div>
        <button @click="show">按钮</button>
    </div>
    
      <script>
        // 创建 Vue 实例,得到 ViewModel
        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() {
              return `rgb(${this.r}, ${this.g}, ${this.b})`
            }
          }
        });
    
        console.log(vm)
      </script>
    
    • 所有的计算属性,都要定义到 computed 节点之下
    • 计算属性在定义的时候,要定义成“方法格式”
  • 计算属性的特点

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

    • 代码复用
    • data变化,计算属性也变化
  • 应用场景

    • 反选
      • return this.list.every(item => item.checked === true)
    • total
      • return this.subjectList.reduce((pre, current) => (pre += current.score), 0)
全选:v-model="allChecked" 反选:computed

computed: {
    allChecked: {
      get() {
        return this.list.every(item => item.checked === true)
      },
      set(allChecked) {
        this.checked = !allChecked
        this.list.forEach(item => (item.checked = allChecked))
      }
    }
  }
计算属性:
    1.定义:要用的属性不存在,要通过`已有属性`计算得来。
    
    2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

    3.get函数什么时候执行?
        (1).初次读取时会执行一次。
        (2).当依赖的数据发生改变时会被再次调用。

    4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
    
    5.备注:
        1.计算属性最终会出现在vm上,直接读取使用即可。
        2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

计算属性 vs 侦听器

1 计算属性侧重于监听多个值的变化,最终计算并返回一个新值

2 侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值

3 computed能完成的功能,watch都可以完成。watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

axios

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

  • 调用 axios 方法得到的返回值是 Promise对象

  • const result = axios({
        method:'GET',
        url:'',
      	// URL 中的查询参数 GET
        // params:{},
        // 请求体参数 POST
        // data:{}
    })
    result.then(res=>{
        console.log(res.data)
    })
    
    $('#btnPost').on('click',async ()=>{
        const { data:res } = await axios({
            method:'POST',
            url:'',
            data:{
                name:'zs',
                age:20
        }
    }) // 返回的是数据对象 可以解构单独拿出 data 把 data 重命名为 res
    })
    

如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await

await 只能在被 async “修饰”的方法中

用 jQuery 可以发起 $.ajax() $.get() $.post() 请求

axios:axios() axios.get() axios.post() axios.delete() axios.put() axios.patch()

axios.get()

$('#btnPost').on('click',async ()=>{
    const { data:res } = await axios.get('url',{params:{id:1}})
})

axios.post()

$('#btnPost').on('click',async ()=>{
    const { data:res } = await axios.post('url',{name:'zs',gender:'女'}) // axios.post()里面的请求体直接写数据对象
})

组件中发起axios 请求,不用每个组件都要导入 axios,在 main.js 导入,变成 Vue内置的成员

// main.js
import axios from 'axios'

// 配置请求根路径
axios.defaults.baseURL = 'http://www.itcbc:3006'

// 把 axios 挂载到 Vue.prototype上,供每个组件的实例直接使用
// 缺点: 不利于接口的 复用
Vue.propotype.$http = axios

// 组件中
methods:{
    async getBooks(){
        const {data : res} = await this.$http.get('http://www.itcbc:3006/api/getbooks')
    }
}

$mount方法

const vm = new Vue({
  data: {
    username: 'admin'
  }
})

vm.$mount('#app')

vue/cli

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

下载: npm i -g @vue/cli 查找安装: vue -V

  1. 在指定目录的 终端下 创建指定名称的项目

    • vue create demo-first

    • 项目名称不能有空格、中文、大写字母

    • 创建冻结 按 ctrl + c

    • 创建成功 cd 项目名称 npm run serve 不要关掉终端

  2. vue 项目中 src 目录的构成

  • assets 放:图片、css 样式等静态资源

  • components :放封装好的 组件

  • main.js :是项目的入口文件,整个项目的运行,要先执行 main.js

![在这里插入图片描述](https://img-blog.csdnimg/721187076eeb40748414a0f0fa998f01.png#pic_center)
  • app.vue:是项目的根组件(render渲染的组件就是根组件)
  1. vue 项目运行流程

    • 通过 main.js 把 App.vue 渲染到 HTML 页面

单页面应用程序

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

vue组件

组件是对 UI 结构的复用

组件化开发

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

组件的后缀名是 .vue

一个重要的内置关系
  • VueComponent.prototype.proto === Vue.prototype
为什么要有这个关系
  • 让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
  • this.$refs.xx(ref=xx).属性/方法()
定义组件
组成部分:
  • 1 template -> 组件的模板结构(必须包含

    • <template> 当前组件的 DOM 结构,需要被定义到 template 标签的内部 </template>
    • template 中只能包含唯一的根节点
  • 2 script -> 组件的 JavaScript 行为

组件配置中: data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】

new Vue(options)配置中: data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

使用组件
  • 1 使用 import 语法导入需要的组件
    • @ 表示定位到 src
    • import MyCount from 'xxx'
  • 2 使用 components 节点注册组件
  • 3 以标签的形式使用刚才注册的组件
组件间的父子关系
  • 组件封装之后,彼此之间是相互独立的,不存在父子关系
  • 在使用组件的时候,根据彼此的嵌套关系构成 父子、兄弟关系
私有组件
  • 使用 components 注册的是 私有组件
  • 在组件 A 的 components 节点下,注册了组件 F。 则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
  • component:{ MyCount }
  • ⭐️component:{ 'my-count', MyCount }
注册全局组件
  • 在 vue 项目的 main.js 入口文件中,通过 Vueponent() 方法,可以注册全局组件。
    • 1 import
    • 2 Vueponent('MyCount',MyCount)

组件的 name 会显示在 devtools 上

组件的 props

props 是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性

exports default{
	props:['自定义属性A''自定义属性B''...'],
    data(){
        return {}
    }
}
  • props 是只读的

    • 修改:把 props 的值 转存 到 data 中,data 中的数据都是可读写的

    • 不要直接在 子组件里修改 props 的值 (会报错,父组件没跟着变)

      props:['init'],
      data(){
          return{
              count:this.init
          }
      }
      
  • props 的 default 默认值

    • props:{ init: { default: 0 }}

    • 用户没传 init 的值时,default 的值生效

    • 对象或数组默认值必须从一个工厂函数获取

      default:function() {
          return {message:'hello'}
      }
      
  • type 值类型

    • eg:type: Number
    • :init="9" v-bind: 加上 js 的数字
    • 写在 props 里面
    • props:{name:String}
  • required 必填项

    • required: true

使用:<my-count :init="9"></my-count>

组件之间的样式冲突问题

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

  • 根本原因

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

    • 1 为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域

      <style> .container[data-v-0001]{ border: 1px solid red;} </style>
      
    • 2 style 的 scoped 属性

      • 防止组件之间的样式冲突问题
      • <style scoped> </style>
      • 则当前组件的样式对其子组件是不生效的
    • 3 /deep/ 样式穿透

      • 让某些样式对子组件生效
  • 当使用第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要用到 /deep/

组件的生命周期

生命周期

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

生命周期函数

是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行,强调的是时间点

  • 组件生命周期函数的分类

    • 组件创建阶段、组件运行阶段

    • 生命周期图示

  • beforeCreate()

    • 创建阶段的第一个生命周期函数
    • 组件的 props/data/methods 尚未被创建,都处于 不可用 状态
  • ⭐️created()

    • 组件的 props/data/methods 已创建好,都处于 可用 状态,但是组件的模板结构未生成
    • 在里面调用 methods 方法,请求服务器的数据,并且,把请求到的数据,转存到 data 中,供 template 使用
    • 有些bus.$on()写在 created 里面
  • beforeMount

    • 浏览器还没当前组件的 DOM结构
  • ⭐️mounted

    • 已渲染 HTML,第一次取到 DOM 结构
    • 启动定时器绑定自定义事件订阅消息等【初始化操作】
  • beforeUpdate

    • 将要 根据变化、更新后的数据,重新渲染组件的模板结构
  • ⭐️updated

    • 已根据最新的数据,完成了组件 DOM 结构 的重新渲染
    • 当数据变化之后,为了能够操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中
    • 但是一般不在这里做什么,因为一个属性变化就会触发 updated
  • beforeDestroy

    • 尚未销毁组件,还处于 正常工作状态
    • 清除定时器解绑自定义事件取消订阅消息等【收尾工作】
  • destroyed

    • 组件已被销毁,DOM 结构已被完全移除
    • 销毁后自定义事件会失效,但原生DOM事件依然有效。
    • 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
⭐️组件之间的数据共享

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

  • 需要使用自定义属性

  • 也就是 使用自定义属性的方法

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

  • 使用自定义事件(以传参的形式 去共享数据)
  • 父:函数
    • 函数接收数据
  • 子:props:[‘函数名称’]
    • 调用函数,传递参数数据

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

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

  • EventBus 的使用步骤

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

4 ⭐️全局事件总线:任意组件间通信

  • main.js

    • 在vue 实例beforeCreate(){ Vue.prototype.$bus = this}
  • 发送方

    • this.$bus.$emit('deleteTodo', 参数)
  • 接收方

     mounted() {
     this.$bus.$on('checkTodo', this.checkTodo)
    this.$bus.$on('deleteTodo', this.deleteTodo)
      },
          
      beforeDestroy() {
        this.$bus.$off('checkTodo')
        this.$bus.$off('deleteTodo')
      }
    

5 消息订阅与发布 实现任意组件间通讯

使用步骤:

  1. 安装pubsub:npm i pubsub-js

  2. 引入: import pubsub from 'pubsub-js'

  3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

    methods(){
      demo(data){......} =》或者写在回调函数里
    }
    ......
    mounted() {
      this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 (msgName,data)=>{...} 写箭头函数
    }
    beforeDestroy(){ pubsub.unsunscribe(this.pid)}    
    
  4. 提供数据:pubsub.publish('xxx',数据)

  5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

ref 引用

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。

每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。

默认情况下, 组件的 this.$refs 指向一个空对象。

  • 使用 ref 引用 DOM 元素

  • 使用 ref 引用组件实例

  • ref=' ' 写在 组件使用标签上

  • 引用到组件的实例后,就可以调用组件上的 methods 方法

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

    通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。

  • 让文本框自动获得焦点

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

  • this.$nextTick(cb) 方法

    组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。

    即等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。

@ 从 src 源目录从外往里找

❗️一个重要的内置关系:

VueComponent.prototype.__proto__ === Vue.prototype

让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

动态组件

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

动态组件的渲染

vue 提供了一个内置的 <component> 组件,专门用来实现动态组件的渲染。

data(){ // 函数 避免组件被复用时,数据存在引用关系。
    return {comName:'Left'}
}

<componet :is="comName"></componet>

<button @click="comName = 'Left'">切换 Left 组件</button>
<button @click="comName = 'Right'">切换 Right 组件</button>
使用 内置的 keep-alive 保持状态

解决默认情况下,切换动态组件时无法保持组件的状态的问题。

<keep-alive>
	<component :is="comName"></component>
</keep-alive>
keep-alive 对应的生命周期函数
  • 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
  • 当组件被激活时,会自动触发组件的 activated 生命周期函数。
    • 当组件第一次被创建是,即会触发created 也会触发 actived
    • 当组件被激活,只会触发 actived,不再触发 created,因为组件没有被重新创建
keep-alive 的 include 属性

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

  • 与 include 相对的是 exclude(指定哪些组件不需要被缓存)只能二选一
<keep-alive include="MyLeft,MyRight">
    <component :is="comName"></component>
</keep-alive>

组件的name属性:

当提供了 name 属性之后,组件的名称,就是 name 属性的值

export default{
    name:'MyRight'
}

对比:

  • name
    • 1 调试的时候 出现的 组件名称
    • 2 与 <keep-alive> 结合 指定被缓存与不被缓存
  • 注册名称
    • 应用场景:以标签的形式,把注册好的组件,渲染和使用到页面结构之中

mixin(混入)

可以把多个组件共用的配置提取成一个混入对象

独立的 js 文件

使用:

1 定义混合

export const xxx = {
    data(){...}
	methods(){...}
	...
}
export const yyy = {
data(){...}
methods(){...}
...
}

2 使用混合

  • 全局混入
    • 在 main.js 中 Vue.mixin(xxx)
  • 局部混入 // App.vue or 某一组件中
    • import {xxx,yyy} from '/mixin'
    • mixins:[xxx,yyy]

插件

独立的 js 文件

// 1 定义插件 plugins.js
export default {
    install(Vue,x,y,z)
    // 1. 添加全局过滤器
    Vue.filter('mySlice',function(value){
        return value.sclice(0,4)
    })
    
    // 2. 添加全局指令
    Vue.directive('fbind',{bind(el,binding){ el.value = binding.value},
	insert(el,binding){el.focus()},update(el,binding){el.value = 		binding.value}
	})

    // 3. 配置全局混入(合)
    Vue.mixin({data(){ return {x:100,y:200 }}})

    // 4. 添加实例方法
    Vue.prototype.$myMethod = function () {...}
    Vue.prototype.$myProperty = xxxx
                                           
    // 5. 添加全局组件
    Vue.component('myButton',{})                                       
}

// 2 引入插件 main.js
import myBtn from '@/plugin/plugins.js'
// 3 使用自定义插件
<my-button/>

插槽

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

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

基础用法:

<template> 中 通过<slot></slot>,为用户预留内容占位符

<my-com-1> <p>用户自定义的内容 </p> </my-com-1>

  • 默认情况下,在使用组件时,提供的内容会被填充到名字为 default 的插槽中

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

后备内容

<slot> 标签里面的 默认内容

具名插槽

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

<slot name="header"></slot>

如果省略了 slot 的 name 属性就是 <slot name="default"></slot>

为具名插槽提供内容

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

v-slot:header 只能加给 <template> 只起到包裹作用

<my-com-2>
    <template v-slot:header>
        <h1>ittle<h1>
    </template>

	<template v-slot:default>
        <h1>ittle<h1>
    </template>
</my-com-2>        
  • 具名插槽的简写形式
    • v-slot: => #
    • 外面没有 template包裹只能写 slot="slotName"
作用域插槽

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

组件(用 slot传)传数据给插槽使用者(用 template接收)

插槽使用者接收

  • 匿名插槽
    • <template scope="xx{games}">
    • 只能有一个
  • 具名插槽
    • <tempalte #header="{msg,user}">

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

使用作用域插槽

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

<template #header="scope"> {{scope}} // 使用作用域插槽的数据 </template> 直接接受一个对象 scope

解构插槽 Prop

作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。(子传父)

<template #author="{msg,user}">
  <h3>一行</h3>
  <p>{{msg}}</p>
  <p>{{user.name}}</p>
</template>

自定义指令

私有自定义指令

当指令第一次被绑定到元素上的时候,会立即触发 bind()

1 在 directives 节点下声明私有自定义指令。

directives:{
    color:{
        bind(el){
            // el 是绑定了此指令的、原生的 DOM 对象
            el.style.color = 'red'
        }
    }
}

2 使用指令

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

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

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

通过 binding 获取指令的参数值

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

directives:{
    color:{
        bind(el,binding){
            // 通过 binging 对象的 .value 属性,获取动态的参数值
            // 标签上写 v-color="'red'"/ v-color="color" 都可
            el.style.color = binding.value
        }
    }
}

update 函数

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

directives:{
    color:{
        // 当指令第一次被绑定到元素时被调用(必写)
        bind(el,binding){
            // 通过 binging 对象的 .value 属性,获取动态的参数值
            el.style.color = binding.value
        },
        // 每次 DOM 更新时被调用
        update(el,binding){
            el.style.color = binding.value
        }
    }
}

函数简写

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

directives:{
    color(el,binding){ // 'big-number'(el,binding)
            el.style.color = binding.value
        }
    }
}

全局自定义指令

全局共享的自定义指令需要通过“Vue.directive()”进行声明

Vue.directive('color',function(el,binding){
    el.style.color = binding.value
})
//定义全局指令
Vue.directive('fbind',{
    //指令与元素成功绑定时(一上来)
    bind(element,binding){
        element.value = binding.value
    },
    //指令所在元素被插入页面时
    inserted(element,binding){
        element.focus()
    },
    //指令所在的模板被重新解析时
    update(element,binding){
        element.value = binding.value
    }
})

new Vue({
    el:'#root',
    data:{
        name:'尚硅谷',
        n:1
    },
    directives:{
        //big 函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
        /* 'big-number'(element,binding){
            // console.log('big')
            element.innerText = binding.value * 10
        }, */
        big(element,binding){
            console.log('big',this) //注意此处的 this 是 window
            // console.log('big')
            element.innerText = binding.value * 10
        },
        fbind:{
            //指令与元素成功绑定时(一上来)
            bind(element,binding){
                // 此处的 this 为 window
                element.value = binding.value
            },
            //指令所在元素被插入页面时
            inserted(element,binding){
                element.focus()
            },
            //指令所在的模板被重新解析时
            update(element,binding){
                element.value = binding.value
            }
        }
    }
})

参数:

el:是绑定了此指令的、原生的 DOM 对象

bing: 指令核心对象,描述指令全部信息属性。通过 bing 对象的 .value 属性,获取动态的参数值 {value} = bing

name:指令名

value:指令的绑定值

oldValue:指令绑定的前一个值,仅在 update和 componentUpdated钩子中可用。

expression:绑定值的字符串形式。

arg:传给指令的参数

modifers:modifiers:一个包含修饰符的对象。

vnode  虚拟节点
oldVnode:上一个虚拟节点(更新钩子函数中才有用)

ESLint

约束代码风格,可组装的JavaScript 和 JSX检查工具

ctrl + F 可以 查找规则

路由1

前端路由的概念与原理

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

SPA 与前端路由

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

前端路由

Hash 地址组件之间的对应关系

前端路由:key是路径,value是组件。

锚链接 #

  • 都属于Hash地址
  • <a href="#b1">b1</a> <div id="b1"></div>

前端路由的工作方式

① 用户点击了页面上的路由链接

② 导致了 URL 地址栏中的 Hash 值发生了变化

③ 前端路由监听了到 Hash 地址的变化

④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

实现简易的前端路由

1 通过 <component> 标签,结合 comName 动态渲染组件

<component :is="comName"></component>

export default{
    name:'App',
    data(){
        return{
            comName:'Home'
        }
	}
}

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

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

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

created(){
    window.onhashchange = () =>{
        switch(location.hash){
            case '#home': // 点击了首页链接
                this.comName = 'Home'
                break
            case '#movie':
                this.comName = 'Moive'
                break 
            case '#about':
                this.comName = 'About'
                break    
             
        }
    }
}
vue-router

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

vue-router 安装和配置的步骤

① 安装 vue-router 包

  • npm i vue-router@3.5.2 -S

② 创建路由模块

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

    import Vue from 'Vue'
    import VueRouter from 'vue-router'
    // 调用 Vue.use() 函数,把 VueRouter 安装为 Vue 插件
    Vue.use(VueRouter)
    
    const router = new VueRouter()
    
    export default router
    

③ 导入并挂载路由模块

  • 在 src/main.js 入口文件中,导入并挂载路由模块。

    import Vue from 'vue'
    import App from './App.vue'
    // 1 导入路由模块
    // 在进行模块化导入时,如果给定的是文件夹,则默认导入这个文件夹,名字叫做 index.js 的文件
    import router from '@/router'
    
    new Vue({
        render:h => h(App),
        // 2 挂载路由模块
        router:router
    }).$mount('#app')
    

④ 声明路由链接和占位符

  • 在 src/App.vue 组件中,使用 vue-router 提供的 <router-link> <router-view> 声明路由链接和占位符:
 <template>
      <div class='app-container'>
          <h1> App 组件 </h1>
  
  		// 定义路由链接
  		<router-link to="/home">首页</router-link>
  		<router-link to="/movie">电影</router-link>
  		<router-link to="/about">关于</router-link>
  
  		<hr>
           // 定义路由的占位符  
          <router-view></router-view>
  </template>
声明路由的匹配规则
  • 在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。

    // 导入需要使用路由切换展示的组件
    import Home from '@/components/Home.vue'
    import Home from '@/components/Movie.vue'
    import Home from '@/components/About.vue'
    
    // 创建路由的实例对象
    const router = new VueRouter({
        routers:[ // 在 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({
    routers:[
        // 当用户访问 / 时,通过 redirect 属性跳转到 /home 对应的路由规则 
        {path:'/',redirect:'/home'}
        {path:'/home',component:Home},
        {path:'/movie',component:Movie},
        {path:'/about',component:About}
    ]
})

嵌套路由

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

  • 点击父级路由链接显示模板内容

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

  • 在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。

    <template>
        <div class="about-container">
            <h3>About 组件</h3>
    		// 在关于页面中,声明两个子路由链接
    		<router-link to="/about/tab1">tab1</router-link>
    		<router-link to="/about/tab2">tab2</router-link>
    		<hr/>
            <router-view></router-view>
    </template>
    

2 通过 children 属性声明子路由规则

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

import Tab1 from '@/component/tabs/Tab1.vue'
import Tab1 from '@/component/tabs/Tab2.vue'

const router = new VueRouter({
    routers:[
        { // about 页面的路由规则(父级路由规则)
            path:'/about',
            component:About,
            children:[ // 通过children 属性,嵌套声明子级路由规则
                {path:'tab1',component:Tab1}, // 访问 /about/tab1
                {path:'tab2',component:Tab2}
            ]
        }
    ]
})
动态路由匹配

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

< path:'/movie/:id',component:Movie

$route.params 路由参数对象

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

<template>中使用 <h3>{{this.$route.params.id}}<script> 中写 export default{ name:'Movie' }
路由的query参数

传递参数

<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="`/about/message/detail?id=${item.id}&title=${item.title}`">{{ item.title }}</router-link>
				
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link 
	:to="{
		path:'/home/message/detail',
		query:{
		   id:666,
            title:'你好'
		}
	}"
>跳转</router-link>

接收查询参数:

$route.query.id
$route.query.title

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

< path:'/movie/:id',component:Movie, props:true>

在 Movie 组件中写 props:[‘id’]

直接使用 props 中接收的路由参数

<h3>{{ id }}</h3>

或者 $route.params.id

注意:

1 ‘/’ 后面的参数叫做 路径参数

  • 使用 $route.params.id 访问 路径参数

2 ‘?’ 后面叫查询参数

  • 使用 $route.query.z 访问 查询参数

3 在 this.$route 中 path 只是路径的一部分, fullPath 是完整的地址

4 params 与 query 的区别

  • params传参:是在内存中传参,刷新会丢失
  • query传参:是在地址栏传参,刷新还在
声明式导航 & 编程式导航

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

  • 普通网页中点击 链接、vue 项目中点击 都属于声明式导航

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

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

vue-router 中的 编程式导航 API

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

  • 跳转到指定 hash 地址,并增加一条历史记录,展示对应的组件页面。

  • export default{
        mehtods:{
            getoMovie(){
                this.$router.push('/movie/1')
            }
        }
    }
    

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

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

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

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

  • export default{
        props:['id'],
        methods:{
            goBack(){
                this.$router.go(-1)
            }
        }
    }
    
  • 如果后退的层数超过上限,则原地不动

  • $router.go 的简化用法

    • ① $router.back()
    • ② $router.forward()

导航守卫

导航守卫可以控制路由的访问权限。

全局前置守卫

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

const router =new Vue({...})
                       
router.beforeEach(fn)                       

守卫方法的 3 个形参

const router =new Vue({...})
                       
router.beforeEach((to,from,next)=>{
    // to 是将要访问的路由的信息对象
    // from 是将要离开的路由的信息对象
    // next 是一个函数,调用 next() 表示放行,允许这次路由导航
})     

next 函数的 3 种调用方式

控制后台主页的访问权限

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

路由2

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件。

1.基本使用

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    // router/index.js
    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })
    
    //暴露router
    export default router
    
  4. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
    
  5. 指定展示位置

    <router-view></router-view>
    

2.几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

3.多级路由(多级路由)

  1. 配置路由规则,使用children配置项:

    //  router/index.js
    routes:[
    	{
    		path:'/about',
    		component:About,
    	},
    	{
    		path:'/home',
    		component:Home,
    		children:[ //通过children配置子级路由
    			{
    				path:'news', //此处一定不要写:/news
    				component:News
    			},
    			{
    				path:'message',//此处一定不要写:/message
    				component:Message
    			}
    		]
    	}
    ]
    
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>
    

4.路由的query参数

  1. 传递参数

    // Message.vue
    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>  // 页面中不需要
    				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
  2. 接收参数:

    // Detail.vue
    $route.query.id
    $route.query.title
    

5.命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:

      {
      	path:'/demo',
      	component:Demo,
      	children:[
      		{
      			path:'test',
      			component:Test,
      			children:[
      				{
                            name:'hello' //给路由命名
      					path:'welcome',
      					component:Hello,
      				}
      			]
      		}
      	]
      }
      
    2. 简化跳转:

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
      	:to="{
      		name:'hello',
      		query:{
      		   id:666,
                  title:'你好'
      		}
      	}"
      >跳转</router-link>
      

6.路由的params参数

  1. 配置路由,声明接收params参数

  2. : 占位符

  3. 页面必需的参数,使用内嵌参数 params

    //  router/index.js
    {
    	path:'/home',
    	component:Home,
    	children:[
    		{
    			path:'news',
    			component:News
    		},
    		{
    			component:Message,
    			children:[
    				{
    					name:'xiangqing',
    					path:'detail/:id/:title', //使用占位符声明接收params参数
    					component:Detail
    				}
    			]
    		}
    	]
    }
    
  4. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="`/home/message/detail/${{item.id}}/${{item.title}}`">跳转</router-link>
    				
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
    	:to="{
    	name:'xiangqing', //params to的对象写法不能写 path name是路由的名字
    		params:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  5. 接收参数:

    // Detail.vue
    $route.params.id
    $route.params.title
    

7.路由的props配置

​ 作用:让路由组件更方便的收到参数

//  router/index.js
{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的`key-value`的组合最终都会通过props传给Detail组件(传递死数据,不常用)
	// props:{a:900,b:10} // Detail.vue -> props:['a','b']

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有`params`参数通过props传给Detail组件(query参数收不到
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件 (推荐)
	props({ query }){ // $route
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
    // 连续解构赋值
    props({ params: { id, title } }) {
      return { id: id, title: title }
    }
}

8.<router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace>News</router-link>

9.编程式路由导航

  1. 作用:不借助<router-link> 实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    
    this.$router.replace({
    	name:'xiangqing', // 可以任意指定name跳转
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退
    
    $route: 路由规则
    $router: 路由器
    

10.缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。

  2. 具体编码:

    <keep-alive include="News"> 
        <router-view></router-view>
    </keep-alive>
    

11.两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:
    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。

12.路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    
    // 在需要鉴权的路由规则下:
    meta:{ isAuth:true }
    
    router.beforeEach((to,from,next)=>{
    	console.log('beforeEach',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
    			next() //放行
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    // 作用:点击改变页面 title
    // routes: meta:{title:'about'}
    router.afterEach((to,from)=>{
    	console.log('afterEach',to,from)
    	if(to.meta.title){ 
    		document.title = to.meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    
  4. 独享守卫:

    beforeEnter(to,from,next){
    	console.log('beforeEnter',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){
    			next()
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next()
    	}
    }
    
  5. 组件内守卫:

    //进入守卫:通过`路由规则`,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过`路由规则`,离开该组件时被调用
    beforeRouteLeave (to, from, next) { next()
    }
    

13.路由器的两种工作模式

1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

3. hash模式:
   1. 地址中永远带着#号,不美观 。
   2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
   3. 兼容性较好。
   
4. history模式:
   1. 地址干净,美观 。
   2. 兼容性和hash模式相比略差。
   3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。// nginx
   
// router:
   mode:'hash/history'

路由404

{path:'*',component:NotFound}

声明式导航 - 两个类名

router-link会自动给当前导航添加两个类名

router-link-active: 激活的导航链接   模糊匹配
	to="/my"  可以匹配   /my      /my/a      /my/b    ....
    
router-link-exact-active:  激活的导航链接 精确匹配
	to="/my"  仅可以匹配   /my

Vue封装的过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
      • v 即 <transition>里的name
        • .hello-enter-active
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
      	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

vue脚手架配置代理

方法一

​ 在vue.config.js中添加如下配置:

devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
  4. 8080 请求 5000

方法二

​ 编写vue.config.js配置具体代理规则:

module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

ES6模块化与异步编程高级用法

ES6模块化

ES6 模块化规范是浏览器端与服务器端通用的模块化开发规范。开发者不需再额外学习 AMD、CMD 或 CommonJS 等模块化规范。

定义:
  • 每个 js 文件都是一个独立的模块

  • 导入其它模块成员使用 import 关键字

  • 向外共享模块成员使用 export 关键字

基于 node.js 学习 ES6需配置:

  1. v14.15.1以上版本的 node.js
  2. 在 package.json 的根节点中添加 “type”:“module”
语法:

① 默认导出与默认导入

  • 默认导出:
    • 语法:export default{ 值类型成员,模块私有方法... }(❗️export default只能使用一次)
  • 默认导入:
    • import 接收名称 from '模块标识符'

② 按需导出与按需导入

  • 按需导入

    • import { s1,say } from '模块标识符'
  • 按需导出

    • export let s1 = 'ben';export function...
  • 注意:

    ① 每个模块中可以使用多次按需导出

    ② 按需导入的成员名称必须和按需导出的名称保持一致

    ③ 按需导入时,可以使用 as 关键字进行重命名

    ④ 按需导入可以和默认导入一起使用

③ 直接导入并执行模块中的代码

只想单纯地执行某个模块中的代码,并不需要得到模块中向外共享的成员。

import '模块标识符'

⭐️Promise

回调地狱

多层回调函数的相互嵌套,就形成了回调地狱。

缺点:

  • 代码耦合性太强,牵一发而动全身,难以维护
  • 大量冗余的代码相互嵌套,代码的可读性变差

解决回调地狱–promise

基本概念

Promise 是一个构造函数

  • const p = new Promise()// new实例 代表一个异步操作

Promise.prototype 上包含一个 .then() 方法

  • 实例可以通过 原型链 的方式访问到 .then() 方法,p.then()

.then() 方法用来预先指定成功和失败的回调函数

  • p.then(成功的回调函数【必选】,失败的回调函数【可选】)
    • p.then(result => { }, error => { })
基于回调函数按顺序读取文件内容

基于 then-fs 读取文件内容

1 import thenFs from 'then-fs'

2 thenFs.readFile('./1.txt','utf8').then(r1 => {log r1},err1 => {log err1.message})(重复写)

注意:失败回调可选,无法保证文件读取顺序

基于 Promise 按顺序读取文件的内容

Promise 支持链式调用,从而来解决回调地狱的问题。

import thenFs from 'then-fs'

thenFs.readFile('./1.txt','utf8').then((r1) => {
    return thenFs.readFile('./2.txt''utf8')
}).then((r2) => {
    return thenFs.readFile('./3.txt','utf8')
}).then((r3) => {
    console.log(r3)
}).catch(err => {
    console.log(err.message)
})
通过 .catch 捕获错误
(...).catch(err => {
    console.log(err.message)
})

如果不希望前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前

thenFs.readFile('./1.txt','utf8').catch(err => { console.log(err.message) }).then...
  • 捕获前面发生的错误,并输出错误的信息
  • 由于错误已被及时处理,不影响后面 .then 的正常进行
Promise.all()方法

Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)。

Promise.race() 方法

Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制)。

基于 Promise 封装读文件的方法
function getFile(fpath){
    return new Promise(function(resolve,reject){
        fs.readFile(fpath,'utf8',(err,dataStr)=>{
            if(err) return reject(err)
            resolve(dataStr)
        })
    })
}

getFile('./1.txt').then(resolve,reject)
async/await

async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。

import thenFs from 'then-fs'

async function getAllFile(){
    const r1 = await thenFs.readFile('./1.txt')
    const r2 = await thenFs.readFile('./2.txt')
    const r3 = await thenFs.readFile('./3.txt')
}

getAllFile()

❗️注意

① 如果在 function 中使用了 await,则 function 必须被 async 修饰

② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行

⭐️EventLoop

JavaScript 是单线程的语言

也就是说,同一时间只能做一件事情

问题:

  • 如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。

同步任务和异步任务

JavaScript 把待执行的任务分为了两类,防止某个耗时任务导致程序假死

① 同步任务(synchronous)

  • 又叫做非耗时任务,指的是在主线程上排队执行的那些任务

  • 只有前一个任务执行完毕,才能执行后一个任务

② 异步任务(asynchronous)

  • 又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行

  • 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数

同步任务和异步任务的执行过程

EventLoop 的基本概念

JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这 个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)。

宏任务和微任务

异步任务的两种类别

js 主线程先执行完 宏任务 之后再判断有无微任务,没有再执行下一个 宏任务

① 宏任务(macrotask)

  • 异步 Ajax 请求、

  • setTimeout、setInterval、

  • 文件操作

  • 其它宏任务

② 微任务(microtask)

  • Promise.then、.catch 和 .finally

  • process.nextTick

  • 其它微任务

宏任务和微任务的执行顺序


前端工程化

组件化、模块化、规范化、自动化

  • 前端工程化的解决方案
    • webpack(打包构建)
    • parcel

webpack

  • 解决 浏览器不兼容 ES6 语法

Vue的基本使用

Vue全家桶
  • vue(核心库)
  • vue-router(路由方案)
  • vuex(状态管理方案)
  • vue 组件库(快速搭建页面 UI 效果的方案)

工具:

vue-cli、vite、vue-devtools、vetur

构建用户界面 指令 数据驱动视图 事件绑定 前端框架 构建用户界面的一整套解决方案 vue全家桶 配套工具
Vue 特性
  • 数据驱动视图

  • 双向数据绑定

Vue3 新增功能:

  • 组合式API、多根节点组件、更好的TypeScript支持

废弃的旧功能:

  • 过滤器、不再支持 o n 、 on 、 onoff 和 $once 实例方法等

  • 迁移指南

过滤器(已剔除)

过滤器(Filters)常用于文本的格式化。

可以用在:

  1. 插值表达式

    {{msg | capitalize}}

  2. v-bind 属性绑定

    :id="rawId | formatId"

组件基础
单页面应用程序(SPA)

一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。

SPA 特点:

  • 将所有的功能局限于一个 web 页面中,仅在该 web 页面初始化时加载相应的资源( HTML、JavaScript 和 CSS)。

  • 一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。而是利用 JavaScript 动态地变换 HTML 的内容,从而实现页面与用户的交互。

SPA 优点:

  • 1 良好的交互体验
    • 单页应用的内容的改变不需要重新加载整个页面
    • 获取数据也是通过 Ajax 异步获取
    • 没有页面之间的跳转,不会出现“白屏现象”
  • 2 良好的前后端工作分离模式
    • 后端专注于提供 API 接口,更易实现 API 接口的复用
    • 前端专注于页面的渲染,更利于前端工程化的发展
  • 3 减轻服务器的压力
    • 服务器只提供数据,不负责页面的合成与逻辑的处理,吞吐能力会提高几倍

SPA 缺点:

  • 1 首屏加载慢
    • 路由懒加载
    • 代码压缩
    • CDN 加速
    • 网络传输压缩
  • 2 不利于 SEO
    • SSR 服务器端渲染
两种快速创建工程化的 SPA 项目的方式:
vitevue-cli
支持版本仅支持 vue3支持 3.x 和2.x
基于 webpack?noyes
运行速度较慢
功能完整度小而巧(逐渐完善大而全
建议在企业级开发中使用?目前不建议建议
组件化开发思想

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

好处:

  • 提高了前端代码的复用性和灵活性

  • 提升了开发效率和后期的可维护性

vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。

vue 组件组成结构

  • template -> 组件的模板结构(必选)
  • script -> 组件的 JavaScript 行为(可选)
    • <script> export default { name,data数据、methods方法...} </script>
    • name 节点为当前组件定义一个名称(建议每个单词首字母大写
      • name:'MyApp'
      • 在注册组件时:appponent(Swiper.name,Swiper)
    • data必须是一个函数,不能直接指向一个数据对象
      • data(){return {}}
    • methods:{ 事件处理函数 }
    • components:{ 'my-search':Search,}
  • style -> 组件的样式(可选)
    • <style lang="css"> h1{...}</style>
    • lang="css"(默认)还有 less(npm i less -D)、scss

在 template 中定义根节点:

  • 在 vue2 中,节点内最外层必须由 唯一 的单个根节点包裹

  • 在 vue3 中,中支持定义多个根节点

组件的基本使用:
注册组件
  • 全局注册

    • appponent('my-swiper',Swiper)
    • 直接在template中以标签形式使用 <my-swiper>
    • 使用频率高
  • 局部注册

    • <script>
          import Search for ''
      	export default{
              components:{
                  'my-search':Search,
              }
          }
      
    • 只有特点情况下会被用到

组件注册时名称

  • kebab-case 命名法(俗称短横线命名法,例如 my-swiper)
  • ⭐️PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper)
    • 可以转化成 短横线命名法
组件之间的样式冲突问题
  1. 为每个组件分配唯一的自定义属性,在编写样式时,通过属性选择器来控制样式的作用域

    <template>
        <div class="container" data-v-001>
            <h3 data-v-001>轮播图组件<h3>
        </div>
    </template>
    
    <style>
        .container[data-v-001]{
            border:1px solid red;
        }
    </style>
    
  2. style 节点的 scoped 属性

    自动为当前组件的 DOM 标签和 style 样式应用这个自定义属性,防止组件样式的冲突问题

    <style scoped>
        .container{
            border:1px solid red;
        }
    </style>
    
  3. /deep/样式穿透

    解决scoped 当前组件的样式对子组件是不生效的问题

    <style lang="less" scoped>
        /deep/ .title{
            color:blue;
        }
    </style>
    

    注意:/deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代 /deep/。

全局样式:

1 另外写一个 index.css 文件

2 在 main.js 中引入

组件的 props

为了提高组件的复用性,封装组件时要遵守:

  • 组件的 DOM 结构、Style 样式要尽量复用
  • 组件中要展示的数据,尽量由组件的使用者提供

props 是组件的自定义属性,组件的使用者可以通过 props 把数据传递到子组件内部,供子组件内部进行使 用。

props 的作用:父组件通过 props 向子组件传递要展示的数据。

props 的好处:提高了组件的复用性。

在组件中声明 props

在封装 vue 组件时,可以把动态的数据项声明为 props 自定义属性。自定义属性可以在当前组件的模板结构中被直接使用。

动态绑定 props 的值

可以使用 v-bind 属性绑定的形式,为组件动态绑定 props 的值

props 的大小写命名

组件中如果使用“camelCase (驼峰命名法)”声明了 props 属性的名称,则有两种方式为其绑定属性的值:

1 pubTime = '1889'

2 pub-time = '1889'(推荐)

Class 与 Style 绑定

动态绑定 HTML 的 class

1 可以通过三元表达式,动态的为元素绑定 class 的类名。

:class = "isItalic ? 'isItalic' : ''"

  • 一定要记得带 ''

2 以数组语法绑定 HTML 的 class

:class = "[isItalic ? 'isItalic' : '',isDelete ? 'delete' : '']"

3 以对象语法绑定 HTML 的 class

语法::class="{类名:布尔值}"

:class = "classObj" classObj:{italic:true,delete:false}

4 以对象语法绑定内联的 style

语法::style="样式属性名:样式的值"

  • 样式名要用 小驼峰 命名or 引号

:style="{color:active},fontSize:fsize+'px'"

:style= "styleObj"

props 验证

对象类型的 props 节点

① 基础的类型检查

  • Srting Number Boolean Array Object Date Function Symbol

② 多个可能的类型

  • [String,Number,…]

③ 必填项校验

  • required:true

④ 属性默认值

  • defult:‘’/0 (注意类型要和 type 的相同)

⑤ 自定义验证函数

计算属性

本质上就是一个 function 函数,它可以实时监听 data 中数据的变化,并 return 一个计算后的新值,供组件渲染 DOM 时使用

声明计算属性

① 计算属性必须定义在 computed 节点中

② 计算属性必须是一个 function 函数

③ 计算属性必须有返回值

④ 计算属性必须当做普通属性使用

计算属性 vs 方法

相对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算。因此计算属性的性能更好

自定义事件

在封装组件时:

① 声明自定义事件

  • 在 emits 节点中声明 emits:['change']

② 触发自定义事件

  • methods:{
        onBtnClick(){
            this.$emit('change')
        }
    }
    

在使用组件时:

③ 监听自定义事件

  • @change='getCount'

自定义事件传参

  • this.$emit('change',this.count)

自定义事件解绑

  • 一个事件解绑:
    • this.$off('自定义事件名称')
  • 多个事件解绑
    • this.$off('xxx','yyy')
    • this.$off()
组件上的 v-model

步骤:

父 => 子

  • 父组件
    • :number='count'
  • 子组件
    • props:['number']

子 => 父

  • 父组件
    • v-model:number='count'(不行)
  • 子组件
    • props:['number']
    • emits:['update:number']
    • this.$emit('update:number',this.number+1)

或者:

// 父组件
<aa-input v-model="aa"></aa-input>
// 等价于
<aa-input :value="aa" @input="aa=$event.target.value"></aa-input>
(父组件不用自己拿子组件传过来的数据进行赋值了)

// 子组件:
<input :value="aa" @input="onmessage"></aa-input>

props:['value']
methods:{
    onmessage(e){
        this.$emit('input',e.target.value)
    }
}
组件高级
组件的生命周期
生命周期函数执行时机所属阶段执行次数应用场景
beforeCreate在内存中开始创建组件之前创建阶段唯一1次-
⭐️created组件在内存中创建完毕后创建阶段唯一1次ajax 请求数据
beforeMount在把组件初次渲染到页面之前创建阶段唯一1次
⭐️mounted组件初次在页面中渲染完毕后创建阶段唯一1次操作 DOM
beforeUpdate在组件被重新渲染之前运行阶段0 或 多次
updated组件在页面中被重新渲染完毕后运行阶段0 或 多次
beforeUnmount在组件被销毁之前销毁阶段唯一1次
⭐️unmounted组件被销毁后(页面和内存)销毁阶段唯一1次
组件之间的数据共享

1. 组件之间的关系

① 父子关系

② 兄弟关系

③ 后代关系

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

① 父 -> 子共享数据

    • :msg="massage" :user='userinfo'
    • props:['msg','user']

② 子 -> 父共享数据

    • :change='getNum'
    • emits:['change']
    • this.$emit('change',this.num)

③ 父 <-> 子双向数据同步

    • v-model:number='count'
    • props:['number']
    • emits:['update:props的名字']
    • this.emits('update:props的名字',this.number+1)

3. 兄弟组件之间的数据共享

兄弟组件之间实现数据共享的方案是 EventBus。可以借助于第三方的包 mitt 来创建 eventBus 对象,从而实现兄弟组件之间的数据共享。

1 安装 mitt 依赖包

npm install mitt@2.1.0 -S

2 创建公共的 eventBus 模块

import mitt from 'mitt'
const bus = mitt()
export default bus

3 在数据接收方自定义事件

import bus from './eventBus.js'
export default{
    data(){return { count:0 }}
    created(){
        bus.on('countChange',(num)=> this.count = num)
    }
}

4 在数据接发送方触发事件

bus.emit('countChange',this.count)

4. 后代关系组件之间的数据共享

指的是爷节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂,可以使用 provide 和 inject 实现后代关系组件之间的数据共享。

  • 爷节点

    • 通过 provide 共享数据
    • provide(){ return { color: this.color } }
  • 子孙节点

    • 通过 inject 接收数据
    • inject:['color']
  • 爷节点对外共享响应式的数据

    • 结合 computed 函数向下共享响应式的数据

    • import { computed } from 'vue'
      export default{
          data(){
              return { color:'red' }
          },
          provide(){
              return color:computed(()=> this.color)
          }
      }
      
  • 子孙节点使用响应式的数据

    • inject:['color']
    • 必须以 .value 的形式进行使用
      • {{ color.value }}

5. vuex

vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中,vuex 可以让组件之间的数据共享变得高效、清晰、且易于维护。

  • 全局数据共享
全局配置 axios

在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios

axios.defaults.baseURL = 'http://api'
app.config.globalProperties.$http = axios
ref 引用

每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的实例。默认情况下, 组件的 ​refs 指向一个空对象。

标签上写:ref=mybtn

this.$refs.mybtn.style.color = 'red'

引用到组件的实例之后,就可以调用组件上的 methods 方法

this.$refs.mybtn.add()

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

通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。

v-if="inputVisible " v-else

让文本框自动获得焦点

this.$nextTick(() => {
    this.$ref.ipt.focus() // 异步
})

this.$nextTick(cb) 方法

把 cb 回调推迟到下一个 DOM 更新周期之后执行,从而能保证 cb 回调函数可以操作到最新的 DOM 元素。

动态组件

<component is="要渲染的组件的名称"></component>

使用 keep-alive 保持状态

默认情况下,切换动态组件时无法保持组件的状态。

<keep-alive>
    <component is="要渲染的组件的名称"></component>
</keep-alive>
插槽
// 封装组件
<template>
    <slot></slot>
</template>

// 注册组件
<myCom>
    <p>用户自定义内容</p>
</myCom>
自定义指令
路由

Vuex

在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

何时使用?

多个组件需要共享数据

原理图:

搭建vuex环境

  1. 创建文件:src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state
    })
    
  2. main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	store
    })
    

4.基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //响应组件中加的动作
    	jia(context,value){
    		// console.log('actions中的jia被调用了',miniStore,value)
    		context.commit('JIA',value)
    	},
    }
    
    const mutations = {
        //执行加
    	JIA(state,value){
    		// console.log('mutations中的JIA被调用了',state,value)
    		state.sum += value
    	}
    }
    
    //初始化数据
    const state = {
       sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state,
    })
    
  2. 组件中读取vuex中的数据:$store.state.sum

    ​ 1 在模板里面写不用加 this {{}}

    ​ 2 在脚本里面写要加 this

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$storemit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

5.getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
  3. 组件中读取数据:$store.getters.bigSum

6.四个map方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
         
         简化:sum:state => state.sum...
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$storemit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

7.Modules 模块化+命名空间

将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块

命名空间 namespaced: true

使模块成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
    
methods: {
    ...mapActions('moduleA', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
    ])
}
namespaced: true + createNamespacedHelpers

使 …mapState(‘module1’,[‘count’]) --> …mapState([‘count’])

1 import { createNamespacedHelpers } from 'vuex'

2 const { mapState, mapActions } = createNamespacedHelpers('moduleB')
  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... }, // context 指向当前模块
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

UI组件库

移动端常用 UI 组件库

Vant

Cube UI

Mint UI

PC 端常用 UI 组件库

Element UI

IView UI

案例分析

TodoList1

组件化编码流程:

​ (1) 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

​ (2) 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

​ 1) 一个组件在用:放在组件自身即可。

​ 2) 一些组件在用:放在他们共同的组件上(状态提升)。eg: 读 or 写

​ (3) 实现交互:从绑定事件开始。

props适用于:

​ (1) 父组件 ==> 子组件 通信

​ (2) 子组件 ==> 父组件 通信(要求父先给子一个函数)

技巧:

​ 1 使用 reduce 累计

computed: {
	doneTotal() {
      return this.todos.reduce((pre, current) => pre + (current.isDone ? 1 : 0), 0)
    }
}

​ 2 使用 filter 过滤

clearDone() {
      this.todos = this.todos.filter(todo => !todo.isDone)
    }

​ 3 巧用 计算 属性

computed: {
    isAll() {
      return this.allNum ? this.allChecked : false
    }
  }

​ 4 使用 every 判断

allChecked() {
      return this.todos.every(todo => todo.isDone === true)
    }

​ 5 watch 监视数据 进行存储

todos: localStorage.getItem('todos')
        ? JSON.parse(localStorage.getItem('todos'))
        : [...]
           
watch: {
    todos(newValue) {
      localStorage.setItem('todos', JSON.stringify(newValue))
    }
  }
注意:

使用 v-model 时要切记:v-model 绑定的值不能是props传过来的值,因为props是不可以修改的!

props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

⭐️杂记

cmtCount: {
    // 通过数组的形式 , 为当前属性定义多个可能的类型
  type: [Number, String],
  default: 0
}
  • 在使用组件时,如果某个属性名是“小驼峰”形式,则绑定属性时,建议改写成“连字符”格式,例如:
    • cmtCount => cmt-count

❗️ 修改配置项一定要重新 run 一下项目

EsLint:

'space-before-function-paren': 0

两个重要的原则:

  • 由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
  • 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数, 这样this的指向才是vm 或 组件实例对象。

v-model.number.lazy

当添加了.lazy修饰符后,双向绑定的数据就不同步了,相当于在input输入框失去焦点后触发的change事件中同步。即将数据改到在change事件中发生。

组件化编码流程(通用)

1 实现静态组件

  • 抽取组件,使用组件实现静态页面效果

2 展示动态数据

  • 2.1 数据类型、名称?
  • 2.2 数据保存在哪个组件?

3 交互——从绑定事件监听开始

nanoid

1 安装 npm i nanoid --force

2 import { nanoid } from 'nanoid'

3 id: nanoid()

bootstrap

1 npm i bootstrap

2 // main.js

import 'bootstrap/dist/css/bootstrap.css'

巧用 boolean

不只是 true or false,还可以用来 筛选

条件

eg: :class="{ active: score > 60}"

Vscode 新建文件

// 终端

ni 'xxx'

表单渲染

循环 带有 label标签的 for 和 id 要唯一

:id="'xx'+item.id" :for="'xx'+item.id"

axios

基本语法:
axios发请求的基本语法:
    axios({
        url:'url',
        method:'get/post/put/delete',
        params:{}, //  包含 query 参数对象,问号后面的参数
        data:{}, // 包含请求体参数的对象
    })
    axios.get(url,{配置}) // { params:{id:1} }
    axios.delete(url, {配置})
    axios.post(url, data数据对象)
    axios.put(url, data数据对象)

    使用axios发ajax请求携带参数:
    params参数: 只能拼在路径中: /admin/product/baseTrademark/delete/1
    query参数: 
      拼在路径中的?后面: /admin/product/baseTrademark?id=1
      通过params配置来指定: axios({params: {id: 1}})
    请求体参数: 
      通过data配置或post()/put()的第二个参数指定
循环 带有 label标签的 for 和 id 要唯一

:id="'xx'+item.id" :for="'xx'+item.id"

babel
  • 语法降级

⭐️Vue3

vue3基础入门参考文章!必看

本文标签: 完整版 知识