这里写目录标题

Vue2 介绍

Vue2 不支持 IE8 及以下版本Vue3 不支持 IE11 及以下版本,兼容ie就别用vue3了因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性(Object.defineProperty())。但它支持所有兼容 ECMAScript 5 的浏览器。
Vue是构造函数 使用new创建,是一套用于构建用户界面的渐进式框架。
渐进式代表的含义是:主张最少。我们可以简单的理解为,用自己想用或者能用的功能特性,不想用的部分功能可以先不用。VUE不强求你一次性接受并使用它的全部功能特性。

vue 单向数据流

props emit不能直接修改父级的数据,vuex不能直接修改数据,直接修改会报错(引用类型不会)
在开发中如果有多个子组件依赖与父组件的某个数据,万一子组件真的可以直接修改父组件的数据,那么一个子组件的变化将会引发所有依赖于这个数据的子组件的变化,

数据双向绑定v-model

数据双向绑定和响应式不要搞混
数据和视图相互驱动更新,是相互影响的关系

双向数据绑定通常是指我们使用的v-model指令的实现,是Vue的一个特性,也可以说是一个input事件和value的语法糖。

```javascript
  <input
    :value="age"
    @input="age = $event.target.value"
  />

vue数据响应式

Vue采用的是数据劫持结合发布和-订阅者模式的方式,通过拦截对数据的操作,在数据变动时发 布消息给订阅者,触发相应的监听回调。

vue2

VUE2.0 通过 Object.defineProperty 来劫持对象属性
Vue对数组的7个变异方法(push、pop、shift、unshift、splice、sort、reverse)实现了响应式

缺点
1.Vue 无法检测 property 的添加或移除——通过Vue.set(vm.someObject, ‘b’, 2),或者 this. $ set(原数组, 索引值, 需要赋的值) this.$set(this.arr, 0, “老李”);
2.无法检测数组更改例如 a[1]=0;——同上方法Vue.set(vm.items, indexOfItem, newValue)
3.不能在data里面增加项——初始时写入data,值为空
4.不能检测数组和对象的变化

    // 假如node是遍历到的input节点
    node.addEventListener("input",function(e){
        vm.name=e.target.value;
    })  

2.通过defineProperty来监听每一个属性给input赋值

    Object.defineProperty(data,"name",{
        get(){
            return data["name"];
        },
        set(newVal){
            let val=data["name"];
            if (val===newVal){
                return;
            }
            data["name"]=newVal;
            // 监听到了属性值的变化,假如node是其对应的input节点
            node.value=newVal;
        }    
    })

vue3

VUE3.0 通过 Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

let obj = {
    a: 1,
    b: 2
}
const proxy = new Proxy(obj, {
    get: function(target, prop, receiver) {
        return prop in target ? target[prop] : 0
    },
    set: function(target, prop, value, receiver) {
        target[prop] = 666
    }
})
console.log(proxy.a) // 1
console.log(proxy.c) // 0
proxy.a = 10
console.log(proxy.a) // 666
obj.b = 10
console.log(proxy.b) // 不是666 而是10 

数据驱动视图——MVVM

是一种软件架构模式,是MVC的改进版,MVVM将其中View的状态和行为抽象化,让我们将视图的UI和业务上的逻辑进行分开。简单来说,MVVM是Model-View-ViewModel的简写。即是模型-视图-视图模型。
【model 模型】指的是后端传递的数据。

【view 视图】指的是所看到的页面,亦可以理解为将数据以某种方式呈现给用户

【Viewmodel 视图模型】mvvm模式的核心,它是连接view和model的桥梁

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,不需要手动操作DOM,就可以完成更新
MVVM与MVC最大的区别就是:它实现了View和Model的自动同步

MVC

MVC 是Model-View-Controller的缩写,即模型-视图-控制器。
MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。

Model:后端传递的数据;
View:展示出来的页面;
Controller:业务逻辑。

MVC 是单向通信,即 View 和 Model,必须通过 Controller 来进行控制。使用 MVC 的目的就是将 Model 和 View 代码分离。

  1. View 传送指令到 Controller;
  2. Controller 完成业务逻辑后要求 Model 改变状态;
  3. Model 将新的数据发送到 View,用户得到反馈。

原理

Vue数据驱动,就是利用Object.defineProperty或Proxy,将属性全部转为 getter/setter。
同时每一个实例对象都有一个watcher实例对象,用getter去访问data的属性,watcher此时就会把用到的data属性记为依赖,这样就建立了视图与数据之间的联系。
当之后我们渲染视图的数据依赖发生改变(即数据的setter被调用)的时候,watcher会对比前后两个的数值是否发生变化,然后确定是否通知视图进行重新渲染。

vue渲染过程

  1. new Vue 执行初始化
  2. 挂载$mount方法,通过自定义render,template生成render函数
  3. 使用watch监听数据变化
  4. 当数据改变的时候,render函数执行生成vnode对象
  5. 通过path方法对比新旧vnode,diff算法更新

$mount:挂载定义好的模板

new Vue({el:"#app"})
||
var app=new Vue({}).$mount("#app")

Vnode:虚拟dom,用js去描述一个dom

render函数调用creatElement方法,对子节点进行规范生成vnode,然后调用vm._update方法,执行path函数,把vnode渲染到真实dom上

path:虚拟dom的核心,可以把vnode渲染成真实dom,diff算法

虚拟dom

就是将真实的dom节点用JavaScript来模拟出来

diff算法

添加key 可以提升性能,判断是不是同一个节点

  1. 如果不是一个节点,就删除旧的,插入新的
  2. 如果是一个节点(div),就比较children
    (2.1)如果新旧都没有children,就直接替换
    (2.2)如果新旧都有children,判断文字节点一样吗,不一样就代表有新元素了,就添加
    (2.3)新的有children,旧的没有children,添加进去

v2使用递归+双指针双端比较算法,
v3使用最长递增子序列,通过比较更新后的最长序列,如果递增就代表不用变化子序列元素

vue2使用script引入

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.8/dist/vue.js"></script>

使用

差值绑定

1.文本:{{}}

 <div id="box">
 	{{text}}
    </div>
    
 const Vm = new Vue({
        el: '#box',
        data: {
            text: "Hello", 
        },
    })

2.属性:v-bind:class=“name” name不是变量就需要加‘name’

 <div id="box">
     <button :disabled="true">按钮</button>//disabled后面需要跟布尔类型的值
 	  <div :style="'color:'+colorText+';'">111111</div>//需要字符串拼接
    </div>
    
 const Vm = new Vue({
        el: '#box',
        data: {
            colorText: "pink", 
        },
    })

class,style的绑定

数组语法:<p :class="['red','big']">{{msg}}</p>:会依次将'red''big'解析到class里面
对象语法:<p :class="{red:true,'big':isBig}">{{msg}}</p>:键值对的值为true,则将对应的属性名添加到class,为false则不添加

3.直接绑定html:v-html=“span”

<div id="box">
 	<div v-html="text"></div>
</div>
    
 const Vm = new Vue({
        el: '#box',
        data: {
            text: '<span>1111111</span>', 
        },
    })

逻辑v-if,v-else-if,v-else

<div id="box">
	 <button :disabled="num>=100" @click="num++">按钮</button>//超过100就不可以点击了
        {{num}}
        <div v-if="num>=80">优秀</div>
        <div v-else-if="num<=80&&num>=60">及格</div>
        <div v-else="num<60">不及格</div>
</div>
    
 const Vm = new Vue({
        el: '#box',
        data: {
            num: 45, 
        },
    })

显示隐藏 v-show

<div id="app">
<div v-show="false">显示/隐藏</div>//等于dispaly=“none”
</div>

循环:v-for

<div id="app">

  <div v-for="(item,index) in list" :key="index"> {{item}}-{{index+1}}</div>

</div>

var app = new Vue({
  el: '#app',
  data: {
    list: ['苹果', '香蕉', '菠萝']
  }
})

事件

style scope

当使用ellementui的时候样式问题,覆盖不住,就可以添加deep属性

2.下载脚手架

(1)国内镜像

npmjs是国外的,下载速度会很慢
镜像把npmjs的所有包,全部复制到国内的服务中,保持同步
npm install 安装包的时候,可以将原始的国外源改成国内的镜像,下载安装会更快

查看npm原地址,改成淘宝镜像

npm config get registry
查看npm原地址

npm config set registry https://registry.npmmirror.com/
改成淘宝镜像

在这里插入图片描述

(2)全局包,局部包,依赖包

全局包:所有路径都可以直接使用
局部包:在哪安装,只能在哪用,在某一个项目中使用
依赖包:一个包依赖多个包

(3) vue --version(查看vue版本)

(4)查看电脑中全局安装的所有包,保证有@vue

npm ls -g --depth=0

在这里插入图片描述

(5)卸载npm uninstall -g @vue/cli

(6)全局安装@vue/cli

找到文件夹,右键管理员打开
npm install -g @vue/cli
请添加图片描述

(7)判断是否安装成功

vue --version
在这里插入图片描述

(8)创建vue项目

(8.1)vue create 名字(不能有中文)

进入文件夹cmd
vue create 名字(不能有中文)

(8.2)选择预设

在这里插入图片描述

  • v3
  • v2
  • 手动选择

(8.3) 从备选项中选择特性

在这里插入图片描述

项目Value
Babel根据项目运行环境针对性的转义es6
TypeScriptjs 的超集,给js添加强类型约束
Progressive Web App (PWA) Support渐进式网络
Router路由
Vuex状态管理(统一数据管理)
CSS Pre-processorscss预处理(sass/scss/less)
Linter / Formatter纠错/代码格式化
Unit Testing单元测试
E2E Testing端对端测试

(8.4)Choose a version of Vue.js选择Vue版本

2.x

(8.5) Use history mode for router是否使用history路由

n

(8.6) Pick a CSS pre-processor选择CSS预处理

Sass/SCSS (with dart-sass)

(8.7) Pick a linter / formatter config选择纠错/格式化配置

选择第一个( ESLint with error prevention only )

ESLint with error prevention only 仅语法或者vue规定纠错
ESLint + Airbnb config 使用Airbnb 公司配置纠错
ESLint + Standard config 使用推荐标准配置纠错
ESLint + Prettier纠错+代码格式化

(8.8) Pick additional lint features什么时候纠错

◉ Lint on save
◯ Lint and fix on commit

(8.9) Where do you prefer placing config for Babel, ESLint, etc. Babel和ESLint的配置存放在哪里

❯ In dedicated config files单独的文件中
In package.json

(8.10) Save this as a preset for future projects前面的配置项是否存起来,以后复用,N

请添加图片描述

文件构成

在这里插入图片描述
请添加图片描述

单文件组件 1.vue

data必须为函数

防止产生数据污染。initData时会将其作为工厂函数都会返回全新data对象,操作数据不会影响其他实例,
这也就意味着如果你的data是一个普通的对象,那么所有复用这个实例的组件都将引用同一份数据,这就造成了数据污染!

//安装插件后可以用快捷键vbase
<template>//只可以包一个标签<div class="home">
  <div class="home">    
    <h1>{{num}}</h1>
    <button @click="add">按钮</button>
  </div>
</template>
<script>
//单文件组件的script中,需要导出默认对象
export default {
  name: 'HomeView',
  data() {//以函数的方法书写
    return {
      num: 1
    }
  },
  methods: {
    add(){
      this.num++
    }
  }
}
</script>
<style lang="sass" scoped>
</style>

事件

v-on:click=“my”
@click=“my”

v-if

<div v-if="true">

v-for

<template v-for="(item,index) in arr" :key="item.id">
{{item.name}}
</template>

v-for和v-if的优先级

vue2vue3
v-for > v-ifv-if > v-for
不建议一起使用因为每个循环都要判断if ,消耗性能因为v-if优先级在前,会消失掉,v3中可以< template v-for=“i in arr” :key =“i.id”>< div v-if=“i.name”></ div>< /template>

优化computed

<ul><li
    v-for="user in activeUsers"
    :key="user.id"
  > {{ user.name }} </li></ul>
computed: {
  activeUsers: function () {
    return this.users.filter(function (user) {
      return user.isActive
    })
  }
}

v-model

v-model是双向绑定,给表单项插值绑定一个变量,当用户操作这个表单项时,会自动将该变量做同步(修改变量)

原理

v-bind+@input见下面例子

表单修饰符

项目Value
lazy在change
trim$1600
number$1600
<!--  lazy修饰符:使得用户在输入数据之后,当数据失去焦点或点击回车时,才会进行数据的更新-->
  <input type="text" v-model.lazy="message">
  <h2>用户输入的内容: {{message}}</h2>
<!--  number修饰符:将用户输入的类型由string型转为number型-->
  <input type="number" v-model.number="num">
  <h2>{{num}}: {{typeof num}}</h2>
  <!--  trim修饰符:将用户输入的文本信息中开头的空格,结尾的空格都删除-->
  <input type="text" v-model.trim="name">
  <h2>用户输入的内容: {{name}}</h2>

事件修饰符

1.prevent:阻止默认事件(常用)
​ 2.stop:阻止事件冒泡(常用)
​ 3.once:事件只触发一次(常用)
​ 4.capture:使用事件的捕获模式
​ 5.self:只有 event.target 是当前操作的元素时才触发事件
​ 6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕

按键修饰符

@keyup.entry=“my”

项目Value
.entry当key是entry时候
.esc当key是esc的时候

鼠标修饰符

<button @click.left="shout(1)">ok</button>//左
<button @click.right="shout(1)">ok</button>//zhon
<button @click.middle="shout(1)">ok</button>

v-bind修饰符

能对props进行一个双向绑定

//父组件
<comp :myMessage.sync="bar"></comp> 
//子组件
this.$emit('update:myMessage',params);

相当于下面的写法

//父亲组件
<comp :myMessage="bar" @update:myMessage="func"></comp>
func(e){
 this.bar = e;
}
//子组件js
func2(){
  this.$emit('update:myMessage',params);
}

如果不用v-model如何绑定数据

  <input
    :value="age"
    @input="age = $event.target.value"
  />

组件v-model+inpu v-model

字符串

//a.vue引入b.vue(是input组件)
   <item-git v-model="inp"></item-git>
 inp: "123",
//b.vue
<input :value="value" @input="$emit('input', $event.target.value)" />
 props: {
        value: {
            type: String,
        },
    },

对象

//b.vue
  <input :value="value.age" @input=" $emit('input', { age: $event.target.value, name: value.name })  "  />
  <input  :value="value.name" @input=" $emit('input', { name: $event.target.value, age: value.age })  " />
   props: {
        value: {
            type: Object,
        },
    },
 <item-git v-model="inp"></item-git>
     inp: { age: 19, name: "张三" },

单输入框使用v-model

<input v-model="age" />

data() {
    return {
      age: 16,
    };

单个多选框使用v-model,一个值true/false

//如果勾选中对钩则点击按钮可以点击
  <input
    type="checkbox"
    v-model="checked"
  />
  <a href="./#/about">用户协议</a>
  <button :disabled="!checked">提交注册</button>
data() {
    return {
      checked: true,
    };
  },

在这里插入图片描述
在这里插入图片描述

多个多选框v-model 数据是数组

<label>
    <input
      type="checkbox"
      value="A"
      v-model="aList"
    >A. JSONP
  </label>
  <label>
    <input
      type="checkbox"
      value="B"
      v-model="aList"
    >B. 服务器代理
  </label>
  
 data() {
    return {
      aList: ['A'],//默认选中A
      //选中添加到数组里
    };

select 下拉列表使用v-model

//默认选中sh
 <select v-model="city">
    <option value="bj">北京</option>
    <option value="sh">上海</option>
    <option value="gz">广州</option>
  </select>

   data() {
    return {
      city: 'sh'
    };

计算属性computed

根据依赖关系进行缓存的计算,只有在它的相关依赖发生改变时才会进行更新
比如搜索数据,拖动排序 使用vuex,不能直接修改component,使用computed的get和set

//验证表单数据 ,满足条件才可提交
<input v-model="formData.phone" placeholder="手机号" />
  <input v-model="formData.code" placeholder="验证码" />
  <button :disabled="isSubmitDisabled">登陆</button>
  
  computed: {
    isSubmitDisabled() {
      return !/^1\d{10}$/.test(this.formData.phone) || !/^[0-9]{4}$/.test(this.formData.code);
    }
  },

get和set

默认只有 getter,可以设置set

<button @click="onchangName">按钮修改值</button>
        {{changName}}

		 data() {
            return {
               name:'李四'
            }
        },
        methods: {
          onchangName(){
            this.changName='张三'//点击按钮后执行set之后还会在执行一次get
            //所以控制台一开始打印1 
            //点击按钮后打印2,1
          }
        },
        computed: {
            changName: {
                get(){ console.log(1)
                    return this.name
                },
                set(val){ console.log(2)
                    this.name=val
                }
            }
        },

新闻列表搜索

searchList(){
                if(this.keyword){
                    return this.list.filter(i=>{ return i.includes(this.keyword)})
                }else{
                    return this.list
                }
                
            }

计算属性传值, return一个 function

const comp = computed((a, b) => {
  return function (a: number, b: number) {
    return a + b
  }
})

监听器watch

监听data中的数据 当data中的数据改变的时候 watch就会触发
也可以监听一个计算属性变化,购物车商品结算的时候,动态获取数据,一开始还没数据监听数据变化

watch在vue中是一个单独的配置项
watch中的函数名称必须和属性名称一致,且不能人为调用
没有返回值,如需使用处理结果,必须将结果赋值给data中的成员再进行使用

1.简单监听

<input type="text" v-model="name"/>
 data(){
    return {
      name: ""
    }
  },
 
 watch: {
    name(newValue, oldValue) {//新值和旧值
      
    }
  },

2.复杂监听

watch只会监听数据的值是否发生改变,而不会监听地址的变化,如果需要监听引用类型的数据变化,需要深度监听:obj:{handler(newVal){},deep:true}------用handler+deep的方式进行深度监听。
在特殊的情况下(更改数组中的数据时,数组已经更改,但是视图没有更新),watch无法监听数组的变化,更改数组必须要用splice()或者$set。
immediate: true, // 立即执行
immediate会让watch执行顺序提升至created之前,这个可是坑点,如果你在immediate中修改了初始data值,就会导致created获取的值是修改过的值,导致一些人瞬间懵逼。
deep: true, // 深度侦听复杂类型内变化

<template>
  <div>
    <input type="text" v-model="user.name">
    <input type="text" v-model="user.age">
  </div>
</template>
 
<script>
export default {
  data(){
    return {
      user: {
        name: "",
        age: 0
      }
    }
  },
  // 目标: 侦听对象
  watch: {
        // 第一种:监听整个对象,对象里的每个属性值的变化都会执行handler
        // 注意:属性值发生变化后,handler执行后获取的 newVal 值和 oldVal 值是一样的
    user: {
      handler(newVal, oldVal){
        console.log(newVal, oldVal);
      },
      deep: true,
      immediate: true
    }
  }
// 目标: 精确侦听对象中的某个值
 watch: {
       // 第二种方式:监听对象的某个属性,被监听的属性值发生变化就会执行函数
       // 函数执行后,获取的 newVal 值和 oldVal 值不一样
    'user.name': {
      handler(newVal, oldVal){
        console.log(newVal, oldVal);
      },
      deep: true,
      immediate: true
    }
  }
}
</script>

如何监听多个值的变化?watch+computed

data() {
    return {
       password:"",
       userCode:""
    }
},
computed: {
   info() {
      const { password, userCode } = this
      return {
          password,
          userCode
      }
   },
},
watch:{
    info:{
       handler: function(newval , oldval) {
          if(newval.password && newval.userCode){
            this.sign = true
          }else{
            this.sign = false
          }
       },
       deep:true
   },
}

computed和watch的区别

computedwatch
计算的内容需要依赖多个属性的情况计算的内容依赖一个属性的情况
监听值未在data中定义,以return返回值形式;监听值要在data中先定义,可以不写return返回值;
计算属性的值会被缓存,只有实例中相关依赖值改变时,才重新计算,性能好但不适合做异步请求;不支持缓存,可以做异步操作;
计算属性默认只有get来读取,手动修改计算属性时,会触发手写的set函数。监听值改变,回调函数自动调用。
项目应用:(1)平均分,(2)总价,(3)成功/失败文字的切换,(4)拖动组件(Vue.Draggable)的时候使用vuex的数据不允许直接修改state,使用computed的get和set处理处理异步传进来props的数据问题,监听路由$router变化,搜索数据筛选

节流防抖用在什么地方

防抖:输入框的搜索功能 ,页面resize事件,提交按钮
节流:scroll 事件

过滤器filter

vue中的过滤器可以用在两个地方:双花括号插值和 v-bind 表达式
平时开发中,需要用到过滤器的地方有很多,比如单位转换、数字打点、文本格式化、时间格式化之类的等,
比如我们要实现将30000 => 30,000,这时候我们就需要使用过滤器

在这里插入图片描述

基本使用

在这里插入图片描述

全局定义

在这里插入图片描述

传值

在这里插入图片描述
filterA收到message的值,处理完成后的值传到filterB

{{name|newFilter('---')}}//张三---

name:'张三',

 newFilter: function(value,str) {
                return value+str;
            }

data数据

重置data数据

  Object.assign(this._data, this.$options.data());
//  this.$options.data()执行完成后,返回初始化data数据,赋值给data

组件component

views里面放路由组件,components里面放非路由组件

全局组件main.js

所有组件都可以使用,自己也可以调用自己

import ModalBox from './components/ModalBox.vue'

Vue.component('modal-box',ModalBox)//全局定义   参数一组件名,参数二组件定义

局部组件

A.vue
B.vue
B引到A里面
1.在A页面引入B组件 import Bvue from ‘./B.vue’;
2.在components中挂载组件
3.在页面中使用

//A.vue
<template>
  <div class="home">    
    <Bvue></Bvue>//3.页面中使用
  </div>
</template>

<script>
//单文件组件的script中,需要导出默认对象
import Bvue from './B.vue';//1.引入组件
export default {
  components: {//2.挂载组件
    Bvue,//相当于Bvue:Bvue
  },
}
</script>

组件之间通信总结

props / $emit(vue2)父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信
defineProps/defineEmits(vue3)写法
子defineExpose——>父ref(vue3)暴露子组件的方法属性
$ emit/$onvue2 eventBus
mitt.jsvue3的eventBus(Vue3 从实例中完全删除了 $ on、$off 和 $once 方法)
v-model语法糖
slot插槽
vuex全局事件总线
piniavue3
provide/injectv3 多用于祖孙通信(祖传孙)依赖注入
$ parent/$children 与 ref通过父链 / 子链也可以通信
插槽父子组件通信作用域插槽

父——>子

通过自定义属性,子组件用过props接收

//父
<modal-box :show="showModal" > </modal-box>

//子
  props: {//不允许修改组件的props,vue是单向数据流,如果要修改,只能派发一个自定义事件,让父组件改
            show: {
                type: Boolean,
                default: false,
                require:true,//必传项
                 //如果是对象默认值default:()=>({}),
                 //数组 default: () => []
        },}

 props:['show'],//简写方式

如果子组件没有声明props 会在$attrs身上

如果是对象默认值default:()=>({}),
props 默认值需要函数返回原因和data是一样的吧。多个父组件引用同一个子组件时,引用类型 props 要相互隔离。

子——>父

通过自定义事件,子组件通过 this.$emit(‘close’,num1,num2)

//父
<modal-box @close="showModal=false" ></modal-box>

//子
<button @click="onClose">X</button>


methods: {
            onClose(num1,num2) {
                this.$emit('close')
            }
        },

兄弟——>兄弟(eventBus/eventHub)

1, main.js中定义实例

//main.js
//利用公共组件实例上的$on 和$emit通信
//在已经创建好的Vue实例原型中创建一个EventBus
Vue.prototype.$EventBus = new Vue()   
//使用a.vue 发送
 methods: {
    sendMsg() {
      /*调用全局Vue实例中的$EventBus事件总线中的$emit属性,发送事
      件"aMsg",并携带A组件中的Msg*/
      this.$EventBus.$emit("aMsg", '123');
    }
  }


//b.vue 接收
 mounted() {
    /*调用全局Vue实例中的$EventBus事件总线中的$on属性,监听A组件发送
    到事件总线中的aMsg事件*/
    this.$EventBus.$on("aMsg", (data) => {
      //将A组件传递过来的参数data赋值给msgB
      this.search(data)
    });
  }
    methods:{
        search(data){console.log(data)}
    }
  //需要解绑
 beforeDestroy() {
    this.$EventBus.$off("abc", this.search);
  },

v3移出了 官方移除 $ on、$ off 和 $once 方法 需要使用第三方库
mitt
tiny-emitter添加链接描述

eventbus原理

利用的发布订阅,$on订阅消息, $emit发布消息

class EventBus {
    constructor() {
        this.eventObj = {}
    }
//注册事件(订阅)
    on(eventName, cb) {//第一个参数是事件名,第二个是回调函数
        if(this.eventObj[eventName]) {
            // 如果事件存在则push进去
            this.eventObj[eventName].push(cb)
        } else {
           // 不存在则新建一个数组存储
            this.eventObj[eventName] = [cb]
        }
    }
// 触发事件(发布) 
    emit(eventName, ...params) {//第一个参数是事件名,后面的都是参数
        if(!this.eventObj[eventName]){
            console.log('事件不存在')
        } else {
            this.eventObj[eventName].map(cb => {
                cb(...params)//添加到数组里面,并且执行
            })
        }
    }
 
     off(eventName) {
        if(this.eventObj[eventName]){
            delete this.eventObj[eventName]
        }
    }
}
 
const eventBus = new EventBus()
export default eventBus

$parent和 $refs实现通信

ref获取某一子组件
$parent 获取当前组件父组件,可以操作父组件的数据和方法

//a.vue获取
this.$parent.$refs.list.listArr//[{},{}...]
//父.vue
 <b ref="list"></b>//需要定义ref才可以找到

$children

获取当前组件下的所有子组件,返回数组

//item.vue
<a-view/>
<b-view/>
this.$children//a和b组件的实例

vuex任意组件通信

//a.vue发送到vuex
this.$store.commit('setFormData', JSON.parse(JSON.stringify(this.formData)));
//store/index.js
   setFormData(state, formData) {
      state.formData = formData;
    }
//b.vue监听vuex里面的数据变化,拿到数据
watch: {
     '$store.state.formData'() {
      this.pageNo = 1;
      this.getList();
    }
  },

组件的v-model

组件的v-model 是当子组件满足props是value 抛出事件叫input 同时要同步更新value 就可以用v-model
子组件进行操作更改父组件的一个值的时候用到

//父
	<start-list @input="star=$event" :value="star"></start-list>
        <!-- 下面这句是上面的语法糖 -->
     <start-list v-model="star"></start-list>
//子
<li v-for="item in 5" :key="item" 
                :class="{'active':item<=value)}"
                @click="$emit('input',item)"//抛出事件input
            >
                
            </li>
 props: {
            value: {//传入value
                type: Number,
                default: 3
            }
        },

插槽slot

组件的子标签

默认插槽

<slot></slot>

具名插槽

//注意 v-slot 只能添加在 < template> 上

//父
<Left>
   //<template slot=“mySlot”>老写法2.60之前的写法
   //<template #mySlot>#mySlot缩写
    <template v-slot:mySlot>//注意 v-slot 只能添加在 <template> 上
        <p>这是在 Left 组件声明的p标签</p>
    </template>
</Left>
//子
<slot name="mySlot"></slot>

如果没有子组件定义slot 会在$slots数组有vnode虚拟dom
在这里插入图片描述

作用域插槽

数据在子组件内部,现在需要的父组件引用,现在的问题是 父组件拿不到子组件的数据,但是又需要从父组件通过插槽传递数据
注意默认插槽的缩写语法不能和具名插槽混用

//父
<SlotComponent   v-slot="slotProps">
    <button>{{slotProps.item}}  序号 {{slotProps.index}}</button>
  </SlotComponent>
//子
<slot :item="item" :index="index"/>
//子
<div>
      <slot :user="user">
            {{ user.lastName }}
       </slot>
</div>
 data() {
            return {
                 user:{
                    lastName:'zhang',
                    firstName:'li'
                },
            }
        },
//父
<zuo-yong>
			//<template slot-scope="slotProps">//老的写法
			//<template #default="slotProps">//简写
            //<template v-slot:default="slotProps">//默认
             <template v-slot="slotProps">
			    {{ slotProps.user.firstName }}
			  </template>
</zuo-yong>

只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:

<current-user>
  <template v-slot:default="slotProps">//需要加default,完整写法
    {{ slotProps.user.firstName }}
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</current-user>

动态组件component is:

用在选项卡

//<keep-alive include="Left">
//      <component :is="name"></component>
//</keep-alive>

<component :is="componentTag"></component>
data() {
    return {
        componentTag: '',//写组件名
    }
},
//选项卡
<template>
 <el-tabs v-model="name">//获得组件名
      <el-tab-pane
        label="未入库设备状态"
        name="PreStatusTab"
      ></el-tab-pane>
      <el-tab-pane
        label="已入库设备状态"
        name="EquipStatusTab"
      ></el-tab-pane>
      <el-tab-pane
        label="设备分类"
        name="CategoryStatusTab"
      ></el-tab-pane>
    </el-tabs>
    <!-- <pre-status-tab v-if="name==='PreStatusTab'"></pre-status-tab>
    <equip-status-tab v-if="name==='EquipStatusTab'"></equip-status-tab>
    <category-status-tab v-if="name==='CategoryStatusTab'"></category-status-tab> -->
//选项卡底部切换组件
    <component :is="name" />//等于上面三行
  </div>
</template>

<script>
import PreStatusTab from "@/components/PreStatusTab.vue";
....注册组件
export default {
  components: { PreStatusTab, EquipStatusTab, CategoryStatusTab },
  data() {
    return {
      name: "PreStatusTab",//变量存储当前显示组件
    };
  },
};
</script>

封装过的公共组件

二次封装element-ui的table,表单
动态生成table,只修要修改传进去的数组,就可以对表格进行操作,比如减少两列,改变顺序,通过数据判断添加slot
按钮的二次封装
el-button 动态传递文字和图标
页面的头部,底部,轮播图,

分页器,日历

使用slot 实现组件的可扩展性
利用props和emit 实现传递参数

$实例系列

vm.$el

在左右列表联动的时候用到,获取到顶部的距离
获取Vue实例关联的DOM元素;
$el用于vue组件,普通dom不行

获取当前组件的template的根标签

<template>
    <div class="life-time">
    </div>
</template>
 mounted(){ console.log(this.$el)},

在这里插入图片描述

父子组件的$el

// 子组件
<template>
  <div>
    测试
  </div>
</template>
<script>
export default {
  name: 'TestComs'
};
</script>

// 父组件
   <test ref="testCom" />
   <div ref="test">11</div>
  mounted() {
    console.log(this.$refs.testCom, '组件ref'); // 获取组件实例
    console.log(this.$refs.testCom.$el, '组件el'); // 获取组件实例的dom元素
    console.log(this.$refs.test, '元素ref'); // 获取dom元素
    console.log(this.$refs.test.$el, '元素el'); // $el用于vue组件,普通dom元素不能用
  },

在这里插入图片描述

vm.$data

获取Vue实例的data选项(对象)

vm.$options

获取Vue实例的自定义属性(如vm.$options.methods,获取Vue实例的自定义属性methods)

vm.$refs

获取页面中所有含有ref属性的DOM元素(如vm.$refs.hello,获取页面中含有属性ref = “hello”的DOM元素,如果有多个元素,那么只返回最后一个)

ref加在普通元素上

获取用this.$ref.xxx可以获取到dom元素。

 <div ref="system">测试</div>
// 获取
mounted() {
  console.log(this.$refs.system);
}

.ref加在子组件上

用this.$ref.xxx可以获取到组件实例,可以使用子组件的所有方法。

// this.$ref.xxx.方法名()
// 父组件
<contact-info ref="contactInfo"/>
import ContactInfo from './ContactInfo'
components: { ContactInfo },
mounted() {
   this.$refs.contactInfo.initVal(data) // 调用子组件方法
}
// 子组件
methods: {
  initVal(data){
    Object.keys(this.contactInfo).forEach(val=>{
      this.contactInfo[val] = data[val]
    })
  }
}

注意:

  1. 在created()中使用
    在这个生命周期中进行数据观测、属性和方法的运算,watch事件回调,但是此时dom还没有渲染完成,是不能通过ref调用dom的。
    解决在mounted中调用或者使用nextTick。
  2. 父元素或者当前元素使用了v-if或者v-show
    因为 $ref不是响应式的,只在组件渲染完之后才会生效,在初始渲染的时候是不存在的
    因为是非响应式的,所有动态加载的模板更新它都无法相应的变化
    解决:通过setTimeout(() => {…}, 0)来实现

$ parent和$ children

$ children返回的是数组

$parent指向调用该组件的直接组件(父组件)实例,
通过 $parent 获取到 父组件 中的 props 、 data或者方法,

this.$parent.$refs.list.listArr

vm.$nextTick

修改完数据,DOM 更新完毕后,真实dom放入页面之后,vue帮你调用执行 this.$nextTick 内的代码

//vue修改数据后dom的更新异步的,解决setTimeout/$nextTick

场景:当使用插件,同时数据要动态请求的时候

new Vue({
  // ...
  methods: {
    // ...
    example: function () {
      // 修改数据
      this.message = 'changed'
      // DOM 还没有更新
      this.$nextTick(function () {
        // DOM 现在更新了
        // `this` 绑定到当前实例
        this.doSomethingElse()
      })
    }
  }
})

vm.$emit( eventName, […args] )

触发当前实例上的事件。附加参数都会传给监听器回调。

$ attrs (v-bind=“$ attrs”:父组件传过来的属性,props没有接收就会在$attrs中显示 )

//hinButton.vue
 <el-button v-bind="$attrs" v-on="$listeners"></el-button>
 <hin-button title="">添加</hin-button>

生命周期

和小程序声明周期不太一样app+page+component (本身,所处页面)
vue 都是组件生命周期

在这里插入图片描述

项目Value
beforeCreate创建前,准备要创建了(没有this,不能访问data、method)
created创建完成(有this,data,method 但是视图还没好,可以调用接口,调用方法,设置data,数据初始化)
beforeMount挂载前(模板在内存中编译好了,但是还渲染到页面,此时是旧页面)
mounted挂载完成(dom好了,可以操作dom,使用dom 做个图表,swiper,读取节点尺寸 ,可以调用this.$el)
beforeUpdate更新前(只有view上面的数据变化才会触发beforeUpdate和updated,仅属于data中的数据改变是并不能触发。)
updated完成更新(数据已经更改完成,dom也重新render完成。)
beforeDestroy销毁前(收拾烂摊子,停掉定时器,解绑自定义事件)
destroyed销毁后(Dom元素存在,只是不再受vue控制,卸载watcher,事件监听,子组件。)

父子组件生命周期执行顺序

父子组件加载渲染数据过程

在这里插入图片描述

父子组件更新渲染数据过程

父组件 beforeUpdate -->

子组件 beforeUpdate -->

子组件 updated -->

父组件 updated -->

父子组件销毁组件数据过程

父组件 beforeDestroy -->

子组件 beforeDestroy -->

子组件 destroyed -->

父组件 destroyed -->

不同页面跳转时各页面生命周期

先新页面的beforeCreate、created、beforeMount,然后旧页面的beforeDestroy、destroyed,再然后新页面的mounted等。

路由

路由原理

  • hash模式,改变location属性的hash值,灵活运用了html的瞄点功能、改变#后的路径本质上是更换了当前页面的瞄点,所以不会刷新页面。
  • history模式基于location属性的pathname,和HTML5新增的pushState()和replaceState()两个api,以及浏览器的popstate事件,实现前进后退等功能。url地址就是向服务器实际请求的地址,页面刷新会基于当前url向服务器请求,服务器返回当前url对应数据。

H5 提供的pushState(data, title, targetURL) 和 replaceState(data, title, targetURL)

h5中提供了只修改地址栏、不修改页面内容的api:
replaceState类似于pushState,但是会直接替换掉当前url,而不会在history中留下记录

pushState(添加浏览历史),
replaceState(修改当前浏览历史),

        history.pushState('#1', null, '#1');
        history.pushState({ a: 1, b: 2 }, null, '#2');
        history.pushState('#3', null, '#3');
        window.onpopstate = function(event) {
            console.log('location: ' + document.location);
            console.log(event.state);
        };
        //另一种添加事件的方法
        window.addEventListener('popstate', function(event) {
            console.log('location: ' + document.location);
            console.log(event.state);
        });
        history.go(-1)//触发popstate

超链接跳转地址栏显示#/home…
根据onhashchange 监听location.hash的变化
筛选出routes [ ]对应的数据
更改 id="router-view"的里面内容,

<body>
<a href="#/home">首页</a> | 
<a href="#/about">关于</a>

<div id="router-view"></div>

<script>
const routes = [{
    path: '/home',
    component: '<div class="home">首页</home>'
}, {
    path: '/about',
    component: '<div class="about">关于</home>'
}];

function navigate(url) {
    const route = routes.find(i => i.path === url);
    document.querySelector('#router-view').innerHTML = route.component;
}

const url = location.hash.slice(1);
navigate(url);

window.onhashchange = () => {
    const url = location.hash.slice(1);
    navigate(url);
};
</script>
</body>

vue中使用

下载去看官网

//页面.vue中
 <router-link to="/week" active-class="active">week</router-link>//可以高亮设置
 <router-link to="/book" active-class="active">week</router-link>
 <router-link to="/about" active-class="active">about</router-link>
 
  <router-view/>//路由展示区域

在这里插入图片描述

//router/index.js中配置
import Vue from 'vue'
import VueRouter from 'vue-router'//引入路由
import WeekView from '../views/WeekView.vue';//引入页面
...

Vue.use(VueRouter)//注册路由

const routes = [
  {
    path: '/week',
    name: 'week',
    component: WeekView
  },
  {
    path: '/about',
    name: 'about',
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')//第二种引入方式
  }
]

const router = new VueRouter({
  routes
})

export default router//导出路由

//在main.js中引入并且注册传入根实例
//在任意组件中就可以this.$router

原生js获得hash路由传参参数

encodeURL() //只把需要的中文编码,
encodeURIComponent()//全部都编码
decodeURIComponent()//解码

 created () {
            const url=location.href
            const queryStr=url.split('?')[1]||''
            const queryArr=queryStr.split('&')
            const query={}
            queryArr.forEach(i=>{
                const arr=i.split('=');
                query[arr[0]]=decodeURIComponent(arr[1])
            })

            console.log(query)
            console.log(this.$route.query)//vue获取方法
        },

vue-router的hash模式和history模式

mode:'hash/histroy',
history 模式打包上线后 ,刷新会404
由后端配置。
hash模式history模式
优点优点
兼容性强,兼容性达到了IE8,带有#不需要#, 使用起来比较美观
#后面不会作为路径的一部分发送给服务器可以使用history.state获取完整的路由信息
改变#后的路径、不会自动刷新页面后端可以获取到完整的路由信息
无需服务端进行配合
缺点缺点
访问路径上包含#,不美观兼容性只到IE10
对于需要锚点功能的需求会与当前路由机制发生冲突改变url路径后、会重新请求资源。
重定向操作时,后段无法获取url完整路径。若访问的路由地址不存在时、会报404,需服务端配合支持重定向返回统一的404页面。

路由跳转的方式

声明式导航

router-link是个组件,定义多了会有卡顿的效果,所以可以考虑编程导航

//router-link(to属性)

编程式导航

//利用实例的$router.push|replace方法

传递参数

传递query参数

vue官网详细解释说明使用同一路由携带不同参数,本质上是重用相同的组件实例,默认在跳转路由时会采用缓存策略,并不会刷新当前路由组件,因此不会调用组件的生命周期挂钩

解决办法:
1.使用watch监听 $route(to, from) {}
2.给路由添加唯一key
3.provide和inject结合使用-利用v-if原理重载路由

 接收参数:this.$route.query
 //{{$route.query.id}}

1.通过类似get参数,用?拼接 /pagetwo?name=老王&age=20

(1)to写法:<router-link to="/pagetwo?name=老王&age=20">跳转到页面2</router-link>
(2): this.$router.push('/pagetwo?name=小红&age=10')
     this.$router.push({name:'pagetwo',query:{name:'张飞',age:10}})

传递params参数

params参数可传可不传?,使用undefined解决传递字符串问题
this.$router.push({name:‘search’,params:{keyword:‘ ’ || undefined}})

this.$route.params
//传递的params参数{{$route.params.id}}

2.把参数作为url的一部分params参数,用/分割,/my/singdetail/001
必须需要提前留好位置router下的index.js,path: ‘/pageone/:name?/:age?’,(?代表可选参数)

1.to写法	<router-link :to="/pagetwo/老王/30">跳转到页面2</router-link>
//需要给路由设置name
2.push写法   	this.$router.push({
			                    name:'pageone',//******params传递参数,必须用name,*******
			                    params:{//不能刷新页面,刷新就消失了,解决:在index.js  path:'/pageone/:name?/:age?',
			                        name:'老北鼻',
			                        age:60
				                    }
				  })
	this.$router.push('/search/' + this.keyword)

query和params区别

queryparams
不属于路径一部分(/home?name=123)属于路径当中的一部分,配置路由的时候要占位(/home/123)

传递props参数

{
	path:'search/:keyword?',
	component:Search,
	name:"Search",
	props:true,//1.布尔值写法,只能传递params参数
	props:{//2.对象写法,额外传递的props参数
		a:2,
		b:3
	},
	props:($route)=>{//函数写法,可以把query和params参数
		return {keyword:$route.query.keyword,
				k:$route.params.k
				}
	}
	
}
//search组件接收props参数
props:['keyword','a','b']

编程式路由

因为router-link最终会转化为a标签,但如果我需要button标签,所以不能在写router-link,还有一个场景需要编程路由,页面加载3秒后跳转

//push跳转
<button @click="goTo">点我 跳转</button>
goTo(){
this.$router.push({
	path:'',
	query:{
	id:'',title:''
}
})
}
//replace跳转

历史记录操作模式push,replace模式

$router.push(‘/login’) ,本质是向history栈中添加一个路由,在我们看来是切换路由,但本质是在添加一个history记录,跳转到指定路由

开启replace模式

< router-link replace to="/about">

$router.replace({path:‘home’}),//替换当前栈顶的路由,以前的不管,没有历史记录

router/index.js配置

{
    path: '/pageone/:name?/:age?',//(?)可选参数
    // path: '/pageone',
    name: 'pageone',//路由名称
    component: PageOne,
    redirect:'/pageone/news',//路由重定位,父路由展示子路由
    meta:{title:'页面一'},//路由原信息,用于配置路由守卫
    children:[//子路由
                {
                    path:'news',//这里的路径就不要加'/'了
                    component:News,
                }
            ],
     props(route){//让路由组件更方便收到参数
     //页面的props:['id','title']
     	return {
     		id:route.query.id,
     		title:route.query.title
			}
		}
  },

二级路由

routes:[
        {
            //第一组路由规则
            path:'/home',
            component:Home
            children:[
                {
                    path:'news',//这里的路径就不要加'/'了
                    component:News,
                }
            ]
        },
    ]
<nav>
<router-link to="/home/news">News</router-link>
<router-link to="/home/message">Message</router-link>
</nav>
<router-view></router-view>

$ router和$ route

$ router API

$router是VueRouter的一个对象,通过Vue.use(VueRouter)和Vue构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由,包含了许多关键的对象和属性

  • $router.push(‘/login’)
    ,本质是向history栈中添加一个路由,在我们看来是切换路由,但本质是在添加一个history记录,跳转到指定路由
  • $router.replace({path:‘home’}),//替换路由,没有历史记录
  • $router.back() 返回
  • $router.forward() 前进
  • $router.go(num) //num正数前进,负数后退
<button @click="goBack">后退</button>
goBack(){
this.$router.back()
}

$ route API

r o u t e 是一个跳转的路由对象,每一个路由都会有一个 route是一个跳转的路由对象,每一个路由都会有一个 route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等

  • $route.path 字符串,等于当前路由对象的路径,会被解析为绝对路径,如/home/ews

  • $route.params 对象,含路有种的动态片段和全匹配片段的键值对,不会拼接到路由的url后面

  • $route.query 对象,包含路由中查询参数的键值对。会拼接到路由url后面

  • $route.router 路由规则所属的路由器

  • $route.matchd 数组,包含当前匹配的路径中所包含的所有片段所对象的配置参数对象

  • $route.name 当前路由的名字,如果没有使用具体路径,则名字为空

路由高亮

1.使用类名,router-link-active或则router-link-exact-active

我们只需要在 style 样式里面,实现 .router-link-active 样式,即可实现选中的路由高亮显示。

  • router-link-active:设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选linkActiveClass
var routerObj = new VueRouter({
       //路由匹配规则
        routes:[
          {path:'/login',component:login},
          {path:'/register',component:register},
        ],
        linkActiveClass:'myactive'
      })


.myactive{
  color: blue;
}

来全局配置

  • router-link-exact-active:配置当链接被精确匹配的时候应该激活的 class。注意默认值也是可以通过路由构造函数选项
    linkExactActiveClass 进行全局配置的
<style>
	.router-link-exact-active{
	  color: blue;
	}
	 .router-link-active{//这个解决不了 ,用exact
	  color: green;
	} 

</style>

2.使用active-class

 <router-link to="/" active-class="active" exact>//会有一个问题,/开始的会一直高亮
// to="/”和to="/my"都可以匹配到,可以加exact解决

< router-link>API

项目Value
to表示目标路由的链接(to 的值传到 router.push())
replace会调用 router.replace(),不会留下 history 记录< router-link :to=“” replace>
active-class设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置(“router-link-active”)
exact使用“精确匹配模式”to=“/“不会一直高亮,< router-link to=”/” exact>//这个链接只会在地址为 / 的时候被激活
exact-active-class(“router-link-exact-active”)配置当链接被精确匹配的时候应该激活的 class。注意默认值也是可以通过路由构造函数选项 linkExactActiveClass 进行全局配置的。

路由的滚动行为,

打开新的路由页面,使页面展示一开始部分

const router = new VueRouter({
  routes,
  scrollBehavior(to,from,savedPasition){
    return {x:0,y:0}//和routes平级别,控制滚动条
  }
})

< keep-alive>

要求切换,输入框的内容不变,保持挂载,不被销毁,

切换路由上使用( 缓存路由)

在这里插入图片描述

<nav>
	<router-link to="/home"></router-link>
	<router-link to="/about"></router-link>
</nav>
<keep-alive :include="['AboutView','HomeView']"(组件名)>//如果不配置include 会把router-view展示区域的所有东西都缓存下来,10个切换项就要保存10个浪费性能
	<router-view></router-view>
</keep-alive>

或者当点击列表进入详情页,详情页会请求一次缓存不会来回切换再请求一次

//点击列表vue
数据一
数据二
数据三
//详情页vue
要求第一次点击进去发送请求,再一次点击相同的进去不发送请求,点击不一样的从新发送请求
1.保存id值
id=""
created(){
	this.id=this.$route.query.id
	this.getList()//先发一次请求
}
methods:{
getList(){
id:this.id
}//发送请求
}
activeted(){
	//this.id//之前记录下来的id
	//this.$route.query.id//最新的id
	//判断俩是否相等
	if(this.id!==this.$route.query.id){
	this.id=this.$route.query.id
		this.getList()//发送请求
	}
}

activated 激活,路由独有生命周期,用于捕获路由组件的激活状态

设置keep-alive第二次或者第n次进入主见只会执行activeted

当给about组件进行缓存< keep-alive >,切走之后不会触发beforeDestroy ,就不能再里面卸载定时器了。
所以就需要这个生命周期,在里面卸载掉计时器

在这里插入图片描述

deactivated 失活,路由独有生命周期

activated (){}//激活,切换显示出来就是激活
deactivated (){}//失活,切走了就是失活

动态组件上使用

//缓存 name: "EditTab1",的组件
 <keep-alive include="EditTab1">
      <component :is="name" />
 </keep-alive>

路由守卫(权限设置)

执行顺序

  • 一、打开页面的任意一个页面,没有发生导航切换。

全局前置守卫beforeEach (路由器实例内的前置守卫)
路由独享守卫beforeEnter(激活的路由)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)

  • 二、如果是有导航切换的(从一个组件切换到另外一个组件)

组件内守卫beforeRouteLeave(即将离开的组件)
全局前置守卫beforeEach (路由器实例内的前置守卫)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)

  • 完整的导航解析流程

导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

在这里插入图片描述

router/index.js

全局前置路由守卫beforeEach

{
    path: '/about',
    name: 'about',
    component: AboutView,
    meta:{isAuth:true}//需要权限校验就写这个
  },
{
    path: '/home',
    name: 'home',
    component: HomeView,
  },
router.beforeEach((to,from,next)=>{//全局前置路由守卫,在切换之前进行调用+初始化的时候调用
  //to:到去哪 from:从哪来 next:next() 放行
  if(to.meta.isAuth){//根据meta是否有isAuth属性判断需不需鉴定权限
    if(localStorage.getItem('username')==='张三'){//切换路径之前判断一下,本地存储里面用户名是否是张三,是就放行
      next()
    }else{
      alert('权限不够')
    }
  }else{
    next()
  }
})

全局后置路由守卫afterEach

用于配置网站标题


router.afterEach((to,from)=>{//没有next
  if(to.meth.title){
    document.title=to.meta.title
  }else{
    document.title='vue'
  }
})

独享路由守卫

 {
    path: '/my',
    name: 'my',
    component: MyView,
    beforEnter:(to,form,next)=>{//独享路由守卫

    }
  },

组件内路由守卫

export default {
  name: "Cart",
  beforeRouteEnter(to, from, next) {
    // 进入当前组件  当前组件不存在  this不存在
    //console.log(to,from);
    //如果想获取组件的实例
    //给next 添加一个匿名函数回调  形参vm 代表当前组件
    next((vm)=>{
    //   console.log(vm);
    //   vm等价this  指当前组件实例  可以操作当前组件的相关变量
    });
  },
  beforeRouteUpdate(to,from,next){
      // 在当前路由改变,但是该组件被复用时调用
      // 由于会渲染同样的组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
      // 可以访问组件实例 `this`
    },
  beforeRouteLeave(to, from, next) {
    // 离开当前组件
    //console.log(this);
    next();
  },
};

router.addRoute()动态添加路由

动态设置路由权限,渲染左侧边栏

const routes = [
    {
        path: '/',
        name: 'Login',
        component: () => import(/* webpackChunkName: "about" */ '@/views/Login.vue')
    },
    {
        path: '/index',
        name: 'index',
        meta: { title: '首页', noCache: true },
        component: () => import(/* webpackChunkName: "about" */ '@/views/index.vue'),
        children:[]
        // children: [{
        //     path: '/test',
        //     name: 'test',
        //     component: () => import('../views/test.vue')
        // }
                // ]
    }
]

const routeObj = {
    path: 'index/test', // 这里要把父路由的路径也带上
    name: 'test',
    meta: { title: '测试路由test', noCache: true },
    component: () =>
        import('../test/test.vue'),
}
this.$router.addRoute('index', routeObj)

let routerObj = { path: '/about', name: 'about', component: () => import('../views/about.vue') }
router.addRoute(routerObj)   //vue-router4版本后要使用addRoute,参数是对象

resetRouter 重置路由

下面

router.matcher (负责route匹配)

重置路由方法

//router/index.js

const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()//重置路由
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

router.addRoute(动态添加路由)

添加链接描述

$route.fullPath(当前页面路由的完整地址,用于请求接口后的重新渲染)

当我们在一个页面上,页面上有不同的分类,点击不同的分类需要传不同的参数来请求接口,当接口返回数据后,需要将页面的数据重新渲染,而不是之前的数据。

比如:
path只是路径部分,fullPath是完整地址
fullPath: “/movie/2?name=zs%20age%3D28”
path: “/movie/2”
query: {name: ‘zs age=28’}

我们每次只改变url后面的id,同时将页面重新渲染

<router-view :key='$route.fullPath'>
或者
this.$router.push(`/login?redirect=${this.$route.fullPath}`);

通过绑定一个fullPath,可以识别当前页面路由的完整地址,当地址发生改变(包括参数改变)则重新渲染页面(例如动态路由参数的变化)

当然也可以通过这两种办法:(缺点出现空白页)

1.this.$router.go(0)
2.location.reload()

router.removeRoute()动态删除路由

页面刷新的几种方法

项目Value
this.$router.go(0)缺点出现空白页
location.reload()缺点出现空白页
使用provide / inject+if强制页面刷新

路由切换不刷新的解决方法

详情见vue遇到的小问题,封装功能博客

项目Value
1created改为activated
2watch监听
3使用VUE的v-if控制DOM
4在router-view上添加 :key=“$route.fullPath”
5this.$router.push 的query 添加new Date().getTime()

router-view的key属性

由于Vue 会复用相同组件, 即 /page/1 => /page/2 或者 /page?id=1 => /page?id=2 这类链接跳转时, 将不在执行created, mounted之类的钩子,

解决办法:
1。这时候你需要在路由组件中, 添加beforeRouteUpdate钩子来执行相关方法拉去数据,
2.设置 router-view 的 key 属性值为 $ route.fullPath
3. router-view 的 key 属性值为 $ route.path。/page?id=1 => /page?id=2, 由于这两个路由的$ route.path一样, 所以和没设置 key 属性一样, 会复用组件, 钩子函数只会执行beforeRouteUpdate

404路由

 // 所有未定义路由,全部重定向到404页
 //必须放到最后定义
  {
    path: '*',
    redirect: '/404'
  }

axios

https://juejin.cn/post/7102056757161099295#heading-16
axios npm官网

1.下载

npm install axios

页面 
import axios from 'axios';

post

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

get

axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  .then(function () {
    // always executed
  });  

组合用法

 axios({
        method:'post',//post,get
        url:'/api/song-search',//地址
        data:{keyword: keyword1},//数据
        headers: {'X-Requested-With': 'XMLHttpRequest'},//请求头
        timeout: 1000,//超过多少秒终止
        
     }).then((res) => {
        // 将请求的结果赋值给personData全局变量,用于展示搜索结果
        if (res.data.code === '0') {
            this.personData = res.data.data || [];
        } else {
            this.personData = [];
        }
    })

并发请求

axios.all()+axios.spread(function(a,b)=>{a是第一个请求结果,b是第二个})。用于指定请求多少个并发请求的时候使用

axios.all方法跟promise.all方法一样里边传一个数组,当数组内的请求全部完成时,再进行下一步,如果有一步没有完成就停止操作。
axios.spread的作用就是对返回数据进行处理。

Home.vue
 
//在methods中定义请求方法,并return出去,不要写请求回调then()
methods:{
    getAllTask:function(){
     return this.$axios({url:'http://192.168.*.**:***/api/getTask/getAllData', method:'GET', })},
    getAllCity:function(){
     return this.$axios({url:'http://192.168.*.**:***/city/getCities',method:'GET',})
    }},
//在mounted周期同时发送两个请求,并在请求都结束后,输出结果
 mounted:function(){
    var me = this; 
    this.$axios.all([me.getAllTask(),me.getAllCity()])
    .then(me.$axios.spread(function(allTask, allCity){
        console.log('请求1结果',allTask)
        console.log('请求2结果',allCity) 
    }))}

创建实例create使用axios

// 通过创建实例的方式使用axios
import axios from 'axios';

// 创建实例,对改实例做公共配置
const instance = axios.create({
  baseURL: '/equipment'
});

instance.interceptors.response.use(
  ({ data }) => data,
  err => Promise.reject(err)
);

export default instance;

import axios from './axios.js';
export default {
  created() {
    axios.post('/login');
  }
};


多个并发请求

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

Promise.all([getUserAccount(), getUserPermissions()])
  .then(function (results) {
    const acct = results[0];
    const perm = results[1];
  });

拦截器

请求拦截器

请求拦截器作用是在发出请求时,拦截下用户的请求,执行完一系列处理再发送出去(像添加cookie、token,请求头等),参数是两个回调函数,前一个是成功的回调,后一个是失败的回调;

import axios from 'axios';
axios.interceptors.request.use(
  (config) => {  
    Indicator.open({  //加载提示框
        text: '加载中...',
        spinnerType: 'fading-circle'
      });
 
    if (localStorage.getItem('token')) {//添加请求头
      config.headers["Authorization"] = "Bearer " + localStorage.token;
    }
    return config;
  },
  (err) => {
    return Promise.reject(err);
  }
);

响应拦截器

当服务器产生响应时,对响应数据进行拦截,并对拦截的数据进行处理,处理完数据再返回,比如对于成功的返回,我们只需要返回数据,那么我们可以直接处理数据,把不需要的数据去除再返回。如果出错的话,就返回错误信息,并用message显示错误。参数是两个回调函数,前一个是成功的回调,后一个是失败的回调;

import axios from 'axios';

axios.interceptors.response.use(
  (res) => {
    Indicator.close();  //关闭加载框
    return Promise.resolve(res.data);
  },
  (e) => {
	    // 401 一般是token 失效,403 是没有权限
    if (e.response?.status === 401) {
      window.location.reload();
      return Promise.reject("rmp.vehicle.Nopermission");
    }
    if (e.response?.status === 403) {
      return Promise.reject("rmp.vehicle.Nopermission");
    }
    if (e.response.status >= 500) {
      return Promise.reject("serverError");
    }
    return Promise.reject(e);
  }
);

main.js里全局配置 axios

import axios from 'axios';

axios.defaults.baseURL = '/api';
// 响应拦截器
axios.interceptors.response.use(
  ({ data }) => data,
  err => Promise.reject(err)
);

// 在构造函数的原型上定义公共属性axios,所有的实例/组件都可以通过this.axios来访问它
Vue.prototype.axios = axios;
//页面使用
  async created() {
    const data = await this.getList();
    this.setList(data);
  }
  methods:{
   async getList() {
      const res = await this.axios.get('/singer-list');
      return res.code === 0 ? res.data : [];
    },
    }

封装axios 见另一个博客

proxy 跨域

vue.config.js里面

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  // 开发服务器
  devServer: {
    // 服务器接口代理
    proxy: {
      // 请求的路径中包含/api就转发
      '/api': {
        // 代理的不同域远程服务器
        target: 'http://m.jxsjs.com'
	       pathRewrite: {//让最后服务器地址不包含/api字段
	            '^/api': ''
	        }
        },
    }
  }
})

qs

npm install qs
import qs from 'qs';
方法:qs.parse()和qs.stringify()

vue 动画

1.transition想让谁产生动画就用transition标签包起来

<transition>
<h1>你好啊</h1>
</transition>

2.用@keyframes+.v-enter-active写

<button @click="isShow=!isShow">显示隐藏</button>
<transition name="hello" appear>//appear一开始进来就有动画
<h1 v-show="isShow">你好啊</h1>
</transition>


isShow:true

 .hello-enter-active{//来的时候动画
        animation: atguigu 1s;
    }
    .hello-leave-active{//走的时候动画
    
        animation: atguigu 1s reverse;
    }
@keyframes atguigu {
    from{
        transform: translateX(-100%);
    }
    to{
        transform: translateX(0px);
    }
}

3.用vue的动画写

//v可以换成<transition name="hello" appear>name名字
	 .v-enter {//从哪进入
        transform: translateX(100%);
    }
    .v-enter-to {//进入到那
        transform: translateX(0);
    }
    .v-enter-active {//过渡效果
        transition: transform 1s;
    }
    .v-leave {
        transform: translateX(0);
    }
    .v-leave-to {
        transform: translateX(-100%);
    }
    .v-leave-active {
        transition: transform 1s;
    }
<transition :name="isShow?'left':'right'" appear>
     <h1 v-show="isShow">你好的点点滴滴</h1>
</transition>

 isShow:true, 
 
	 .left-enter {
        transform: translateX(100%);
    }
    .left-enter-to {
        transform: translateX(0);
    }
    .left-enter-active {
        transition: transform 1s;
    }
    .left-leave {
        transform: translateX(0);
    }
    .left-leave-to {
        transform: translateX(-100%);
    }
    .left-leave-active {
        transition: transform 1s;
    }
 
    .right-enter {
        transform: translateX(-100%);
    }
    .right-enter-to {
        transform: translateX(0);
    }
    .right-enter-active {
        transition: transform 1s;
    }
    .right-leave {
        transform: translateX(0);
    }
    .right-leave-to {
        transform: translateX(100%);
    }
    .right-leave-active {
        transition: transform 1s;
    }

初始延时2秒出现动画

<transition>
      <div  v-show="show" ></div>
</transition>

data{
	show:''
}
 mounted() {
    setTimeout(() => {
      this.show = true;
    }, 2000);
  },

4.多个元素过渡transition-group

在这里插入图片描述

//一起隐藏一起显示
<transition-group>
	<h1 v-show="isShow" :key="1">模块1</h1>//必须有key值
	<h1 v-show="isShow" :key="2">模块2</h1>
</transition-group>

5. 使用Animate.css

官网

1.安装

npm install animate.css --save

2.引入

//需要使用的页面引入
import 'animate.css';

3.使用

//类名放到name下
<transition 
 enter-active-class="animate__animated animate__heartBeat"//animate__animated 固定类名不可变,后面是选好的效果
 leave-active-class="animate__animated animate__fadeInBottomRight"
 appear//页面一呈现就进入动画
>
    <h1 v-show="isShow">nihaoaoajo</h1>
</transition>

如何使用animate.css在vue2

vuex

集中式状态(数据)管理的一个vue插件,组件中通信的方式,单项数据流只能自己更改自己,外部不能更改会报错。v-model绑定vuex也会报错
对多个组件的共享状态进行集中管理(读写)

原理: state状态是响应式的,借助Vue实例data实现
getters则是借助于Vue的计算属性实现

vuex的state是借助vue的响应式data实现的,当我们 在一个组件实例中 对state.xxx进行 更新时,基于vue的data的响应式机制,所有相关组件的state.xxx的值都会自动更新。
getter是借助vue的计算属性computed特性实现的。

单向数据流,直接修改会报错,必须使用mutations修改

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
//创建了vuex.store实例,任何组件可以使用this.$store访问
export default new Vuex.Store({

  state: {//定义数据
    count: 0
  },
  actions: {//想用组件动作,
  //this.$store.dispatch('increment ',1)
    increment (context,val) {
      context.commit('increment',val)
  }
  mutations: {//修改数据的地方,当业务比较简单的时候直接用mutations
  //页面直接使用:this.$store.commit('INCREMENT',1)
    INCREMENT(state,val) {
      state.count+=val
    }
  },
  getters:{//相当于计算属性,对state的数据进行加工
  //this.$store.getters.bigCount-----20
  //this.$store.state.count  ----2
  	bigCount(state){
  		return state.count*10
  	}
  }
})
 


actions和mutaions的区别

  • Action:不能直接操作State,有业务逻辑、异步请求用这个,Action再触发Mutation(可以处理异步程序)。
  • Mutation:理论上是修改State的唯一途径(也不一定actions也可以修改,但是不推荐)。必须同步执行。没有业务逻辑用这个

为何mutation不能包含异步操作?

使用层面:代码更高效易维护, 逻辑清晰(规范,而不是逻辑的不允许);
具体原因:为了让devtools 工具能够追踪数据变化;

modules模块化使用

在这里插入图片描述

1.引入

在这里插入图片描述
//cartList.js
里面需要加命名空间:namespaced:true

export default{
    namespaced:true,
    state:{
        num:[1]
    },
    getters:{},
    mutations:{},
    actions:{}
}

2.使用

(1)数据state

{{cartList}}
computed:{
	...mapState('cart',['cartList'])
}

(2)mutations,actions,getters

写法一:store.dispatch('user/userInfo')
写法二:
{{cartList}}
methodes:{
	...mapMutation('cart',['add'])
	...mapActions('cart',['jiaWait'])
	...mapGetters('cart',['bignum'])
}

@click=“increment”
对象写法{increment:‘JIA’},调用vuex的JIA
在这里插入图片描述

actions向后端发送请求

//定义文件.js
//暴露
import axios from 'axios'
export default{
  namespaced:true,//必须写这个才可以使用mutations
  state:{
    name:'张三',
    person:''
  },
  mutations:{
    setName(state,val){
      state.name=val
    },
    setPersion(state,val){
    	state.person=val
  	}
  },
  actions:{
  	addPerson(context){//发送请求获取数据
  		axios.get('').then((res)=>{
  			context.commit('setPersion',res.data)
		})
  	}
  },
  getters:{
  	firstName(state){
  		return state.splice(0,1)
  	},
  
  }
}
import abc from './name' //引入
export default new Vuex.Store({
  state: {//定义公共数据的地方
  },
  getters: {
  },
  mutations: {//修改state的方法
  },
  actions: {
  },
  modules: {//模块化写法
    abc,//2.导出
    }
})
//页面中使用
//state的数据:
{{$store.state.abc.name}}
//使用mutations:
@click="$store.commit('abc/setName','李四')"
//使用getters
{{this.$store.getter['abc/firstName']}}

mapstate用法

在store里面有str属性,页面使用
在这里插入图片描述

mapGettters

在这里插入图片描述

mapMutations

可以传值btn(‘aaa’)
在这里插入图片描述

mapactions

在这里插入图片描述

实现持久化存储

不能做持久化存储,刷新会被初始化,使用localstorage
或者使用插件vuex-persistedstate

刚刚在登录时,已经可以成功的将token存到vuex中,但是vuex刷新会丢失,所以我们需要结合web存储实现持久化。我们可以使用localstorage或者cookie进行存储,然后在utils文件夹中新建storage.js,把本地存储数据的方法写进去

import Cookies from 'js-cookie'

const TokenKey = 'qm-token'
const ThemeKey = 'qm-theme'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

export function getTheme() {
  return Cookies.get(ThemeKey)
}

export function setTheme(Theme) {
  return Cookies.set(ThemeKey, Theme)
}

export function removeTheme() {
  return Cookies.remove(ThemeKey)
}
//vuex中把token信息改为getToken(),一进来先从本地拿
const state = {
  // 一进来优先从缓存中取
  token: getToken() // token字符串
}

//在存token的时候,本地也存一份
const mutations = {
  // 设置token
  setToken(state, newToken) {
    state.token = newToken
    // 设置了 token 的同时, 同步到本地cookies中
    setToken(newToken)
  }
}

vue原型上挂个方法

import toast from './components/Toast'
Vue.prototype.$toast=toast
//使用
this.$toast ('123')

vue定义全局变量的方法

1.Vue.prototype

Vue.prototype不是全局变量,而是原型属性, 不支持修改

Vue.prototype.name = ‘张三’
Vue.prototype.$myName= ‘李四’
 name和 $ myName的区别:
在于 name可以被data(){return{……}}修改。
而$ myName不可以, $作为vue中的一个简单约定,用于避免和已被定义的数据、方法、计算属性产生冲突。
所以在不想污染全局作用域时,尽量给变量$或其他标识符区别开来

1.1Vue.prototype定义插件

当在main.js中定义以下$就可以在组件内部直接调用this.$message()了
函数原型上的属性/方法, 在函数实例化后, 可以在任意实例上读取

Vue.prototype.$message = Message;
Vue.prototype.$axios = axios;

1.2 Vue.use()初始化插件

Vue.use()就是运行插件对象必备的install()函数方法,
初始化插件对象需要通过Vue.use().

import dicts from './dicts'
Vue.use(dicts);

1.3 二者的区别

Vue.prototype适合于注册Vue生态外的插件
vue.prototype.$ xxx 为了挂载任何想在全局频繁调用的 js 函数/对象
vue.prototype.$xxx :axios

Vue.use适合于注册Vue生态内的插件
Vue.use() 必须暴露 install 方法
Vue.use() 需要在 new Vue() 之前完成
vue.use(xxx) : ElementUI,Viewer,echarts

2.全局变量的定义还可以通过在main.js中new Vue,支持修改

new Vue({
  ……
  data(){
      return{
          name: '张三'
      }
  }
})

然后就可以在组件中通过this.$root.name来访问根实例中的数据

3.通过vuex定义全局变量

在state中定义data,可以通过this.$store.state访问

混入 mixin

使用场景:页面大小发生改变桌面和手机端切换,有的div显示还是隐藏的时候,利用引入来判断比如大于992就是桌面

来分发 Vue 组件中的可复用功能。
比如:页面——>组件——>mixin
页面的公共部分可以抽离成组件,组件的js公共部分可以抽离成mixin

混入和vuex的区别

mixin的数据和方法使独立的,组件之间使用后互不影响
vuex公共状态管理,如果一个组件更改了数据,其他所有引入的组件都会变化

缺点

不可知,不易维护
当mixins文件达到多个,去维护修改时就会不知道这个方法、属性来自那个mixins文件

混入冲突规则

(1)生命周期函数

不会覆盖,先执行mixin的生命周期函数,再执行组件的

(2)data数据

组件覆盖掉mixin的

(3)方法

组件覆盖掉mixin的

1.定义

在这里插入图片描述

mixin就是个对象,包含data,methods,created等
src下创建mixin文件夹

export const mixins={
    data(){
        return{
            msg:'',
        }
    },
    computed:{},
    created () {
        console.log('created声明周期')
    },
    methods: {
        clickMe() {
            console.log('点我了')
        }
    },
}

2.引入

局部引入

import {mixins} from '../mixin/index'
    export default {
        mixins: [mixins],
     }

全局引入


import {popButton} from "./mixin/popButton.js"
 
Vue.mixin(popButton);

自定义指令directive+inserted

项目中使用,权限自己干掉自己 v-if 判断vuex里面的属性,
触底加载下几条
input 获取焦点
固定一个位置不动
button的按钮权限隐藏消失
对dom进行操作
弹出框,点击其他地方消失,点击子元素不消失

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
//局部指令
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}
//使用
<input v-focus>/

触底加载下几条原文连接

1.<ul  v-init-scroll="loadmore">绑定自定义指令和函数
2.自定义指令函数,判断是否到底部,执行函数page++,请求数据
Vue.directive("init-scroll", {
  // bind只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  bind(el, binding) {
    var p = 0;
    var t = 0;
    var down = true;
    //这里是:拿到当前绑定的dom,并监听当前的dom的scroll事件完成下拉加载
    var selectWrap = el;
    selectWrap.addEventListener("scroll", function () {
      //判断是否向下滚动
      p = this.scrollTop;
      if (t < p) {
        down = true;
      } else {
        down = false;
      }
      t = p;
      //判断是否到底
      const sign = 10;
      const scrollDistance =
        this.scrollHeight - this.scrollTop - this.clientHeight;
      if (scrollDistance <= sign && down) {
        binding.value();
      }
    });
  },
}); 

弹出框,点击其他地方消失,点击子元素不消失 原文链接

v3+ts 写法
//1.在自定义指令directive中   //判断是否是当前组件的后代
el.contains(target)
//2.页面使用
<a @click.prevent="toggleOpen"> 点击 </a>
<ul class="dropdown-menu" v-if="isOpen" style="display: block">
      <slot name="dropdown"></slot>
      <p>1</p>//这块添加数据
      <p>2</p>
      <p>3</p>
      <p>4</p>
</ul>

binding.value() 执行绑定的函数

  <div v-abc="my">123456789876543</div>
  methods: { my() {}},
    directives: {
        abc: {
            inserted: function (el, binding) {
                binding.value();}}},

钩子函数

bind:只调用一次,指令第一次绑定到元素时调用

inserted:被绑定元素插入父节点时调用

update:所在组件的 VNode 更新时调用,

钩子函数参数

在这里插入图片描述

<div v-abc="2">123456789876543</div>
directives: {
        abc: {
            inserted: function (el, binding, vnode) {
            //el,元素,可以对元素进行操作
            // binding,一个对象
            // vnode,!虚拟dom
                console.log(el, binding, vnode);
            },
        },
    },

在这里插入图片描述

动态参数

//动态设置定位在那个地方多少像素
direction=‘left’
  <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
  Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    var s = (binding.arg == 'left' ? 'left' : 'top')
    el.style[s] = binding.value + 'px'
  }
})

在这里插入图片描述

自定义插件install

1.  自定义按钮通过type不同修改颜色 <my-button type='warning'>warning</my-button>
export default {
    install(app, option) {
        app.component("myButton", {
            template: '<button :class="btn"><slot/></button>',
            props: { type: { type: String} },
            computed: {
                btn() {
                    return `btn ${this.type}`
                }} })}}
  1. 自定义toast 点击显示3秒的提示
    详情点链接自定义toast
//index.js(与Toast.vue在同一个目录)
import Toast from "./Toast"
const obj = {}
obj.install = function(Vue) {
    //创建组件构造器
    const toastConstrustor = Vue.extend(Toast);
    //new方式,根据组件构造器,可以创建出来一个组件对象
    const toast = new toastConstrustor();
    //将组件对象,手动挂载到一个元素上
    toast.$mount(document.createElement("div"))
        //toast.$el对应的就是div
    document.body.appendChild(toast.$el);
    Vue.prototype.$toast = toast
}
export default obj;

  1. 点击图片放大显示
 需要组件的插件,难点在于,生成组件,并且把组件的标签手动挂载在DOM

所谓vue插件其实就是一个js对象,然后暴露一个 install 方法, 此函数接受两个参数。第一个参数是 Vue 构造器 , 第二个参数是一个可选的选项对象。

//用来注册组件
//page1.js
import HButton from './components/...'
const components=[HButton]
export default newInstall={
	install(Vue){//当使用vue。use就会自动调用install里面的东西
		components.forEach(i=>{
			Vue.component(,i)
		})
	}
}

//main.js
import newInstall from ''
Vue.use(newInstall)

创建js对象

src下创建plugins文件夹,

//所谓vue的插件,就是一个js对象
let myplugin={
    install:function(Vue,Options){
        // 添加属性与方法
        //这里我写的$testProp等加了$符号的,表示他为vue全局的,但实际上不加也可以的,访问时也不加就行了
        Vue.prototype.$myoption='我是来自插件的属性',
        Vue.prototype.$myfn=function(){
            console.log('我是来自插件的方法')
        }
        // 添加全局混入
        Vue.mixin({
            mounted() {
                console.log('组件创建成功')
            },
        })
        // 添加全局指令
        Vue.directive('dir',{
            inserted:function(ele){
                ele.style.border='2px solid green'
            }
        })
    }
}
export default myplugin;

在这里插入图片描述
在这里插入图片描述

vue打包(webpack)

1.npm run build
2.生成一个dist文件包含html,css,js
3.生成的文件需要部署到服务器

打包空白页

//v2
1.修改vue.config.js
publicPath:“./2.如果使用histor打包需要和后端商量配置
3.把histor更改为hash路由模式
//v3
//路由配置hash
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});
//vite.config.ts
  base: "./",

1.做项目的时候考虑过浏览器兼容性吗

vue2 不支持ie8, vue3不支持ie11
通过跟目录.browserslistrc文件夹


> 1%//浏览器时长份额大于1%
last 2 versions
not dead
not ie 11 
ie > 8 //大于ie8
chrome > 80 //大于chrome80

如果移动端项目,手机出现白屏,就需要在.browserslistrc把那个浏览器和手机型号配置加上

2.关闭map文件

//vue.config.js
  productionSourceMap:false//打包时,不给js产生map文件

在这里插入图片描述

3.public静态资源和assets

//不想被打包的文件放在public文件下,打包后不会被处理直接放在dist文件下
//public下的文件可以直接/public/1.img 需要使用绝对路径,打包后是直接在dist文件下

在这里插入图片描述

4.vue 优化—— cdn

CDN优化(cdn引入第三方资源包)

添加链接描述

Vue 项目打包后首屏加载慢 。其原因是第三方库比如(vue、vue-router、vuex、axios等)都会被打包到 vendor.js 文件里面,而浏览器是在加载该文件之后,才开始显示首屏的。

把第三方库比如(vue、vue-router、vuex、axios等)通过cdn的方式引入到项目中。这样浏览器可以开启多个线程,将vendor.js、外部js、css等加载下来(bootcdn等资源,或其他服务器资源),使得vendor.js文件减少,从而提升首屏加载速度。

index.html script标签引入cdn vue,vuex,
在vue.config.js配置externals,不让vue打包的

在这里插入图片描述
在这里插入图片描述

cdn引用

静态资源放到cdn上

在阿里云CDN上面配置域名管理 ,配置成后返回一个CNAME

在这里插入图片描述
在这里插入图片描述

cdn静态资源刷新 /预热

cdn阿里云刷新/预热设置

如果没有配置缓存过期时间,且源站和CDN上也没有配置缓存策略,则CDN采用默认缓存时长3600秒

gzip压缩

利用webpack插件生成gz后缀名文件
npm install compression-webpack-plugin --save-dev
nginx也需要配置一下

//vue.config.js
  const CompressionPlugin = require('compression-webpack-plugin');
  module.exports = {
    plugins: [
      new CompressionPlugin({
        algorithm: 'gzip', // 使用gzip压缩
        test: /\.js$|\.css$/, // 匹配文件名
        filename: '[path].gz[query]', // 压缩后的文件名(保持原文件名,后缀加.gz)
        minRatio: 1, // 压缩率小于1才会压缩
        threshold: 10240, // 对超过10k的数据压缩
        deleteOriginalAssets: false, // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
      }),
    ],
  },
  };

5.publicPath:‘./’ 公共路径前缀,打包后的

vue.config.js
页面出不来,就可以看看是不是路径的问题
publicPath:'./' 
//publicPath:'js/' 

6. 环境变量区分开发环境和生产环境

见上面

7.css后处理postCss

css前处理less,sass
px转rem

8.开启本地服务器

1.方式一

npm i express --save -dev
npm run preview

2.方式二

npm install http-server -g
进入到dist 文件下http-server

上线流程

npm run build
把打包后的文件件上传到服务器,或自动绑定
上传到测试服务器,合格后上传到正式的

配置环境变量

可以通过配置文件的方法.env.production
或者使用js,process.env.判断

官网配置

1.创建文件

在这里插入图片描述

2.配置

NODE_ENV=development//什么环境
VUE_APP_BASE_URL="http://localhost:8001"//访问地址

console.log(process.env.VUE_APP_BASE_URL)//页面中这么访问到

在这里插入图片描述

3.来到 package.json 文件夹找到scripts 利用 --mode 来分配我们项目跑起来的指令分别对应的是生产模式开发模式

"scripts": {
    "dev": "vue-cli-service serve --mode development",
    "pro": "vue-cli-service serve --mode production",
    "build": "vue-cli-service build"
  },

4.只需要判断是什么环境在请求的时候用哪个路径就可以

//process.env.NODE_ENV 
console.log(process.env.VUE_APP_BASE_URL)//可以获取到.env文件配置的变量
//axios封装的里面判断
// 环境的切换
if (process.env.NODE_ENV == 'development') {    
    axios.defaults.baseURL = '/api';
} else if (process.env.NODE_ENV == 'debug') {    
    axios.defaults.baseURL = '';
} else if (process.env.NODE_ENV == 'production') {    
    axios.defaults.baseURL = 'http://api.123dailu.com/';
}

5.用js来判断变量

if (process.env.NODE_ENV == 'development') {    
    axios.defaults.baseURL = '/api';
} else if (process.env.NODE_ENV == 'debug') {    
    axios.defaults.baseURL = '';
}  else if (process.env.NODE_ENV == 'test') {    //测试环境
    axios.defaults.baseURL = '';
}else if (process.env.NODE_ENV == 'production') {    
    axios.defaults.baseURL = 'http://api.123dailu.com/';
}

vue源码

模板解析

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

this.$el就是获取页面dom元素,创建vue实例
修改data数据执行this.compile()
在compile()里面判断节点类型是元素还是文本,如果是文本就直接修改,不是就递归

vue生命周期

根据一个判断的顺序

在这里插入图片描述

vue源码添加事件

在这里插入图片描述

大概意思就是在元素节点上找有没有绑定@click,有就去找对应的函数并给他执行

数据劫持

在这里插入图片描述

v-model

> 先劫持object

在这里插入图片描述

为什么SEO不好

爬虫抓取是先从一个页面出发,从中提取出其他页面的链接,然后当作下一个请求的对象,一直重复这个过程。所以要有良好的SEO,需要拥有外链,这样会提高被搜索引擎爬虫的几率。
爬虫拿到HTML之后,就会对其内容进行分析。
搜索会根据你输入的关键词,分别查询其对应的索引数据库,并对结果进行处理和排序。
所以网站结构要清晰。一般网站的结构是树形的,一般分为三个层次:首页 → 频道页(列表页) → 文章页(详情页)。
vue等js的数据绑定机制来展示页面数据,爬虫获取到的html是你的模型页面而不是最终数据的渲染页面,所以说用js来渲染数据对seo并不友好 

单页面打包成多页面

前端vue等框架打包的项目一般为SPA应用,而单页面是不利于SEO的,现在的解决方案有两种:

1、SSR服务器渲染

原理: 使用vue-server-renderer的createRenderer,创建renderer
renderer .renderToString(app)能生成html
然后通过send方法返回文档
在这里插入图片描述

vue项目重构ssr

添加链接描述
添加链接描述

使用nuxt.js创建项目

2、预渲染模式

npm install prerender-spa-plugin --save这个安装有bug 官方也很长时间没有维护了
所以用prerender-spa-plugin-next插件

添加链接描述
这比服务端渲染要简单很多,而且可以配合 vue-meta-info 来生成title和meta标签,基本可以满足SEO的需求

在这里插入图片描述

  1. 下载prerender-spa-plugin-next插件
  2. 配置vue.config.js,中routes: [ ‘/’, ‘/about’, ‘/some/deep/nested/route’ ],
  3. 打包

!!!!!! vue3 !!!!!!

和v2区别

  • v2>ie8 ,v3>ie11

  • 响应式修改 Proxy,definepropty

  • 使用ts+vite+v3

  • 新的虚拟 DOM,编译器主动进行静态标记,(v2,所有到要对比一下,v3只有标记flag的才对比)

  • Composition API,组合和重用逻辑的新方式

  • 生命周期重命名,beforeDestroy 改为 beforeUnmount,destroyed 改为 unmounted

  • 过渡类名修改,v-enter 改为 v-enter-from,v-leave 改为 v-leave-from

  • 移除了实例方法 $ on、$ off 和 $once(可采用官方推荐的 mitt 库)

  • 删除了过滤器 filter

  • vite按需编译打包

安装

对vue版本有要求大于4.5.0

vue --version

1.使用vue2方式创建

2.使用vite创建

在这里插入图片描述

1.npm init vite@latest
2.选择配置
Done. Now run:
  cd vue3ViteHomeWork
  npm install
  npm run dev
//进入文件夹
//初始化 npm install

//打开项目:npm run dev
  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose

//安装依赖:npm install vue-router pinia axios
//安装sass :npm i sass -save-dev

启动项目npm run dev

webpack和vite的区别

vite是新一代的前端构建工具

webpackvite
Webpack基于commonjsVite基于es6module,项目开发浏览器要支持esmodule
热更新,更改一个模块,其他有依赖关系的模块都会重新打包;改动一个模块仅仅会重新请求该模块,轻量快捷的热重载(HMR)
webpack,npm run serve等待的就是打包时间打包更快,开发环境中,无需打包操作,可快速冷启动
——按需编译,不用等待整个应用编译完,动态的分析

ts的项目中使用js

//tsconfig.json
"compilerOptions": {
   "allowJs": true
}
//页面就可以删除
<script></script>

main.js

//引入的不再是Vue构造函数(需要new调用),引入的是命为createApp的工厂函数,(不用使用new调用)
import { createApp } from 'vue'
import App from './App.vue'

//创建应用实例对象app+挂载,类似以vue2的vm,但是app比vm更‘轻’——没有那么多属性
createApp(App).mount('#app') 

v3组合式API

更好的复用,更加灵活
一块代码逻辑可以放一块,不用再分散在各个地方

v2(optionsAPI)v3(ComponsitionAPI)
新增或者修改一个需求,需要分别在data,methods中修改,一个功能分散,修改要找好多地方hook,按照每个功能点进行打包

在这里插入图片描述

setup

  • 1.setup是 Componsition API(组合API)的舞台
  • 2.组件所用到的数据,方法,均要配置在setup中
  • 3.setup函数的两种返回值
    (3.1)返回一个对象,对象的属性和方法在模板中可以直接使用
    (3.2)返回一个渲染函数
  • 4.不要和vue2的配置混用
    (4.1)有重名setup优先
    (4.2)setup中不能访问vue2的配置,vue2可以访问setup的
    (4.3)setup不能是async,因为返回值不再是对象了,而是一个被包裹的对象
<template>
  <h1>{{name}}---{{age}}</h1>
  <button @click="seyHello">点我</button>
</template>
<script>
export default {
  name: "App",
  setup() {
    let name = "张三";
    let age = "12";
    //方法
    function seyHello() {
      alert("你好啊" + name + age);
    }
    return {name, age, seyHello,};
  },
};
</script>

ref 函数

可以把基本类型数据,对象类型数据变成响应式

要实现响应式,就要把数据丢给ref,包装完成后会变成一个引用对象(reference对象)
使用name.value来获取和修改数据,原理是调用原型上的get和set方法实现响应式
基本类型的数据用的是Object.defineProperty()的get和set
对象数据类型,内部求助reactive函数(封装的proxy)

 <h1>{{name}}</h1>
 <h1>{{obj.age}}</h1>
//1.需要引入ref
import { ref } from "vue";
export default {
  name: "App",
  setup() {
    let name = ref("张三");//2.修饰数据
    //要实现响应式,就要把数据丢给ref,包装完成后会变成一个引用对象
     let obj = ref({
      age: 19,
    });
    //方法
    function seyHello() {
      console.log(name.value);//获取数据
      name.value = "lishi";//3.使用value来修改数据,和获取数据
    }
    return {
      name,
      seyHello,
    };
  },
};

在这里插入图片描述
对象数据类型proxy
在这里插入图片描述

reactive函数

只能定义对象类型的响应数据,不能定义基本的,内部基于proxy实现,通过代理对象操作源对象内部数据进行操作

//清空
let obj=reactive({})
不能obj=[],要么reactive({obj:{}})套一层
要么ref包一下
要么 obj.length=0
 <h1>{{ data.list[0].age1   }}</h1>
 
import { reactive } from "vue";

export default {
  name: "App",
  setup() {
    let data= reactive({//会把用到的数据封装到一个对象里面,抛出这个对象
      age: 89,
      list: [{ age1: 1 }, { age2: 2 }],
    });
    function seyHello() {
      data.list[0].age1 = 0; //不需要使用value获取,vue2里面不能直接[0]进行修改,vue3可以
    }
    return {data,seyHello};
  },
};

reactive赋值初始值

{
  setup () {
    const original= () => {
      return {
        a: 1
      }
    }
    
    const state = reactive(original())
    
    const resetState = () => {
      Object.assign(state, original())
    }
  }
}

ref和reactive的区别

refreactive
定义基本类型数据,也可以定义对象(自动通过reactive代理对象)定义对象类型数据
Object.defineProperty()proxy+Reflect
操作数据需要.value不用value

setup可以收到2个参数 setup(props, context)

1.第一个参数props ——对象 组件外部传递进来,且声明接收的值
在这里插入图片描述

//父组件
 <hello-world  msg="123"  school="456" ></hello-world>
//子组件
{{msg}}//msg直接获取到
<script>
import { reactive } from "vue";
export default {
  name: "HelloWorld",
  props: {
    //传几个就要写几个,漏传会报错,props定义多了是undefined
    msg: {
      type: String,
    },
    school: {
      type: String,
    },
  },
  setup(props, context) {
  //在setup中使用需要 props.msg
    console.log(props, context);
    let data = reactive({});
    return {
      data,
    };
  },
};
</script>

2.第二个参数context ——上下文对象
在这里插入图片描述

(1)attrs——>相当于vue2 中的$ attrs 组件外传递进来但没有props声明的值

//和vue2的attrs一样,见上面

(2)emit——>相当于vue2 中的$ emit(触发父组件的自定义事件)需要子组件内接收 emits: [“事件名字”]

//父组件
<hello-world  @hello="seyHello1"  ></hello-world>
function seyHello1(value) {
      console.log("你好啊" + value);//你好啊666
    }
//子组件
<button @click="sayhello2">你好</button>
<template>
  <div class="hello">
    222222222
    <button @click="sayhello">你好</button>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  emits: ["hello"], //父组件绑定的自定义事件需要在子组件声明
  setup(props, context) {
    let data = reactive({});
    function sayhello2() {
      context.emit("hello", 666);//想父组件传值
    }
    return { data,sayhello };
  },
};
</script>

(3)slots——>相当于vue2 中的$ slots 插槽
在这里插入图片描述

//父组件
<hello-world >
    <h1>啦啦啦啦</h1>
</hello-world>
//具名插槽
<hello-world>
	22222222222222
	<template  v-slot:top>
		<h1>啦啦啦啦</h1>
	</template>
</hello-world>
//子组件 默认插槽
<div class="hello">
	22222222222222
    <slot name="top"></slot>
</div>
//子组件 具名插槽
<div class="hello">
	22222222222222
	<slot name="top"></slot>
</div>
console.log(context.slots);

components 组件

//全局定义
import AddView from ''
app.component('add-view',AddView)

ts+组合式api +数据

<script setup lang="ts">
import { reactive, ref, computed, toRefs, onMounted } from "vue";
type DState = {//ts
  count: number;
  double: number;
};
const refs = ref(1);//1.ref变量
const state: DState = reactive({//2.reactive变量
  count: 0,
  double: computed(() => {
    return state.count + 2;
  }),
});
 const name = computed(() => {//这样获取的不能修改
   return refs.value + 1;
 });

function increment() {//方法
  state.count++;
}
const root = ref<HTMLInputElement | null>(null)//ref元素
onMounted(() => {//生命周期
  console.log(root.value.value); //获取到dom元素<input  type="text" />
});
const { count, double } = { ...toRefs(state) }; //3.结构state数据
</script>

获得html标签的ref

//获取元素
<template>
    <input  type="text"  ref="root" value="使用ref" />
</template>

<script setup lang="ts">
//defineProps
import {  ref, onMounted } from "vue";

const refs = ref(1);//定义数据
const root =ref<HTMLInputElement | null>(null)//获取ref
//HTMLInputElement 可以改成HTMLDivElement ...
onMounted(() => {
 console.log(root.value); //获取到dom元素<input  type="text" />
 //root.value.value 获取元素input内容
});
</script>

父——>子组件传值ts+defineProps

//父.vue
    <SonVue :msg="msg" />
//子.vue
<template>
    <p>{{props.msg}}</p>
</template>

<script setup lang="ts">
// 非ts的用法
const props = defineProps({
  msg: {
    type: String,
    default: ''
  }
});

// ts的用法,可以写默认值
interface PropsType {
  msg: string
}
const props = withDefaults(defineProps<PropsType>(), {
  msg: '' // 默认值
});
</script>

withDefaults用来给defineProps的props 提供默认值的方式

export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

子——>父组件传值ts+defineEmits

//父.vue
<template>
    <SonVue @onReceiveMsg="onReceiveMsg" />
</template>
<script setup lang="ts">
import SonVue from './Son.vue';
// 接收子组件的传入的值
const receiveMsg = ref('');
// 子组件触发的方法
const onReceiveMsg = (params:string) => {
  receiveMsg.value = params;//接收子组件的值
}
//子.vue
<template>
	<input v-model="inputVal"/>
    <button @click="sendMsg">点击传值给父组件参数</button>
 //    <button @click="$emit(`update:${type}`, $event)">点击传值给父组件参数</button>
  </div>
</template>

<script setup lang="ts">
const inputVal = ref('');
// 非ts的用法
const emit = defineEmits(["onReceiveMsg"]);
const sendMsg = () => {
  // 传input值给父组件
  emit("onReceiveMsg", inputVal.value);
}


// ts的用法
interface EmitType {
  (e: "onReceiveMsg", params: string): void
}
const emit = defineEmits<EmitType>();
const sendMsg = () => {
  // 传input值给父组件
  emit("onReceiveMsg", inputVal.value);
}

简单defineProps和defineEmits

<script setup>
const props = defineProps(["foo"])
const emit = defineEmits(['change', 'delete'])
</script>

子——>父ref + defineExpose(暴露组件自己的属性 )

在vue3中增加了暴露组件自己属性的方法,让子组件暴露自己的属性,然后父组件应用,也类似子组件直接传值给父组件了。

//子.vue
<script setup lang="ts">
import { ref,defineExpose } from 'vue'
const inputVal = ref('');
const exposeFun = (name:string) => {
  console.log('子组件暴露自己的方法', name);
}
// 使用defineExpose暴露inputVal和exposeFun
defineExpose({
  inputVal,
  exposeFun
});
//父.vue
<template>  
    <button @click="useSonExpose" >使用子组暴露的属性</button>
    <SonVue ref="sonRef" />
</template>

<script setup lang="ts">
import SonVue from './Son.vue';
import { ref } from 'vue';
// 接收子组件的传入的值
const receiveMsg = ref(null);
//ts写法
// 父组件接收子组件暴露的方法,使用子组件的ref
const sonRef = ref<{
  inputVal: string;
  exposeFun(name:string): void;
}>();
// 使用子组件暴露的内容
const useSonExpose = () => {
  // 由于ts认为inputVal可能位undefined不能赋值给string的reveiveMsg
  // 因此使用了一个断言
  receiveMsg.value = sonRef.value?.inputVal as string;

  // 触发子组件暴露的方法
  sonRef.value?.exposeFun('父组件');
}
</script>


//官网
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'

const modal = ref<InstanceType<typeof MyModal> | null>(null)
//MyModal组件名称
const openModal = () => {
  modal.value?.open()
}
</script>
//使用子组件的方法和属性,非ts
//子.vue
const refs = ref(1);
defineExpose({
  refs
});

//父.vue
 <AboutView :msg="123" ref="child1"></AboutView>
 //ref不能加:
 <script setup lang="ts">
import AboutView from "./views/AboutView.vue";
import {ref,onMounted} from 'vue';
const child1=ref<any>(null)
  onMounted(()=>{
    console.log(child1.value.refs)
  })
</script>

祖——>孙provide和inject 祖孙组件通信

父子一般不用这个用props,生命周期在create前面
provide和inject可以轻松实现跨层级组件通讯,provide在父组件中返回要传给下级的数据,inject在需要使用这个数据的子辈组件或者孙辈等下级组件中注入数据。
原理:允许祖先向子组件注入依赖,不论层级多深。
provide:每个组件provide对象会保存在vue实例的_provide属性上
inject:遍历当前组件的inject数据的key,然后从当前组件开始看是否存在_provide[ ’ ’ ]如果存在就用,不存在就以$parent为链,父组件的_provide[ ’ ’ ]上,一级一级寻找

//爷爷
<template>
    祖先{{name}}---{{price}}
    <son-view></son-view>
</template>

<script>
import { reactive, toRefs, provide } from "vue";//1
import SonView from "./SonView.vue";
export default {
  components: { SonView },
  setup() {
    const car = reactive({
      name: "奔驰",
      price: 40,
    });
    provide("car", car); //2.给自己的后代组件传递数据
    return { ...toRefs(car) };
  },
};
</script>
//中间有个父亲.vue
//孙子
<template>
    孙子{{car}}
</template>

<script>
import { inject } from "vue";//1.
export default {
  setup() {
    let car = inject("car");//2.接收
    return { car };
  },
};
</script>

祖——>孙provide/inject的ts,v3用法

//provide
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
  location.value = 'South Pole'
}
provide('location', {
  location,
  updateLocation
})
//如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))

</script>

//接收inject
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>
//ts
const foo = inject<string>('foo') // 类型:string | undefined

计算属性computed

<template>
  <div class="hello">
    :<input v-model="data.firstName"><br>
    :<input v-model="data.lastName">
    <p>{{data.fullName}}</p>
    全名:<input v-model="data.fullName">//修改fullName 的值
  </div>
</template>

<script>
import { reactive, computed } from "vue";
export default {
  name: "HelloWorld",
  setup() {
    let data = reactive({
      firstName: "张",
      lastName: "三",
      fullName :'',`
    });
    
    // data.fullName = computed(() => {//简写形式,不能更改fullName 的值
    //   return data.firstName + data.lastName;
    // });
    
    data.fullName = computed({//计算属性完整写法
      get() {
        return data.firstName + data.lastName;
      },
      set(value) {
        const nameArr = value.split("-");
        data.firstName = nameArr[0];
        data.lastName = nameArr[2];
      },
    });
    return { data};
  },
};
</script>
<script setup lang="ts">
import { reactive, ref, computed} from "vue";

