vue基础知识-学习笔记

347 阅读29分钟

指令

v- 开头都是vue 的指令

v-text 用来显示文本

v-html 用来展示富文本

v-if 用来控制元素的显示隐藏(切换真假DOM) 会重新渲染组件和销毁组件 触发onBeforeUnmount/onUnmounted

v-else-if 表示 v-if 的“else if 块”。可以链式调用

v-else v-if条件收尾语句

v-show 用来控制元素的显示隐藏(display none block Css切换)

v-on 简写@ 用来给元素添加事件

v-bind 简写: 用来绑定元素的属性Attr

v-model 双向绑定

v-for 用来遍历元素

v-on修饰符 冒泡案例

.stop 点击事件冒泡

<template>
  <div @click="parent">
    <div @click.stop="child">child</div>
  </div>
</template>


<script setup lang="ts">
  const child = () => {
    console.log('child');

  }
  const parent = () => {
    console.log('parent');
  }

</script>

.prevent 阻止表单提交

<template>
  <form action="/">
    <button @click.prevent="submit" type="submit">submit</button>
  </form>
</template>
 
<script setup lang="ts">
const submit = () => {
  console.log('child');
 
}
</script>

v-bind 绑定class

<template>
  <div :class="flag">{{flag}}</div>
</template>

<script setup lang="ts">
type Cls = {
  other: boolean,
  h: boolean
}
const flag: Cls = {
  other: false,
  h: true
};
</script>

<style>
.active {
  color: red;
}
.other {
  color: blue;
}
.h {
  height: 300px;
  border: 1px solid #ccc;
}
</style>

v-model

<template>
  <input v-model="message" type="text" />
  <div>{{ message }}</div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const message = ref("v-model")
</script>

虚拟Dom和diff算法

介绍虚拟dom

虚拟DOM就是通过js来生成的节点树
为什么要有虚拟DOM?
我们可以通过下面的例子

let div = document.createElement('div')
let str = ''
for (const key in div) {
  str += key + ''
}
console.log(str)

image-20220915180500009

可以发现dom上面的属性非常多
所以直接操作dom会非常浪费性能
解决方案就是 我们可以用JS的计算性能来换取操作DOM所消耗的性能,既然我们逃不掉操作DOM这道坎,但是我们可以尽可能少的操作DOM

ref 全家桶

ref

受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。
我们这样操作是无法改变message 的值 因为message 不是响应式的无法被vue 跟踪,要改成ref

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
<script setup lang="ts">
let message: string = "我是message"
 
const changeMsg = () => {
   message = "change msg"
}
</script>
<template>
  <div>{{ mes }}</div>
  <button @click='change'>修改</button>
</template>
<script setup lang="ts">
import {ref} from "vue";

const mes = ref({name: 'haoran'})
const change = () => {
  mes.value.name = '浩然'
  console.log(mes)
}
</script>

ifRef

判断是不是一个ref对象

<template>
  <button @click='change'>修改</button>
</template>
<script setup lang="ts">
import {ref, isRef} from "vue";
const mes = ref({name: 'haoran'})
const mes2 = {name: 'zhangSan'}
const change = () => {
  console.log(isRef(mes)) // true
  console.log(isRef(mes2)) // false
}
</script>

shallowRef

ref 是深层响应式
shallowRef 是浅层响应式

点击修改后页面数据并没有发生改变 但是内部已经改变

<template>
  <div>ref:{{ mes }}</div>
  <hr>
  <div>shallowRef:{{ mes2 }}</div>
  <button @click='change'>修改</button>
</template>
<script setup lang="ts">
import {ref, isRef, shallowRef} from "vue";

const mes = ref({name: 'haoran'})
const mes2 = shallowRef({name: 'zhangSan'})
const change = () => {
  mes2.value.name = '张三'
  console.log(mes2)
}
</script>

image-20220915180523812


要直接修改value 页面才会对视图造成更新

const change = () => {
  mes2.value = {name: '张三'}
  console.log(mes2)
}

image-20220915180539598

ref和shallowRef不能一起写,如果一起写shallowRef会被ref影响 :::tips 因为ref底层更新逻辑的时候,他会调用triggerRef这个函数 :::

const change = () => {
  mes.value.name = '浩然' // 如果这个数据在页面渲染了 那么shallowReactive会收到影响
  mes2.value.name = '张三'
}

image-20220915180546721

triggerRef

强制更新页面DOM,也可以改变shallowRef里面的值
使用ref时 底层会调用triggerRef,所以会影响shallowRef

const change = () => {
  mes2.value.name = '张三'
  triggerRef(mes2)
}

image.png

customRef

自定义ref
customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的
跟ref原理差不多

<template>
  <div>customRef: {{ obj }}</div>
  <hr>
  <button @click='change'>修改</button>
</template>
<script setup lang="ts">
import {customRef} from "vue";

// 适合做防抖,连续点击500毫秒内触发一次
function MyRef<T>(value: T) {
  return customRef((track, trigger) => {
    let timer:any
    // 要返回一个对象
    return {
      get() {
        track()  // 收集依赖
        return value
      },
      set(newVal) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          console.log('触发了')
          value = newVal
          trigger()  // 触发依赖
        },500)
      }
    }
  })
}

const obj = MyRef<string>('浩然')
const change = () => {
  obj.value = '我可以被修改'
}
</script>

ref小妙招

image-20220915180556202

是个这玩意 查看起来很不方便 Vue 已经想到 了 帮我们做了格式化

image-20220915180707594

image-20220915180602929

image-20220915180607833

image-20220915180611410

此时观看打印的值就很明了
想要查看原来的对象形式,可以右键Ref<"小满">,选择 show as JavaScript object

使用ref获取dom元素

<template>
  <div ref="dom">我是dom</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
const dom = ref()
console.log(dom.value.innerHTML)  // 在这里打印会undefined 因为还没有渲染dom结构
const change = () => {
  console.log(dom.value.innerHTML)  // 我是dom
}
</script>

Reactive 全家桶

reactive

用来绑定复杂的数据,例如:数组、对象
reactive 源码约束了我们的类型

image-20220915180617916

他是不可以绑定普通的数据类型这样是不允许 会给我们报错

import { reactive} from 'vue'
 
let person = reactive('sad')

image-20220915180621714

要绑定普通的数据类型
可以使用ref
如果使用ref来绑定数组或者对象等复杂数据类型,从源码中可以看出也是去调用reactive
使用reactive修改值无需.value
reatcive 基础用法

import { reactive } from 'vue'
let person = reactive({
   name:"小满"
})
person.name = "大满"

数组异步赋值问题
不能给reactive直接覆盖复制,因为reactive是Proxy代理
这样赋值页面是不会变化的因为会脱离响应式

let person = reactive<number[]>([])
setTimeout(() => {
  person = [1, 2, 3]
  console.log(person);
  
},1000)

解决方案1
使用push

import { reactive } from 'vue'
let person = reactive<number[]>([])
setTimeout(() => {
  const arr = [1, 2, 3]
  person.push(...arr)
  console.log(person);
  
},1000)

方案2
包裹一层对象

type Person = {
  list?:Array<number>
}
let person = reactive<Person>({
   list:[]
})
setTimeout(() => {
  const arr = [1, 2, 3]
  person.list = arr;
  console.log(person);
  
},1000)

readonly

拷贝一份proxy对象将其设置为只读

<template>
  <div>
      <button @click.prevent="show">查看</button>
  </div>
</template>
<script setup lang="ts">
import {ref, reactive, readonly} from "vue";
let obj = reactive({
  name: '浩然'
})
const read = readonly(obj)

const show = () => {
  read.name = 'test' // 直接对read进行修改会报错
  console.log(read)
}
</script>

image-20220915180632396


如果更改obj 那么readonly会受影响

const show = () => {
  obj.name = 'test'
  console.log(read)
}

image-20220915180736011

shallowReactive

<template>
  <div>
    <div>{{ obj2 }}</div>
      <button @click.prevent="edit">修改</button>
  </div>
</template>
<script setup lang="ts">
import { reactive, shallowReactive } from "vue";
let obj = reactive({name: '浩然'})

const obj2:any = shallowReactive({
  foo: {
    bar: {
      num : 1
    }
  }
})
const edit = () => {
  // obj2.foo.bar.num = 2 // 页面数据不会发生改变 非响应式
  obj2.foo = {name: '浩然'}  // 页面数据发生改变 响应式数据
  console.log(obj2)
}
</script>

shallowReactive 与 shallowRef一样,不能同reactive同时写

<template>
  <div>
    <div>obj: {{obj}}</div>  
    <div>obj2: {{ obj2 }}</div>
    <button @click.prevent="edit">修改</button>
  </div>
</template>
<script setup lang="ts">
import {reactive, shallowReactive} from "vue";

let obj = reactive({name: '浩然'})

const obj2: any = shallowReactive({
  foo: {
    bar: {
      num: 1
    }
  }
})

const edit = () => {
  obj.name = 'test' // 如果这个数据在页面渲染了 那么shallowReactive会收到影响
  obj2.foo.bar.num = 2 // 页面数据不会发生改变 非响应式
  console.log(obj2)
}
</script>

to系列全家桶

toRef

非响应式数据使用

只能修改响应式对象的值 非响应式数据的视图毫无变化(非响应式数据使用toRef没有任何作用)

<template>
  <div>
    {{ hao }}
  </div>
  <hr>
  <div>toRef: {{like}}</div>
  <hr>
  <button @click='change'>修改</button>
</template>
<script setup lang="ts">
import {reactive, toRef, toRefs, toRaw} from "vue";

const hao = {name: '浩然', age: 19, like: 'zjl'}
const like = toRef(hao,'like')
const change = () => {
  like.value = 'eat'
  console.log(like) // eat
}
</script>

image-20220915180744816

响应式数据使用

// 改为响应式数据
const hao = reactive({name: '浩然', age: 19, like: 'zjl'})

image-20220915180747293


用途:单独提取reactive对象中的一个属性进行操作、传递

toRefs

结构响应式数据对象,对每个属性进行toRef提取(结构响应式数据)

<template>
  <div>
    {{ hao }}
  </div>
  <hr>
  <div>toRefs_name: {{name}}</div>
  <hr>
  <div>toRefs_age: {{age}}</div>
  <hr>
  <div>toRefs_like: {{like}}</div>
  <hr>
  <button @click='change'>修改</button>
</template>
<script setup lang="ts">
import {reactive,toRefs, toRef, toRaw} from "vue";
const hao = reactive({name: '浩然', age: 19, like: 'zjl'})
const {name, age, like} = toRefs(hao) // 结构hao
const change = () => {
  like.value = 'JK'
  console.log(name)
  console.log(age)
  console.log(like)
}
</script>

image-20220915180752106

手写源码

<script setup lang="ts">
import {reactive, toRef, toRaw} from "vue";

const hao = reactive({name: '浩然', age: 19, like: 'zjl'})
const toRefs = <T extends object>(object:T) => {
  const map:any = {}
  for (let key in object) {
    map[key] = toRef(object, key)
  }
  return map
}
const {name, age, like} = toRefs(hao)
const change = () => {
  like.value = 'JK'
  console.log(name)
  console.log(age)
  console.log(like)
}
</script>

