vue3学习日志(vite+ts+vue3)

Vue3简介

渐进式框架

  • 无需构建步骤,渐进式增强静态的 HTML
  • 在任何页面中作为 Web Components 嵌入
  • 单页应用 (SPA)
  • 全栈 / 服务端渲染 (SSR)
  • Jamstack / 静态站点生成 (SSG)
  • 开发桌面端、移动端、WebGL,甚至是命令行终端中的界面

API 风格

Vue 的组件可以按两种不同的风格书写:选项式 API 和组合式 API。
vue2中的风格为选项式api

<script>
export default {
  // data() 返回的属性将会成为响应式的状态
  // 并且暴露在 `this` 上
  data() {
    return {
      count: 0
    }
  },

  // methods 是一些用来更改状态与触发更新的函数
  // 它们可以在模板中作为事件监听器绑定
  methods: {
    increment() {
      this.count++
    }
  },

  // 生命周期钩子会在组件生命周期的各个不同阶段被调用
  // 例如这个函数就会在组件挂载完成后被调用
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

组合式api在vue3中应用更为广泛,需要注意的是在一个组件中可以有多个script标签,但是只能有一个标签有setup参数

<template>
  <div>
    <div>{{choose}}</div>
    <button @click="changeFood">变换</button>
  </div>
</template>

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

  let food = [{name:'自助餐',num:1},{name:'快餐',num:2},{name:'石锅饭',num:3},{name:'烤鸭',num:4},{name:'汉堡',num:5}]

  let choose  = ref<string>('')

  let count = 0;

  const changeFood = () => {
    console.log(food)
    food.forEach((foo) => {
      count = Math.round(Math.random()*10)

      if (count == foo.num) {
        choose.value = foo.name
        console.log(choose)
      }
    })
  }

</script>

<style lang="less" scoped>

</style>

模板语法和插值语法

和vue2类似,可以查阅这个地址:vue2学习

虚拟dom

vue提供了一种虚拟dom的方法减少性能消耗,其本质通过js生成一个ast节点树,在vue进行更改时,通过比对前后vnode节点确定更改哪些节点,而不是全局节点刷新,提高了性能。

ref全家桶

ref

  const message = ref<string>("jth")
  let notRef:string = "message"
  console.log(isRef(message))
  console.log(isRef(notRef))
  message.value = liu

在这里插入图片描述
ref

interface Ref<T> {
  value: T
}

更改ref定义的值需要加上.value
定义ref后可以实现将定义的数据实现vue2中的响应式变化。
isRef:通过isRef判断是否为ref对象

shallowRef

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

什么时候使用?

  • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
  • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

例子1:由于修改的属性是非响应式的,所以不会改变

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
 
 
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "jth"
})
 
const changeMsg = () => {
  message.value.name = 'liu'
}
</script>
 
 
<style>
</style>

例子2:

动画

transation

单元素/组件的过渡

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

transition-group

  • 在处理多个元素位置更新时,使用 组件,通过 FLIP 技术来提高性能。
    <button @click="add">添加</button>
    <button @click="pop">删除</button>
    <transition-group
    leave-active-class="animate__animated animate__backOutDown"
    enter-active-class="animate__animated animate__bounceIn">
    <div v-for="item in list" :key="item">{{item}} </div>
    </transition-group>
<script setup lang="ts">
  import { ref, reactive } from 'vue';
  import gsap from 'gsap'
  import 'animate.css'
  const list = reactive<number[]>([1,4,3,2,3,5,4,3])
  const add = () => {
    list.push(list.length+1);
  }
  const pop = () => {
    list.pop();
  }


</script>

通过animite.css和vue的过渡方法可以实现动态效果

provide/reject依赖注入

function provide<T>(key: InjectionKey<T> | string, value: T): void

provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
provide() 必须在组件的 setup() 阶段同步调用

<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 提供静态值
provide('foo', 'bar')

// 提供响应式的值
const count = ref(0)
provide('count', count)

// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>

inject()

// 没有默认值
function inject<T>(key: InjectionKey<T> | string): T | undefined

// 带有默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T

// 使用工厂函数
function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T

  • 注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。
  • 第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key
    提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回undefined,除非提供了一个默认值。
  • 第二个参数是可选的,即在没有匹配到 key
    时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。
  • 与注册生命周期钩子的 API 类似,inject() 必须在组件的 setup() 阶段同步调用。
  • 当使用 TypeScript 时,key 可以是一个类型为 InjectionKey 的 symbol。InjectionKey 是一个
    Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide() 和 inject() 之间值的类型。
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 注入值的默认方式
const foo = inject('foo')

// 注入响应式的值
const count = inject('count')

// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)

// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')

// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('foo', () => new Map())

// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject('function', () => {}, false)
</script>