const refs = ref(1);
// const name = computed(() => {//这样获取的不能修改,是只读的readonly
//   return refs.value + 1;
// });
const name = computed({//这样可以修改
  get() {
    return refs.value + 1;
  },
  set(val: number) {
    return (refs.value = val);
  },
});
console.log((name.value = 10));//10
</script>


watch 监听

和vue2的watch一样
但是vue2只能写一个watch配置项
vue3中可以写多个watch

watch watch(参数一:要监听的值, (newValue, oldValue) => { 参数二:回调 },参数三:配置项);

1.watch监听ref

import { reactive, ref, watch } from "vue";
setup() {
    let name = ref("小明");
    let msg = ref("你好啊");

//监视1:监视一个ref数据,watch(要监视谁,()=>{},watch配置项)
     watch(name, (newValue, oldValue) => {
       console.log(newValue, oldValue);
     },{immediate:true});
     
//监视2:监视ref多个响应数据,以数组的形式
     watch([name,msg], (newValue, oldValue) => {
       console.log(newValue, oldValue);//console值也是数组
     },{immediate:true});
       
    return { name, msg};
  },

2.watch监听reactive

import { reactive, ref, watch } from "vue";
 setup() {
    let data = reactive({
      sum: 0,sum2: 0,
      job: {work: {name: "程序员"}},
    });

//监视3:监视一个reactive数据,全部属性
//(1).无法正确获得oldValue,因为oldValue会和newValue的值一样,可以用ref来监听,单独某个属性
//(2).强制开启deep:true,
     watch(data,(newValue, oldValue) => {
         console.log(newValue, oldValue); 
       },{ immediate: true } );
       
//监视4:监视reactive数据的某一个属性
//(1)监听的数据要写成一个函数,newValue和oldValue都可以获取到
     watch(() => data.sum,(newV, oldV) => {
         console.log(newV, oldV);
       });
       
//监视5:监视reactive中一个对象里面多个属性的变化
     watch([() => data.sum,() => data.sum2,],(newV, oldV) => {
         console.log(newV, oldV);
       })
      
//特殊情况,当监听的是对象属性 ,需改的是job里面比较深的属性,需要加deep:true
      watch(() => data.job,(newV, oldV) => {
        console.log(newV, oldV);
       },{deep:true});

   
    return { data}},

