官网:Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)
V3
V3 工程可以通过 vue-cli 脚手架进行创建,但是现在流行用 create-vue 脚手架进行创建 V3 工程
create-vue:这个脚手架是基于 vite 项目构建工具来实现的(vue-cli 是通过 webpack 项目构建工具来实现的)
vite 的特点,一个字 快,服务器启动快,修改代码之后重新响应也很快(vite 也是尤雨溪团队开发的)
要使用 vue-cli 创建 V3 工程,那么 vue-cli 的版本要求最低 4.5.0,可以使用 vue --version 查看版本,如果版本较低可以通过 npm install -g @vue-cli 来进行升级
Vite 脚手架官网:Vite中文网 (vitejs.cn)
创建工程
官方指导:快速上手 | Vue.js (vuejs.org)
在工程目录下使用创建命令:npm create vue@latest(安装vue-create脚手架并创建V3工程,如果脚手架存在的话不会重新安装)
vite 与 webpack 创建的工程有何不同
入口:index 文件放在了 public 文件夹外面,以 index.html 作为入口,不在以 main.js 作为入口
配置文件:对于 vite 构建工具来说,配置文件是 vite.config.js 他与 webpack 中的 vue.config.js 类似,配置文档 配置 Vite | Vite 官方中文文档 (vitejs.dev)
setup
setup是一个函数,V3中新增的配置项,组件中所用到的data、methods、钩子等都要配置到 setup 中
<template>
<h1>{{ name }}</h1>
<button @click="getName">输出名字</button>
</template>
<script>
export default {
name: 'App',
setup() {
// 定义的变量相当于 data
let name = '123'
// 定义的函数相当于 methods
function getName() { alert(name) }
// 想要使用对应的数据必须进行返回 以 K:V 的方式,如果返回名称一致直接使用变量名也行
return {name, getName}
}
}
</script>语法糖
在 script 标签中加入 setup 那么下面代码就不需要写了。组件导入之后不需要注册直接用
export default {
name: "User",
components: {xxx},
setup(props, context) {
return {xxx}
}
}props
父组件
<template>
<User :age="user.age" :name="user.name"/>
</template>
<script>
import User from "@/components/User.vue";
export default {
name: 'App',
// 导入组件
components: {User},
setup() {
let user = {
name: '张三',
age: 18,
}
return {user}
}
}
</script>子组件
<template>
<h1>{{ user.name }} - {{ user.age }}</h1>
</template>
<script>
export default {
name: "User",
// 需要声明 props
props: ["name", "age"],
setup(props) {
return {user: props}
}
}
</script>V3在调用 setup 函数之前会传递参数,第一个是 props。它被包装成了一个代理对象,具有响应式的 proxy 对象
生命周期
跟V2的声明周期作比较
- 生命周期最后两个钩子由原来的
beforeDestory、destoryed变为beforeUnmount、unmounted setup函数执行在beforeCreate之前
在V3中生命周期钩子函数由两种写法:选项式、组合式
选项式API:在 setup 外面写生命周期钩子(V2的写法)
组合式API:在 setup 中写生命周期钩子,并且缺少 beforeCreate、beforeed 因为 setup 代替了(如果想使用这两个钩子的话,只能采用选项式的写法)
// 从这里导入对应的钩子
import {onBeforeMount} from "vue";
export default {
// ...
setup() {
onBeforeMount(() => {
// ...
});
// ...
}
}自定义事件
父组件
<template>
<!-- 创建自定义事件 -->
<User @event="log"/>
</template>
<script>
import User from "@/components/User.vue";
export default {
name: 'App',
components: {User},
setup() {
// 事件触发时输出内容
function log(name, sex) {
alert(`姓名:${name},性别:${sex}`)
}
return {log}
}
}
</script>子组件
<template>
<button @click="trigger">点击触发自定义事件</button>
</template>
<script>
export default {
name: "User",
setup(props, context) {
function trigger() {
// 触发自定义事件 并传参
context.emit("event", "张三", "男")
}
return {trigger}
}
}
</script>setup 的第二个参数是组件的上下文对象,然后使用 emit 触发事件,第一个是事件的名称,往后所有都是传递的参数
全局事件总线
V3的全局事件总线需要借助第三方库 mitt
安装
安装:npm i mitt
然后封装一个 src/util/event-bus.js 的文件
import mitt from "mitt";
export default mitt();使用
父组件
<template>
<User/>
</template>
<script>
import User from "@/components/User.vue";
import {onMounted} from "vue";
// 导入全局事件总线对象
import emitter from './util/event-bus'
export default {
name: 'App',
components: {User},
setup() {
// 生命周期钩子 mounted
onMounted(() => {
// 给全局事件总线绑定事件 | 绑定事件 - 触发的函数
emitter.on("event", (user) => {
console.log(user.name, user.sex)
})
})
}
}
</script>子组件
<template>
<button @click="trigger">点击触发自定义事件</button>
</template>
<script>
import emitter from '../util/event-bus'
export default {
name: "User",
setup(props, context) {
function trigger() {
// 触发自定义事件 并传参
emitter.emit("event", {
name: "张三",
sex: "男"
})
}
return {trigger}
}
}
</script>在以上的代码中通过全局事件总线实现的事件的绑定与触发。但是,与自定义事件不同的是,全局事件总线传递的参数只有一个,并且也不需要在组件上进行 @事件名 了
在使用是需要导入总线对象
import emitter from './util/event-bus'绑定事件:emitter.on("事件名称", 回调函数)
触发事件:emitter.emit("事件名称", 参数)
解绑事件(全部):emitter.all.clear()
解绑事件(指定):emitter.off("事件名")
计算属性
在V3中计算属性是组合式写法,也就是得写在 setup 中
import {computed} from "vue";
export default {
name: 'App',
setup() {
// 简写
let com = computed(() => {
return xxx;
})
// 完整写法
let com = computed({
set(value) { ... },
get() { return xxx }
})
// 返回
return {com}
}
}简写:如果只需要使用计算属性的 get 方法的情况下,就可以使用简写
完整:这种写法是包含 get/set 的方法的适合在需要直接改变计算属性的情况下使用
侦听属性
被监视的属性必须具有响应式才能够监听
侦听 ref
<template>
<input type="text" v-model="name"/>
</template>
<script>
import User from "@/components/User.vue";
import {ref, watch} from "vue";
export default {
name: 'App',
components: {User},
setup() {
let name = ref("张三")
// 参数:被侦听的属性 回调函数 配置信息
watch(name, (newValue, oldValue) => {
console.log(`老数据:${oldValue} -> 新数据:${newValue}`)
}, {
// 立即执行
immediate: true,
// 深度监听
deep: true
})
return {name}
}
}
</script>在上面的 watch 写法中,如果由多个要被侦听的属性就需要写多个 watch 但是,如果某些属性执行的逻辑是相同的,可以采用数组的形式进行简写
watch([name, age], (newValue, oldValue) => {
console.log(`老数据:${oldValue} -> 新数据:${newValue}`)
}, {
// 立即执行
immediate: true,
// 深度监听
deep: true
})因为采用的是数组,那么新数据和旧数据打印的也是数组,需要获取某个属性的新老数据时,采用数组的形式获取
当 ref 包裹的是对象的时候,监视属性的位置如果填写:xxx.value ,就相当于下面的 reactive 深度监视是开启的,但是如果直接填写 xxx 那就可以控制深度监视(默认关闭),想要监视单一属性就需要使用函数进行返回
// 默认不开启深度监视
watch(user, (newValue, oldValue) => {
console.log(`老数据:${oldValue.name} -> 新数据:${newValue}`)
}, {deep: true})
watch(user.value, (newValue, oldValue) => {
console.log(`老数据:${oldValue.name} -> 新数据:${newValue}`)
})
watch(() => user.value.name, (newValue, oldValue) => {
console.log(`老数据:${oldValue.name} -> 新数据:${newValue}`)
})侦听 reactive
let user = reactive({
name: "张三",
age: 18
})
watch(user, (newValue, oldValue) => {
console.log(oldValue, newValue)
})在侦听 reactive 函数获取的代理对象时,只能获取最新数据(newValue)老数据(oldValue)无法获取。并且对于代理对象来说,默认开启深度监视,而且这种深度监视是不可关闭的
如果想要进行单一的属性,不能直接 对象.属性 这种事错误的,应该采用一个函数把需要侦听的属性进行返回
watch(() => user.name, (newValue, oldValue) => {
console.log(`老数据:${oldValue} -> 新数据:${newValue}`)
})但是对于 对象.属性 是错误的也不是很对,在有一种场景下可以使用这种方式,那就是这个属性也是一个对象
let user = reactive({
name: "张三",
age: 18,
xxx: {
a: 1,
b: 2
}
})在以上的情况下是可以直接 user.xxx 进行监听的,但是这种方式的老数据也是不能拿到的
在 reactive 中如果逻辑相同的话也是可以通过数组的形式来进行减少代码的,在
[]中也是需要写函数的形式
watchEffect
这是 V3 新增的组合式API,作用就是返回监控数据变化。用法在里面跟上回调函数
setup() {
let user = reactive({
name: "张三",
age: 18,
})
watchEffect(() => {
const n = user.name
})
return {user}
}回调函数的执行时机,按照上面的写法,每当 user.name 发生变化时,就会执行回调函数,如果有多个也是同理。但是 age 的变化不会让回调函数执行
自定义钩子函数(hook)
这个自定义钩子与V2中的 mixin 有点相同都是为了让代码得到复用
使用:在 src 目录下创建一个 hooks 的目录,然后在其中创建 js 文件(文件名一般是功能名)
// 这是一个简单的求和函数
export default function (a, b) {
return a + b;
}在使用的位置进行导入即可正常使用
import sum from "@/hooks/sum.js";Fragment 组件
Fragment翻译:碎片、片段
在V2中每个组件必须有一个根标签,在组件嵌套组件的时候性能问题,因为有些时候没必要去嵌套这些根标签。然后再V3中没有根标签或者说有多个根标签,实际上再所有标签嵌套完成后会再最外层添加一个 Fragment 用这个当根标签
Teleport 组件
Teleport翻译:远距离传送、瞬间移动。用于设置组件的显示位置
在组件套组件中如果想要对最下层的某一个盒子做定位,那么定位是要有参照物的默认时body,但是如果某一层的组件添加上的某个属性作为了参照物,那么就会导致效果混乱,而 Teleport 组件就是把搬来应该再最下层的盒子移动到指定的位置,比如body的子盒子
<template>
<Teleport to="[标签名|ID选择器]">
<User />
</Teleport>
</template>provide & inject 传递数据
在这种数据传递方式中,适合在祖宗组件对孙子组件传递数据的场景
// 先声明
provide("名称", 数据);
// 取值
let aaa = inject("名称");自定义 ref
import {customRef} from "vue";
export default {
name: 'App',
setup() {
// 自定义 ref
function customizeRef(value) {
// 返回需要是一个响应式的 ref
return customRef((track, trigger) => {
// 自定义 ref 的 get 和 set 并返回
return {
get() {
// 追踪依赖
track()
return value;
},
set(newValue) {
value = newValue
// 触发依赖
trigger()
}
}
})
}
let hello = customizeRef("你好,世界");
return {hello}
}
}通过以上的方式进行自定义一个 ref 这样做的目的是在输入和返回数据的时候进行夹带点私货