image-20220915180758264

toRaw

当你不想这个数据作为响应式,可以使用toRaw

<script setup lang="ts">
import {reactive,toRefs, toRef, toRaw} from "vue";
const hao = reactive({name: '浩然', age: 19, like: 'zjl'})
const change = () => {
  // 打印响应式数据hao和使用toRaw的响应式数据
  console.log(hao, toRaw(hao))
}
</script>

image-20220915180802101

另一种实现方式,与toRaw效果相同

<script setup lang="ts">
import {reactive} from "vue";
const hao = reactive({name: '浩然', age: 19, like: 'zjl'})
const change = () => {
  console.log(hao, hao['__v_raw'])
}
</script>

computed计算属性

计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。

写法一:

回调函数的方法

<template>
  <input v-model="firstname">
  <input v-model="lastname">
  <div>{{name}}</div>
</template>
<script setup lang="ts">
import {computed, ref} from "vue";
let firstname = ref('')
let lastname  = ref('')

// 当firstname、lastname 发生变化会触发computed
const name = computed(() => {
  return firstname.value + '---' + lastname.value
})
</script>

写法二:

对象的方法

const name = computed({
  get() {
    return firstname.value + '---' + lastname.value
  },
  set() {
    // firstname.value + '---' + lastname.value
  }
})
</script>

watch侦听器

watch第一个参数监听源
watch第二个参数回调函数cb(newVal,oldVal)
watch第三个参数一个options配置项是一个对象{
immediate:true //是否立即调用一次
deep:true //是否开启深度监听
}

<template>
  <input v-model="message" type="text">
</template>
<script setup lang="ts">
import {ref, watch} from "vue";
let message = ref<string>('')
watch(message, (value, oldValue) => {
  console.log(value)
  console.log(oldValue)
  console.log('-------------')
})
</script>

image-20220915180814016

侦听多个值

<template>
  <input v-model="message" type="text">
  <input v-model="message2" type="text">
</template>
<script setup lang="ts">
import {ref, watch} from "vue";
let message = ref<string>('')
let message2 = ref<string>('')
watch([message,message2], (value, oldValue) => {
  console.log('new',value)
  console.log('old',oldValue)
  console.log('-------------')
})
</script>

image-20220915180819469

深度侦听

ref数据是无法侦听到深层数据的 ,reactive可以侦听深层数据

let message = ref<object>({
  nav: {
    bar: {
      name: '浩然'
    }
  }
})

当值有多层时,watch侦听就会失效,需要我们手动开启深度侦听

watch(message, (value, oldValue) => {
  console.log('new',value)
  console.log('old',oldValue)
  console.log('-------------')
},{
  deep: true
})

image-20220915180824771 :::tips 但是也会出现一个bug,newVal和oldVal相同 ::: watch默认不会去执行,加上immediate: true会默认执行

侦听reactive单一值

<template>
  <input v-model="message.name1" type="text">
  <input v-model="message.name2" type="text">
</template>
<script setup lang="ts">
import {reactive,  watch} from "vue";
let message = reactive<object>({
  name1: 'haoran', // 监听这个值
  name2: '浩然'
})
watch(()=>message.name1, (value, oldValue) => {
  console.log('new',value)
  console.log('old',oldValue)
  console.log('-------------')
})

watchEffect 高级侦听器

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次

<template>
  <input v-model="message" type="text">
  <input v-model="message2" type="text">
</template>
<script setup lang="ts">
import { ref, watchEffect } from "vue";
let message = ref('飞机')
let message2 = ref('大大的飞机')
watchEffect(() => {
  console.log('message====>',message.value)
})
</script>

清除副作用

就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖

watchEffect((onCleanup) => {
  console.log('message====>',message.value)
  onCleanup(() => {
    console.log('清除副作用') 
  })
})

image-20220915180910566

停止侦听器

const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

更多的配置项和调试

副作用刷新时机 flush 一般使用post

​ {

​ flush: 'post'

​ }