3.watchEffect

不需要手动传入依赖
初始化执行依赖
无法获取原始值,只能获取改变后的
可以在里面执行异步操作
在onMounted之前调用

import { reactive, ref, watchEffect } from "vue";
 setup() {
    let data = reactive({
      sum: 0,
      sum2: 0,
      job: {
        work: {
          name: "程序员",
        },
      },
    });
    let name = ref("小明");

//watchEffect在回调里面用到了什么属性,就自动监听那个属性
     watchEffect(() => {
      const x1 = name.value;
      const x2=data.job.work.name
      console.log('数据发送改变了');
    });
}

watchEffect+ts

<script setup lang="ts">
import { reactive,watchEffect,onMounted} from "vue";
const obj=reactive({
  name:1,
  age:39
})
const stop=watchEffect((onInvalidate)=>{
console.log(obj.age)
onInvalidate(()=>{
  // 清除一些副作用
})
},{
  onTrigger(e){
    debugger
  }})
stop()//停用监听
</script>

4.watch监听defineProps传递数据

const props = defineProps(["modelItemId"]);

watch( props,(newValue) => {
    console.log(newValue.modelItemId);
  },{ immediate: true });

v3生命周期

在这里插入图片描述

通过配置项在setup外面配置

export default {
  name: "HelloWorld",
  beforeCreate() {
    console.log("beforeCreate");
  },
  created() {
    console.log("created");
  },
  beforeMount() {
    console.log("beforeMount");
  },
  mounted() {
    console.log("mounted");
  },
  beforeUpdate() {
    console.log("beforeUpdate");
  },
  updated() {
    console.log("updated");
  },
  beforeUnmount() {
    console.log("beforeUnmount");
  },
  unmounted() {
    console.log("unmounted");
  },
  setup() {
    let sum = ref(0);
    return {
      sum,
    };
  },
};

