vue3官网
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 的参数:
| 参数 | 类型 | 作用 | |
|---|---|---|---|
| type | string | symbol | 方法名 |
| handler | function | 接收到数据之后要做什么处理的回调函数 |
这里的 handler 建议使用具名函数,因为匿名函数无法销毁
emit 的参数:
| 参数 | 类型 | 作用 | |
|---|---|---|---|
| type | string | symbol | 与on对应的方法名 |
| data | any | 与on对应的,允许接收数据 |
off 的参数:
| 参数 | 类型 | 作用 | |
|---|---|---|---|
| type | string | symbol | 与on对应的方法名 |
| handler | function | 要删除的,与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 和旧版是非常接近的,只是去掉了 $ 符号。
如果你要对旧的项目进行升级改造,因为原来都是使用了 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 实例对象的方法
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 在应用的任何地方都是可用的
自己查看文档和自己实践 有不对的地方往提出