是什么
vue2 的升级版, 使用 ts 重构了代码, 带来了 Composition API RFC。 类似于 react hook 的写法。
- ts 重构,代码可读性更强
- vue3.x 使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty
- 实现了 TreeShaking (当 Javascript 项目达到一定体积时,将代码分成模块会更易于管理。但是,当这样做时,我们最终可能会导入实际上未使用的代码。Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法。)
- 支持 hook 写法 CompositionAPI。受 ReactHook 启发
- 支持 jsx
- Vue 3 的 Template 支持多个根标签,Vue 2 不支持
- 对虚拟 DOM 进行了重写、对模板的编译进行了优化操作
- 在 Vue2.x 中具名插槽和作用域插槽分别使用 slot 和 slot-scope 来实现, 在 Vue3.0 中将 slot 和 slot-scope 进行了合并 v-slot
- 在 Vue 3 中对自定义指令的 API 进行了更加语义化的修改,名称和组件生命周期名称相同
- v-model 变更:在自定义组件上使用 v-model 时,同一组件可以同时设置多个 v-model, 开发者可以自定义 v-model 修饰符
学到什么
- vue3 与 vue2 的核心区别
- Tree-Shaking
- 函数 setup()
- 函数 ref()
- 函数 isRef()
- 函数 toRefs()
- 函数 reactive()
- 函数 computed()、watch()
- LifeCycle Hooks(新的生命周期)
- Template refs
- vue3 的全局配置
- vue3 组件模板结构
- 实现 自定义 Hook
- 组件 teleport 任意门
- 组件 异步组件
vue3 与 vue2 的核心区别
- vue3.x 将使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty
- Object.defineProperty 只能劫持对象的属性, 而 Proxy 是直接代理对象,由于 Object.defineProperty 只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是 Proxy 直接代理对象,不需要遍历操作
- Object.defineProperty 对新增属性需要手动进行 Observe
Tree-Shaking
当 Javascript 项目达到一定体积时,将代码分成模块会更易于管理。但是,当这样做时,我们最终可能会导入实际上未使用的代码。Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法。
为什么:因为 Vue 实例是作为单个对象导出的,打包器无法分辨出代码中使用了对象的哪些属性。所以,我们需要单独引用
抽离了 一部分 vue2 中的公用函数,需要单独引用。以前的全局 API 现在只能通过具名导入,这一更改会对以下 API 有影响:
- Vue.nextTick
- Vue.observable(用 Vue.reactive 替换)
- Vue.version
- Vue.compile(仅限完整版本时可用)
- Vue.set(仅在 2.x 兼容版本中可用)
- Vue.delete(与上同)
setup 函数
组件提供的新属性,为了使用 vue3 CompositionAPI 新特性而启用的函数,它有自己独立的生命周期
vue3 取消了 beforeCreate 、created 两个钩子函数,统一用 setup 代替
- props 用来接收 props 数据
- context 上下文对象
- return 返回模板中需要使用的函数
setup(props, context) {context.attrscontext.slotscontext.emitreturn {}}
ref() 函数
组件提供的新特性函数
创建一个响应式的数据对象,这个对象是响应式的,只返回一个 { value: ""} 值
import { defineComponent, ref } from "vue";
export default defineComponent({setup() {const name = ref < string > "hello";// 在js 中获取ref 中定义的值, 需要通过value属性console.log(name.value);return {name,};},
});
isRef() 函数
组件提供的新特性函数
isRef() 用来判断某个值是否为 ref() 创建出来的对象
import { defineComponent, isRef, ref } from "vue";
export default defineComponent({setup(props, context) {const name: string = "vue";const age = ref<number>(18);console.log(isRef(age)); // trueconsole.log(isRef(name)); // falsereturn {age,name,};},
});
toRefs() 函数
组件提供的新特性函数
toRefs() 函数可以将响应式对象,转换为普通的对象。只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({setup(props, context) {let state = reactive({name: "hello",});const age = ref(18);return {...toRefs(state),age,};},
});
reactive() 函数
组件提供的新特性函数
reactive() 函数接收一个普通对象,返回一个响应式的数据对象, 想要使用创建的响应式数据也很简单,创建出来之后,在 setup 中 return 出去,直接在 template 中调用即可
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({setup(props, context) {let state = reactive({name: "hello",});return state;},
});
computed()、watch() 函数
组件提供的新特性函数
computed() 函数 ,用来计算属性,返回的值是一个 ref 对象。
import { computed, defineComponent, ref } from "vue";
export default defineComponent({setup(props, context) {const age = ref<number>(18);const computedAge = computed({get: () => age.value + 1,set: (value) => age.value + value,});// 为计算属性赋值的操作,会触发 set 函数, 触发 set 函数后,age 的值会被更新age.value = 100;return {age,computedAge,};},
});
watch() 函数,用来监听属性, 当数据源变化的时候才会被执行。
import { computed, defineComponent, reactive, toRefs, watch } from "vue";
interface Person {name: string;age: number;
}
export default defineComponent({setup(props, context) {const state = reactive<Person>({ name: "vue", age: 10 });watch([() => state.age, () => state.name],([newName, newAge], [oldName, oldAge]) => {console.log(newName);console.log(newAge);console.log(oldName);console.log(oldAge);});// 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调state.age = 100;state.name = "vue3";return {...toRefs(state),};},
});
LifeCycle Hooks 生命周期
组件提供的新特性函数
生命周期组件中新的写法
import { set } from "lodash";
import {defineComponent,onBeforeMount,onBeforeUnmount,onBeforeUpdate,onErrorCaptured,onMounted,onUnmounted,onUpdated,
} from "vue";
export default defineComponent({setup(props, context) {onBeforeMount(() => {console.log("beformounted!");});onMounted(() => {console.log("mounted!");});onBeforeUpdate(() => {console.log("beforupdated!");});onUpdated(() => {console.log("updated!");});onBeforeUnmount(() => {console.log("beforunmounted!");});onUnmounted(() => {console.log("unmounted!");});onErrorCaptured(() => {console.log("errorCaptured!");});return {};},
});
模板 Template refs
组件提供的新特性函数
通过 refs 来回去真实 dom 元素,onMounted 中可以得到 ref 的 RefImpl 的对象, 通过.value 获取真实 dom
<template><div class="mine" ref="elmRefs"><span>hello</span></div>
</template><script lang="ts">
import { set } from "lodash";
import { defineComponent, onMounted, ref } from "vue";
export default defineComponent({setup(props, context) {// 获取真实domconst elmRefs = ref<null | HTMLElement>(null);onMounted(() => {console.log(elmRefs.value); // 得到一个 RefImpl 的对象, 通过 .value 访问到数据});return {elmRefs,};},
});
</script>
vue 的全局配置
Vue3 可以在组件用通过 getCurrentInstance() 来获取全局 globalProperties 中配置的信息
const app = Vue.createApp({});
app.config.globalProperties.$http = "axios";setup( ) {const { ctx } = getCurrentInstance();ctx.$http
}
vue3 组件模板结构
<template><div class="mine" ref="elmRefs"><span>{{ name }}</span><br /><span>{{ count }}</span><div><button @click="handleClick">测试按钮</button></div><ul><li v-for="item in list" :key="item.id">{{ item.name }}</li></ul></div>
</template><script lang="ts">
import {computed,defineComponent,getCurrentInstance,onMounted,PropType,reactive,ref,toRefs,
} from "vue";interface IState {count: 0;name: string;list: Array<object>;
}export default defineComponent({name: "demo",// 父组件传子组件参数props: {name: {type: String as PropType<null | "">,default: "vue3.x",},list: {type: Array as PropType<object[]>,default: () => [],},},components: {/// TODO 组件注册},emits: ["emits-name"], // 为了提示作用setup(props, context) {console.log(props.name);console.log(props.list);const state = reactive<IState>({name: "vue 3.0 组件",count: 0,list: [{name: "vue",id: 1,},{name: "vuex",id: 2,},],});const a = computed(() => state.name);onMounted(() => {});function handleClick() {state.count++;// 调用父组件的方法context.emit("emits-name", state.count);}return {...toRefs(state),handleClick,};},
});
</script>
实现 自定义 Hook
功能性组件可以封装成 hook, 以 use 作为前缀,和普通的函数区分
import { ref, Ref, computed } from "vue";type CountResultProps = {count: Ref<number>;multiple: Ref<number>;increase: (delta?: number) => void;decrease: (delta?: number) => void;
};export default function useCount(initValue = 1): CountResultProps {const count = ref(initValue);const increase = (delta?: number): void => {if (typeof delta !== "undefined") {count.value += delta;} else {count.value += 1;}};const multiple = computed(() => count.value * 2);const decrease = (delta?: number): void => {if (typeof delta !== "undefined") {count.value -= delta;} else {count.value -= 1;}};return {count,multiple,increase,decrease,};
}
使用 hook
<template><p>count: {{ count }}</p><p>倍数: {{ multiple }}</p><div><button @click="increase()">加1</button><button @click="decrease()">减一</button></div>
</template><script lang="ts">
import useCount from "../hooks/useCount";setup() {const { count, multiple, increase, decrease } = useCount(10);return {count,multiple,increase,decrease,};},
</script>
组件 teleport 任意门
目的: 即希望继续在组件内部使用 Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中
场景: 弹框
我们可以用<Teleport>包裹 Dialog, 此时就建立了一个传送门,可以将 Dialog 渲染的内容传送到任何指定的地方。使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与 <div id="app"></div> 同级,也就是在 body 下,但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制.
<body><div id="app"></div><div id="dialog"></div>
</body>
// Dialog.vue
<template><teleport to="#dialog"><div class="dialog"><div class="dialog_wrapper"><div class="dialog_header" v-if="title"><slot name="header"><span>{{ title }}</span></slot></div></div><div class="dialog_content"><slot></slot></div><div class="dialog_footer"><slot name="footer"></slot></div></div></teleport>
</template>// Footer.vue 子组件<div class="footer">...<Dialog v-if="dialogVisible"></Dialog>
</div>
组件 异步组件
Vue3 中 使用 defineAsyncComponent 定义异步组件,配置选项 component 替换为 loader ,Loader 函数本身不再接收 resolve 和 reject 参数,且必须返回一个 Promise。
<template><!-- 异步组件的使用 --><AsyncPage />
</template><script>import { defineAsyncComponent } from "vue";export default {components: {// 无配置项异步组件AsyncPage: defineAsyncComponent(() => import("./NextPage.vue")),// 有配置项异步组件AsyncPageWithOptions: defineAsyncComponent({loader: () => import(".NextPage.vue"),delay: 200,timeout: 3000,errorComponent: () => import("./ErrorComponent.vue"),loadingComponent: () => import("./LoadingComponent.vue"),}),},};
</script>
参考
- vue3 官网 https://v3.vuejs.org/
- 构建 vite https://github.com/vitejs/vite
- vue3 源码 https://github.com/vuejs/vue-next
- vue-cli https://cli.vuejs.org/
- vue-router https://github.com/vuejs/vue-router-next
喜欢的朋友记得点赞、收藏、关注哦!!!