vue3 自己总结

378 阅读5分钟

vue3官网

cn.vuejs.org/guide/intro…


vue3 全局传参

vue3.chengpeiquan.com/communicati…

创建 3.x 的 EventBus

这里以 mitt 为例,示范如何创建一个 Vue 3 的 EventBus 。

首先,需要安装 mitt :
npm install --save mitt
然后在 libs 文件夹下,创建一个 bus.ts 文件,内容和旧版写法其实是一样的,只不过是把 Vue 实例,换成了 mitt 实例。
import mitt from 'mitt';

export default mitt();

然后就可以定义发起和接收的相关事件了,常用的 API 和参数如下:

方法名称作用
on注册一个监听事件,用于接收数据
emit调用方法发起数据传递
off用来移除监听事件

on 的参数:

参数类型作用
typestringsymbol方法名
handlerfunction接收到数据之后要做什么处理的回调函数

这里的 handler 建议使用具名函数,因为匿名函数无法销毁

emit 的参数:

参数类型作用
typestringsymbol与on对应的方法名
dataany与on对应的,允许接收数据

off 的参数:

参数类型作用
typestringsymbol与on对应的方法名
handlerfunction要删除的,与on对应的handler函数名

使用

创建和移除监听事件

在需要暴露交流事件的组件里,通过 on 配置好接收方法,同时为了避免路由切换过程中造成事件多次被绑定,多次触发,需要在适当的时机 off 掉:

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

<script setup>

import moduleVue from './module/index.vue'

import { defineComponent, onBeforeUnmount } from 'vue'

import bus from '../../utils/bus.js'
// 定义一个打招呼的方法
const sayHi = (msg = 'Hello World!') => {
    console.log(msg, 'msg');
}

// 启用监听
bus.on('sayHi', sayHi);

// 在组件卸载之前移除监听
onBeforeUnmount(() => {
    bus.off('sayHi', sayHi);
})

</script>

调用监听事件

在需要调用交流事件的组件里,通过 emit 进行调用:

<template>

    <div class="ch" @click="sayHi">
        子组件
    </div>
    
</template>

<script setup>

import bus from '@/utils/bus.js'

function sayHi () {
    bus.emit('sayHi');
}

</script>

<style lang="scss">

    .ch{
         font-size: 30px;
         color: #fff;
    }
    
</style>

在 Vue 3 的 EventBus,我们可以看到它的 API 和旧版是非常接近的,只是去掉了 $ 符号。

如果你要对旧的项目进行升级改造,因为原来都是使用了 onon 、 emit 等旧的 API ,一个一个组件去修改成新的 API 肯定不现实。

我们可以在创建 bus.ts 的时候,通过自定义一个 bus 对象,来挂载 mitt 的 API 。

在 bus.js 里,改成以下代码:

import mitt from 'mitt';

// 初始化一个 mitt 实例
const emitter = mitt();

// 定义一个空对象用来承载我们的自定义方法
const bus: any = {};

// 把你要用到的方法添加到 bus 对象上
bus.$on = emitter.on;
bus.$emit = emitter.emit;

// 最终是暴露自己定义的 bus
export default bus;

全局绑定
import mitt from 'mitt';

const vue = createApp(App)

// 把插件的 API 挂载全局变量到实例上
vue.config.globalProperties.$mitt = mitt();

<template>

    <div>
        <moduleVue />
    </div>

</template>

<script setup>
import moduleVue from './module/index.vue'

import {  onBeforeUnmount, getCurrentInstance } from 'vue'

import bus from '../../utils/bus.js'

const app = getCurrentInstance()

// 定义一个打招呼的方法
const sayHi = (msg = 'Hello World!') => {
    console.log(msg, 'msg');
}

// 启用监听
app.appContext.config.globalProperties.$mitt.on('sayHi', sayHi);

// 在组件卸载之前移除监听
onBeforeUnmount(() => {
    bus.off('sayHi', sayHi);
})

</script>

<!--子组件-->

<template>

    <div class="ch" @click="sayHi">
        子组件
    </div>