组合式API生命周期

没有beforeCreate,created 有setup
其他的前面加on就可以了

<script>
import {
  onBeforeMount,
  onMounted,
  onUpdated,
  onBeforeUpdate,
  onBeforeUnmount,
  onUnmounted,
} from "vue";

export default {
  name: "HelloWorld",
  setup() {
    console.log("setup");//1
    onBeforeMount(() => {
      console.log("onBeforeMount");
    });
    onMounted(() => {
      console.log("onMounted");
    });
    onUpdated(() => {
      console.log("onUpdated");
    });
    onBeforeUpdate(() => {
      console.log("onBeforeUpdate");
    });
    onBeforeUnmount(() => {
      console.log("onBeforeUnmount");
    });
    onUnmounted(() => {
      console.log("onUnmounted");
    });
  },
};

组合式api和setup外面的配置项都存在的情况下,on的级别要高
在这里插入图片描述

ts+组合式api的生命周期

<script setup lang="ts">
//defineProps
import {  onMounted } from "vue";
console.log(1)//先出来
onMounted(() => {
  console.log(2); //后出来
});

</script>

hook函数——复用js部分相当于mixin

hook本质是一个函数,把setup函数中使用的composition API进行了封装
类似于v2的mixin
优势,复用代码,让setup逻辑更清晰