example

<template>
  <div>
    <h1>grandFather</h1>
    <label>
      <input type="radio" v-model="colorVal" value="red">
      红色
    </label>

    <label>
      <input type="radio" v-model="colorVal" value="pink">
      粉色
    </label>

    <label>
      <input type="radio" v-model="colorVal" value="yellow">
      黄色
    </label>

  </div>

  <div class="box">

  </div>
  <hr>
  <ProvideA></ProvideA>
</template>

<script setup lang="ts">
import {ref, reactive, provide, readonly} from 'vue';
import ProvideA from "./provideA.vue";

let colorVal = ref<string>("red")
provide("color",readonly(colorVal))//使得子组件无法修改

</script>

<style scoped>
.box{
  height: 50px;
  width: 50px;
  border: 1px solid #ccc;
  background-color: v-bind(colorVal);
}
</style>

祖父组件,使用provide方法进行注入,将colorval传递

tips:v-bind在vue3中可以在css中使用。

<template>
  <div>
    <h1>father</h1>
    <div class="box">

    </div>
    <hr>
    <ProvideB></ProvideB>
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive,inject } from 'vue';
  import ProvideB from './provideB.vue'
  import type {Ref} from "vue";//引入ref类型

  let color = inject<Ref<string>>('color')//可以使用ref类型,inject可以接受泛型
</script>

<style scoped>
  .box{
    height: 50px;
    width: 50px;
    border: 1px solid #ccc;
    background-color: v-bind(color);
  }
</style>

子组件可以通过inject接受父组件传来的值,注意参数要和provide的第一个参数的名称相同。

<template>
  <div>
    <h1>son</h1>
    <button @click="changeColor">改变为黄色</button>
    <div class="box">

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

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

  import type {Ref} from "vue";//引入ref类型
let color = inject<Ref<string>>('color')//默认值也可以(非空断言)
  // let color = inject<Ref<string>>('color',ref('red'))//默认值也可以(非空断言)

  const changeColor = () => {
    // color?.value = yellow 可选链操作符无法赋值,可能为undefined
    color!.value = "yellow"//非空断言

  }
</script>

<style scoped>
  .box {
    height: 50px;
    width: 50px;
    border: 1px solid #ccc;
    background-color: v-bind(color);
  }
</style>

也可以通过子组件改变父组件的值,但是不符合设计模式中的开闭原则,我们可以加readonly将父组件的值包裹,当子组件试图改变父组件的值时,会进行进行警告

在这里插入图片描述

兄弟组件间传参

elder.vue

<template>
  <div class="a">
    <button @click="emity">派发一个事件</button>
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue';
  const emit = defineEmits(['toYounger'])
  let flag:boolean = false
  const emity = () => {
    flag = !flag
    emit('toYounger',flag)
  }
</script>

app.vue

<template>
  <div>
    <Elder @toYounger="getFlag"></Elder>
    <Younger :flag="Flag"></Younger>
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue';
  import Elder from "./elder.vue"
  import Younger from './younger.vue'
  let Flag = ref(false)

  const getFlag = (params:boolean) => {
    Flag.value = params
  }
</script>

<style scoped less>

</style>

younger.vue

<template>
  <div class="b">
    <h1>b</h1>
    {{flag}}
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue';
  type Props = {
    flag : boolean
  }

  defineProps<Props>()
</script>

兄弟之间的传参可以借助自定义事件进行传递,并借助app.vue父组件进行传递。

bus全局事件总线

Bus.ts

type BusClass = {
    emit:(name:string) => void
    on:(name:string,calback:Function) => void
}

type PramsKey = string | number | symbol

type List = {
    [key:PramsKey]:Array<Function>
}
class Bus implements BusClass{
    list:List

    constructor() {
        this.list = {}
    }
    emit(name:string,...args:Array<any>){
        let eventName:Array<Function> = this.list[name]
        eventName.forEach(fn=>{
            fn.apply(this,args)
        })

    }
    on(name:string,callback:Function) {
        let fn: Array<Function> = this.list[name] || [];
        fn.push(callback)
        this.list[name] = fn
    }
}

export default new Bus()

使用bus.ts后,可以不再使用app.vue父亲组件通信
younger.vue

<template>
  <div class="b">
    <h1>b</h1>
    {{Flag}}
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue';
  import Bus from '../../Bus'
  // type Props = {
  //   flag : boolean
  // }
  //
  // defineProps<Props>()
  let Flag = ref(false)
  Bus.on('toYounger',(flag:boolean)=>{
    Flag.value = flag
  })
</script>

elder

<template>
  <div class="a">
    <button @click="emity">派发一个事件</button>
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue';
  // const emit = defineEmits(['toYounger'])
  import Bus from '../../Bus'
  let flag:boolean = false
  const emity = () => {
    flag = !flag
    // emit('toYounger',flag)
    Bus.emit('toYounger',flag)
  }
