Vue3学习笔记

171 阅读6分钟

1. vue3 各种 API 使用注意点

1.1 vue3 获取 dom

import {ref, onMounted} from "vue"
onMounted(() => {
  const dom = ref()
  dom.value.innerHTML     // 获取 dom 属性
})

获取 dom 属性的代码需要写在 setup 生命周期之后,否则获取不到 dom

1.2 ref 和 shallowRef 的区别

ref 是【深层次】的响应,而 shallowRef 是【浅层次】的响应,也就是说修改了用 shallowRef 定义的对象的【其中的属性值】时,视图并不会发生变化,如果修改一整个对象还是会触发视图更新的。需要注意的是,ref 定义的变量 不能和 shallowRef 定义的变量一起使用,会触发更新。

const obj = shallowRef({ name: 123 })
obj.value.name = 321        // 不会触发视图更新
obj.value = { name: 321 }   // 会触发视图更新

1.3 triggerRef

强制更新依赖,有点类似于 vue2 强制更新视图,使用方式是:triggerRef(variable)。 上面为什么说 ref 定义的变量 不能和 shallowRef 定义的变量一起使用会触发更新呢,因为 ref 底层会调用这个 API

1.4 customRef

自定义响应式函数

function MyRef<T>(value: T) {
  return customRef((track, trigger) => {
    return {
      get () {
        track()     // 
        return value
      },
      set(newValue: T) {
        value = newValue
        trigger()
      }
    }
  })
}

const c = MyRef(2)
++c.value

1.5 reactive

与 ref 不同的是:

  • ref 支持所有类型;取值、赋值都需要添加 .value
  • reactive 只支持引用类型 Array Object Map Set;取值、赋值都不需要添加 .value;proxy 不能直接赋值,否则破 坏对象的响应式 ,解决方法1:数组直接用 push + 解构(list.push(...newList)),解决方法2:把数组作为一个属性去赋值

1.6 toRef

toRef 用于对响应式对象的其中一个属性赋值给单独的一个变量,且这个变量也是响应式的,非响应式对象使用 toRef 则还是非响应式变量

const obj = reactive({ name: 'a', age: 12 })
const name = toRef(obj, 'name')

1.7 toRefs

toRefs 用于对响应式对象的所有属性 解构 出来

const obj = reactive({ name: 'a', age: 12 })
const { name, age } = toRefs(obj)

1.8 toRaw

toRaw 用于解除响应式对象,变成普通对象

const obj = reactive({ name: 'a', age: 12 })
const newObj = toRaw(obj)
// 或者
const newObj2 = obj['__v_raw']

1.9 watch 监听器

vue3的 【watch】 监听器有一定的变化:

  1. 其中监听 【对象】 时,其旧值跟新值是一样的,也就是无法捕获到旧值;
  2. 监听使用【ref】定义的对象需要添加配置项【deep: true】,而监听使用【reactive】的对象则不需要;
  3. 只监听对象中的其中一个属性时,需要用一个【函数返回其对象属性】,例如 watch(() => obj.foo.name, (newVal, oldVal) => {})
  4. watch 配置项里有一项 -- flush,有三个值【pre】-- 组件更新之前调用,【sync】-- 同步执行,【post】-- 组件更新之后执行

1.10 watchEffect 高级监听器

这是一个新的监听器,可以实现和普通 watch 监听器一样的功能,但是写法更简单,其特点如下:

  1. 只要回调中有使用到的 响应式变量 均可以监听到其值的改变
  2. 还可以在回调中传入一个 参数(也是一个回调函数),其可以在监听之前先执行(首次监听不会执行)
  3. 整个 watchEffect 有一个返回值,是一个函数,可以执行这个函数达到停止监听的作用
  4. 配置项里有一项 -- flush,有三个值【pre】-- 组件更新之前调用,【sync】-- 同步执行,【post】-- 组件更新之后执行
  5. 配置项中有一项 -- onTrigger,此函数用来调试,在监听触发时启动,可以监听到整个 watchEffect 的内容
const obj = reactive({ name: 'a', age: 12 })
const stop = watchEffect((onInvalidate) => {
  console.log('obj.name===>', obj.name)
  if (obj.name === 'a111') stop()   // 执行stop即可停止监听,而 onInvalidate 却会再执行最后一次
  onInvalidate(() => {    // 无论此回调的编写顺序在其他代码之后也会优先执行,但首次监听不会被执行
    console.log('I am do the first.')
  })
}, {
  flush: 'post',
  onTrigger (e) {
    console.log(e)
    debugger
  }
})

1.11 生命周期

  1. beforeCreate create 在 setup 语法糖模式是没有这两个生命周期的,setup 代替了
  2. onBeforeMount 读不到 dom,onMounted 可以读取到 dom
  3. onBeforeUpdate 获取的是更新之前的 dom ,onUpdated 获取更新之后的 dom
  4. onRenderTracked 和 onRenderTriggered 是两个调试API,回调中可以传递一个参数,用于展示组件渲染和更新时组件的状态
  5. getCurrentInstance 可以获取当前组件的实例

1.12 父子组件通信

  1. 父传子:props
// 没有ts的写法
const props = defineProps(['name'])
const props = defineProps({
  name: {
    type: String,
    default: '123'
  }
})
// 有ts的写法,withDefaults 方法可以给 props 设置默认值
interface Props {
  name: string
}
const props = withDefaults(defineProps<Props>(), 
  name: '123'
)
console.log(props.name)
  1. 子传父:emit
// 没有ts的写法
const emit = defineEmits(['on-click'])