1.创建hooks文件夹

在这里插入图片描述

2.定义

//hooks /userPoint.js
//定义一个方法,实现复用,点击屏幕显示x,y坐标
import { reactive, onMounted, onBeforeUnmount } from "vue";
export  default function(){
    let point = reactive({ x: 0, y: 0});
      function savePoint(event) {
        point.x = event.pageX;
        point.y = event.pageY;
      }
      onMounted(() => {
        window.addEventListener("click", savePoint);
      });
      onBeforeUnmount(() => {
        window.removeEventListener("click", savePoint);
      });
      return {point}
}

3.引入

//vue
<script>
import usePoint from "@/hooks/usePoint";//引入
export default {
  name: "HelloWorld",
  setup() {
    let point = usePoint();//使用这个方法
    return { point};
  },
};
</script>

组件上使用v-model

element-plugs控制子组件弹窗

//父.vue
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const message = ref('hello')
</script>
<template>
  <CustomInput v-model="message" /> {{ message }}
  //<CustomInput //展开写法
 // :modelValue="searchText"
 // @update:modelValue="newValue => searchText = newValue"
///>
 
</template>
//子.vue
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
<!-- 父组件 -->
<template>
  <div>
    <child 
    v-model="message"
    ></child>
  </div>
  // <NewHouseHelpFindHouse v-model:dialogVisible="dialogVisible"  @update:dialogVisible="updateDialogVisible" ></NewHouseHelpFindHouse>
