vue3学习笔记(总)——ts+组合式API(setup语法糖)

文章目录

    • 1. ref全家桶
      • 1.1 ref()
      • 1.2 isRef()以及isProxy()
      • 1.3 shallowRef()
      • 1.4 triggerRef()
      • 1.5 customRef()
      • 1.6 unref()
    • 2. reactive全家桶
      • 2.1 reactive()
      • 2.2 readonly()
      • 2.3 shallowReactive() 和 shallowReadonly()
    • 3. to系列全家桶
      • 3.1 toRef()
      • 3.2 toRefs()
      • 3.3 toRaw()
    • 4. computed计算属性
        • 案例:购物车总价
    • 5. watch监听属性
      • 5.1 watch()
      • 5.2 watchEffect()
      • 5.3 总结
    • 6. 组件
      • 6.1 组件的生命周期
      • 6.2 全局组件的注册以及批量注册
      • 6.3 defineProps(父给子传值)
          • 响应性语法糖
      • 6.4 defineEmits(子给父传值)
      • 6.5 defineExpose
      • 6.6 递归组件
      • 6.7 动态组件
        • markRaw
    • 7. 插槽
      • 7.1 匿名插槽
      • 7.2 具名插槽
      • 7.3 动态插槽
      • 7.4 作用域插槽
    • 8. 内置组件
      • 8.1 异步组件&代码分包&suspense
        • 顶层 await
        • 异步组件以及defineAsyncComponent()方法
        • Suspense
        • 案例可见:vue3笔记案例——Suspense使用之骨架屏
      • 8.2 Transition&TransitionGroup动画组件
      • 8.3 Teleport传送组件
        • 基本使用
        • 案例可见:vue3笔记案例——Teleport使用之模态框
        • 禁用 Teleport
      • 8.4 KeepAlive缓存组件
        • 包含/排除(include/exclude)
        • 最大缓存实例数(max)
        • 缓存实例的生命周期
        • 案例:
    • 9. 依赖注入(Provide/Inject)
      • 9.1 Provide(提供)
      • 9.2 Inject (注入)
        • 注入默认值
        • 和响应式数据配合使用
          • 案例
      • 9.3 使用 Symbol 作注入名
    • 10. 兄弟组件传参以及Mitt
      • 10.1 event-bus
      • 10.2 Mitt
    • 11. TSX
    • 12. v-model
      • 12.1 组件中的v-model
        • 案例:v-model的实现
      • 12.2 内置修饰符
        • .lazy
        • .number
        • .trim
      • 12.3 自定义修饰符Modifiers
        • 基本使用
    • 13. 全局API
      • 13.1 app.config.globalProperties
      • 13.2 nextTick()
        • EventLoop
    • 14. 自定义指令
      • 14.1 指令钩子
        • 钩子参数
      • 14.2 简写形式
        • 案例:简单实现权限指令dome
      • 案例:自定义指令实现拖拽效果
    • 15. 组合式函数——“vue的hooks”
      • 15.1 基本使用
        • 命名
        • 输入参数
        • 返回值
    • 16. 插件
    • 17. 样式穿透及CSS 新特性
    • 18. h函数
      • 案例
    • 19. 环境变量及proxy代理
      • 19.1 环境变量
      • 19.2 proxy代理

1. ref全家桶

1.1 ref()

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

type M = {
  name: string,
}

const msg1: Ref<string> = ref('字符串')
// const msg1 = ref('字符串')
const msg2 = ref<M>({name: '多多'})

const changeMsg = () =>{
  msg1.value = '已修改'
  msg2.value.name = '小多改变了'
}
  • ref也可以获取dom属性
<div ref="dom">dom内容div>
// 名字要与ref绑定的名字一样
const dom = ref<HTMLElement | null>(null)

const changeMsg = () => {
  console.log('dom.value?.innerText :>> ', dom.value?.innerText);
  console.log('dom :>> ', dom)
}

在这里插入图片描述

1.2 isRef()以及isProxy()

  • isRef:检查某个值是否为 ref
  • isProxy:检查一个对象是否是由 reactive()readonly()shallowReactive()shallowReadonly() 创建的代理。