</script>

mitt

 npm install mitt -s

自动引入(vue3小彩蛋)

 npm i -D unplugin-auto-import
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'//vite版本
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(),AutoImport({
    imports:['vue'],//vue注入
    dts:'src/auto-import.d.ts'//生成声明文件
  })]
})

可以直接使用vue的相关api,无需使用import语句

v-mode详解(vue3版本)

v-mode在vue3中进行了较大更新
v-model本质为一个语法糖,通过props和emit组合完成的

  • v-model 默认绑定的属性名为:modelValue
  • v-model 默认绑定的事件名为:update:modelValue
  • 支持自定义修饰符 Modifiers
  • v-bind的sync修饰符和组件的model选项已经移除

son

<template>
  <div>
    <div class="son">
      {{modelValue}}
      <div>内容<input type="text" :value="textValue" @input="change"></div>
      <button @click="toFather" >传给父亲</button>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue';
  const props = defineProps<{
    modelValue:string,
    textValue:string,
    //如果使用默认值的话为 modelModifiers
    textValueModifiers?:{
      jth:boolean
    }
  }>()

  const emit = defineEmits(['update:modelValue','update:textValue'])

  const toFather = () => {
    emit('update:modelValue','fromSon')
  }

  const change = (e:Event) => {
    const target = e.target as HTMLInputElement
    emit('update:textValue',props?.textValueModifiers ? target.value + 'hantai' : target.value)
  }
</script>

<style scoped>

</style>

father

<template>
  <div>
    <div class="module">
      <div>父亲</div>
      <h1>{{data}}</h1>
      <h1>{{text}}</h1>
      <div>===========================================</div>
<!--      多个v-model-->
      <Son v-model:textValue.jth="text" v-model="data"></Son>
    </div>
  </div>
</template>

<script setup lang="ts">
  import Son from './son.vue'
  let data = ref("fromFather")
  let text = ref('fromFatherText')
</script>

<style scoped>

</style>

v-model:textValue.jth=“text” 在之前有.trim(去除空格).lazy懒加载,这些vue2的属性,但vue3允许我们自定义修饰符,接受数据的话可以看代码。

自定义指令(directive)

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

在vue2中为 bind inserted update componentUpdated unbind

<template>
  <div>
    <button>switch</button>
    <div v-move:aaa="{jth:'red'}">hahah</div>
  </div>
</template>

<script setup lang="ts">
import {Directive, DirectiveBinding, ref} from "vue";

  let flag = ref<boolean>(true);

  //定义一个泛型
  type Dir= {
    jth:string
  }
  const vMove:Directive = {
    created () {
      console.log('created')
    },
    beforeMount (...args:any) {
      console.log(args)
      console.log("before mounted")
    },
    mounted (el:HTMLElement,dir:DirectiveBinding<Dir>) {//<Dir>进行类型提示,因为如果没有提示的话取不到jth
      el.style.background = dir.value.jth
      console.log('mounted')
    },
    beforeUpdate (){

    },
    updated(){

    },
    beforeUnmount(){

    },
    unmounted(){

    }

  }

</script>

在这里插入图片描述

在这里插入图片描述
0:节点
1:instance:当前组件实例
value:传入的参数
arg:绑定的值
dir:生命周期函数
2:虚拟dom
3:上一个虚拟dom preVnode

函数简写

  const vMo = (el:HTMLElement,binding:DirectiveBinding<Dir>) => {
    
  }

自定义hooks

vue2中有mixin封装公用的数据和函数

但是mixin的生命周期比组件要快,导致组件的数据核函数可能会覆盖掉mixin的数据。变量来源也不确定,不好查阅

手写hooks
目的:生成图片的base64格式


import {onMounted} from "vue";

type Options = {
    el:string
}