</template>

<script setup>
let message = ref("Hello World Vue3");
const updateMessage = (val) => {
  message.value = val
}
</script>

<!-- 子组件 -->
    <input type="text" v-model="propsMessage">
  //    <el-dialog v-model="propsMessage" title="详情" width="30%" draggable>   </el-dialog>
<script setup>
import { computed } from "vue";
let props = defineProps({
  dialogVisible: Boolean,
});

const emmits = defineEmits(["update:modelValue"]);
const propsMessage = computed({
  get: () => {
    return props.dialogVisible;
  },
  set: (val) => {
    emmits("update:dialogVisible", val);
  },
});
</script>

父子组件for循环修改绑定v-model

//父
<script setup lang="ts">
import { reactive, ref } from 'vue';
let obj = reactive([{ name: '张1', id: 1 }, { name: '张2', id: 2 }, { name: '张3', id: 3 }])
</script>
        <li v-for="t,i in obj" :key="t.id">
            <list-view v-model="obj[i]"></list-view>
        </li>
//子
<script setup lang="ts">
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const change = () => {
    emit('update:modelValue', { name: '历史', id: 9 })
}
</script>
<template>
    <p>{{props.modelValue}}</p>
    <button @click="change">那妞</button>
</template>

其他配置

1.toRef()和toRefs()