presyncpost
更新时机组件更新前执行强制效果始终同步触发组件更新后执行
watchEffect(() => {}, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

组件

每一个.vue 文件呢都可以充当组件来使用
每一个组件都可以复用

image-20220915180957772

引入组件

<template>
  <hello-world></hello-world>
</template>
<script setup lang="ts">
  import HelloWorld from './components/HelloWorld.vue' // 组件的地址

</script>

项目文件结构image-20220915181007500

组件的生命周期

简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期
在我们使用Vue3 组合式API 是没有 beforeCreate 和 created 这两个生命周期的

image-20220915181018748

onBeforeMount()
在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。

onMounted()
在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问

onBeforeUpdate()
数据更新时调用,发生在虚拟 DOM 打补丁之前。

updated()
DOM更新后,updated的方法即会调用。

onBeforeUnmount()
在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。

onUnmounted()
卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
[

<template>
  <hello-world v-if="flag"></hello-world>
  <br>
  <button @click="flag = !flag">销毁和启用helloWord组件</button>
</template>
<script setup lang="ts">
import {ref} from "vue";
import HelloWorld from './components/HelloWorld.vue'
const flag = ref(true)
</script>
<template>
  <h1>{{val}}</h1>
  <button @click="change">更改数据</button>
</template>
<script setup lang="ts">
import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from "vue";
const val = ref('helloWord组件')
console.log('setup')
// onBeforeMount 挂载之前 获取不到dom
onBeforeMount(() => {
  console.log('挂载之前》》'+document.querySelector('h1')) // null
  console.log('onBeforeMount 挂载之前')
  console.log(' ')
})
// onMounted 挂载完成
onMounted(() => {
  console.log('挂载之后》》'+document.querySelector('h1')) // null
  console.log('onMounted 挂载完成')
  console.log(' ')
})
// onBeforeUpdate 数据更新前
onBeforeUpdate(() => {
  console.log('onBeforeUpdate 数据更新前')
  console.log(' ')
})
// onUpdated 数据更新完成
onUpdated(() => {
  console.log('onUpdated 数据更新完成')
  console.log(' ')
})
// onBeforeUnmount 组件卸载前
onBeforeUnmount(() => {
  console.log('onBeforeUnmount 组件卸载前')
  console.log(' ')
})
// onUnmounted 组件完成
onUnmounted(() => {
  console.log('onUnmounted 组件卸载完成')
  console.log(' ')
})
const change = () => {
  val.value = '我被修改了'
}
</script>

<style scoped>
</style>

初始

image-20220915181136764

更改数据

image-20220915181143529

销毁组件

image-20220915181151352

实操组件和认识less和scoped

less概览

什么是less?

Less (Leaner Style Sheets 的缩写) 是一门向后兼容的 CSS 扩展语言。这里呈现的是 Less 的官方文档(中文版),包含了 Less 语言以及利用 JavaScript 开发的用于将 Less 样式转换成 CSS 样式的 Less.js 工具。
因为 Less 和 CSS 非常像,因此很容易学习。而且 Less 仅对 CSS 语言增加了少许方便的扩展,这就是 Less 如此易学的原因之一。

官方文档 Less 快速入门 | Less.js 中文文档 - Less 中文网

在vite中使用less
npm install less -D 安装即可

在style标签注明即可

<style lang="less">
 
</style>

& 父级拼接

.layout {
  height: 100%;
  overflow: hidden;
  display: flex;
  /* .layout_right */
  &_right {  
    background: red;
  }
}

什么是scoped

实现组件的私有化, 当前style属性只属于当前模块.
在DOM结构中可以发现,vue通过在DOM结构以及css样式上加了唯一标记,达到样式私有化,不污染全局的作用,

image-20220915181212500

实操组件(自适应)

项目文件目录

image-20220915181216573

/*这里存放全局样式*/
*{
  padding: 0;
  margin: 0;
}
html,body,#app {
  height: 100%;
  overflow: hidden;
}
<template>
  <Layout></Layout>
</template>
<script setup lang="ts">
import Layout from './layout/index.vue'
</script>
<style lang="less" scoped>

</style>
<template>
  <div class="layout">
      <Menu></Menu>
    <div class="layout_right">
      <Header></Header>
      <Content></Content>
    </div>
  </div>
</template>

<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
</script>

<style lang="less" scoped>
.layout {
  height: 100%;
  overflow: hidden;
  display: flex;
  &_right {
    flex: 1;
    display: flex;
    flex-direction: column;
  }
}
</style>
<template>
<div class="menu">
  menu
</div>
</template>

<script setup lang="ts">

</script>

<style lang="less" scoped>
.menu {
  width: 200px;
  border-right: 1px solid black;
}
</style>
<template>
  <div class="header">
    header
  </div>
</template>

<script setup lang="ts">

</script>

<style lang="less" scoped>
.header {
  height: 60px;
  border-bottom: 1px solid black;
}
</style>
<template>
  <div class="content">
    <div class="content_items" :key="item" v-for="item in 100">
      {{item}}
    </div>
  </div>
</template>

<script setup lang="ts">

</script>

<style lang="less" scoped>
.content {
  flex: 1;
  margin: 20px;
  border: 1px solid black;
  overflow: auto;
  &_items {
    margin: 5px;
    padding: 10px;
    border: 1px solid #ccc;

  }
}
</style>

最终效果

image-20220915181236931

父子组件传参

父传子

父组件通过v-bind绑定一个数据,然后子组件通过defineProps接受传过来的值,
如以下代码
给Menu组件 传递了一个title 字符串类型是不需要v-bind

<template>
    <div class="layout">
        <Menu v-bind:data="data"  title="我是标题"></Menu>
        <div class="layout-right">
            <Header></Header>
            <Content></Content>
        </div>
    </div>
</template>
 
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
 
const data = reactive<number[]>([1, 2, 3])
</script>

子组件接受值
通过defineProps 来接受 defineProps是无须引入的直接使用即可
如果我们使用的TypeScript
可以使用传递字面量类型的纯类型语法做为参数
如 这是TS特有的

<template>
    <div class="menu">
        菜单区域 {{ title }}
        <div>{{ data }}</div>
    </div>
</template>
 
<script setup lang="ts">
defineProps<{
    title:string,
    data:number[]
}>()
</script>

如果你使用的不是TS

defineProps({
    title:{
        default:"",
        type:string
    },
    data:Array
})

TS 特有的默认值方式
withDefaults是个函数也是无须引入开箱即用接受一个props函数第二个参数是一个对象设置默认值

type Props = {
    title?: string,
    data?: number[]
}
withDefaults(defineProps<Props>(), {
    title: "张三",
    data: () => [1, 2, 3]
})

子传父

子组件给父组件传参
是通过defineEmits派发一个事件

<template>
    <div class="menu">
        <button @click="clickTap">派发给父组件</button>
    </div>
</template>
 
<script setup lang="ts">
import { reactive } from 'vue'
const list = reactive<number[]>([4, 5, 6])
 
const emit = defineEmits(['on-click'])
const clickTap = () => {
    emit('on-click', list)
}
</script>

我们在子组件绑定了一个click 事件 然后通过defineEmits 注册了一个自定义事件
点击click 触发 emit 去调用我们注册的事件 然后传递参数
父组件接受子组件的事件

<template>
    <div class="layout">
        <Menu @on-click="getList"></Menu>
        <div class="layout-right">
            <Header></Header>
            <Content></Content>
        </div>
    </div>
</template>
 
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
 
const data = reactive<number[]>([1, 2, 3])
 
const getList = (list: number[]) => {
    console.log(list,'父组件接受子组件');
}
</script>

我们从Menu 组件接受子组件派发的事件on-click 后面是我们自己定义的函数名称getList
会把参数返回过来

父获取子组件内部属性

子组件暴露给父组件内部属性
通过defineExpose
我们从父组件获取子组件实例通过ref

<Menu ref="menus"></Menu>

<script setup lang="ts">
  const menus = ref(null)
</script>

这时候打印menus.value 可以发现没有任何属性
如果父组件想要读到子组件的属性可以通过 defineExpose暴露

const list = reactive<number[]>([4, 5, 6])
 
defineExpose({
    list
})

配置全局组件

例如组件使用频率非常高(table,Input,button,等)这些组件 几乎每个页面都在使用便可以封装成全局组件
案例------我这儿封装一个Card组件想在任何地方去使用
组件目录

image-20220915181250822

<template>
  <div class="card">
    <div class="card-header">
      <div>主标题</div>
      <div>副标题</div>
    </div>
    <div class="card-content" v-if="content">
      {{content}}
    </div>
  </div>
</template>

<script setup lang="ts">
defineProps<{
  content?:string
}>()
</script>

<style scoped lang="less">
@border:1px solid #ccc;
.card {
  border: @border;
  cursor: pointer;
  &:hover {
    box-shadow: 0 0 2px 3px #ccc;
  }
  &-header {
    display: flex;
    justify-content: space-between;
    padding: 10px;
    border-bottom: @border;
  }
  &-content {
    padding: 10px;
  }
}
</style>

全局注册
在main.ts 引入我们的组件跟随在createApp(App) 后面 切记不能放到mount 后面这是一个链式调用用
其次调用 component 第一个参数组件名称 第二个参数组件实例

import {createApp} from 'vue'
import './style.css'
import App from './App.vue'
import Card from './components/Card/index.vue'

createApp(App).component('Card', Card).mount('#app')

使用方法
直接在其他vue页面 立即使用即可 无需引入

<template>
 <Card></Card>
</template>

递归组件

<template>
<div class="menu">
  menu
  <Tree @on-item="getItem" :data="data"></Tree>
</div>
</template>

<script setup lang="ts">
import {reactive} from "vue";
import Tree from '../../components/Tree/index.vue'
type TreeList = {
  name: string,
  icon?: string,
  children?: TreeList[] | []
}
const data = reactive<TreeList[]>([
  {
    name: 'no.1',
    children: [
      {
        name: 'no.1-1',
        children: [
          {
            name: 'no.1-1-1',
          }
        ]
      }
    ]
  },
  {
    name: 'no.2',
    children: [
      {
        name: 'no.2-1',
      }
    ]
  },
  {
    name: 'no.3',
  },
  {
    name: 'test'
  }
])

const getItem = (item:TreeList) => {
  console.log(item)
}
</script>

<style lang="less" scoped>
.menu {
  width: 200px;
  border-right: 1px solid black;
}
</style>
<template>
  <div>
    <div @click.stop="clickItem(item)" style="margin-left:16px " :key="index" v-for="(item,index) in data">
      {{item.name}}
      <TreeItem @on-item="clickItem" v-if="item?.children?.length" :data="item.children"></TreeItem>
    </div>
  </div>
</template>

<script setup lang="ts">
import TreeItem from './index.vue'
type TreeList = {
  name: string,
  icon?: string,
  children?: TreeList[] | []
}
defineProps<{
  data?: TreeList[]
}>()
const emit = defineEmits(['on-item'])
const clickItem = (item:TreeList) => {
  // console.log(item)
  emit('on-item',item)
}
</script>

<style scoped lang="less">

</style>

image-20220915181313074

<TreeItem @on-item="clickItem" v-if="item?.children?.length" :data="item.children">
不写问号获取不存在的数据length 会报错

image-20220915181317863

动态组件实现tab栏切换

什么是动态组件 就是:让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
在挂载点使用component标签,然后使用v-bind:is=”组件”
用法如下
引入组件

import A from './A.vue'
import B from './B.vue'
  <component :is="A"></component>
<template>
  <div class="content">
    <div class="tab">
      <div @click="tabCom(item)"  v-for="(item,i) in data" :key="i">
        {{item.name}}
      </div>
      <component :is="current.comName"></component>
    </div>
  </div>
</template>

<script setup lang="ts">
import A from './A.vue'
import B from './B.vue'
import C from './C.vue'
import {reactive, markRaw} from "vue";

type Tabs = {
  name: string,
  comName: any
}
type Com = Pick<Tabs,'comName'>
const data = reactive<Tabs[]>([
  {
    name: '我是A组件',
    comName: markRaw(A)
  },
  {
    name: '我是B组件',
    comName: markRaw(B)
  },
  {
    name: '我是C组件',
    comName: markRaw(C)
  },
])
let current = reactive<Com>({
  comName: data[0].comName
})
const tabCom = (item:Tabs) => {
  current.comName = item.comName
}
</script>
<style lang="less" scoped>
  .tab{
    display: flex;
    .active{
      background: skyblue;
      color: #fff;
    }
    div{
      padding: 5px 10px;
      border: 1px solid #ccc;
      margin: 6px 10px;
    }
  }

</style>
// A
<template>
  <div>AAAAAAAAAAA</div>
</template>
// B
<template>
  <div>B</div>
</template>
  // A
<template>
  <div>CCCCCCC</div>
</template>

通过is 切换 A B 组件

使用场景

tab切换 居多

注意事项

1.在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的

2.如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref.
Component that was made reactive:

这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处 节省性能开销 推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理

修改如下

const tab = reactive<Com[]>([
  {
    name: "A组件",
      comName: markRaw(A)
  }, {
    name: "B组件",
      comName: markRaw(B)
  }
])

markRaw:

import {reactive, markRaw} from "vue";

let obj = {name: 1}
console.log(markRaw(obj))

如果__v_skip: true 会跳过proxy代理
image-20220915181338359

插槽slot

插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。

匿名插槽

1.在子组件放置一个插槽

<template>
    <div>
       <slot></slot>
    </div>
</template>

2.父组件使用插槽
在父组件给这个插槽填充内容

<Dialog>
  <template v-slot>
  	<div>2132</div>
  </template>
</Dialog>

具名插槽

具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中

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

父组件使用需对应名称

<Dialog>
  <template v-slot:header>
		<div>1</div>
  </template>
  <template v-slot>
  	<div>2</div>
  </template>
  <template v-slot:footer>
  	<div>3</div>
  </template>
</Dialog>

插槽简写

        <Dialog>
            <template #header>
               <div>1</div>
           </template>
           <template #default>
               <div>2</div>
           </template>
           <template #footer>
               <div>3</div>
           </template>
        </Dialog>

作用域插槽

在子组件动态绑定参数 派发给父组件的slot去使用

    <div>
        <slot name="header"></slot>
        <div>
            <div v-for="item in 100">
                <slot :data="item"></slot>
            </div>
        </div>
 
        <slot name="footer"></slot>
    </div>

通过结构方式取值

<Dialog>
  <template #header>
  <div>1</div>
  </template>
  <template #default="{ data }">
  <div>{{ data }}</div>
  </template>
  <template #footer>
  <div>3</div>
  </template>
</Dialog>

动态插槽

插槽可以是一个变量名

        <Dialog>
            <template #[name]>
                <div>
                    23
                </div>
            </template>
        </Dialog>

const name = ref('header')

异步组件&代码分包&suspense

异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块 并且减少主包的体积
这时候就可以使用异步组件

顶层 await

在setup语法糖里面 使用方法

<script setup>
const post = await fetch(`/api/post/1`).then(r => r.json())
</script>

父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包

<script setup lang="ts">
import { reactive, ref, markRaw, toRaw, defineAsyncComponent } from 'vue'
 
const Dialog = defineAsyncComponent(() => import('../../components/Dialog/index.vue'))

suspense

组件有两个插槽。它们都只接收一个直接子节点。default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点。

     <Suspense>
            <template #default>
                <Dialog>
                    <template #default>
                        <div>我在哪儿</div>
                    </template>
                </Dialog>
            </template>
 
            <template #fallback>
                <div>loading...</div>
            </template>
        </Suspense>

Teleport传送组件&源码解析

Teleport Vue 3.0新特性之一。

Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。

主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响

使用方法

通过to 属性 插入指定元素位置 to="body" 便可以将Teleport 内容传送到指定位置

<Teleport to="body">
    <Loading></Loading>
</Teleport>

也可以自定义传送位置 支持 class id等 选择器

    <div id="app"></div>
    <div class="modal"></div>
<template>
 
    <div class="dialog">
        <header class="header">
            <div>我是弹框</div>
            <el-icon>
                <CloseBold />
            </el-icon>
        </header>
        <main class="main">
            我是内容12321321321
        </main>
        <footer class="footer">
            <el-button size="small">取消</el-button>
            <el-button size="small" type="primary">确定</el-button>
        </footer>
    </div>
 
</template>
 
<script setup lang='ts'>
import { ref, reactive } from 'vue'
 
</script>
<style lang="less" scoped>
.dialog {
    width: 400px;
    height: 400px;
    background: #141414;
    display: flex;
    flex-direction: column;
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -200px;
    margin-top: -200px;
 
    .header {
        display: flex;
        color: #CFD3DC;
        border-bottom: 1px solid #636466;
        padding: 10px;
        justify-content: space-between;
    }
 
    .main {
        flex: 1;
        color: #CFD3DC;
        padding: 10px;
    }
 
    .footer {
        border-top: 1px solid #636466;
        padding: 10px;
        display: flex;
        justify-content: flex-end;
    }
}
</style>

image-20220915181403944

多个使用场景

<Teleport to=".modal1">
     <Loading></Loading>
</Teleport>
<Teleport to=".modal2">
     <Loading></Loading>
</Teleport>

动态控制teleport

使用disabled 设置为 true则 to属性不生效 false 则生效

    <teleport :disabled="true" to='body'>
      <A></A>
    </teleport>

源码解析

在创建teleport 组件的时候会经过patch 方法 然后调用teleport 的process 方法

image-20220915181409447

主要是创建 更新 和删除的逻辑

image-20220915181415423

他通过 resolveTarget 函数 获取了props.to 和 querySelect 获取 了目标元素
然后判断是否有disabled 如果有则 to 属性不生效 否则 挂载新的位置

image-20220915181418915

新节点disabled 为 true 旧节点disabled false 就把子节点移动回容器
如果新节点disabled 为 false 旧节点为true 就把子节点移动到目标元素

image-20220915181424853

遍历teleport 子节点进行unmount方法去移除

image-20220915181428162

keep-alive缓存组件

内置组件keep-alive
有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。

开启keep-alive 生命周期的变化

  • 初次进入时: onMounted> onActivated
  • 退出后触发 onDeactivated
  • 再次进入:
  • 只会触发 onActivated
  • 事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中
<!-- 基本 -->
<keep-alive>
  <component :is="view"></component>
</keep-alive>
 
<!-- 多个条件判断的子组件 -->
<keep-alive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</keep-alive>
 
<!-- 和 `<transition>` 一起使用 -->
<transition>
  <keep-alive>
    <component :is="view"></component>
  </keep-alive>
</transition>

include 和 exclude

 <keep-alive :include="" :exclude="" :max=""></keep-alive>

include 和 exclude 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
max

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

transition动画组件

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

自定义 transition 过度效果,你需要对transition组件的name属性自定义。并在css中写入对应的样式

1.过渡的类名

在进入/离开的过渡中,会有 6 个 class 切换。

  1. #过渡 class
  2. 在进入/离开的过渡中,会有 6 个 class 切换。
  3. v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  4. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  5. v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
  6. v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  7. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  8. v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。

如下

       <button @click='flag = !flag'>切换</button>
       <transition name='fade'>
         <div v-if='flag' class="box"></div>
       </transition>
//开始过度
.fade-enter-from{
   background:red;
   width:0px;
   height:0px;
   transform:rotate(360deg)
}
//开始过度了
.fade-enter-active{
  transition: all 2.5s linear;
}
//过度完成
.fade-enter-to{
   background:yellow;
   width:200px;
   height:200px;
}
//离开的过度
.fade-leave-from{
  width:200px;
  height:200px;
  transform:rotate(360deg)
}
//离开中过度
.fade-leave-active{
  transition: all 1s linear;
}
//离开完成
.fade-leave-to{
  width:0px;
  height:0px;
}

2.自定义过渡 class 类名

trasnsition props

  • enter-from-class=“class”
  • enter-active-class=“class”
  • enter-to-class=“class”
  • leave-from-class=“class”
  • leave-active-class=“class”
  • leave-to-class=“class”
<transition enter-from-class="e-from" leave-from-class="l-from" name="fade">
  <div v-if="flag" class="box"></div>
</transition>

自定义过度时间 单位毫秒
你也可以分别指定进入和离开的持续时间:

// 自定义过度时间 单位毫秒
<transition :duration="1000">...</transition>
 
 // 分别指定进入和离开的持续时间
<transition :duration="{ enter: 500, leave: 800 }">...</transition>

通过自定义class 结合css动画库animate css
安装库 npm install animate.css
引入 import 'animate.css'
使用方法
官方文档 Animate.css | A cross-browser library of CSS animations.

        <transition
            leave-active-class="animate__animated animate__bounceInLeft"
            enter-active-class="animate__animated animate__bounceInRight"
        >
            <div v-if="flag" class="box"></div>
        </transition>

3.transition 生命周期8个

  @before-enter="beforeEnter" //对应enter-from
  @enter="enter"//对应enter-active
  @after-enter="afterEnter"//对应enter-to
  @enter-cancelled="enterCancelled"//显示过度打断
  @before-leave="beforeLeave"//对应leave-from
  @leave="leave"//对应enter-active
  @after-leave="afterLeave"//对应leave-to
  @leave-cancelled="leaveCancelled"//离开过度打断

当只用 JavaScript 过渡的时候,在 enter 和 leave 钩子中必须使用 done 进行回调
结合gsap 动画库使用 GreenSock

const beforeEnter = (el: Element) => {
    console.log('进入之前from', el);
}
const Enter = (el: Element,done:Function) => {
    console.log('过度曲线');
    setTimeout(()=>{
       done()
    },3000)
}
const AfterEnter = (el: Element) => {
    console.log('to');
}

4.appear

通过这个属性可以设置初始节点过度 就是页面加载完成就开始动画 对应三个状态

appear-active-class=""
appear-from-class=""
appear-to-class=""
appear

transition-group过度列表

列表的添加删除

  • 单个节点
  • 多个节点,每次只渲染一个

那么怎么同时渲染整个列表,比如使用 v-for?在这种场景下,我们会使用 组件。在我们深入例子之前,先了解关于这个组件的几个特点:

  • 默认情况下,它不会渲染一个包裹元素,但是你可以通过 tag attribute 指定渲染一个元素。
  • 过渡模式不可用,因为我们不再相互切换特有的元素。
  • 内部元素总是需要提供唯一的 key attribute 值。
  • CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
<template>
  <div class="content">
    <el-button @click="add">add</el-button>
    <el-button @click="pop">pop</el-button>
    <div class="wraps">

      <transition-group
          enter-active-class="animate__animated animate__rollIn"
          leave-active-class="animate__animated animate__rollOut">
        <div class="item" :key="item" v-for="item in list">{{item}}</div>
      </transition-group>
    </div>
  </div>
</template>

<script setup lang="ts">
import "animate.css"
import {reactive, ref} from "vue";
const list = reactive<number[]>([1,2,3,4,5])
const add = () => {
  list.push(list.length + 1)
}
const pop = () => {
  list.pop()
}
</script>
<style lang="less" scoped>
.content {
  button {
    padding: 8px 14px;
    margin-left: 10px;
  }
}
.wraps {
  display: flex;
  flex-wrap: wrap;
  word-break: break-all;
  border: 1px solid black;
  .item {
    margin: 20px;
    font-size: 40px;
  }
}

</style>

依赖注入(Provide&Inject)

Provide&Inject

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

官网的解释很让人疑惑,那我翻译下这几句话:

provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。

image-20220915181504047

父组件传递数据

<template>
    <div class="App">
        <button>我是App</button>
        <A></A>
    </div>
</template>
    
<script setup lang='ts'>
import { provide, ref } from 'vue'
import A from './components/A.vue'
let flag = ref<number>(1)
provide('flag', flag)
</script>
    
<style>
.App {
    background: blue;
    color: #fff;
}
</style>

子组件接受

<template>
    <div style="background-color: green;">
        我是B
        <button @click="change">change falg</button>
        <div>{{ flag }}</div>
    </div>
</template>
    
<script setup lang='ts'>
import { inject, Ref, ref } from 'vue'
 
const flag = inject<Ref<number>>('flag', ref(1))
 
const change = () => {
    flag.value = 2
}
</script>
    
<style>
</style>

TIPS 你如果传递普通的值 是不具有响应式的 需要通过ref reactive 添加响应式
使用场景
当父组件有很多数据需要分发给其子代组件的时候, 就可以使用provide和inject。

兄弟组件传参和Bus

1.借助父组件传参

例如父组件为App 子组件为A 和 B他两个是同级的

<template>
    <div>
        <A @on-click="getFalg"></A>
        <B :flag="Flag"></B>
    </div>
</template>
    
<script setup lang='ts'>
import A from './components/A.vue'
import B from './components/B.vue'
import { ref } from 'vue'
let Flag = ref<boolean>(false)
const getFalg = (flag: boolean) => {
   Flag.value = flag;
}
</script>
    
<style>
</style>

A 组件派发事件通过App.vue 接受A组件派发的事件然后在Props 传给B组件 也是可以实现的
缺点就是比较麻烦 ,无法直接通信,只能充当桥梁

2.Event Bus

我们在Vue2 可以使用emit传递emit 传递 on监听 emit传递过来的事件
这个原理其实是运用了JS设计模式之发布订阅模式
我写了一个简易版

 
type BusClass<T> = {
    emit: (name: T) => void
    on: (name: T, callback: Function) => void
}
type BusParams = string | number | symbol 
type List = {
    [key: BusParams]: Array<Function>
}
class Bus<T extends BusParams> implements BusClass<T> {
    list: List
    constructor() {
        this.list = {}
    }
    emit(name: T, ...args: Array<any>) {
        let eventName: Array<Function> = this.list[name]
        eventName.forEach(ev => {
            ev.apply(this, args)
        })
    }
    on(name: T, callback: Function) {
        let fn: Array<Function> = this.list[name] || [];
        fn.push(callback)
        this.list[name] = fn
    }
}
 
export default new Bus<number>()

然后挂载到Vue config 全局就可以使用啦

Mitt

在vue3中onon,off 和 $once 实例方法已被移除,组件实例不再实现事件触发接口,因此大家熟悉的EventBus便无法使用了。然而我们习惯了使用EventBus,对于这种情况我们可以使用Mitt库(其实就是我们视频中讲的发布订阅模式的设计)

安装:

:::tips npm install mitt -S :::

main.ts 初始化

import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt'
 
const Mit = mitt()
 
//TypeScript注册
// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
    export interface ComponentCustomProperties {
        $Bus: typeof Mit
    }
}
 