export default function (options:Options):Promise<{baseUrl:string}>{
    return new Promise((resolve) => { //返回promise对象
        onMounted(()=>{
            let img : HTMLImageElement = document.querySelector(options.el) as HTMLImageElement
            console.log(img,"===========")
            img.onload = () => { //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("img/jpg")
        }

    })

}
<template>
  <div >
    <div>jth</div>
    <img id="img" height="722" width="1298" src="../../assets/img/15.jpg">
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive } from 'vue';
  import useBase64 from '../../hooks'

  useBase64({el:'#img'}).then(res=>{
    console.log(res)
  })
 </script>

vue3定义全局函数和变量

vue3中没有prototype属性,使用app.config.globalProperties代替
vue2

vue.prototype.$http = () => {}

vue3

const app = createApp(App)
app.config.globalProperties.$http =  () => {}

vue3中移除了过滤器,我们可以自己写一个

const app = createApp(App)
app.config.globalProperties.$filters = {
    format<T> (str:T){
        return `jth-${str}`
    }
}
type Filter = {
    format<T>(str:T):string
}
// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型
declare module 'vue' {
    export interface ComponentCustomProperties{
        $Bus : typeof Mit
        $filters:Filter
        env:string
    }
}

使用
第一种

<div>{{$filters?.format('的飞机')}}</div>

第二种

  console.log(App?.proxy?.$env)

vue3编写插件

插件编写

<template>
  <div>
    <div  v-show="isShow">
      <div  class="loading">loading</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {ref, reactive, defineExpose} from 'vue';
const  isShow = ref<boolean>(false)

const show = () => {
  isShow.value = true
}

const hide = () => {
  isShow.value = false
}

//通过expose抛出相关函数和变量
defineExpose({
  hide,
  show,
  isShow
})
</script>
  • defineExpose是由于vue不允许操作setupState这个属性,但是可以使用expose获取
import type {App, VNode} from 'vue'
import {createVNode,render} from "vue";
import Loading from './index.vue'
export default {
    //对象形式
    install(app:App) {
        const Vnode:VNode = createVNode(Loading)
        //component 为 null ,使用render函数挂载
        render(Vnode,document.body)
        app.config.globalProperties.$loading = {
            show:Vnode.component?.exposed?.show,
            hide:Vnode.component?.exposed?.hide
        }

        // app.config.globalProperties.show()
        console.log(app,Vnode.component?.exposed)//通过Vnode.component?.exposed获取相关函数变量
    }
}
  • 引入Vnode为了为了使节点变为虚拟dom方便操作它上面的属性。

在这里插入图片描述

//编写ts智能提示和报错
type Lod = {
    show: () => void,
    hide: () => void
}

declare module '@vue/runtime-core'{
    export interface  ComponentCustomProperties{
        $loading:Lod
    }
}

app.use(loading);
  • 全局注册
  const instance =  getCurrentInstance()
  instance?.proxy?.$loading.show()

  setTimeout(()=>{
    instance?.proxy?.$loading.hide()
  },5000)

使用插件

use函数手写

MyUse.ts

import type{App} from "vue";

import {app} from "./main"

interface Use  {
    //类型约束,插件必须有install函数
    install:(app:App,...options:any[]) =>void
}

const installList = new  Set()

export function MyUse<T extends Use>(plugin:T,...options:any[]){
    //缓存结构
    if(installList.has(plugin)){
        console.log("this plugin has registed",plugin)
    }else{
        plugin.install(app,...options)
        installList.add(plugin)
    }
}
//导出use使用
export const app = createApp(App)
MyUse(loading)

ui库

antDesign

elementUi plus

viewDesign

vant

样式穿透:deep()

scoped的原理

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

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

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

使用深度选择器可以让子组件使用该样式

新增css特性

插槽选择器

<div>
	<slot></slot>
</div>
:slotted(.a){
	color:red;
}

这样可以让样式在子组件中的插槽生效,写在子元素中

全局使用

:global(div){
}

动态css

  /*对象写法*/
  color: v-bind('style.color');

css module

   <div :class="[$style.loading,$style.border]"></div>
<style  module="jth">

这种形式可以自定义,不用写$style,只要jth.loading就可以了

vue3集成TailwindCss

Tainwind官网

下载tailwind

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

创建配置文件

npx tailwindcss init -p

修改配置文件 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: [],
}

event loop and nextTick

js执行机制
js为单线程,但是随着html5的到来js也支持了多线程webWorker,但是不允许操作dom

同步任务
代码从上到下一次执行

异步任务

  • 宏任务
    script整体代码
    setTimeOut,
    setInterVal
    ajax
  • 微任务
    promise.then .catch .finally 、
    mutationObserver

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

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

在这里插入图片描述
一个小练习

 setTimeout(()=>{
    instance?.proxy?.$loading.hide()
  },5000)



  async function Prom(){
    console.log("Y");
    await Promise.resolve()
    console.log("X")
  }

  setTimeout(() => {
    console.log(1)
    Promise.resolve().then(()=> {
      console.log(2)
    })
  })

  setTimeout(() => {
    console.log(3)
    Promise.resolve().then(()=> {
      console.log(4)
    })
  })

  Promise.resolve().then(()=> {
    console.log(5)
  })

  Promise.resolve().then(()=> {
    console.log(6)
  })

  Promise.resolve().then(()=> {
    console.log(7)
  })

  Prom()
  console.log(0);

nextTick用法


  const message = ref<string>("jth")
  const div = ref<HTMLElement>()

  const change = async () => {
    message.value = "jth is haitai"
    await nextTick()
    console.log(div.value?.innerText)
  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值