创建一个ref对象,其value值指向另一个对象的某个属性
将响应式的某个属性单独拎出来给外部使用的时候,简化差值表达式外面嵌套的一层{{persion.name}}变成{{name}},
const refName=toRef(persion,‘name’)
const refNames=toRefs(persion)

<template>
  <div class="hello">
    {{name}}
    {{job1}}
  </div>
</template>

<script>
import { reactive, toRef,toRefs } from "vue";
export default {
  setup() {
    let persion = reactive({
      name: "张三",
      age: 18,
      job: {j1: {salary: 20}},
    });
    const name1 = persion.name; //name1不是响应式
    console.log(name1);
    const name2 = toRef(persion, "name");
    console.log(name2); //name2是响应式——ObjectRefImpl{_object: Proxy, _key: 'name', ....}
    const x=toRefs(persion)
    console.log(x)//把persion里面所有属性的都变成toRef
    return {
      persion,
      name: toRef(persion, "name"),//单拎出来一个数据简化了外部{{peison.name}},现在可以直接{{name}}
      job1: toRef(persion.job.j1, "salary"),
      ...toRefs(persion)//解构出对象的所有属性只包括第一层
    };
  },
};
</script>

2.shallowReactive和ShallowRef(浅响应)

shallowReactive,ShallowRef和 reactive,ref的区别:
shallowReactive是浅层次的响应式,只能响应第一层,reactive会遍历所以层级的数据形成响应式
ShallowRef不去处理对象类型的响应式,ref如果传入对象类型会以reactive形式去处理

什么时候使用?
如果一个对象数据,嵌套特别深,但是变化只是外层属性变化——shallowReactive
如果一个对象数据,后期都不会修改改对象的属性,而是生成新的对象替换——ShallowRef

import { shallowReactive,ShallowRef} from "vue";
    let persion = shallowReactive({
      name: "张三",
      age: 18,
      job: {//只能shallowReactivejob,修改j1不会响应式
        j1: {salary: 20},
      },
    });
     let x = ref({
      a: 1,
    });
    let x2 = shallowRef(0);

3.readonly和shallowReadonly

readonly:让一个响应式数据变为只读的(深只读)
shallowReadonly:(浅只读)
应用环境 :不希望数据被修改,当你用别的组件的数据,如果改了数据会受到影响的时候,就可以定义readonly

import { readonly,shallowReadonly} from "vue";
 setup() {
    let sum = ref(0);
    let persion = reactive({
      name: "张三",
      age: 18,
      job: {j1: {salary: 20}},
    });
    sum = readonly(sum);//变为深只读
    persion = readonly(persion);//变为深只读
    //persion = shallowReadonly(persion);//浅只读
    return {
      sum,
      ...toRefs(persion), //只会解构出第一层
    };
  },

4.toRaw 和 markRow转普通对象

toRaw :
将一个reactive生成的响应式对象,变为普通对象
使用场景:读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新

markRow:
标记一个对象让他永远不会变成响应对象,比toRaw用的多
应用场景:
1.有些值不应被设置成响应式,比如第三方类库(axios)
2,当渲染不可变比较大的数据,跳过响应式转变会提高性能

import { toRaw ,markRow} from "vue";
setup() {
    let sum = ref(0);
    let person = reactive({
      name: "张三",
      age: 18,
      job: {
        j1: {
          salary: 20,
        },
      },
    });
    console.log(toRaw(person)); //原始变量,不带proxy响应式{name: '张三', age: 18, job: {…}}
    function add() {
      let p = toRaw(person);
      p.age++;
    }
    function Car() {
      let car = { name: "奔驰" };
      person.car = markRaw(car);//person是响应式的,给他加属性默认也会变为响应式,markRaw是当car的数据嵌套十分多的时候同时又不需要更改,把car变为原始数据,操作car还可以更改,但是不更新页面了
    }
    return {
      sum,
      Car,
      add,
      ...toRefs(person), //只会解构出第一层
    };
  },

和readonly 的区别,readonly是只读不能修改,toRaw,会更改数据但是不渲染页面,不是响应式

5.customRef 自定义ref

并对其依赖项跟踪和更新触发进行显示控制
customRef——毛坯房, ref——精装房

实现输入框v-model的效果
在这里插入图片描述

<template>
  <div class="hello">
    <input v-model="keyword">
    <h3>{{keyword}}</h3>
  </div>
</template>

<script>
import { customRef } from "vue";
export default {
  name: "HelloWorld",
  setup() {
    function myRef(value) {
      //自定义一个ref
      return customRef((track, trigger) => {
        //调用一个方法自定义ref
        return {
          get() {
            track(); //通知vue追踪数据的变化,需要在return前面
            return value;
          },
          set(newValue) {
            
            value = newValue;
            trigger(); //通知vue从新解析模板
          },
        };
      });
    }

    // let keyword = ref("hello");//使用内部的ref
    let keyword = myRef("hello"); //使用自定义的
    return {
      keyword,
    };
  },
};
</script>

响应式数据判断

import {toRefs, reactive,ref,readonly,
  isRef,//检查是否是ref对象
  isReactive,//检查是否是Reactive对象
  isReadonly,//检查是否只读
  isProxy,//检查是否是reactive或者readonly方法创建的代理
} from "vue";
export default {
  setup() {
    let car = reactive({ name: 123 });
    let sum = ref(0);
    let car2 = readonly(car);

    console.log(isRef(sum)); //true
    console.log(isReactive(car)); //true
    console.log(isReadonly(car2)); //true
    console.log(isProxy(car)); //true
    console.log(isProxy(car2)); //true
    console.log(isProxy(sum)); //false
    
    return { ...toRefs(car) };
  },
};

自定义指令

主要对dom进行操作,比如focus
在 < script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。在上面的例子中,vFocus 即可以在模板中以 v-focus 的形式使用。

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>
//滑到底,监听滚动事件,并执行回调函数
  <div class="box2" v-scroll="abc">
    <p>11111111111111</p>
		。。。
  </div>
const vScroll = {
  mounted(el, binding) {
    let p = 0;
    el.addEventListener("scroll", function () {
      p = this.scrollTop;
      if (el.clientHeight + p >= el.scrollHeight) {
        binding.value();
      }
    });
  },
};

新的组件

Fragment

v2里面需要根标签,v3不需要根标签,实际上是内部会把多个标签包在一个fragment虚拟元素中,好处减少标签层级,减小内存占用

Teleport

能让我们的组件html结构移动到指定的位置,比如移动到body下

   <tepleport to="body">
      <!-- 把内部元素放到body下,就不会引入当前组件影响其他组件的大小,相对于body进行定位 -->
      <div class="box">弹窗</div>
    </tepleport>

在这里插入图片描述

在这里插入图片描述

Suspense

和defineAsyncComponent异步组件配合使用,等待异步组件渲染时额外内容,让应用有更好的用户体验
内部也是用插槽显示的,分为两个插槽,一个用于放要展示的组件,另一个是用来还没有呈现组件之前展示的东西

//父.vue
<template>
  主体
  <Suspense>
    <template v-slot:default>
      <!-- //最后展示组件 -->
      <hello-world />
    </template>
    <template v-slot:fallback>
      <!-- //加载过程中展示的 -->
      <h1>加载中....</h1>
    </template>
  </Suspense>

</template>

<script>
import { defineAsyncComponent } from "vue";
const HelloWorld = defineAsyncComponent(
  () => import("./components/HelloWorld.vue") //异步引入
);
export default {
  components: { HelloWorld },
  name: "App",
  setup() {
    return {};
  },
};
</script>

//子组件
<template>
  <div class="hello">
    内容出来了
    {{sum}}
  </div>
</template>

<script>
import { ref } from "vue";
export default {//这段意思是3秒之后显示组件
  async setup() {//当组件用了Suspense和异步加载的时候,setup可以返回promise实例对象
    let sum = ref(0);
    let p = new Promise((resolve) => {
      setTimeout(() => {
        resolve({ sum });
      }, 3000);
    });
    return await p;
  },
};
</script>

<style scoped>
</style>

defineAsyncComponent()异步引入组件

defineAsyncComponent谁先加载完谁先展示,但是页面有抖动,如何解决用Suspense
同步引入,一起出现

import { defineAsyncComponent } from "vue";
const HelloWorld = defineAsyncComponent(
  () => import("./components/HelloWorld.vue") //异步引入
);
// import HelloWorld from "./components/HelloWorld.vue";//静态引入
export default {
}

vue3 的其他变化

全局api转移

//v2里面的全局配置不能使用
Vue.component()
Vue.directive()
//v3,将Vue.xxx调整到应用实例app上
v2v3
Vue.config.xxxapp.config.xxx
Vue.config.productionTip.xxx(关闭vue生产提示)移出
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties
const app = createApp(App)
app.use(router).use(ElementPlus).use(BaselineUI)
app.mount('#app')
/
import MyUI from '@/components'
createApp(App)
  .use(store)
  .use(router)
  .use(MyUI )
  .mount('#app')
//app.config.globalProperties
app.config.globalProperties.msg = 'hello'
//获取组合式api中
onMounted(() => {
    // 通过getCurrentInstance().appContext访问全局属性
    const { msg } = getCurrentInstance().appContext.config.globalProperties
})
//在选项式api中
export default {
  mounted() {
    console.log(this.msg) // 'hello'
  }
}

动画类名修改

在这里插入图片描述

移出keyCode作为v-on的修饰符,不再支持config.keyCodes

@keyup.13 ——不行了
Vue.config.keyCodes.huiche=13——按键别名不行了

v3 移除 @click.native 事件修饰符

//v2
 //如果不加native,@click会被当做自定义事件
 <student @click.native="show"> </student>
//v3
emits:["close"]//中指定

移除filter

v3 路由

//router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import HomeView from "../views/HomeView.vue";
const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    redirect: "/home",
  },
  {
    path: "/home",
    name: "home",
    component: HomeView,
  },];
const router = createRouter({
  history: createWebHashHistory(),
  routes,
});
export default router;
//页面中使用需用引入
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
 function pushWithQuery(query) {
      router.push({
        name: 'search',
        query: {
          ...route.query,
        },
      })
    }

全局路由守卫

//router/index
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

路由独享守卫

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

组件内路由 onBeforeRouteLeave,onBeforeRouteUpdate

import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

 onBeforeRouteUpdate(async (to, from) => {
      //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })

监听路由router变化

onBeforeRouteUpdate路由守卫
import { useRouter,onBeforeRouteUpdate } from 'vue-router';

let router = useRouter()
//路由守卫
onBeforeRouteUpdate((to) => {
        // console.log('onBeforeRouteUpdate',to.path);     
 });

//watch监听
watch(() =>router.currentRoute.value.path,(newValue,oldValue)=> {
        console.log('watch',newValue);
},{ immediate: true })

路由懒加载

路由的按需加载

// 将
// import UserDetails from './views/UserDetails'
// 替换成
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})

//简写
{ path: '/users/:id', component: ()=>import("@/views/home.vue")}

mitt.js 替代EventBus

在Vue3中就没有EventBus了,可以使用mitt.js来替代

1.安装:$ npm install --save mitt

2.定义

//方式1,全局总线,vue 入口文件 main.js 中挂载全局属性。
import { createApp } from 'vue';
import App from './App.vue';
import mitt from "mitt"

