文章目录
- template 的变化
- 新增语法
- setUp
- ref
- reactive
- toRefs
- 生命周期的变化
- watch 的变化
- computed 的变化
- 新增标签
- Teleport
- Suspense
- 自定义 HOOKS
- 结合 TypeScript 开发组件
template 的变化
在 Vue2 中,每个 template 节点只能有一个根节点:
<template> <div>123</div> </template>复制代码
而在 Vue3 中,可以有多个根节点:
<template> <div>123</div> <div>456</div> </template>复制代码
这种变化,让我们在开发过程中,减少了不必要html标签的书写。例如下面这个例子:
<template> <table> <tr> <columns /> </tr> </table> </template>复制代码
在这里,<columns />组件需要返回多个<td>元素。如果在<columns />组件中使用了父 div:
<template> <div> <td>Hello</td> <td>World</td> </div> </template>复制代码
得到一个<Table />输出:
<template> <table> <tr> <div> <td>Hello</td> <td>World</td> </div> </tr> </table> </template>复制代码
而这段生成的 HTML 是无效的。Vue3 中 Template 的变化解决了这个问题。
新增语法
setUp
setUp()可以代替了原来的data(),且只执行一次。
setUp()可以接收两个参数,props和context(没有用到的时候可以省略不写)。
<template> <div> <h1>num:{{ num }}</h1> </div> <button @click="add">加1</button> </template> <script lang="js"> export default { name: 'App', setup() { let num = 0; const add = () => { num++; }; console.log(num); return { add, num, }; }, }; </script>复制代码
上述例子是一个基本的setup的过程。但我们试着点击一下加1按钮,会发现num并没有发生改变。看一下控制台,发现输出的是一个number的值,但我们点击并没有发生改变。为了解决这个问题,我们将用到一个新的 API:Ref。
ref
我们改造下上面的代码:
<template> <div> <h1>msg:{{ msg }}</h1> <h1>num:{{ num }}</h1> </div> <button @click="add">加1</button> </template> <script> import { ref } from 'vue'; export default { name: 'App', setup() { const msg = ref(0); let num = 0; const add = () => { msg.value++; num++; }; console.log(msg); console.log(num); return { msg, add, num, }; }, }; </script>复制代码
先看下ref的导入方式,可以看到这里是按需导入。我们再看下这里的控制台的输出,就会发现:msg打印是RefImpl,num打印是数值0。点击按钮发现,msg可以改变,而num不行。
RefImpl是什么呢?我把它理解为代理对象。就比如我们知道Vue2中data的数据是通过Object.defineProperty()来进行拦截。从而达到数据响应式的目的。而Vue3是利用了ES6中的proxy达到数据响应式的目的。
关于proxy的使用,我举一个简单的例子:
const target = { message1: "hello", message2: "everyone"};const handler = { get: function(target, prop, receiver) {return "world"; } };const proxy = new Proxy(target, handler);console.log(proxy.message1); // worldconsole.log(proxy.message2); // world复制代码
ref是一个函数,它接受一个参数,返回的就是一个响应式对象。例子中,我们初始化的这个0作为参数包裹到这个对象中去,在未来操作这个值的时候,可以检测到改变并作出对应的相应。
reactive
创建一个对象的反应式状态,就要使用reactive方法。示例代码如下:
<template> <div> <h1>count:{{ obj.count }}</h1> <h1>double:{{ obj.double }}</h1> </div> <button @click="obj.increase">加1</button> </template> <script> import { reactive, computed } from 'vue'; export default { name: 'App', setup() { const obj = reactive({ count: 0, increase: () => { obj.count++; }, double: computed(() => obj.count * 2), // 这里是computed在vue3中的用法 }); return { obj, }; }, }; </script>复制代码
这时候我们点击按钮就可以改变状态了。但像{{obj.count}}这样写的时候有些繁琐,这里我们可以解构下obj:
<template> <div> <h1>count:{{ count }}</h1> <h1>double:{{ double }}</h1> </div> <button @click="increase">加1</button> </template> <script lang="js"> import { reactive, computed } from 'vue'; export default { name: 'App', setup() { const obj = reactive({ count: 0, increase: () => { obj.count++; }, double: computed(() => obj.count * 2), }); return { ...obj, }; }, }; </script>复制代码
但这时我们点击按钮,会发现状态改变不了,为什么呢?这是因为,解构会破坏代理,把他变成一个普通值。就跟上面的ref的例子一样,所以点击按钮并没有发生变化。这时候,就要请出另外一个新加的api了,toRefs:
toRefs
使用起来 比较简单,就返回的时候加上就可以:
<!-- html和上面一样的,省略了。 --> <script lang="js"> import { reactive, computed, toRefs } from 'vue'; export default { name: 'App', setup() { const obj = reactive({ count: 0, increase: () => { obj.count++; }, double: computed(() => obj.count * 2), }); return { ...toRefs(obj), }; }, }; </script>复制代码
toRefs从组合函数返回反应对象时,此函数很有用,以便使用组件可以对返回的对象进行解构/扩展而不会失去反应性。
生命周期的变化
Vue2 | Vue3 |
---|---|
beforeCreate | beforeCreate,use setup() |
created | created,use setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
示例:
<script lang="js"> import { onMounted } from 'vue'; export default { setup() { // mounted onMounted(() => { console.log('Componentis mounted!'); }); }, }; </script>复制代码
watch 的变化
watch 在vue3中的用法与在vue2中的用法类似。
computed 的变化
computed 在vue3中的用法与在vue2中的用法类似。但在vue3中,可以写在reactive内部,也可写在外部。
watch和computed的用法如下:
<script lang="js"> import { reactive, computed, watch, toRefs } from 'vue'; export default { setup() { const data = reactive({ count: 0, increase: () => { data.count++; }, double: computed(() => data.count * 2), }); const conComputed = computed(() => data.count * 2); const number = ref(0); watch(data, () => { console.log(data); document.title = 'updated ' + data.count; }); watch(number, () => { console.log(number); }); return { number, conComputed, ...toRefs(data), }; }, }; </script>复制代码
在vue3中,watch和computed都要先引入才能使用。
新增标签
Teleport
平时我们的遮罩层都存在于某个多级标签下面,这样其实是不合理的。Teleport的出现可以让我们写的组件移动到指定标签下面。to是要移动到哪个标签下,它支持选择器。
示例代码如下:
<template> <teleport to="#modal"> <div id="center"> <h1>this is a modal</h1> </div> </teleport> </template> <script lang="js"> export default { name: 'modal', }; </script> <style scoped> #center { width: 200px; height: 200px; background: red; position: fixed; left: 50%; top: 50%; margin-left: -100px; margin-top: -100px; } </style>复制代码
<template> <div id="modal"> <div> <div> <modal v-if="show"></modal> </div> </div> </div> <button @click="show = !show">show</button> </template> <script lang="js"> import { ref } from 'vue'; import Modal from './components/modal.vue'; export default { name: 'App', components: { Modal, }, setup() { const show = ref(false); return { show, }; }, }; </script>复制代码
点击按钮之前:
点击按钮之后:
观察点击按钮前后的DOM结构,我们会发现,即使modal组件在div#modal下被嵌套了多层div后才使用,当显示时,它依然移动到div#modal下面。
Suspense
Suspense 在异步请求的场景下是很实用的。示例如下:
<template> <h1>I have some async work to do before I can render</h1> </template> <script> export default { name: 'MyAsyncComponent', async setup() { await someAsyncWork(); } } </script>复制代码
<template> <slot v-if="error" name="error"></slot> <Suspense v-else> <template #default> <slot name="default"></slot> </template> <template #fallback> <slot name="fallback"></slot> </template> </Suspense> </template> <script> import { ref, onErrorCaptured } from 'vue' export default { name: 'SuspenseWithError', setup() { const error = ref(null); onErrorCaptured((e) => { error.value = e; return true; }); return { error }; } } </script>复制代码
<template> <SuspenseWithError> <template #default> <MyAsyncComponent /> </template> <template #fallback> <span>Loading... Please wait.</span> </template> <template #error> <h1>I failed to load</h1> </template> </SuspenseWithError> </template> <script> import MyAsyncComponent from '@/components/MyAsyncComponent.vue'; import SuspenseWithError from '@/components/SuspenseWithError.vue'; export default { name: 'App', components: { MyAsyncComponent, SuspenseWithError }, } </script>复制代码
自定义 HOOKS
示例如下:
<script lang="ts">import { onMounted, onUnmounted, ref } from 'vue';const useMousePosition = () => { const x = ref(0); const y = ref(0); const updateMouse = (e: MouseEvent) => { x.value = e.pageX; y.value = e.pageY; }; onMounted(() => {document.addEventListener('mousemove', updateMouse); }); onUnmounted(() => {document.removeEventListener('mousemove', updateMouse); }); return { x, y }; };export default useMousePosition; </script>复制代码
<script lang="js"> import useMousePosition from '@/hooks/useMousePosition'; export default { setup() { const { x, y } = useMousePosition(); return { x, y, }; }, }; </script>复制代码
结合 TypeScript 开发组件
在vue3中,创建组件需要用defineComponent包裹。
示例如下:
<script lang="ts">import { defineComponent, PropType, computed } from 'vue';// 把接口类型导出。在使用的过程中导入接口,对接口进行定义export interface ColumnProps { id: number; title: string; avatar?: string; des: string; }export default defineComponent({ name: 'ColumnList', props: {list: { type: Array as PropType<ColumnProps[]>, required: true, }, }, setup(props) {// 这里使用到了propsconst ColumnList = computed(() => { return props.list.map((item) => {if (!item.avatar) { item.avatar = require('@/assets/logo.png'); // 默认图片}return item; }); });return { ColumnList, }; }, }); </script>复制代码
<template> <div id="container"> <column-list :list="list"></column-list> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import ColumnList, { ColumnProps } from '@/components/column-list.vue'; const testDate: ColumnProps[] = [ { id: 1, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, { id: 2, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, { id: 3, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, { id: 4, title: 'test1', des: 'test1', avatar: 'https://picsum.photos/id/239/200/200', }, ]; export default defineComponent({ name: 'App', components: { ColumnList, }, setup() { return { list: testDate, }; }, }); </script>复制代码