重大变化
Vue3没有默认导出 不能直接导入vue
只能导入Vue里面具体的模块
import Vue from 'vue' //错误
import { createApp } from 'vue' //创建项目
this指向不同
vue2 this指向当前组件实例
vue3 this指向当前组件的代理对象,如果取this.xxx的话会先发送到代理对象,然后再发送给当前组件实例
composition API
所有的东西写在setup函数里面 vue3也兼容vue2那样的分模块的写法(方法写在methods里,数据写在data里)
setup 利用vue的函数
setup(props, ctx) //可以传入两个参数一个表示props里面的参数,ctx里面有一些方法
ref
创建一个响应式对象
watchEffect
监听响应式对象,对象改变之后就会触发
<script>
import { ref } from "vue";
export default {
setup() {
console.log("在所有生命周期之前执行");
console.log("这里没有this"); //this是undefined
let count = ref(0); //给一个数据添加响应式
console.log(count);
//封装响应式的格式,值存在value里面
// RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 0, _value: 0}
// dep: Set(1) {ReactiveEffect}
// __v_isRef: true
// __v_isShallow: false
// _rawValue: 0
// _value: 0
// value: (...)
const increase = () => {
count.value++; //值存在value里面
};
return {
count,
increase,
};
},
};
</script>
写法示例
//获取和保存数据
import { saveTodos, fetch } from "../utils/todosStorage";
import { ref, watchEffect } from "vue";
//todosRef 保存的数据名
export default function todoList() {
const todosRef = ref(fetch());
watchEffect(() => {
// 监听ref的数据,改变了就会触发
console.log("数据更新了");
saveTodos(todosRef.value);
});
return {
todosRef,
};
}
在setup函数中定义生命周期
1、beforeCreate -> 使用 setup()
2、created -> 使用 setup()
3、beforeMount -> onBeforeMount
4、mounted -> onMounted
5、beforeUpdate -> onBeforeUpdate
6、updated -> onUpdated
7、beforeDestroy -> onBeforeUnmount
8、destroyed -> onUnmounted
9、errorCaptured -> onErrorCaptured
如何定义计算属性
const completedNum = computed(() => {
return filterTodo(todosRef.value, "completed").length;
});
模板中的变化
v-model
//子组件
<div
class="checkbox"
:class="{ checked: modelValue }" //依靠modelValue改变class的值
@click="handleCheck" //
></div>
setup(props, ctx) {
const handleCheck = () => {
ctx.emit("update:modelValue", !props.modelValue); //触发update:modelValue事件,把传过来的值传过去
};
return {
handleCheck,
};
},
//父组件
<CheckboxEdit
:modelValue="task.sell" //传递一个布尔值
@update:modelValue="task.sell = $event" //$event是子组件传过来的值,对sell重新赋值,赋值之后就会再次触发props再次传递数据,这样子组件里面的值就改变了
/>
//官方示例
<ChildComponent v-model:title="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
允许自定义v-model
修饰符
titleModifiers: { //获取修饰符
default: () => ({}),
},
v-if v-for
v-if
的优先级 现在高于 v-for
v-for和v-if一起用会报错
key
-
当使用
<template>
进行v-for
循环时,需要把key
值放到<template>
中,而不是它的子元素中 -
当使用
v-if v-else-if v-else
分支的时候,不再需要指定key
值,因为vue3
会自动给予每个分支一个唯一的key
即便要手工给予
key
值,也必须给予每个分支唯一的key
,不能因为要重用分支而给予相同的 key
组件中的变化
在页面中导入异步组件
import Error from "./Error.vue";
import Loading from "./Loading.vue";
import { defineAsyncComponent, h } from "vue"; //defineAsyncComponent,返回的一个异步组件。 render中的h
//得到一个异步组件
const Block3 = defineAsyncComponent({
loader: () => {
return import("./Block3.vue"); //返回的组件
},
loadingComponent: Loading, //组件未完成加载时。触发的组件
errorComponent: {
//组件加载出错时触发
render() {
return h(Error, "出错了!!!"); //h函数需要自己的导入
},
},
});
const Block5 = defineAsyncComponent(() => import("./Block5.vue")); //这个Block5是由defineAsyncComponent函数返回的Block5
配置路由
//router.js
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes"; //路由配置
const router = createRouter({
history: createWebHistory(), //history模式
routes,
});
export default router;
//main.js
import router from "./router";
createApp(App).use(router).mount("#app");
获取响应式数据
四种获取响应式数据的方法
//在一个普通的js文件中也可以使用,导入相应的模块就行
import { reactive, readonly, ref,computed} from “vue”;
API | 传入 | 返回 | 备注 |
---|---|---|---|
reactive | plain-object | 对象代理 | 深度代理对象中的所有成员 |
readonly | plain-object or proxy | 对象代理 | 只能读取代理对象中的成员,不可修改 |
ref | any | { value: ... } | 对value的访问是响应式的 如果给value的值是一个对象, 则会通过 reactive 函数进行代理如果已经是代理,则直接使用代理 |
computed | function | { value: ... } | 当读取value值时, 会根据情况决定是否要运行函数 |
应用:
- 如果想要让一个对象变为响应式数据,可以使用
reactive
或ref
- 如果想要让一个对象的所有属性只读,使用
readonly
- 如果想要让一个非对象数据变为响应式数据,使用
ref
- 如果想要根据已知的响应式数据得到一个新的响应式数据,使用
computed
为什么有了reactive和readonly之后还需要ref?
因为proxy不能直接代理一个原始值,所以可以使用ref进行代理,ref会把数据封装成一个对象。如果用ref代理的是一个对象的话,ref会继续使用reactive进行代理,reactive会深度代理对象中的所有成员。
用了computed返回的也是一个代理对象
watchEffect传入函数里面的数据发生变化时就会重新运行函数。会把更新的数据推到微队列中,这样就只会更新一次,和vue的异步更新组件一样。
使用watch进行监听
watch(
() => state.count, //第一个参数需要传一个对象,如果不是对象就要用现在这种方式传递,如果是对象就可以直接写,为什么传函数就可以?因为函数不是刚刚开始就会运行的,函数是在watch内部运行的,调用函数之后就手机依赖了,运行了数据的get,可以监控多个数据【xxxx,xxxx】
(count, oldCount) => {
console.log("watch", count, oldCount);
}
);
watchEffect
const stop = watchEffect(() => {
// 该函数会立即执行,然后追中函数中用到的响应式数据,响应式数据变化后会再次执行
})
// 通过调用stop函数,会停止监听
stop(); // 停止监听
watch
// 等效于vue2的$watch
// 监听单个数据的变化
const state = reactive({ count: 0 })
watch(() => state.count, (newValue, oldValue) => {
// ...
}, options)
const countRef = ref(0);
watch(countRef, (newValue, oldValue) => {
// ...
}, options)
// 监听多个数据的变化
watch([() => state.count, countRef], ([new1, new2], [old1, old2]) => {
// ...
});
注意:无论是watchEffect
还是watch
,当依赖项变化时,回调函数的运行都是异步的(微队列)
应用:除非遇到下面的场景,否则均建议选择watchEffect
- 不希望回调函数一开始就执行
- 数据改变时,需要参考旧值
- 需要监控一些回调函数中不会用到的数据
判断
API | 含义 |
---|---|
isProxy | 判断某个数据是否是由reactive 或readonly |
isReactive | 判断某个数据是否是通过reactive 创建的详细:https://v3.vuejs.org/api/basic-reactivity.html#isreactive |
isReadonly | 判断某个数据是否是通过readonly 创建的 |
isRef | 判断某个数据是否是一个ref 对象 |
转换
unref
等同于:isRef(val) ? val.value : val
应用:
function useNewTodo(todos){
todos = unref(todos);
// ...
}
toRef
得到一个响应式对象某个属性的ref格式
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo'); // fooRef: {value: ...}
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
toRefs
把一个响应式对象的所有属性转换为ref格式,然后包装到一个plain-object
中返回
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs: not a proxy
{
foo: { value: ... },
bar: { value: ... }
}
*/
应用:
setup(){
const state1 = reactive({a:1, b:2});
const state2 = reactive({c:3, d:4});
return {
...state1, // lost reactivity
...state2 // lost reactivity
}
}
setup(){
const state1 = reactive({a:1, b:2});
const state2 = reactive({c:3, d:4});
return {
...toRefs(state1), // reactivity
...toRefs(state2) // reactivity
}
}
// composition function
function usePos(){
const pos = reactive({x:0, y:0});
return pos;
}
setup(){
const {x, y} = usePos(); // lost reactivity
const {x, y} = toRefs(usePos()); // reactivity
}
composition api(setup)
不同于reactivity api,composition api提供的函数很多是与组件深度绑定的,不能脱离组件而存在。
setup
// component
export default {
setup(props, context){
// 该函数在组件属性被赋值后立即执行,早于所有生命周期钩子函数
// props 是一个对象,包含了所有的组件属性值
// context 是一个对象,提供了组件所需的上下文信息
}
}
context对象的成员
成员 | 类型 | 说明 |
---|---|---|
attrs | 对象 | 同vue2 的this.$attrs |
slots | 对象 | 同vue2 的this.$slots |
emit | 方法 | 同vue2 的this.$emit |
生命周期函数
vue2 option api | vue3 option api | vue 3 composition api |
---|---|---|
beforeCreate | beforeCreate | 不再需要,代码可直接置于setup中 |
created | created | 不再需要,代码可直接置于setup中 |
beforeMount | beforeMount | onBeforeMount |
mounted | mounted | onMounted |
beforeUpdate | beforeUpdate | onBeforeUpdate |
updated | updated | onUpdated |
beforeDestroy | 改 beforeUnmount | onBeforeUnmount |
destroyed | 改unmounted | onUnmounted |
errorCaptured | errorCaptured | onErrorCaptured |
- | 新renderTracked | onRenderTracked |
- | 新renderTriggered | onRenderTriggered |
新增钩子函数说明:
钩子函数 | 参数 | 执行时机 |
---|---|---|
renderTracked | DebuggerEvent | 渲染vdom收集到的每一次依赖时 |
renderTriggered | DebuggerEvent | 某个依赖变化导致组件重新渲染时 |
renderTracked 在render函数运行时,每收集到一个依赖就会触发一次,数据发生变化也会重新运行render函数
renderTriggered 当一个依赖发生变化了,导致页面重新渲染时运行触发,可以获取到改变的值
DebuggerEvent:
- target: 跟踪或触发渲染的对象
- key: 跟踪或触发渲染的属性
- type: 跟踪或触发渲染的方式
###数据共享
vuex方案
安装vuex@4.x
两个重要变动:
- 去掉了构造函数
Vuex
,而使用createStore
创建仓库 - 为了配合
composition api
,新增useStore
函数获得仓库对象
global state
由于vue3
的响应式系统本身可以脱离组件而存在,因此可以充分利用这一点,轻松制造多个全局响应式数据
// store/useLoginUser 提供当前登录用户的共享数据
// 以下代码仅供参考
import { reactive, readonly } from "vue";
import * as userServ from "../api/user"; // 导入api模块
// 创建默认的全局单例响应式数据,仅供该模块内部使用
const state = reactive({ user: null, loading: false });
// 对外暴露的数据是只读的,不能直接修改
// 也可以进一步使用toRefs进行封装,从而避免解构或展开后响应式丢失
export const loginUserStore = readonly(state);
// 登录
export async function login(loginId, loginPwd) {
state.loading = true;
const user = await userServ.login(loginId, loginPwd);
state.loginUser = user;
state.loading = false;
}
// 退出
export async function loginOut() {
state.loading = true;
await userServ.loginOut();
state.loading = false;
state.loginUser = null;
}
// 恢复登录状态
export async function whoAmI() {
state.loading = true;
const user = await userServ.whoAmI();
state.loading = false;
state.loginUser = user;
}
Provide&Inject
在vue2
中,提供了provide
和inject
配置,可以让开发者在高层组件中注入数据,然后在后代组件中使用
除了兼容vue2
的配置式注入,vue3
在composition api
中添加了provide
和inject
方法,可以在setup
函数中注入和使用数据
考虑到有些数据需要在整个vue应用中使用,vue3
还在应用实例中加入了provide
方法,用于提供整个应用的共享数据
creaetApp(App)
.provide("foo", ref(1))
.provide("bar", ref(2))
.mount("#app");
因此,我们可以利用这一点,在整个vue应用中提供共享数据
// store/useLoginUser 提供当前登录用户的共享数据
// 以下代码仅供参考
import { readonly, reactive, inject } from "vue";
const key = Symbol(); // Provide的key
// 在传入的vue应用实例中提供数据
export function provideStore(app) {
// 创建默认的响应式数据
const state = reactive({ user: null, loading: false });
// 登录
async function login(loginId, loginPwd) {
state.loading = true;
const user = await userServ.login(loginId, loginPwd);
state.loginUser = user;
state.loading = false;
}
// 退出
async function loginOut() {
state.loading = true;
await userServ.loginOut();
state.loading = false;
state.loginUser = null;
}
// 恢复登录状态
async function whoAmI() {
state.loading = true;
const user = await userServ.whoAmI();
state.loading = false;
state.loginUser = user;
}
// 提供全局数据
app.provide(key, {
state: readonly(state), // 对外只读
login,
loginOut,
whoAmI,
});
}
export function useStore(defaultValue = null) {
return inject(key, defaultValue);
}
// store/index
// 应用所有store
import { provideStore as provideLoginUserStore } from "./useLoginUser";
// 继续导入其他共享数据模块...
// import { provideStore as provideNewsStore } from "./useNews"
// 提供统一的数据注入接口
export default function provideStore(app) {
provideLoginUserStore(app);
// 继续注入其他共享数据
// provideNewsStore(app);
}
// main.js
import { createApp } from "vue";
import provideStore from "./store";
const app = createApp(App);
provideStore(app);
app.mount("#app");
对比
vuex | global state | Provide&Inject | |
---|---|---|---|
组件数据共享 | ✅ | ✅ | ✅ |
可否脱离组件 | ✅ | ✅ | ❌ |
调试工具 | ✅ | ❌ | ✅ |
状态树 | ✅ | 自行决定 | 自行决定 |
量级 | 重 | 轻 | 轻 |