</template>

<script setup>

import { defineComponent, onBeforeUnmount, getCurrentInstance } from 'vue'

import bus from '@/utils/bus.js'

const app = getCurrentInstance()

function sayHi () {
    app.appContext.config.globalProperties.$mitt.emit('sayHi');
}

</script>

<style lang="scss">

    .ch{
        font-size: 30px;
        color: #fff;
    }
    
</style>

子父传参 defineEmits

defineEmits() 宏不能在子函数中使用,它必须直接放置在

    // 子组件
    
    const Emit = defineEmits(['closeFn'])
    
    let defineEmit = (value) => {
    
      Emit('closeFn', value)
      
    }
    
    
    // 父组件
    
    <component @closeFn="closeFn" />
    
    function closeFn () {
    
        dialogVisible.value = false
        
    }
    
事件校验
  • 和对 props 添加类型校验的方式类似,所有触发的事件也可以使用对象形式来描述。

  • 要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 emit 的内容,返回一个布尔值来表明事件是否合法。

<script setup>
    
    const emit = defineEmits({
    
      // 没有校验
      click: null,
    
      // 校验 submit 事件
      submit: ({ email, password }) => {
      
        if (email && password) {
        
          return true
          
        } else {
        
          console.warn('Invalid submit event payload!')
         
          return false
          
        }
        
      }
      
    })
    
    function submitForm(email, password) {
    
      emit('submit', { email, password })
      
    }
    
    </script>
    

父子传参 defineProps

defineProps({

  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  
  // 多种可能的类型
  propB: [String, Number],
  
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  
  // Number 类型的默认值
  propD: {
    type: Number,
    default: 100
  },
  
  // 对象类型的默认值
  propE: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  
  // 自定义类型校验函数
  propF: {
    validator(value) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  
  // 函数类型的默认值
  propG: {
    type: Function,
    // 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
  
})

祖先传值

Provide (提供)
  • provide 和 inject 可以帮助我们解决这一问题。 [1] 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖
    // 要为组件后代提供数据,需要使用到 provide() 函数:
    
    <script setup>
    
        import { provide } from 'vue'
        
        provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
    
    </script>
    
  • provide() 函数接收两个参数。第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会用注入名来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。
    // 第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref:
    
    import { ref, provide } from 'vue'

    const count = ref(0)
    
    provide('key', count)
    
Inject (注入)
  • 如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。

    要注入上层组件提供的数据,需使用 inject() 函数:
    
    <script setup>
    
    import { inject } from 'vue'
    
    const message = inject('message')
    
    </script>
    

和响应式数据配合使用# 当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。

有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数:

<!-- 在供给方组件内 -->
<script setup>

import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})

</script>


<!-- 在注入方组件 -->
<script setup>
    
    import { inject } from 'vue'
    
    const { location, updateLocation } = inject('location')
    
</script>
    
<template>

    <button @click="updateLocation">{{ location }}</button>
    
</template>

  • 最后,如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。
<script setup>

import { ref, provide, readonly } from 'vue'

const count = ref(0)

provide('read-only-count', readonly(count))

</script>

Vue3中获取 store 实例对象的方法

blog.csdn.net/qq_45934504…

vue2 中可以通过 this.$store.xxx 的方式拿到 store 的实例对象。

vue3 中的 setup 在 beforecreate 和 created 前执行,此时 vue对象还未被创建,没有了之前的this,所以此处我们需要用到另一种方法来获取到 store 对象。

 import { useStore } from 'vuex'

 必须先声明调用
 const store = useStore()
	
 获取Vuex的state
 store.state.xxx

 触发mutations的方法
 store.commit('fnName')

 触发actions的方法
 store.dispatch('fnName')

 获取Getters
 store.getters.xxx

路由

基础跳转
<template>
  <router-link to="/home">首页</router-link>
</template>

导入路由组件
import { useRouter } from 'vuex' 

该方法用于返回router 实例
const router = useRouter()  