const app = createApp(App)
 
//Vue3挂载全局API
app.config.globalProperties.$Bus = Mit
 
app.mount('#app')

方法

使用方法通过emit派发, on 方法添加事件,off 方法移除,clear 清空所有
A组件派发(emit)

<template>
    <div>
        <h1>我是A</h1>
        <button @click="emit1">emit1</button>
        <button @click="emit2">emit2</button>
    </div>
</template>
 
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
const emit1 = () => {
    instance?.proxy?.$Bus.emit('on-num', 100)
}
const emit2 = () => {
    instance?.proxy?.$Bus.emit('*****', 500)
}
</script>
 
<style>
</style>

B组件监听(on)

<template>
    <div>
        <h1>我是B</h1>
    </div>
</template>
 
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
instance?.proxy?.$Bus.on('on-num', (num) => {
    console.log(num,'===========>B')
})
</script>
 
<style>
</style>

监听所有事件( on("*") )

instance?.proxy?.$Bus.on('*',(type,num)=>{
    console.log(type,num,'===========>B')
})

移除监听事件(off)

const Fn = (num: any) => {
    console.log(num, '===========>B')
}
instance?.proxy?.$Bus.on('on-num',Fn)//listen
instance?.proxy?.$Bus.off('on-num',Fn)//unListen

清空所有监听(clear)

instance?.proxy?.$Bus.all.clear() 

TSX

我们之前呢是使用Template去写我们模板。现在可以扩展另一种风格TSX风格
vue2 的时候就已经支持jsx写法,只不过不是很友好,随着vue3对typescript的支持度,tsx写法越来越被接受

安装插件

npm install @vitejs/plugin-vue-jsx -D
vite.config.ts 配置

image-20220915181520647

配置完成就可以使用啦
在目录新建一个xxxxxx.tsx文件

使用TSX

tsx支持 v-model 的使用

 
import { ref } from 'vue'
 
let v = ref<string>('')
 
const renderDom = () => {
    return (
        <>
           <input v-model={v.value} type="text" />
           <div>
               {v.value}
           </div>
        </>
    )
}
 
export default renderDom

v-show

 
import { ref } from 'vue'
 
let flag = ref(false)
 
const renderDom = () => {
    return (
        <>
           <div v-show={flag.value}>景天</div>
           <div v-show={!flag.value}>雪见</div>
        </>
    )
}
 
