vue2到vue3的变化及vue3方法入门

重大变化

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传入返回备注
reactiveplain-object对象代理深度代理对象中的所有成员
readonlyplain-object or proxy对象代理只能读取代理对象中的成员,不可修改
refany{ value: ... }对value的访问是响应式的
如果给value的值是一个对象,
则会通过reactive函数进行代理
如果已经是代理,则直接使用代理
computedfunction{ value: ... }当读取value值时,
根据情况决定是否要运行函数

应用:

  • 如果想要让一个对象变为响应式数据,可以使用reactiveref
  • 如果想要让一个对象的所有属性只读,使用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判断某个数据是否是由reactivereadonly
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对象vue2this.$attrs
slots对象vue2this.$slots
emit方法vue2this.$emit

生命周期函数

vue2 option apivue3 option apivue 3 composition api
beforeCreatebeforeCreate不再需要,代码可直接置于setup中
createdcreated不再需要,代码可直接置于setup中
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroy beforeUnmountonBeforeUnmount
destroyedunmountedonUnmounted
errorCapturederrorCapturedonErrorCaptured
-renderTrackedonRenderTracked
-renderTriggeredonRenderTriggered

新增钩子函数说明:

钩子函数参数执行时机
renderTrackedDebuggerEvent渲染vdom收集到的每一次依赖时
renderTriggeredDebuggerEvent某个依赖变化导致组件重新渲染时

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中,提供了provideinject配置,可以让开发者在高层组件中注入数据,然后在后代组件中使用

除了兼容vue2的配置式注入,vue3composition api中添加了provideinject方法,可以在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");

对比
vuexglobal stateProvide&Inject
组件数据共享
可否脱离组件
调试工具
状态树自行决定自行决定
量级
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值