// 有ts的写法
interface Emits {
  // 事件名,传参,无返回值
  (e: 'on-click', data: string): void
}
const emit = defineEmits<Emits>()
// 调用
emit('on-click', '123')

2. 常用vue3插件

2.1 mitt 全局事件总线

2.1.1 安装
npm install mitt -S
2.1.2 配置
// main.ts
import mitt from 'mitt'
const Mit = mitt()
const app = createApp(App)
app.config.globalProperties.$Bus = Mit
declare module 'vue' {    // 配置提示
  export interface ComponentCustomPropertied {
    $Bus: typeof Mit
  }
}
2.1.3 使用
// ChildA.vue
<script setup lang="ts">
  import { ref, getCurrentInstance } from 'vue'
  const instance = getCurrentInstance()   // 获取当前对象实例
  const num = ref<number>(0)
  instance.proxy.$Bus.emit('addNum', num)   // 发布事件
</script>

// ChildB.vue
<script setup lang="ts">
  import { ref, getCurrentInstance } from 'vue'
  const instance = getCurrentInstance()   // 获取当前对象实例
  const num1 = ref<number>(0)
  instance.proxy.$Bus.on('addNum', (num: number) => num1.value = num) // 订阅事件
  instance.proxy.$Bus.off('addNum')   // 删除某个事件
  instance.proxy.$Bus.all.clear()   // 删除所有事件
</script>

2.2 unplugin-auto-import 自动引入 vue 组合式API(ref, reactive ... )

2.2.1 安装

npm install -D unplugin-auto-import

2.2.2 配置

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'  // [/vite] 是在【vite】构建工具中使用,【webpack】使用 [/webpack]
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      import: ['vue'],
      dts: 'src/auto-import.d.ts'
    })
  ]
})

2.2.3 配置完以上代码就可以在整个项目中随意使用 vue 的组合式API,而不需要手动引入

3. vue3 新特性

3.1 Css新特性

// 父组件
<script lang="ts" setup>
import CssNew from '@/components/HelloWorld2/cssNew/cssNew.vue'
</script>
<template>
  <div class="container">
    <CssNew>
      <div class="cssNewDiv">123123</div>
      <div>321321</div>
    </CssNew>
  </div>
</template>
<style scoped lang="scss">
// 全局样式
:global(div) {
  color: red;
}
</style>

// 子组件
<script lang="ts" setup>
// 动态Css
import { ref, useCssModule } from 'vue'
type Style = {
    background: string,
    border: string
}
const style: Style = ref({
    background: 'transparent',
    border: 'none'
})
setTimeout(() => {
    style.value.background = 'gray'
    style.value.border = '1px solid #666'
}, 2000)

// cssModule 形式
const cssModule = useCssModule('cssM')
console.log(cssModule)
</script>
<template>
  <div class="cssNew">
    <slot></slot>

    <div :class="[cssModule.color, cssModule.border]">
        <p>cssModule</p>
    </div>
  </div>
</template>
<style scoped lang="scss">
    .cssNew {
        background: v-bind('style.background');
        border: v-bind('style.border');
        transition: all .5s;
    }

   //:slotted  插槽样式 
   :slotted(.cssNewDiv) {
        color: pink;
    }
</style>
<style module="cssM">
.color {
    color: blue;
}
.border {
    border: 1px solid #666;
}
</style>

4. H5适配

利用 postcss 来实现对H5的适配

4.1 修改 index.html 文件,添加如下样式

  <style>
    html, body, #app {
      height: 100%;
      overflow: hidden!important;
    }
    * {
      padding: 0;
      margin: 0;
    }
    /**
      px 固定的单位,不会随着屏幕的变化而变化
      rem r = root  1rem = html { font-size: 16px } 1rem = 16px
      375屏幕 适合多少html-font size 引入淘宝的flexible.js
      vw vh 相对于视口宽高进行控制的 375屏幕 1vw = 3.75px
      百分比是相对于父元素的 viewport 是相对于视口
      UI 设计稿给的 px   px to viewport 插件
    */
  </style>

4.2 修改 tsconfig.json 文件,添加如下两行配置

{
  "compilerOptions": {
    "noImplicitAny": false
  },
  "include": [ "plugins/**/*"]
}

4.3 在根目录下创建【plugins】文件夹,并在此文件夹中新建【postcss-px-to-viewport.ts】文件

// postcss 的插件在 vite 内置了 postCss
import { Plugin } from 'postcss'
const Options = {
    viewportWidth: 375      // 设计稿默认 375px
}
type Options = {
    viewportWidth?: number
}
export const PostCssPxToViewport = (options: Options = Options): Plugin => {
    const opt = Object.assign({}, Options, options)
    return {
        postcssPlugin: 'postcss-px-to-viewport',
        // 钩子函数
        Declaration(node) {
            if (node.value.includes('px')) {
                console.log(node.prop, node.value)
                const num = parseFloat(node.value)
                node.value = `${((num / opt.viewportWidth) * 100).toFixed(2)}vw`
            }
        }
    }
}

4.4 修改 vite.config.ts 文件,添加如下代码

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { PostCssPxToViewport } from './plugins/postcss-px-to-viewport'
export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/bem.scss";`
      }
    }
  }
})

5. 其他

5.1 对于vue3赋予 ref,reactive 类型的变量

在浏览器中打印时需要一层层点开,可以通过控制台右上角的【设置】按钮,勾选【启用自定义格式化程序】,之后打印的 ref 或 reactive 变量就不用一层层点开查看value值