export default renderDom	

v-if是不支持的

需要改变风格

import { ref } from 'vue'
 
let flag = ref(false)
 
const renderDom = () => {
    return (
        <>
            {
                flag.value ? <div>景天</div> : <div>雪见</div>
            }
        </>
    )
}
 
export default renderDom

v-for也是不支持的

需要使用Map

import { ref } from 'vue'
 
let arr = [1,2,3,4,5]
 
const renderDom = () => {
    return (
        <>
            {
              arr.map(v=>{
                  return <div>${v}</div>
              })
            }
        </>
    )
}
 
export default renderDom

v-bind使用

直接赋值就可以

import { ref } from 'vue'
 
let arr = [1, 2, 3, 4, 5]
 
const renderDom = () => {
    return (
        <>
            <div data-arr={arr}>1</div>
        </>
    )
}
 
export default renderDom

v-on绑定事件 所有的事件都按照react风格来

  • 所有事件有on开头
  • 所有事件名称首字母大写
 
const renderDom = () => {
    return (
        <>
            <button onClick={clickTap}>点击</button>
        </>
    )
}
 
const clickTap = () => {
    console.log('click');
}
 
export default renderDom

Props 接受值

import { ref } from 'vue'
 
type Props = {
    title:string
}
 
const renderDom = (props:Props) => {
    return (
        <>
            <div>{props.title}</div>
            <button onClick={clickTap}>点击</button>
        </>
    )
}
 
const clickTap = () => {
    console.log('click');
}
 
export default renderDom

Emit派发

type Props = {
    title: string
}
 
const renderDom = (props: Props,content:any) => {
    return (
        <>
            <div>{props.title}</div>
            <button onClick={clickTap.bind(this,content)}>点击</button>
        </>
    )
}
 
const clickTap = (ctx:any) => {
 
    ctx.emit('on-click',1)
}

深入v-model

v-model

TIps 在Vue3 v-model 是破坏性更新的 v-model在组件里面也是很重要的

v-model 其实是一个语法糖 通过props 和 emit组合而成的

1.默认值的改变

  • prop:value -> modelValue;
  • 事件:input -> update:modelValue;
  • v-bind 的 .sync 修饰符和组件的 model 选项已移除
  • 新增 支持多个v-model
  • 新增 支持自定义 修饰符

案例 子组件

<template>
     <div v-if='propData.modelValue ' class="dialog">
         <div class="dialog-header">
             <div>标题</div><div @click="close">x</div>
         </div>
         <div class="dialog-content">
            内容
         </div>
         
     </div>
</template>
 
<script setup lang='ts'>
 
type Props = {
   modelValue:boolean
}
 
const propData = defineProps<Props>()
 
const emit = defineEmits(['update:modelValue'])
 
const close = () => {
     emit('update:modelValue',false)
}
 
</script>
 
<style lang='less'>
.dialog{
    width: 300px;
    height: 300px;
    border: 1px solid #ccc;
    position: fixed;
    left:50%;
    top:50%;
    transform: translate(-50%,-50%);
    &-header{
        border-bottom: 1px solid #ccc;
        display: flex;
        justify-content: space-between;
        padding: 10px;
    }
    &-content{
        padding: 10px;
    }
}
</style>		

父组件

<template>
  <button @click="show = !show">开关{{show}}</button>
  <Dialog v-model="show"></Dialog>
</template>
 
<script setup lang='ts'>
import Dialog from "./components/Dialog/index.vue";
import {ref} from 'vue'
const show = ref(false)
</script>
 
<style>
</style>

绑定多个案例

子组件

<template>
     <div v-if='modelValue ' class="dialog">
         <div class="dialog-header">
             <div>标题---{{title}}</div><div @click="close">x</div>
         </div>
         <div class="dialog-content">
            内容
         </div>
         
     </div>
</template>
 
<script setup lang='ts'>
 
type Props = {
   modelValue:boolean,
   title:string
}
 
const propData = defineProps<Props>()
 
const emit = defineEmits(['update:modelValue','update:title'])
 
const close = () => {
     emit('update:modelValue',false)
     emit('update:title','我要改变')
}
 
</script>
 
<style lang='less'>
.dialog{
    width: 300px;
    height: 300px;
    border: 1px solid #ccc;
    position: fixed;
    left:50%;
    top:50%;
    transform: translate(-50%,-50%);
    &-header{
        border-bottom: 1px solid #ccc;
        display: flex;
        justify-content: space-between;
        padding: 10px;
    }
    &-content{
        padding: 10px;
    }
}
</style>

父组件

<template>
  <button @click="show = !show">开关{{show}} ----- {{title}}</button>
  <Dialog v-model:title='title' v-model="show"></Dialog>
</template>
 
<script setup lang='ts'>
import Dialog from "./components/Dialog/index.vue";
import {ref} from 'vue'
const show = ref(false)
const title = ref('我是标题')
</script>
 
<style>
</style>

自定义修饰符

添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件。在下面的示例中,我们创建了一个组件,其中包含默认为空对象的 modelModifiers prop

<script setup lang='ts'>
 
type Props = {
    modelValue: boolean,
    title?: string,
    modelModifiers?: {
        default: () => {}
    }
    titleModifiers?: {
        default: () => {}
    }
 
}
 
const propData = defineProps<Props>()
 
const emit = defineEmits(['update:modelValue', 'update:title'])
 
const close = () => {
    console.log(propData.modelModifiers);
 
    emit('update:modelValue', false)
    emit('update:title', '我要改变')
}

自定义指令directive

directive-自定义指令(属于破坏性更新)

Vue中有v-if,v-for,v-bind,v-show,v-model等等一系列方便快捷的指令 今天一起来了解一下vue里提供的自定义指令

1.Vue3指令的钩子函数

  • created 元素初始化的时候
  • beforeMount 指令绑定到元素后调用 只调用一次
  • mounted 元素插入父级dom调用
  • beforeUpdate 元素被更新之前调用
  • update 这个周期方法被移除 改用updated
  • beforeUnmount 在元素被移除前调用
  • unmounted 指令被移除后调用 只调用一次

Vue2 指令 bind inserted update componentUpdated unbind

2.在setup内定义局部指令

但这里有一个需要注意的限制:必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。

js代码

<script setup lang="ts">
import A from './components/A.vue'
import {Directive, DirectiveBinding , ref} from "vue";
const flag = ref<boolean>(true)
const vMove:Directive = {
  created() {
    console.log('created------')
  },
  beforeMount() {
    console.log('beforeMount-----')
  },
  mounted(el:HTMLElement, dir:DirectiveBinding) {
    console.log('mounted-----')
    el.style.background = dir.value.background
  },
  beforeUpdate(){
    console.log('beforeUpdate-----')
  },
  updated() {
    console.log('updated--------')
  },
  beforeUnmount() {
    console.log('beforeUnmount------')
  },
  unmounted() {
    console.log('unmounted-----')
  }
}

created------ beforeMount----- mounted-----

<template>
  <div class="App">
    <el-button>toggle</el-button>
    <A v-move="{background:'red'}"></A>
  </div>
</template>

beforeUnmount------ unmounted-----

<template>
  <div class="App">
    <el-button @click="flag = !flag">toggle</el-button>
    <A v-if="flag" v-move="{background:'red'}"></A>
  </div>
</template>

beforeUpdate----- updated--------

<template>
  <div class="App">
    <el-button @click="flag = !flag">toggle</el-button>
    <A v-move="{background:'red',flag: flag}"></A>
  </div>
</template>

3.生命周期钩子参数详解

第一个 el 当前绑定的DOM 元素

第二个 binding

  • instance:使用指令的组件实例。
  • value:传递给指令的值。例如,在 v-my-directive="1 + 1" 中,该值为 2。
  • oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。
  • arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 "foo"。
  • modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}。
  • dir:一个对象,在注册指令时作为参数传递。例如,在以下指令中

image-20220916084341850

第三个 当前元素的虚拟DOM 也就是Vnode

第四个 prevNode 上一个虚拟节点,仅在 beforeUpdateupdated 钩子中可用

4.函数简写

你可能想在 mountedupdated 时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现

<template>
  <div class="App">
    <input type="text" v-model="value">
    <A v-move="{background: value}"></A>
  </div>
</template>
<script setup lang="ts">
import A from './components/A.vue'
import {Directive, DirectiveBinding , ref} from "vue";
const value = ref<string>('')
const vMove:Directive = (el:HTMLElement,binding:DirectiveBinding) => {
  el.style.background = binding.value.background
}
</script>

image-20220916085046899

案例自定义拖拽指令

<template>
  <div v-move class="box">
    <div class="header"></div>
    <div>content</div>
  </div>
</template>
<script setup lang="ts">
import {Directive, DirectiveBinding} from "vue";

const vMove: Directive = (el: HTMLElement, binding: DirectiveBinding) => {
  let moveEl: HTMLElement = el.firstElementChild as HTMLDivElement
  const mouseDown = (e:MouseEvent) => {
    let X = e.clientX - el.offsetLeft
    let Y = e.clientY - el.offsetTop
    const Move = (e:MouseEvent) => {
      el.style.left = e.clientX - X + 'px'
      el.style.top = e.clientY - Y + 'px'
    }
    document.addEventListener('mousemove', Move)
    document.addEventListener('mouseup', () => {
      document.removeEventListener('mousemove', Move)
    })
  }
  moveEl.addEventListener('mousedown', mouseDown)
}
</script>
<style lang="less" scoped>
.box {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 200px;
  border: 1px solid #ccc;
  user-select: none;
  .header {
    height: 20px;
    background: black;
    cursor: move;
  }
}
</style>

自定义Hooks

Vue3 自定义Hook

主要用来处理复用代码逻辑的一些封装

这个在vue2 就已经有一个东西是Mixins

mixins就是将这些多个相同的逻辑抽离出来,各个组件只需要引入mixins,就能实现一次写代码,多组件受益的效果。

弊端就是 会涉及到覆盖的问题

组件的data、methods、filters会覆盖mixins里的同名data、methods、filters。 1

第二点就是 变量来源不明确(隐式传入),不利于阅读,使代码变得难以维护。

Vue3 的自定义的hook

Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数 Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数 Vue3 hook 库Get Started | VueUse

案例

import {onMounted} from "vue";

type Options = {
    el: string
}

export default function (options: Options):Promise<{baseUrl:string}> {
    return new Promise((resolve) => {
        onMounted(() => {
            let img: HTMLImageElement = document.querySelector(options.el) as HTMLImageElement
            console.log(img)
            img.onload = () => {
                resolve({
                    baseUrl: base64(img)
                })
            }
        })
        const base64 = (el: HTMLImageElement) => {
            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('2d')
            canvas.width = el.width
            canvas.height = el.height
            ctx?.drawImage(el, 0, 0, canvas.width, canvas.height)
            return canvas.toDataURL('image/png')
        }
    })

}

使用定义的hook

<template>
  <div>
    <img id="img" src="src/assets/vue.svg" alt="" srcset="">
    <A a="123" title="456"></A>
  </div>