const app = createApp(App)
app.config.globalProperties.$mybus = mitt()

3.使用

import mitt from 'mitt'
const emitter = mitt()

// 监听foo 获取数据
emitter.on('foo', e => console.log('foo', e) )

// 监听所有事件
emitter.on('*', (type, e) => console.log(type, e) )

// 推送数据
emitter.emit('foo', { a: 'b' })

// 清除所有
emitter.all.clear()

//处理程序引入
function onFoo() {}
emitter.on('foo', onFoo)   // listen
emitter.off('foo', onFoo)  // unlisten

Transition 动画

试例:透明度变化消失隐藏

<button @click="show = !show">Toggle</button>
<Transition appear mode="out-in"  name="fade">
// name="fade" 名字
//appear:初次渲染执行过渡
//mode="out-in" :先执行离开动画

  <p v-if="show">hello</p>
</Transition>
//.fade-enter-active,定义名字
//.fade-leave-active
.v-enter-active, .v-leave-active {
  transition: opacity 0.5s ease;
}
.v-enter-from, .v-leave-to {
  opacity: 0;
}

路由切换动画

//两个路由切换从右边移出,从右边再回来
<template>
  <router-link to="/QieHuan"> 切换 1 </router-link>
  <router-link to="/QieHuan2"> 切换 2 </router-link>
  <router-view v-slot="{ Component }">
  <transition mode="out-in">//mode="out-in"先移出后出现
    <component :is="Component" />
  </transition>
</router-view>
</template>

<script setup lang="ts">
import "vant/es/toast/style";
</script>
<style lang="scss">
.v-enter-active, .v-leave-active {
  transition: 0.5s;
}
.v-enter-from, .v-leave-to {
  transform: translateX(10%);
}
</style>



v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。

v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。

v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。

v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。

v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。

v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

JavaScript 钩子

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- ... -->
</Transition>

TransitionGroup:用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。

<TransitionGroup name="list" tag="ul">
  <li v-for="item in items" :key="item">
    {{ item }}
  </li>
</TransitionGroup>
css
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

app.config.globalProperties 定义一个全局使用的方法

vue.protoptype.a=a
app.config.globalProperties.$translate =()=>{}

TS

约束js的,Typescript 为 Javascript 增加类型能力,主要为了避免 JS 弱类型下产生的各种有意无意的问题

基本类型

let a:string=‘123'
let arr:string[]=[ ]
let obj:Object                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        ={}

ts 新增类型

联合类型 : number|null

// | 联合类型  变量可以是两种类型之一
let timer:number|null = null

在这里插入图片描述

并集只能是number|string类型

type num=‘one’|‘two’|‘three’
let a:num=‘one’ //只能是one|two|three

元组:是一种特殊的数组

它约定了的元素个数
它约定了特定索引对应的数据类型

type Gender = 'girl' | 'boy'
// 声明一个类型,他的值 是 'girl' 或者是 'boy'
let g1: Gender = 'girl' // 正确
let g2: Gender = 'boy' // 正确

type 类型别名,给其他类型起别名

任何情况都可以用type,type val=类型
后面等于一个类型,而不是一个值
type n=0 //代表n=一个集合,集合中就一个0
type likeFalse=false|0|‘’|undefined|null //都是类型

在这里插入图片描述

type s = string // 定义

const str1:s = 'abc'
const str2:string = 'abc'

type NewType = string | number
let a: NewType = 1

声明接口:interface 描述对象的属性

在这里插入图片描述

interface IPerson {
	name: string,
	age: number
}

const user1:IPerson = {
	name: 'a',
	age: 20
}
//继承
interface a { x: number; y: number }
// 继承 a
// 使用 extends(继承)关键字实现了接口
//b 同时有 x、y、z 三个属性
interface b extends a {
  z: number
}
//自动合并
interface a { x: number; }
interface a { y: number; }
let obj:a={x:10,y:10}

扩展一个定义好的interface,如axios的get
在这里插入图片描述
在这里插入图片描述
在页面中出现一个菊花,加载完成后消失

interface 和type的区别

type是类型别名,可以定义任意类型,不可重新赋值
interface是类型声明,用于面向对象,在里面定义属性什么的,就是用面向对象的方式把type做的事情再干一边。axios中的比如get类型,第二个参数会传值需要用interface定义,用type就不能扩展
type大于interface,tyoe是任意类型,interface只用来对象

字面量类型

let str1 = 'hello TS' //类型是string
const str2 = 'hello TS'//类型是hello TS因为const 不能修改

枚举:enum

字符串枚举的每个成员必须有初始值,enum一个数字可以,enum一个字符串就显的很呆,可以使用联合类型来规范

使用场景1
比如:后端传过来一个状态1-4分别代表未完成,已完成,等4个状态。
我们使用1-4的时候忘记代表什么意思

使用场景2
二进制权限管理0001,0010,0100判断用户是否有某个权限

enum State{
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
enum Num{
  one=0,//不写值默认往后加一
two,three
}
let s:State=State.UP //'UP'

在这里插入图片描述

void

any 任意的。当类型设置为 any 时,就取消了类型的限制

类型断言

可以用来手动指定一个值的类型

<类型>值

<>写法
let value: any = "this is a string";
let length: number = (<string>value).length;

as写法

as写法
let value: any = "this is a string";
let length: number = (value as string).length;

?可选参数

interface IDemo {
    x?: number
}

非空断言 !

当你明确知道某个值不可能为 undefinednull 时,你可以用 在变量后面加上一个 !(非空断言符号)来告诉编译器:"嘿!相信我,我确信这个值不为空!"。
非空断言具体的使用场景如下:
function fun(value: string | undefined | null) {
  const str: string = value; // error value 可能为 undefined 和 null
  const str: string = value!; //ok
  const length: number = value.length; // error value 可能为 undefined 和 null
  const length: number = value!.length; //ok
}

泛型 T 值<类型>

泛型:在定义函数,接口,类的时候,不预先指定具体的类型,而在使用的时候在去指定类型的一种特征
就相当于变量

//函数泛型格式如下   函数方法名<T>(参数):返回值
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']

createArray< string>泛型T赋值为string。
value: T就表示value类型是string

在这里插入图片描述

event 事件类型(去取input元素的value值的时候会报错)

Event.target.value去取input元素的value值的时候会报错,这个时候只需要把ev.target做类型断言为HTMLInputElement即可,

changeValue($event)
//点击事件事件对象的类型为MouseEvent,input事件对象的类型为Event。但是我们直接
//(e: MouseEvent)
//(e: Event)
function fileSelected(e: Event) {
  const target = e.target as HTMLInputElement
  const file: File = (target.files as FileList)[0]
  // ...
}

转 as Ref< string>类型

as Ref<string>
//基本类型
	const num:number=123
	const str:string='123'
	const bool:boolean=true
//数组
	const arr:number[]=[1,2,3]//const arr:Array<string>=[1,2,3]
//对象
	interface OJ{
		name:string,
		age:number,
		list:object[]
	}
	const obj:OJ={
		name:'张三',
		age:12,
		list:[{a:1},{b:1}]
	}
//数组包对象
interface Item{
    id:number
    name:string
    list:string[]
    [propName]:string:any//除了其他必填项,可以随机传值
}
const list =ref<Item[]>([])
[{id:1,name:'1',list:['a','b']},...]
//函数
	function my(num:unknow):string{
	//(a?: number=10, b?: number=10)是可选参数和默认值
	//:never 返回throw newError
	//:void 没有返回值
	//:string 函数返回值是string

	//num不知道是什么类型,需要自己判断一下
		if(typeof num =='string')return '1'
	}
	my(123)
//包含多个类型
	(number|string)[]=[1,'2']
//type定义类型
	type Type='number'|'string'
	ref<Type>()
	funxtion my(num:Type):Type{
	}
//泛型T:泛型:在定义函数,接口,类的时候,不预先指定具体的类型,而在使用的时候在去指定类型的一种特征
	function createArray<T>(length: number, value: T): Array<T> {
	    let result: T[] = [];
	    for (let i = 0; i < length; i++) {
	        result[i] = value;
	    }
	    return result;
	}
	createArray<string>(3, 'x'); // ['x', 'x', 'x']

// 使用泛型创建一个函数:
function id<Type>(value: Type): Type {
    return value
}
//调用泛型函数:
// 1 以number类型调用泛型函数
const num = id<number>(10)

// 2 以string类型调用泛型函数
const str = id<string>('a')

// 3 以boolean类型调用泛型函数
const ret = id<boolean>(true)
[添加链接描述](https://blog.csdn.net/xuxuii/article/details/126724000)

any和unknow区别

any可以是任意类型,unknow需要自己判断一下if(){typeof msg ==‘string’}

强制转换类型

//转响应式ref的string
变量名 as unknow as Ref<string>

ts报错

ts 对象可能为“未定义”。

result.data!.verification

Typescript: type‘string’|‘undefined’类型不能赋值给type‘string

let name1:string = person.name || '';

高级类型

原文

1.交叉类型

是通过&符号将多个类型进行合并成一个类型,然后用type来声明新生成的类型

interface ClassA{
    name:string;
    age:number
}
interface ClassB{
    name:string;
    phone:number;
}
将接口ClassA和接口ClassB通过&进行合并创建一个新的接口类型Class

type Class = ClassA & ClassB
let info:Class = {
    name:'zhagsan',
    age:18,
    phone:1573875555
}

2.联合类型

通过|符号连接多个类型从而生成新的类型。它主要是取多个类型的交集,即多个类型共有的类型才是联合类型最终的类型。

let multiType: number | boolean;
multiType = 20;         //* Valid
multiType = true;       //* Valid
multiType = "twenty";   //* Invalid

3.type 类型别名

4.类型保护as断言

5.null和undefined

可选链式操作?.

let s2: string | null = 'hi'

装饰器@expression

在不改变对象自身结构的前提下,向对象添加新的功能。
expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入

开启装饰器特性,需要在tsconfig.json中开启experimentalDecorators:

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
} 

参考1
参考2

  1. 类装饰器
  2. 属性
  3. 方法
  4. 参数

在这里插入图片描述

// 类装饰器
@classDecorator
class Person {
  // 属性装饰器
  @propertyDecorator
  name: string;

  // 方法装饰器
  @methodDecorator
  intro(
    // 方法参数装饰器
    @parameterDecorator words: string
  ) {}

  // 访问器装饰器
  @accessDecorator
  get Name() {}
}

// 此时的 Person 已经是被装饰器增强过的了
const p=new Person()

interface Person {
  name: string
  age: string
}
function enhancer(target: any) {
  target.xx = 'Person' ; // 给类增加属性
  target.prototype.name = '金色小芝麻'
  target.prototype.age = '18'
}
@enhancer // 名字随便起
class Person {
  constructor() { }
}
let p = new Person()
console.log(Person.name); // Person
console.log(p.age) // 18

在项目中的使用

Pinia

官网

和vuex区别

  • pinia它没有mutation,他只有state,getters,action【同步、异步】使用他来修改state数据
  • pinia语法上比vuex更容易理解和使用,灵活。
  • pinia没有modules配置,没一个独立的仓库都是definStore生成出来的

1.下载

npm install pinia

2.定义

src目录下创建store/index.ts文件

main.ts

import {createPinia} from 'pinia'  
const app = createApp(App)

app.use(createPinia()).use(router).mount('#app')

/store/index.ts

/**
 * 一般在容器中做这4件事
 *    1. 定义容器并导出
 *    2. 使用容器中的state
 *    3. 修改容器中的state
 *    4. 使用容器中的action
 */
import { defineStore } from "pinia";

/**
 * 1. 定义容器并导出
 * 参数一: 容器ID, 唯一, 将来 Pinia 会把所有的容器挂载到根容器
 * 参数二: 选项对象
 * 返回值: 函数, 调用的时候要空参调用, 返回容器实例
 */
 //mainStore 是需要其他页面引入的名字
export const mainStore = defineStore('main', {
    /**
     * 类似组件的 data, 用于存储全局的的状态
     * 注意:
     *    1.必须是函数, 为了在服务端渲染的时候避免交叉请求导致的数据交叉污染
     *    2.必须是箭头函数, 为了更好的 TS 类型推导
     */
    state: () => {
        return {
            count: 100,
          	foo: 'bar',
          	age: 18
        }
    },
    /**
     * 类似组件的 computed, 用来封装计算属性, 具有缓存特性
     */
    getters: {},
    /**
     * 类似组件的 methods, 封装业务逻辑, 修改state
     * 注意: 里面的函数不能定义成箭头函数(函数体中会用到this)
     */
    actions: {
 		del(id:string){
	      const a=this.listArr.findIndex(i=>i.id==id)
	      this.listArr.splice(a,1)
	    },
}
})

3.页面使用

state获取/修改

<script lang="ts" setup>
  import {mainStore} from '../store'
  import { storeToRefs } from 'pinia';

  const mainStoreI = mainStore()
  const {count, foo, age} = storeToRefs(mainStoreI)//解构出来并保持ref响应式,在store里面是reative响应
  
  /** 修改store中的1个数据 */
  const handleClick = () => {
    // 修改单个数据,方式一
     count.value++

    // 修改单个数据,方式二
//    mainStoreI.count++
  }
  /** 批量修改store中的数据 */
  const handleClick = () => {
  // 给$patch传入一个函数,函数体中修改数据项
  // 形参state即为容器
  mainStoreI.$patch((state) = {
    state.count++;
    state.foo = 'hello';
    state.arr.push(4)
  })
}
</script>

actions

修改 Store 状态时, 如果逻辑较多, 可以借助 actions
actions 中的函数, 不能定义成箭头函数
因为箭头函数中没有this, 在运行时, 会向外部的作用域找

/**
 * 类似组件的 methods, 封装业务逻辑, 修改state
 */
actions: {
    changeState(num: number): void {
        // this访问当前容器的实例
        this.count += num
        this.foo='你好啊'
        this.arr.push(555)
      
        // 这里也可以使用$patch
        // this.$patch({})
        // this.$patch(state => {})
    }
}

/** 页面使用 */
<script setup lang="ts">
import {mainStore} from '../store'
const mainStoreI = mainStore()
const handleClick = () => {    
	// 当逻辑较多时,可以封装到actions中处理  
	mainStoreI.changeState(10)
}
<script>

getters

具有缓存特性

 getters: {       
  	 computeds():number{
	    return this.count+2
   },
  }
//getters传值
  getters: {
    // 定义的 getters,第一个参数就是该容器的 state
    comp(state) {
      return (num: number) => {
        return state.count + num
      }
    }
  },
  <p>{{ mainStoreI .comp(2) }}</p>

mainStore.$reset()重置


**// 重置 count 数据的方法
const resetCount = () => {
  mainStore.$reset()
}**

跨容器调用

import { defineStore } from 'pinia' 
const useMainStore =  defineStore('main', {
  state: () => {
    return { count: 1}
  }
})

const useProjectStore =  defineStore('project', {
  state: () => {
    return { age: 21 }
  },
  actions: {
    // 1、age 加法方法
    addAge() {
      // 实例化 pinia 容器
      const mainState = useMainStore()
      this.age += this.age + mainState.count
    }
  }
})
// 导出容器
export {
  useMainStore,
  useProjectStore
}

//页面使用不同容器
// 实例化容器
const mainStore = useMainStore()
const projectStore = useProjectStore()

4.持久化 pinia-plugin-persist插件

解决刷新数据消失的问题
npm i pinia-plugin-persist

//Vue3 main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'

const pinia = creatPinia()
pinia.use(piniaPersist)
createApp({})
  .use(pinia)
  .mount('#app')
//tsconfig.json
{
  "compilerOptions": {
    "types": [
      "pinia-plugin-persist"
    ]
  },
}
//使用
export const useUserStore = defineStore('storeUser', {
  state () {
    return {
      firstName: 'S',
      lastName: 'L',
      accessToken: 'xxxxxxxxxxxxx',
    }
  },
  persist: {//方式1
    enabled: true,
    strategies: [
      {
        key: 'firstName',  //自定义 Key值
        storage: localStorage,  // 选择存储方式
      },
    ],
  },
   persist: {//方式2全部存储
    enabled: true,
  },
})

vue3 data reactive重新重置

直接length等于0

为什么v3中不能用this

在v3中,使用setup()来代替了以前data、methods函数等。因为,setup执行是在created之前。所以,没有this。但是又想使用 p a r e n t , parent, parent,refs等方法

  import { getCurrentInstance } from 'vue'
  // const { ctx } = getCurrentInstance() as any;
  // 而出现 线上环境 ctx 无法识别问题 解决如下:
  const { proxy  } = getCurrentInstance() as any; 
Logo

前往低代码交流专区

更多推荐