跳转首页
router.push({
  name: 'home'
})

返回上一页
router.back();

带参数的跳转
router.push({
  name: 'article',
  params: {
    id: 123
  }
})

watch 监听

具体看

https://vue3.chengpeiquan.com/component.html#watch

import { watch } from 'vue'

// 一个用法走天下
watch(
  source, // 必传,要监听的数据源
  callback, // 必传,监听到变化后要执行的回调函数
  // options // 可选,一些监听选项
)

// 监听proxy对象
watch(person, (newValue, oldValue) => {
  console.log("newValue", newValue, "oldValue", oldValue);
});

监听proxy数据的某个属性

需要将监听值写成函数返回形式,vue3无法直接监听对象的某个属性变化

watch(
  () => person.Hobby,
  (newValue, oldValue) => {
    console.log("newValue",newValue, "oldvalue", oldValue);
  }
);

当监听proxy对象的属性为复杂数据类型时,需要开启deep深度监听
watch(
  () => person.city,
  (newvalue, oldvalue) => {
    console.log("person.city newvalue", newvalue, "oldvalue", oldvalue);
  },{
    deep: true
  }
);

监听proxy数据的某些属性

watch([() => person.age, () => person.name], (newValue, oldValue) => {
  // 此时newValue为数组
  console.log("person.age", newValue, oldValue);
});



两个坑

监听reactive定义的proxy代理数据时
oldValue无法正确获取
强制开启deep深度监听(无法关闭)


监听reactive定义的proxy代理对象某个属性时deep配置项生效

watchEffect

如果一个函数里包含了多个需要监听的数据,一个一个数据去监听太麻烦了,在 Vue 3 ,你可以直接使用 watchEffect API 来简化你的操作。


const state = reactive({ count: 0, name: 'zs' })
watchEffect(() => {
      console.log(state.count)
      console.log(state.name)
      /*  初始化时打印:
              0
              zs

        1秒后打印:
              1
              ls
      */
      })

      setTimeout(() => {
        state.count ++
        state.name = 'ls'
      }, 1000)
watchEffect 它与 watch 的区别
不需要手动传入依赖
每次初始化时会执行一次回调函数来自动获取依赖
无法获取到原值,只能得到变化后的值

computed

// 在 Vue 3 的写法:
import { defineComponent, ref, computed } from 'vue'

export default defineComponent({
  setup() {
    // 定义基本的数据
    const firstName = ref('Bill')
    const lastName = ref('Gates')

    // 定义需要计算拼接结果的数据
    const fullName = computed(() => `${firstName.value} ${lastName.value}`)

    // 2s 后改变某个数据的值
    setTimeout(() => {
      firstName.value = 'Petter'
    }, 2000)

    // template 那边在 2s 后也会显示为 Petter Gates
    return {
      fullName,
    }
  },
})

需要注意的是:

定义出来的 computed 变量,和 ref 变量的用法一样,也是需要通过 .value 才能拿到它的值

但是区别在于, computed 的 value 是只读的

vite

环境判断

在 Webpack ,你可以使用 process.env.NODE_ENV 来区分开发环境( development )还是生产环境( production ),它会返回当前所处环境的名称。

在 Vite ,你还可以通过判断 import.meta.env.DEV 为 true 时是开发环境,判断 import.meta.env.PROD 为 true 时是生产环境(这两个值永远相反)。

unplugin-auto-import

npm i -D unplugin-auto-import

解放双手,自动导入composition api 和 生成全局typescript说明

import AutoImport from "unplugin-auto-import/vite"

export default defineConfig({
  plugins: [
    ...
     AutoImport ({
      imports: ["vue", "vue-router"], // 自动导入vue和vue-router相关函数
      dts: "src/auto-import.d.ts" // 生成 `auto-import.d.ts` 全局声明
    })
  ]
})

全局注册组件

app.component('TodoDeleteButton', TodoDeleteButton)
    
这使得 TodoDeleteButton 在应用的任何地方都是可用的
自己查看文档和自己实践 有不对的地方往提出