</template>
<script setup lang="ts">
import A from './components/A.vue'
import userBase64 from './hooks'
userBase64({
  el: '#img'
}).then(data => {
  console.log(data.baseUrl)
})
</script>

定义全局函数和变量

globalProperties

由于Vue3 没有Prototype 属性 使用 app.config.globalProperties 代替 然后去定义变量和函数

Vue2

// 之前 (Vue 2.x)
Vue.prototype.$http = () => {}

Vue3

// 之后 (Vue 3.x)
const app = createApp({})
app.config.globalProperties.$http = () => {}

过滤器

在Vue3 移除了

我们正好可以使用全局函数代替Filters

案例: main.ts

type Filter = {
    format: <T>(str:T) => string
}
declare module '@vue/runtime-core' {
    export interface ComponentCustomProperties {
        $filters: Filter,
       	$uname: string
    }
}
const app = createApp(App)
app.config.globalProperties.$filters = {
    format<T>(str: T): string {
        return `真·${str}`
    }
}
app.config.globalProperties.$uname = '帅哥'
app.mount('#app')

app.vue

  <div>
    {{$filters.format('我是你爹')}}
    {{$uname}}
  </div>

image-20220916110122378

编写vue3插件

插件

插件是自包含的代码,通常向 Vue 添加全局级功能。你如果是一个对象需要有install方法Vue会帮你自动注入到install 方法 你如果是function 就直接当install 方法去使用

使用插件

在使用 createApp() 初始化 Vue 应用程序后,你可以通过调用 use() 方法将插件添加到你的应用程序中。

实现一个Loading

Loading.Vue

<template>
    <div v-if="isShow" class="loading">
        <div class="loading-content">Loading...</div>
    </div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
const isShow = ref(false)//定位loading 的开关
 
const show = () => {
    isShow.value = true
}
const hide = () => {
    isShow.value = false
}
//对外暴露 当前组件的属性和方法
defineExpose({
    isShow,
    show,
    hide
})
</script>
<style scoped lang="less">
.loading {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.8);
    display: flex;
    justify-content: center;
    align-items: center;
    &-content {
        font-size: 30px;
        color: #fff;
    }
}
</style>

Loading.ts

import {  createVNode, render, VNode, App } from 'vue';
import Loading from './index.vue'
 
export default {
    install(app: App) {
        //createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
        const vnode: VNode = createVNode(Loading)
        //render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
        render(vnode, document.body)
        // Vue 提供的全局配置 可以自定义
        app.config.globalProperties.$loading = {
            show: () => vnode.component?.exposed?.show(),
            hide: () => vnode.component?.exposed?.hide()
        }
    }
}

Main.ts

import Loading from './components/loading'
 
let app = createApp(App)
 
app.use(Loading)
 
type Lod = {
    show: () => void,
    hide: () => void
}
//编写ts loading 声明文件放置报错 和 智能提示
declare module '@vue/runtime-core' {
    export interface ComponentCustomProperties {
        $loading: Lod
    }
}
app.mount('#app')

了解UI库ElementUI,AntDesigin等

vue作为一款深受广大群众以及尤大崇拜者的喜欢,特此列出在github上开源的vue优秀的UI组件库供大家参考

这几套框架主要用于后台管理系统和移动端的制作,方便开发者快速开发

Element UI Plus

官网:element-plus.gitee.io/zh-CN/

安装方法

# NPM
$ npm install element-plus --save
 
# Yarn
$ yarn add element-plus
 
# pnpm
$ pnpm install element-plus

main ts引入

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
 
const app = createApp(App)
 
app.use(ElementPlus)
app.mount('#app')

volar插件支持

{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

Ant Design Vue

官网:www.antdv.com/docs/vue/in…

安装

使用 npm 或 yarn 安装

$ npm install ant-design-vue --save
yarn add ant-design-vue

示例

import { DatePicker } from 'ant-design-vue';
app.use(DatePicker);

引入样式:

import 'ant-design-vue/dist/antd.css'; // or 'ant-design-vue/dist/antd.less'

Iview

基于 Vue.js 3 的企业级 UI 组件库和前端解决方案,

官网:www.iviewui.com/

安装:

npm install view-ui-plus --save

引入:

import { createApp } from 'vue'
import ViewUIPlus from 'view-ui-plus'
import App from './App.vue'
import router from './router'
import store from './store'
import 'view-ui-plus/dist/styles/viewuiplus.css'

const app = createApp(App)

app.use(store)
  .use(router)
  .use(ViewUIPlus)
  .mount('#app')

Vant 移动端

官网:vant-contrib.gitee.io/vant/v4/#/z…

安装

npm i vant -S

使用

import Vant from 'vant'
import 'vant/lib/index.css';
createApp(App).use(vant).$mount('#app)

Scoped和样式穿透deep()

主要是用于修改很多vue常用的组件库(element, vant, AntDesigin),虽然配好了样式但是还是需要更改其他的样式

就需要用到样式穿透

scoped的原理

vue中的scoped 通过在DOM结构以及css样式上加唯一不重复的标记:data-v-hash的方式,以保证唯一(而这个工作是由过PostCSS转译实现的),达到样式私有化模块化的目的。

总结一下scoped三条渲染规则:

  1. 给HTML的DOM节点加一个不重复data属性(形如:data-v-123)来表示他的唯一性
  2. 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-123])来私有化样式
  3. 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性

PostCSS会给一个组件中的所有dom添加了一个独一无二的动态属性data-v-xxxx,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom, 从而达到了'样式模块化'的效果.

案例修改Element ui Input样式

发现没有生效

<template>
  <div class="bg">
    <el-input class="ipt"></el-input>
  </div>
</template>
<script setup lang="ts">

</script>
<style lang="less" scoped>
.bg {
  background-color: black;
  height: 100vh;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;

  .ipt {
    width: 200px;
    .el-input__inner {
      background-color: red;
    }
  }
}
</style>

image-20220919080324872

如果不写Scoped 就没问题

原因就是Scoped 搞的鬼 他在进行PostCss转化的时候把元素选择器默认放在了最后

11111

Vue 提供了样式穿透:deep() 他的作用就是用来改变 属性选择器的位置

100a14026698

185233f9afe8422fa

css Style完整新特性

插槽选择器:slotted

父组件

<template>
  <div>
    <A>
      <div class="a">私人定制div</div>
    </A>
  </div>
</template>

<script setup lang="ts">
import A from "./components/A.vue"
</script>

子组件

<template>
    <div>
        我是插槽
        <slot></slot>
    </div>
</template>

在子组件写css,改变父组件class=a的样式

<style scoped>
.a{
    color:red
}
</style>

可以发现并无效果

默认情况下,作用域样式不会影响到 <slot/> 渲染出来的内容,因为它们被认为是父组件所持有并传递进来的。

解决方案 slotted

<style scoped lang="less">
:slotted(.a) {
  color: red;
}
</style>

全局选择器:global

在之前我们想加入全局 样式 通常都是新建一个style 标签 不加scoped 现在有更优雅的解决方案

<style>
 div{
     color:red
 }
</style>
 
<style lang="less" scoped>
 
</style>
<style lang="less" scoped>
:global(div){
    color:red
}
</style>

效果等同于上面

动态css

单文件组件的 <style> 标签可以通过 v-bind 这一 CSS 函数将 CSS 的值关联到动态的组件状态上:

<template>
  <div class="div">
    动态css
  </div>
</template>

<script setup lang="ts">
import {ref} from 'vue'
const style = ref('red')
</script>


<style lang="less" scoped>
.div  {
  color: v-bind(style);
}
</style>

如果是对象 v-bind 请加引号

const style = ref({
  color: 'red'
})
.div  {
  color: v-bind('style.color');
}

css module