1.3 shallowRef()

ref() 的浅层作用形式。

type M = {
  name: string,
}

const msg2 = shallowRef<M>({name: '多多'})

const changeMsg = () =>{
  // msg2.value.name = '小多改变了' // 视图不会改变
  msg2.value = {
    name: '改变了'
  }
}

1.4 triggerRef()

强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。强制更新

注意: ref()和shallowRef()不能一块写,不然会影响shallowRef 造成视图更新

const msg1 = ref('字符串')
const msg2 = shallowRef({name: '多多'})

const changeMsg = () =>{
  msg1.value = '改变了'
  msg2.value.name = '小多改变了,被影响' // 视图也会改变
}

由于 ref底层调用了triggerRef(),所以会造成视图的强制更新

const msg2 = shallowRef({name: '多多'})

const changeMsg = () =>{
  msg2.value.name = '小多改变了'
  triggerRef(msg2)	// 视图强制更新了
}

1.5 customRef()

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

主要应用是:防抖

import { customRef } from 'vue'
function MyRef<T>(value: T, delay = 500) {
  let timer: any
  return customRef((track, trigger) => {
    return {
      get() {
        track() /* 收集依赖 */
        return value
      },
      set(newVal) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          console.log('触发了');
          value = newVal
          timer = null
          trigger() /* 触发依赖,视图更新 */
        }, delay)
      },
    }
  })
}
const msg1 = MyRef<string>('字符串')
// const msg1 = ref('字符串')
const msg2 = MyRef({ name: '多多' })

const changeMsg = () => {
  // msg1.value = '小多改变了'
  msg2.value = {
    name: '改变'
  }
}

1.6 unref()

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

类型

function unref<T>(ref: T | Ref<T>): T

示例

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x)
  // unwrapped 现在保证为 number 类型
}

2. reactive全家桶

2.1 reactive()

返回一个对象的响应式代理。

interface Msg = {
	name: string
}
// ref 支持所有类型,reactive 只支持引用类型 Array Object Map Set...
// ref 取值赋值都需要添加.value	reactive 不需要添加.value
const msg1 = ref({name: 'ref---多多'})
const msg2:Msg = reactive({ name: 'reactive---多多' })
// 不推荐
// const msg2 = reactive({ name: 'reactive---多多' })

const changeMsg = () => {
  msg1.value.name = 'ref---小多'
  msg2.name = 'reactive---小多'
}


  • reactive proxy 不能直接赋值,否则会破坏响应式对象
  • 不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。

解决方案:1. 数组可以使用push加解构

<template>
  <el-button @click="add">添加el-button>
    <hr class="mtb20" />
    <ul>
      <li :key="index" v-for="(item, index) in list">{{ item.name }}li>
    ul>
template>

<script setup lang="ts">
import { reactive } from 'vue'

type List = {
  name: string
}

let list: List[] = reactive([])

const add = () => {
  // 模拟后端获取数据
  setTimeout(() => {
    let res: List[] = [
      { name: '多多' },
      { name: '小多' },
      { name: '凡凡' },
      { name: '小凡' },
    ]
    list.push(...res)
  }, 1000)
}
script>

<style lang="less" scoped>style>

解决方案: 2. 变成一个对象,把数组作为一个属性去解决

<template>
  <el-button @click="add">添加el-button>
    <hr class="mtb20" />
    <ul>
      <li :key="index" v-for="(item, index) in list.arr">{{ item.name }}li>
    ul>
template>

<script setup lang="ts">
import { reactive } from 'vue'

type List = {
  name: string
}

let list: {arr: List[]} = reactive({
  arr: []
})