标签会被编译为 [CSS Modules](https://github.com/css-modules/css-modules) 并且将生成的 CSS 类作为 $style对象的键暴露给组件 <template> <div :class="$style.red"> 小满是个弟弟 </div> </template> ```vue <template> <div :class="$style.red"> 小满是个弟弟 </div> </template> <style module> .red { color: red; font-size: 20px; }

自定义注入名称(多个可以用数组)

你可以通过给 `module` attribute 一个值来自定义注入的类对象的 property 键

```vue
<template>
    <div :class="[zs.red,zs.border]">
        小满是个弟弟
    </div>
</template>
 
<style module="zs">
.red {
    color: red;
    font-size: 20px;
}
.border{
    border: 1px solid #ccc;
}
</style>

与组合式 API 一同使用

注入的类可以通过 useCssModule API 在 setup() 和

<template>
    <div :class="[zs.red,zs.border]">
        小满是个弟弟
    </div>
</template>
<script setup lang="ts">
import { useCssModule } from 'vue'
const css = useCssModule('zs')
</script>
 
<style module="zs">
.red {
    color: red;
    font-size: 20px;
}
.border{
    border: 1px solid #ccc;
}
</style>

Vue3集成Tailwind CSS

Tailwind CSS 是一个由js编写的CSS框架他是基于postCss 去解析的

官网地址Tailwind CSS 中文文档 - 无需离开您的HTML,即可快速建立现代网站。

对于PostCSS的插件使用,我们再使用的过程中一般都需要如下步骤:

  1. postCss 功能介绍PostCSS 配置文件 postcss.config.js,新增 tailwindcss 插件。
  2. TaiWindCss插件需要一份配置文件,比如:tailwind.config.js。

PostCSS - 是一个用 JavaScript 工具和插件来转换 CSS 代码的工具 | PostCSS 中文网

postCss 功能介绍

1.增强代码的可读性 (利用从 Can I Use 网站获取的数据为 CSS 规则添加特定厂商的前缀。 Autoprefixer 自动获取浏览器的流行度和能够支持的属性,并根据这些数据帮你自动为 CSS 规则添加前缀。)

2.将未来的 CSS 特性带到今天!(PostCSS Preset Env 帮你将最新的 CSS 语法转换成大多数浏览器都能理解的语法,并根据你的目标浏览器或运行时环境来确定你需要的 polyfills,此功能基于 cssdb 实现。)

3.终结全局 CSS(CSS 模块 能让你你永远不用担心命名太大众化而造成冲突,只要用最有意义的名字就行了。)

. 4.避免 CSS 代码中的错误(通过使用 stylelint 强化一致性约束并避免样式表中的错误。stylelint 是一个现代化 CSS 代码检查工具。它支持最新的 CSS 语法,也包括类似 CSS 的语法,例如 SCSS 。)

postCss 处理 tailWind Css 大致流程

  • 将CSS解析成抽象语法树(AST树)
  • 读取插件配置,根据配置文件,生成新的抽象语法树
  • 将AST树”传递”给一系列数据转换操作处理(变量数据循环生成,切套类名循环等)
  • 清除一系列操作留下的数据痕迹
  • 将处理完毕的AST树重新转换成字符串

安装

1.初始化项目

npm init vue@latest

2.安装 Tailwind 以及其它依赖项

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

3.生成配置文件

npx tailwindcss init -p

配置 - Tailwind CSS 中文文档

4.修改配置文件 tailwind.config.js

2.6版本

module.exports = {
  purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
}

3.0版本

module.exports = {
  content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
}

5.创建一个index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

在main.ts 引入

f1c25cd3a4e

82e4bef024

<template>
  <div class="w-screen h-screen bg-red-600 flex justify-center items-center text-5xl" >
    hello tailwind
  </div>
</template>

img

Evnet Loop 和 nextTick&源码解析

JS 执行机制

在我们学js 的时候都知道js 是单线程的如果是多线程的话会引发一个问题在同一时间同时操作DOM 一个增加一个删除JS就不知道到底要干嘛了,所以这个语言是单线程的但是随着HTML5到来js也支持了多线程webWorker 但是也是不允许操作DOM

单线程就意味着所有的任务都需要排队,后面的任务需要等前面的任务执行完才能执行,如果前面的任务耗时过长,后面的任务就需要一直等,一些从用户角度上不需要等待的任务就会一直等待,这个从体验角度上来讲是不可接受的,所以JS中就出现了异步的概念。

同步任务

代码从上到下按顺序执行

异步任务

image-20220919093908834

宏任务

script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax

微任务

Promise.then catch finally、MutaionObserver、process.nextTick(Node.js 环境)

<template>
  <div>
    <input v-model="message" type="text">
    <div ref="div">{{message}}</div>
    <button @click="change">change div</button>
  </div>
</template>

<script setup lang='ts'>
import { ref } from 'vue';

const message = ref('浩然')
const div = ref<HTMLElement>()

const change = () => {
  message.value = '浩然最帅'
  console.log(div.value?.innerHTML)
}
</script>

image-20220919095214893

打印出来的是浩然,并不是新的数据

运行机制

所有的同步任务都是在主进程执行的形成一个执行栈,主线程之外,还存在一个"任务队列",异步任务执行队列中先执行宏任务,然后清空当次宏任务中的所有微任务,然后进行下一个tick如此形成循环

nextTick 就是创建一个异步任务,那么它自然要等到同步任务执行完成后才执行。

<script setup lang='ts'>
import { ref,nextTick } from 'vue';

const message = ref('浩然')
const div = ref<HTMLElement>()

const change = async () => {
  message.value = '浩然最帅'
  await nextTick()
  console.log(div.value?.innerHTML)
}
</script>

image-20220919095338438

源码地址 core\packages\runtime-core\src\scheduler.ts

const resolvedPromise: Promise<any> = Promise.resolve()
let currentFlushPromise: Promise<void> | null = null
 
export function nextTick<T = void>(
  this: T,
  fn?: (this: T) => void
): Promise<void> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}

nextTick 接受一个参数fn(函数)定义了一个变量P 这个P最终返回都是Promise,最后是return 如果传了fn 就使用变量P.then执行一个微任务去执行fn函数,then里面this 如果有值就调用bind改变this指向返回新的函数,否则直接调用fn,如果没传fn,就返回一个promise,最终结果都会返回一个promise

在我们之前讲过的ref源码中有一段 triggerRefValue 他会去调用 triggerEffects

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      triggerEffects(ref.dep)
    }
  }
}
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  for (const effect of isArray(dep) ? dep : [...dep]) {
    if (effect !== activeEffect || effect.allowRecurse) {
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      //当响应式对象发生改变后,执行 effect 如果有 scheduler 这个参数,会执行这个 scheduler 函数
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  }
}

那么scheduler 这个函数从哪儿来的 我们看这个类 ReactiveEffect

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined
 
  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T>
  /**
   * @internal
   */
  allowRecurse?: boolean
 
  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void
 
  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null, //我在这儿 
    scope?: EffectScope
  ) {
    recordEffectScope(this, scope)
  }

scheduler 作为一个参数传进来的

   const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(instance.update),
      instance.scope // track it in component's effect scope
    ))

他是在初始化 effect 通过 queueJob 传进来的

//queueJob 维护job列队,有去重逻辑,保证任务的唯一性,每次调用去执行,被调用的时候去重,每次调用去执行 queueFlush
export function queueJob(job: SchedulerJob) {
  // 判断条件:主任务队列为空 或者 有正在执行的任务且没有在主任务队列中  && job 不能和当前正在执行任务及后面待执行任务相同
  // 重复数据删除:
  // - 使用Array.includes(Obj, startIndex) 的 起始索引参数:startIndex
  // - startIndex默认为包含当前正在运行job的index,此时,它不能再次递归触发自身
  // - 如果job是一个watch()回调函数或者当前job允许递归触发,则搜索索引将+1,以允许他递归触发自身-用户需要确保回调函数不会死循环
  if (
    (!queue.length ||
      !queue.includes(
        job,
        isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
      )) &&
    job !== currentPreFlushParentJob
  ) {
    if (job.id == null) {
      queue.push(job)
    } else {
      queue.splice(findInsertionIndex(job.id), 0, job)
    }
    queueFlush()
  }
}

queueJob 维护job列队 并且调用 queueFlush

function queueFlush() {
  // 避免重复调用flushJobs
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
     //开启异步任务处理flushJobs
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}

queueFlush 给每一个队列创建了微任务

Vue开发移动端&打包

开发移动端最主要的就是适配各种手机,为此我研究了一套解决方案

在之前我们用的是rem 根据HTML font-size 去做缩放

现在有了更好用的vw vh

vw 视口的最大宽度,1vw等于视口宽度的百分之一

vh 视口的最大高度,1vh等于视口高度的百分之一

1.安装依赖

npm install postcss-px-to-viewport -D

因为vite中已经内联了postcss,所以并不需要额外的创建 postcss.config.js文件

vite.config.ts

import { fileURLToPath, URL } from 'url'
 
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import postcsspxtoviewport from "postcss-px-to-viewport" //插件
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
  css: {
    postcss: {
      plugins: [
        postcsspxtoviewport({
          unitToConvert: 'px', // 要转化的单位
          viewportWidth: 750, // UI设计稿的宽度
          unitPrecision: 6, // 转换后的精度,即小数点位数
          propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
          viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
          fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
          selectorBlackList: ['ignore-'], // 指定不转换为视窗单位的类名,
          minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
          mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
          replace: true, // 是否转换后直接更换属性值
          landscape: false // 是否处理横屏情况
        })
      ]
    }
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

如果用的vite 是 ts 他这个插件并没有提供声明文件已经写好了声明文件

declare module 'postcss-px-to-viewport' {
 
    type Options = {
        unitToConvert: 'px' | 'rem' | 'cm' | 'em',
        viewportWidth: number,
        viewportHeight: number, // not now used; TODO: need for different units and math for different properties
        unitPrecision: number,
        viewportUnit: string,
        fontViewportUnit: string,  // vmin is more suitable.
        selectorBlackList: string[],
        propList: string[],
        minPixelValue: number,
        mediaQuery: boolean,
        replace: boolean,
        landscape: boolean,
        landscapeUnit: string,
        landscapeWidth: number
    }
 
    export default function(options: Partial<Options>):any
}

引入声明文件 tsconfig.app postcss-px-to-viewport.d.ts跟vite.ts同级

{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "postcss-px-to-viewport.d.ts"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

代码案例

<template>
  <div class="wraps">
    <header class="header">
      <div>left</div>
      <div>中间</div>
      <div>right</div>
    </header>
 
    <main class="main">
      <div class="main-items" v-for="item in 100">
        <div class="main-port">头像</div>
        <div class="main-desc">
          <div>小满{{item}}</div>
          <div>你妈妈喊你回家穿丝袜啦</div>
        </div>
      </div>
    </main>
 
 
    <footer class="footer">
      <div class="footer-items" v-for="item in footer">
        <div>{{ item.icon }}</div>
        <div>{{ item.text }}</div>
      </div>
    </footer>
  </div>
 
</template>
  
<script setup lang='ts'>
import { reactive } from 'vue';
 
type Footer<T> = {
  icon: T,
  text: T
}
 
const footer = reactive<Footer<string>[]>([
  {
    icon: "1",
    text: "首页"
  },
  {
    icon: "2",
    text: "商品"
  },
  {
    icon: "3",
    text: "信息"
  },
  {
    icon: "4",
    text: "我的"
  }
])
</script>
  
<style lang="less">
@import url('@/assets/base.css');
 
html,
body,
#app {
  height: 100%;
  overflow: hidden;
  font-size: 14px;
}
 
.wraps {
  height: inherit;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
 
.header {
  background-color: pink;
  display: flex;
  height: 30px;
  align-items: center;
  justify-content: space-around;
 
  div:nth-child(1) {
    width: 40px;
  }
 
  div:nth-child(2) {
    text-align: center;
  }
 
  div:nth-child(3) {
    width: 40px;
    text-align: right;
  }
}
 
.main {
  flex: 1;
  overflow: auto;
 
  &-items {
    display: flex;
    border-bottom: 1px solid #ccc;
    box-sizing: border-box;
    padding: 5px;
  }
 
  &-port {
    background: black;
    width: 30px;
    height: 30px;
    border-radius: 200px;
  }
  &-desc{
     margin-left:10px;
     div:last-child{
        font-size: 10px;
        color:#333;
        margin-top: 5px;
     }
  }
}
 
.footer {
 
  border-top: 1px solid #ccc;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
 
  &-items {
    font-size: 10px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
}
</style>

unocss原子化

重新构想原子化CSS - 知乎

什么是css原子化?

CSS原子化的优缺点

1.减少了css体积,提高了css复用

2.减少起名的复杂度

3.增加了记忆成本 将css拆分为原子之后,你势必要记住一些class才能书写,哪怕tailwindcss提供了完善的工具链,你写background,也要记住开头是bg

接入unocss tips:最好用于vite webpack属于阉割版功能很少

安装

npm i -D unocss

vite.config.ts

import unocss from 'unocss/vite'
 
 plugins: [vue(), vueJsx(),unocss({
      rules:[
        
      ]
  })],

main.ts 引入

import 'uno.css'

配置静态css

rules: [
  ['flex', { display: "flex" }]
]

使用

  <div class="flex red">
      此刻
  </div>

image-20220919153252320

配置动态css(使用正则表达式

m-参数*10 例如 m-10 就是 margin:100px

    rules: [
      [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })]
    ]
  <div class="flex red m-2">
      此刻
  </div>

image-20220919153852253

shortcuts 可以自定义组合样式

  plugins: [vue(), vueJsx(), unocss({
    rules: [
      [/^m-(\d+)$/, ([, d]) => ({ margin: `${Number(d) * 10}px` })],
      ['flex', { display: "flex" }],
      ['red', { color: 'red' }]
    ],
    shortcuts: {
      cike: "pink flex"
    }
  })],
  <div class="cike">
      此刻
  </div>

image-20220919154135432

unocss 预设

  plugins: [vue(), vueJsx(), unocss({
    presets:[presetIcons(),presetAttributify(),presetUno()]
  })],

1.presetIcons Icon图标预设

首先我们去icones官网(方便浏览和使用iconify)浏览我们需要的icon,比如这里我用到了Google Material Icons图标集里面的baseline-add-circle图标

图标集合安装

npm i -D @iconify-json/ic

ic是指icones官网图标库里的名称,在浏览器地址栏可以查看到

image-20220919155648591

使用图标

image-20220919155835406

<div class="i-ic-baseline-alarm-on"></div>

2.presetAttributify 属性化模式支持

属性语义化 无须class

  <div red m="3">
      此刻
  </div>

image-20220919160122829

3.presetUno 工具类预设

默认的 @unocss/preset-uno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。

例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均会生效。

Tailwind:

  <div class="text-3xl">
      此刻
  </div>

image-20220919160533113

函数式编程&h函数

<template>
  <div>
    <Btn></Btn>
  </div>
</template>
  
<script setup lang='ts'>
import { h } from 'vue'
type Props = {
  text: string
}
const Btn = (propos: Props, ctx: any) => {
  return h()
}
</script>

h 接收三个参数

  • type 元素的类型
  • propsOrChildren 数据对象, 这里主要表示(props, attrs, dom props, class 和 style)
  • children 子节点

h函数拥有多种组合方式

// 除类型之外的所有参数都是可选的
h('div')
h('div', { id: 'foo' })
 
//属性和属性都可以在道具中使用
//Vue会自动选择正确的分配方式
h('div', { class: 'bar', innerHTML: 'hello' })
 
// props modifiers such as .prop and .attr can be added
// with '.' and `^' prefixes respectively
h('div', { '.name': 'some-name', '^width': '100' })
 
// class 和 style 可以是对象或者数组
h('div', { class: [foo, { bar }], style: { color: 'red' } })
 
// 定义事件需要加on 如 onXxx
h('div', { onClick: () => {} })
 
// 子集可以字符串
h('div', { id: 'foo' }, 'hello')
 
//如果没有props是可以省略props 的
h('div', 'hello')
h('div', [h('span', 'hello')])
 
// 子数组可以包含混合的VNode和字符串
h('div', ['hello', h('span', 'hello')])

使用props传递参数

<template>
  <div>
    <Btn text="浩然写的h函数"></Btn>
  </div>
</template>
  
<script setup lang='ts'>
import { h } from 'vue'
type Props = {
  text: string
}
const Btn = (propos: Props, ctx: any) => {
  return h('div', {
      // css使用的是tailwind
    class: ['rounded', 'bg-red-500', 'text-white', 'text-center']
  },
    propos.text
  )
}

</script>

image-20220919164201308

接受emit

<template>
  <div>
    <Btn @on-click="getBtn" text="浩然写的h函数"></Btn>
  </div>
</template>
  
<script setup lang='ts'>
import { h } from 'vue'
type Props = {
  text: string
}
const Btn = (propos: Props, ctx: any) => {
  return h('div', {
    class: ['rounded', 'bg-red-500', 'text-white', 'text-center'],
    onclick: () => {
      ctx.emit('on-click','我是按钮')
    }
  },
    propos.text
  )
}
const getBtn = (str:string) => {
  console.log(str);
}
</script>

<style lang="less">

</style>

定义插槽

<template>
  <div>
    <Btn @on-click="getBtn">
      <template #default>123</template>
    </Btn>
  </div>
</template>
  
<script setup lang='ts'>
import { h } from 'vue'
type Props = {
  text?: string
}
const Btn = (propos: Props, ctx: any) => {
  return h('div', {
    class: ['rounded', 'bg-red-500', 'text-white', 'text-center'],
    onclick: () => {
      ctx.emit('on-click','我是按钮')
    }
  },
    ctx.slots.default()
  )
}
const getBtn = (str:string) => {
  console.log(str);
}
</script>

Vue响应性语法糖

小提示 本章内容所讲的东西都是实验性的产物 暂时不要再生产环境使用,自己开发玩可以使用,不过大体框架应该不会变了。

要求 vue版本 3.2.25 及以上

1.开启配置(开启之后才能使用新特性)

vite 开启 reactivityTransform

import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
  server: {
    port: 3000
  },
  plugins: [
    vue({
        // 配置这条命令
      reactivityTransform:true
    }),
   vueJsx()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
})

如果是 vue-cli

// vue.config.js
module.exports = {
  chainWebpack: (config) => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options) => {
        return {
          ...options,
          reactivityTransform: true
        }
      })
  }
}

第一个例子 $ref

在之前ref 修改值 和 获取值 都要.value 一下 感觉很繁琐,不想用.value 我们可以使用vue3的新特性$ref 。

我们可以直接使用$ref 宏函数 就不需要.value 了。能帮我们快速书写,但是宏函数是基于运行时的他最终还是会转换成ref 加.value 只不过vue帮我们做了这个操作了

<template>
  <div>
    <button @click="add">增加</button>
  </div>
  <div>{{count}}</div>
</template>
  
<script setup lang='ts'>
import {$ref} from "vue/macros";
let count = $ref(0)
const add = () => {
  count++
}
</script>

当然跟ref 有关的函数都做处理 都不需要.value了

$ref 的弊端

应为他编译之后就是 count.value 并不是一个ref对象所以watch 无法监听而且会抛出一个警告

[Vue warn]: Invalid watch source:  0 A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types. 
  at <App>
<script setup lang='ts'>
import {$ref} from "vue/macros";
import {watch} from "vue";

let count = $ref(0)
const add = () => {
  count++
}
watch(count,(v) => {
  console.log(v)
})
</script>

解决这个问题需要$$ 符号 就是再让他编译的时候变成一个ref 对象不加.value

<script setup lang='ts'>
import {$ref,$$} from "vue/macros";
import {watch} from "vue";

let count = $ref(0)
const add = () => {
  count++
}
watch($$(count),(v) => {
  console.log(v)
})
</script>

解构

在之前我们解构一个对象使用toRefs 解构完成之后 获取值和修改值 还是需要.value

vue3 也提供了语法糖 $() 解构完之后可以直接赋值

<template>
  <div>{{name}}</div>
  <div>{{desc}}</div>
</template>
  
<script setup lang='ts'>
import {$} from "vue/macros";
import {reactive, watch} from "vue";
const obj = reactive({
  name: '浩然',
  desc: '长得很帅'
})
// let {name,desc} = obj
// setTimeout(() => {
//   desc ='帅死了' // 无法做出响应式
// },1000)
let {name,desc} = $(obj)
setTimeout(() => {
  desc ='帅死了'
},1000)
</script>

环境变量

环境变量:他的主要作用就是让开发者区分不同的运行环境,来实现 兼容开发和生产

例如 npm run dev 就是开发环境 npm run build 就是生产环境等等

Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量

console.log(import.meta.env)

{
"BASE_URL":"/", //部署时的URL前缀
"MODE":"development", //运行模式
"DEV":true,"  //是否在dev环境
PROD":false, //是否是build 环境
"SSR":false //是否是SSR 服务端渲染模式
}

需要注意的一点就是这个环境变量不能使用动态赋值import.meta.env[key] 应为这些环境变量在打包的时候是会被硬编码的通过JSON.stringify 注入浏览器的

配置额外的环境变量

在根目录新建env 文件 可以创建多个

如下 env.[name]

image-20220919184851172

image-20220919184915733

image-20220919184922881

修改启动命令

在 package json 配置 --mode env文件名称

image-20220919184956968

image-20220919185005344

webpack 构建 Vue3项目

1.初始化项目结构(跟cli 结构保持一致)

image-20220920091512499

2.安装所需要的依赖包

{
    "name": "webpack-vue",
    "version": "1.0.0",
    "description": "",
    "main": "webpack.config.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "webpack-dev-server",
        "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "@vue/compiler-sfc": "^3.2.38", //解析vue文件
        "clean-webpack-plugin": "^4.0.0", //打包 的时候清空dist
        "css-loader": "^6.7.1", //处理css文件
        "friendly-errors-webpack-plugin": "^1.7.0", //美化dev
        "html-webpack-plugin": "^5.5.0", //html 模板
        "less": "^4.1.3",  //处理less
        "less-loader": "^11.0.0", //处理less文件
        "style-loader": "^3.3.1", //处理style样式
        "ts-loader": "^9.3.1", //处理ts
        "typescript": "^4.8.2", //ts
        "vue": "^3.2.38", //vue
        "vue-loader": "^17.0.0", //解析vue
        "webpack": "^5.74.0",
        "webpack-cli": "^4.10.0",
        "webpack-dev-server": "^4.10.0"
    }
}
// 生成package.json
npm init -y
// 生成tsconfig.json
tsc --init
// 安装webpack和webpack-cli
npm i webpack webpack-cli
// 安装启动dev的环境
npm i webpack-dev-server
// webpack插件:html模板 html-webpack-plugin
npm i html-webpack-plugin
// 安装vue
npm i vue
// 安装解析vue的vue-loader @vue/compiler-sfc
npm i vue-loader @vue/compiler-sfc
//打包 的时候清空dist
npm i clean-webpack-plugin
、、、、、、、、、
按着上面的包管理文件安装就行了

如果 tsc --init 不能使用, 安装npm install typescript -g

webpack 版本3以上的版本要安装webpack-cli

配置package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev" : "webpack-dev-server",
    "build": "webpack"
  },

配置webpack.config.js

const { Configuration } = require('webpack')
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader/dist/index');
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
/**
 * @type {Configuration} //配置智能提示
 */
const config = {
    mode: "development",
    entry: './src/main.ts', //入口文件
    output: {
        filename: "[hash].js",
        path: path.resolve(__dirname, 'dist') //出口文件
    },
    module: {
        // rules基本是处理文件的
        rules: [
            {
                test: /\.vue$/, //解析vue 模板
                use: "vue-loader"
            },
            {
                test: /\.less$/, //解析 less
                use: ["style-loader", "css-loader", "less-loader"],
            },
            {
                test: /\.css$/, //解析css
                use: ["style-loader", "css-loader"],
            },
            {
                test: /\.ts$/,  //解析ts
                loader: "ts-loader",
                options: {
                    configFile: path.resolve(process.cwd(), 'tsconfig.json'),
                    appendTsSuffixTo: [/\.vue$/]
                },
            }
        ]
    },
    // 放在plugins里面的都是webpack插件
    plugins: [
        new htmlWebpackPlugin({
            template: "./public/index.html" //html模板
        }),
        new CleanWebpackPlugin(), //打包清空dist
        new VueLoaderPlugin(), //解析vue
        new FriendlyErrorsWebpackPlugin({
            compilationSuccessInfo:{ //美化样式
                messages:['You application is running here http://localhost:9001']
            }
           
        })
    ],
    resolve: {
        alias: {
            "@": path.resolve(__dirname, './src') // 别名
        },
        extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀
    },
    stats:"errors-only", //取消提示,与美化样式一起使用
    devServer: {
        proxy: {},
        port: 9001,
        hot: true,
        open: true,
    },
    externals: {
        vue: "Vue" //CDN 引入,要在index.html 引入vue的cdn
    },
}
 
 
module.exports = config

测试打包

main.ts

const a = 1

执行命令: npm run build

在dist目录下可以看到我们声明的变量

image-20220920094030169

vue性能优化

xiaoman.blog.csdn.net/article/det…