const add = () => {
  // 模拟后端获取数据
  setTimeout(() => {
    let res: List[] = [
      { name: '多多' },
      { name: '小多' },
      { name: '凡凡' },
      { name: '小凡' },
    ]
    list.arr = res
  }, 1000)
script>

<style lang="less" scoped>style>

2.2 readonly()

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

// readonly 无法更改只读, 但会受原始数据的影响,原始数据改变则相应改变
let msg1 = reactive({ name: '改变' })

const change = () => {
  let copy = readonly(msg1)
  msg1.name = '1111'
  // copy.name = '2222' // 无法更改
  console.log('msg1,copy :>> ', msg1, copy)
}

2.3 shallowReactive() 和 shallowReadonly()

  • shallowReactivereactive() 的浅层作用形式
  • shallowReadonlyreadonly() 的浅层作用形式

3. to系列全家桶

只对响应式对象有效果,对普通对象无效

3.1 toRef()

基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

let msg1 = reactive({ name: '多多', age: 18 })
let age = toRef(msg1, 'age')

const edit = () => {
  age.value++
}

应用场景: useDemo(value) 需要一个属性,但定义的是对象,则可以单独把属性取出来使用,而不破坏属性的响应性

3.2 toRefs()

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

let msg1 = reactive({ name: '多多', age: 18 })

// toRefs源码类似
const myTORefs = <T extends object>(object: T) => {
  const map: any = {}
  for (const key in object) {
    map[key] = toRef(object, key)
  }
  return map
}

// let { name, age } = msg1 /* 直接解构 不具备响应性,更改不会造成视图更新 */
let { name, age } = toRefs(msg1)  /* 使其解构的属性具备响应性 */

const edit = () => {
  name.value = '小多'
  age.value++
}

3.3 toRaw()

根据一个 Vue 创建的代理返回其原始对象。

console.log('msg1, toRaw(msg1) :>> ', msg1, toRaw(msg1));

在这里插入图片描述

4. computed计算属性

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

  1. 函数形式
let price ref<number>(0)
let m = computed<string>(()=>{
   return `$` + price.value
})
  1. 对象形式
let price = ref<number | string>(1)//$0
let mul = computed({
   get: () => {
      return price.value
   },
   set: (value) => {
      price.value = 'set' + value
   }
})

案例:购物车总价

<template>
  <table>
    <thead>
      <tr>
        <th align="center">名称th>
        <th align="center">数量th>
        <th align="center">价格th>
        <th align="center">操作th>
      tr>
    thead>
    <tbody>
      <tr :key="index" v-for="(item, index) in shop">
        <td align="center">{{ item.name }}td>
        <td align="center">
          <button @click="addOrSub(item, false)">-button> {{ item.num }}
          <button @click="addOrSub(item, true)">+button>
        td>
        <td align="center">{{ item.price * item.num }}td>
        <td align="center"><button @click="del(index)">删除button>td>
      tr>
    tbody>
    <tfoot>
      <td>td>
      <td>td>
      <td>td>
      <td>总价:{{ $total }}td>
    tfoot>
  table>
template>

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

type Shop = {
  name: string
  price: number
  num: number
}

const shop = reactive<Shop[]>([
  {
    name: '苹果',
    price: 10,
    num: 1,
  },
  {
    name: '蛋糕',
    price: 20,
    num: 1,
  },
  {
    name: '面包',
    price: 5,
    num: 1,
  },
])
let $total = ref<number>(0)

$total = computed<number>(() => {
  return shop.reduce((prev, next) => {
    return prev + next.num * next.price
  }, 0)
})

const addOrSub = (item: Shop, flag: boolean): void => {
  if (item.num > 0 && !flag) {
    item.num--
  }
  if (item.num < 99 && flag) {
    item.num++
  }
}

const del = (index: number) => {
  shop.splice(index, 1)
}
script>

<style lang="less" scoped>
table,
tr,
td,
th {
  border: 1px solid #ccc;
  padding: 20px;
}
style>

5. watch监听属性

详情可了解:Vue3:watch 的使用场景及常见问题

5.1 watch()

  • 第一个参数是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

  • 第二个参数是cb回调函数:(newVal,oldVal,onCleanup)

  • 第三个参数是options配置项(一个对象):

    deep: true // 是否开启深层监听
    immediate: true // 是否立即调用一次
    flush: 'pre ’ | ‘sync’ | ‘post’ // 更新时机
    onTrack:函数,具备 event 参数,调试用。将在响应式 property 或 ref 作为依赖项被追踪时被调用
    onTrigger:函数,具备 event 参数,调试用。将在依赖项变更导致副作用被触发时被调用。

ref监听深层属性需要开启深层监听,深层监听引用类型旧值与新值一样

reactive,隐性开启深层监听

监听属性单一值,需将其变为getter 函数

注意: 深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。

import { watch, reactive, ref } from 'vue'

let msg1 = reactive({
  one: {
    two: {
      three: '内容',
    },
  },
})
let msg2 = ref<string>('测试')
let msg3 = ref<string>('多多')
let msg4 = ref(1)
let msg5 = ref(2)

watch(
  ()=> msg1.one.two.three,
  (newVal, oldVal) => {
    console.log('newVal, oldVal :>> ', newVal, oldVal)
  }
)

watch(
  [msg2, msg3],
  (newVal, oldVal) => {
    console.log('newVal, oldVal :>> ', newVal, oldVal)
  }
)
watch(
  [msg3, ()=> msg4.value + msg5.value],
  (newVal, oldVal) => {
    console.log('newVal, oldVal :>> ', newVal, oldVal)
  }
)

在这里插入图片描述

onCleanup: onCleanup 接受一个回调函数,这个回调函数,在触发下一次 watch 之前会执行,因此,可以在这里,取消上一次的网络请求,亦或做一些内存清理及数据变更等任何操作。
作用场景: 监听数据变化发起网络请求时

let count = 2;
const loadData = (data) =>
  new Promise((resolve) => {
    count--;
    setTimeout(() => {
      resolve(`返回的数据为${data}`);
    }, count * 1000);
  });

// 此时如果直接监听,两次数据变更时间太短,导致最后页面展示的data数据更新为 ’返回的数据为李四‘
// 原因:数据每次变化,都会发送网络请求,但是时间长短不确定,所以就有可能导致,后发的请求先回来了,所以会被先发的请求返回结果给覆盖掉。
setTimeout(() => {
  state.name = '李四';
}, 100);
setTimeout(() => {
  state.name = '王五';
}, 200);

// 第二次更新时间在第一次网络请求结束之前
watch(
  () => state.name,
  (newValue, oldValue, onCleanup) => {
    let isCurrent = true;
    onCleanup(() => {
      // 在下次监听更新之前执行
      isCurrent = false;
    });
    // 模拟网络请求
    loadData(newValue).then((res) => {
      // 取消上次网络请求,上次网络请求还没完成就将isCurrent设置为false, 则不会变成第一次网络请求的结果,顺序执行第二次监听的结果
      if (isCurrent) {
        data.value = res;
      }
    });
  }
);

5.2 watchEffect()

watch() 是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。

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

pre sync post
更新时机 组件更新前执行 强制效果始终同步触发 组件更新后执行

其他配置项:onTrack函数,onTrigger函数

let msg1 = ref('多多测试')
let msg2 = ref('小多')

watchEffect(() => {
  console.log('watchEffect监听 : 默认执行顺序等同于开启立即执行的watch');
  const dom1 = document.querySelector('#dom1')
  console.log('dom1 :>> ', dom1)
  console.log('msg1 :>> ', msg1)
})

watchEffect(() => {
  console.log('watchEffect监听 : flush: "post"');
  const dom1 = document.querySelector('#dom1')
  console.log('post组件更新后执行dom1 :>> ', dom1)
}, {
  flush: 'post'
})

watch(
  msg1,
  (newVal, oldVal) => {
    console.log('watch监听 : ');
    const dom1 = document.querySelector('#dom1')
    console.log('dom1 :>> ', dom1)
    console.log('newVal,oldVal :>> ', newVal, oldVal)
  },
  {
    immediate: true,
  }
)

vue3学习笔记(总)——ts+组合式API(setup语法糖)_第1张图片

  1. watchEffect 默认监听,也就是默认第一次就会执行;
  2. 不需要设置监听的数据,在 effect 函数中,用到了哪个数据,会自动进行依赖,因此不用担心类似 watch 中出现深层属性监听不到的问题;
  3. 只能获取到新值,由于没有提前指定监听的是哪个数据,所以不会提供旧值。

watchEffect监听可能出现的问题:
在异步任务(无论是宏任务还是微任务)中进行的响应式操作,watchEffect 无法正确的进行依赖收集。所以后面无论数据如何变更,都不会触发 effect 函数。

vue3学习笔记(总)——ts+组合式API(setup语法糖)_第2张图片

解决方法:
如果真的需要用到异步的操作,可以在外面先取值,再放到异步中去使用

vue3学习笔记(总)——ts+组合式API(setup语法糖)_第3张图片
清除副作用

watchEffect((onInvalidate) => {
  console.log('msg1 :>> ', msg1)
  onInvalidate(() => {
    // 第一次不执行
    console.log('before')
  })
})

vue3学习笔记(总)——ts+组合式API(setup语法糖)_第4张图片

停止监听
要手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数:

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

// ...当该侦听器不再需要时
unwatch()

5.3 总结

  1. 当监听 Reactive 数据时:
    • deep 属性失效,会强制进行深度监听;
    • 新旧值指向同一个引用,导致内容是一样的。
  2. watchsourceRefImpl 类型时:
    • 直接监听 state 和 监听 () => state.value 是等效的;
    • 如果 ref 定义的是引用类型,并且想要进行深度监听,需要将 deep 设置为 true。
  3. watchsource 是函数时,可以监听到函数返回值的变更。如果想监听到函数返回值深层属性的变化,需要将 deep 设置为 true
  4. 如果想监听多个值的变化,可以将 source 设置为数组,内部可以是 Proxy 对象,可以是 RefImpl 对象,也可以是具有返回值的函数;
  5. 在监听组件 props 时,建议使用函数的方式进行 watch,并且希望该 prop 深层任何属性的变化都能触发,可以将 deep 属性设置为 true
  6. 使用 watchEffect 时,注意在异步任务中使用响应式数据的情况,可能会导致无法正确进行依赖收集。如果确实需要异步操作,可以在异步任务外先获取响应式数据,再将值放到异步任务里进行操作。

6. 组件

6.1 组件的生命周期

vue3学习笔记(总)——ts+组合式API(setup语法糖)_第5张图片

  1. beforeCreatecreated 两个生命周期在setup语法糖模式是没有的,用setup去代替
  2. onBeforeMount 时读不到dom元素,onMouted 以及之后的生命周期可以读取到dom元素。
  3. onBeforeUpdate 获取的是更新之前的dom,onUpdated 获取的是更新之后的dom
  4. onRenderTrackedonRenderTriggerd 用于调试,获取收集依赖

vue3学习笔记(总)——ts+组合式API(setup语法糖)_第6张图片

6.2 全局组件的注册以及批量注册

全局注册

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import MyComponent from './MyComponent .vue'
...

const app = createApp(App)
// 全局注册
app.component('MyComponent', MyComponent)

...
app.mount('#app')

批量注册:例如elmUI的icon

// main.ts

// 如果您正在使用CDN引入,请删除下面一行。
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

6.3 defineProps(父给子传值)


<A :list="[111, 222, 333]" :msg="msgFather">A>
<el-divider> 无传递值 el-divider>
<A>A>
// ts版本
// const props = defineProps<{msg:string}>() /* 不需要定义默认值时 */
// 定义默认值需要使用 withDefaults --- ts专有的
const props = withDefaults(defineProps<{ msg: string, list: number[] }>(), {
  msg: '默认值',
  list: () => []
})

// js版本
// const props = defineProps({
//   msg: {
//     type: String,
//     default: '默认值'
//   },
//   list: {
//     type: Array,
//     default: () => []
//   }
// })
// ts
// 也可以将类型声明提取出来,传递数据多时推荐
type Props = { msg: string, list: number[] }

const props = withDefaults(defineProps<Props>(), {
  msg: '默认值',
  list: () => []
})

// 也可以使用响应性语法糖结构默认值 ---目前为实验性的需要显式启用
const { msg = '默认值', list= []} = defineProps<Props>()
响应性语法糖

响应性语法糖

6.4 defineEmits(子给父传值)

你可能感兴趣的:(vue,学习,javascript,vue.js)