乐趣区

关于vue.js:带你入门体验-Vue3

本文同步公布在我的 Github 集体博客

前言

始终都是 React 进行开发,尽管 Vue 是接触最早的,但当初理论工作也不怎么有机会用,Vue3 都出了。其中新写法有点像 React Hook,于是,这段时间迅速对 Vue3 进行了基本知识入门,体验下 Vue3。

Vue2.x 有哪些痛点

首先 Vue3 呈现之前,让咱们来理理 Vue2.x 有什么痛点,须要 Vue3 补上的。

  • Vue2.x 对数组对象的深层监听无奈实现。因为组件每次渲染都是将 data 里的数据通过 defineProperty 进行响应式或者双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染。
  • 代码的可读性随着组件变大而变差
  • 每一种代码复用的形式,都存在毛病
  • vue2.x 对 typescript 反对不太敌对,须要应用一堆装璜器语法

Options API 到 Composition API 的转变

Options API 即是通过定义 methods,computed,watch,data 等属性与办法,独特解决页面逻辑。

能够看到 Options 代码编写形式,如果是组件状态,则写在 data 属性上,如果是办法,则写在 methods 属性上

当组件变得复杂时,会导致对应属性的列表也会增长,这可能会导致组件难以浏览和了解。

上面示意组件是一个大型组件(不同色彩示意不同逻辑),会看到同个性能逻辑的代码被扩散到各处,这种碎片化使得了解和保护简单组件变得艰难,须要一直 ” 跳 ” 到相干代码处。

而 Composition API 是依据逻辑性能来组织的,一个性能所定义的所有 API 会放在一起(更加的高内聚,低耦合)

用 Composition API 来示意一个大型组件与下面 Options API 的比照,能够直观地感触到 Composition API 在逻辑组织方面的劣势,当前批改一个属性性能的时候,只须要跳到管制该属性的办法中即可,在逻辑组织和逻辑复用方面更好。

Options API 是面向对象的思维,Composition API 是函数式编程的思维。

Vue3 将 Vue2.x 外面的 Options API,细粒度拆分成细小的函数,而后对立放到 setup 这一个入口函数中调用。

这样做的益处:

  • 保留了 Vue2.x 中各个函数的性能,做到兼容
  • 同时以小的函数模式被调用,更加灵便,更好保护,更好 tree-shaking

vue3 有哪些优化

  • 打包大小缩小 41%,首次渲染快 55%,更新快 133%,内存应用缩小 54%(官网数据)
  • 采纳 Typescript 开发,更好反对 Typescript
  • 应用 Proxy 代替 vue2.x 中的 defineProperty,可能深层监听数组对象的变动。
  • Options API 到 Composition API,使简单组件逻辑复用变得更容易和保护。

Composition API

字面意思就是组合式 API,也就是将原来的很多底层的办法拆离开,裸露进去让大家去应用。

setup

setup 是 Composition API 的外围,它在 propsdatacomputedmethods 生命周期函数 之前运行的。

它返回一个对象,该对象上的属性将合并到组件模板的渲染上下文中,还能够返回一个 render 函数。

<template>
  <div>setup</div>
</template>

<script>
export default {setup() {// Composition API 逻辑组织的入口},
}
</script>

它将 Vue2.x 中的 beforeCreatecreated 代替了,以一个 setup 函数的模式灵便组织代码;还能够 return 数据或者 template,相当于把 data 和 render 也一并代替了。

<template>
  <div>{{foo}}</div>
</template>

<script lang="ts">
import {defineComponent} from 'vue'

export default defineComponent({setup() {
    const foo = 2 // 一般变量

    return {foo, // 必须 return 进来能力在模板应用}
  },
})
</script>

顺带一提,通常为了在 TypeScript 下能让组件有正确的参数类型推断、IDE 有更好的提醒成果,会在 export default 处再包一层函数defineComponent(该函数只返回传递给它的对象)

<script lang="ts">
import {defineComponent} from 'vue'

export default defineComponent({setup() {// Composition API 逻辑组织的入口},
})
</script>

setup函数应用须要留神的点:

  • 因为在执行 setup 函数的时候,还没有执行 created 生命周期办法,所以在setup 函数中,无奈应用 datamethods 的变量和办法
  • setup函数只能是同步的不能是异步的

因为 setup 是一个入口函数,实质是面向函数编程,而 this 是面向对象的一种体现,这里其实相当于勾销 this 了,在 Vue3setup 函数里的 thisundefined

而勾销了 this,取而代之的是setup 减少了 2 个参数:

props:组件参数
context:上下文信息(如 attrs、slots、emit)
export default defineComponent({
  props: {title: String,},
  setup(props, context) {
    const foo = 2
    console.log(props.title, context)
    return {foo,}
  },
})
</script>

ref

承受一个外部值并返回一个响应式且可变的 ref 对象(响应式对象),ref()创立的数据会触发模版更新。ref 对象具备指向外部值的单个 property .value

<template>
  <div>
    {{count}}
    <button @click="handleClick"> 加 1 </button>
  </div>
</template>

<script lang="ts">
import {defineComponent, ref} from 'vue'

export default defineComponent({setup() {
    // 被 ref 办法包裹后的元素就变成了一个代理对象
    const count = ref(0)

    const handleClick = () => {count.value++ // 在 setup 外面应用 ref 代理对象须要 `.value`,模板中应用时因为 vue 帮咱们做了主动解析所以不必 `.value`}

    return {
      count,
      handleClick,
    }
  },
})
</script>

reactive

reactive 也是实现响应式的一种办法,它接管一个一般对象而后返回该一般对象的响应式代理,相当于 Vue2.x 中的 Vue.observable()

个别约定 reactive 的参数是一个对象,而 ref 的参数通常是原始数据类型,尽管反过来也能够。

ref的实质还是 reactive 零碎会主动依据 ref() 函数的入参将其转换成 ref(x)reactive({value:x})

<template>
  <div>
    {{obj1.count1}}
    {{count2}}
  </div>
</template>

<script lang="ts">
import {defineComponent, reactive, toRefs} from 'vue'

export default defineComponent({setup() {
    const obj1 = reactive({count1: 10,})
    const obj2 = reactive({count2: 20,})

    return {
      obj1,
      ...toRefs(obj2),
    }
  },
})
</script>

下面代码能够看到,间接 return obj1,在模板应用时须要 obj.count1,那为什么不间接...obj1 呢,那是因为 obj1 是 proxy 代理对象,整个对象都是响应式,所以不能应用残余运算符。

如果感觉麻烦,能够应用 ...toRefs(obj2),这样就能够在模板间接应用定义的数据了。

toRefs 函数是将 reactive 创立的响应式对象,转化成为一般的对象,并且这个对象上的每个节点,都是 ref 类型的响应式数据

toRef

能够用来为源响应式对象上的某个 property 新创建一个 ref。而后,ref 能够被传递,它会放弃对其源 property 的响应式连贯。

export default defineComponent({setup() {
    const state = reactive({
      foo: 1,
      bar: 2,
    })

    const fooRef = toRef(state, 'foo')

    fooRef.value++
    console.log(state.foo) // 2

    state.foo++
    console.log(fooRef.value) // 3
  },
})

toRef()接管两个参数,第一个为对象,第二个为对象中的某个属性。它是对原数据的一个援用,当值扭转时会影响到原始值;创立的响应式数据并不会触发 vue 模版更新。

toRefs

将响应式对象转换为一般对象,其中后果对象的每个 property 都是指向原始对象相应 property 的 ref。

toRefs()接管一个对象作为参数,并遍历对象身上的所有属性,而后一一调用 toRef() 执行。以此,将响应式对象转化为一般对象,便于在模版中能够间接应用属性。

通常用来将一组的响应式对象拆成单个的响应式对象,如将一个 reactive 代理对象打平,转换为 ref 代理对象,使得对象的属性能够间接在 template 上应用。

<template>
  <div>
    {{foo}}
    {{bar}}
  </div>
</template>

<script lang="ts">
import {defineComponent, reactive, toRefs} from 'vue'

export default defineComponent({setup() {
    // 创立一个响应式对象 state
    const state = reactive({
      foo: 1,
      bar: 2,
    })

    const stateAsRefs = toRefs(state) // 将响应式的对象变为一般对象构造, 且能应用 ES6 的扩大运算符,也仍具备响应性

    // ref 和原始 property 曾经“链接”起来了
    state.foo++
    console.log(stateAsRefs.foo.value) // 2

    stateAsRefs.foo.value++
    console.log(state.foo) // 3

    return {...stateAsRefs,}
  },
})
</script>

computed

承受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。

setup() {const count = ref(1)
  const plusOne = computed(() => count.value + 1) // 后果是一个 ref 代理对象,取值须要 .value

  console.log(plusOne.value) // 2
},

与 Vue2.x 中的作用相似,获取一个计算结果。当 computed 参数应用 object 对象书写时,不仅反对取值 get(默认),还反对赋值 set。

setup() {const count = ref(1)
  const plusOne = computed({get: () => count.value + 1,
    set: (val) => {count.value = val - 1},
  })

  plusOne.value = 1
  console.log(count.value) // 0
},

watch

watch API 与选项式 API this.$watch (以及相应的 watch 选项) 齐全等效。watch 须要侦听特定的数据源,并在独自的回调函数中执行副作用。默认状况下,它也是惰性的——即回调仅在侦听源产生更改时被调用。

<template>
  <div>
    {{count}}
    <button @click="changeCount">count 加 1 </button>
  </div>
</template>

<script lang="ts">
import {defineComponent, watch, ref} from 'vue'

export default defineComponent({setup() {const count = ref(0)
    const changeCount = () => {count.value++}
    watch(count, (newCount, prevCount) => {console.log('newCount:', newCount, 'oldCount:', prevCount)
    })

    return {
      count,
      changeCount,
    }
  },
})
</script>

也能够应用数组同时侦听多个源。

watchEffect

在响应式地跟踪其依赖项时立刻执行传入的一个函数,并在更改依赖项时从新运行它。

当组件的 setup()或者生命周期钩子被调用时,watchEffect 会被链接到该组件的生命周期,并在组件卸载时主动进行。

<template>
  <div>
    {{count}}
    <button @click="changeCount">count 加 1 </button>
  </div>
</template>

<script lang="ts">
import {defineComponent, ref, watchEffect} from 'vue'

export default defineComponent({setup() {const count = ref(0)
    const changeCount = () => {count.value++}

    watchEffect(() => console.log(count.value))
    // -> logs 0

    setTimeout(() => {
      count.value++
      // -> logs 1
    }, 100)

    return {
      count,
      changeCount,
    }
  },
})
</script>

watch 与 watchEffect 比拟,watch 容许咱们

  • 惰性地执行副作用;
  • 更具体地说明应触发侦听器从新运行的状态;
  • 拜访被侦听状态的先前值和以后值。

readonly

承受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被拜访的嵌套 property 也是只读的。

setup() {const original = reactive({ count: 0})

  // 返回的 readonly 对象,一旦批改就会在 console 有 warning 正告。程序还是会照常运行,不会报错。const copy = readonly(original)

  watchEffect(() => {
    // 只有有数据变动,这个函数都会执行
    console.log(copy.count)
  })

  // 这里会触发 watchEffect
  original.count++

  // 这里不会触发上方的 watchEffect,因为是 readonly。copy.count++ // warning!
},

Fragments Template

Vue3.x 中,能够不必惟一根节点。

<template>
  <div></div>
  <div></div>
</template>

Teleport 组件

Teleport 在国内翻译成了霎时挪动组件或者独立组件,它能够把你写的组件挂载到任何你想挂载的 DOM 上。

<template>
  <div class="user-card">
    <b> {{name}} </b>
    <button @click="isModalOpen = true"> 删除用户 </button>

    <!-- 留神这一块代码 -->
    <Teleport to="#modal">
      <div v-show="isModalOpen">
        <p> 确定删除?</p>
        <button @click="removeUser"> 确定 </button>
        <button @click="isModalOpen = false"> 勾销 </button>
      </div>
    </Teleport>
  </div>
</template>

<script lang="ts">
import {defineComponent, reactive, ref, toRefs} from 'vue'

export default defineComponent({setup() {const isModalOpen = ref(false)
    const user = reactive({name: 'jacky',})

    const removeUser = () => {console.log('removeUser')
    }

    return {
      isModalOpen,
      removeUser,
      ...toRefs(user),
    }
  },
})
</script>

运行这段代码之前,须要去 ./public/index.html 加上个 id 为 modal 元素供挂载,当然也能够到组件外面增加。

<div id="app"></div>
<div id="modal"></div>

Teleport 提供了一种洁净的办法,容许咱们管制在 DOM 中哪个父节点下渲染了 HTML,而不用求助于全局状态或将其拆分为两个组件。

Suspense 组件

Suspense 提供两个 template 的地位,一个是没有申请未实现或失败显示的内容,一个是全副申请结束的内容。这样进行异步内容的渲染就会非常简单。

上面 Demo 我就模仿一下用法,不写实在申请了。

新建 AsyncShow.vue 组件

<template>
  <div>
    <h1>{{result}}</h1>
  </div>
</template>

<script>
import {defineComponent} from 'vue'

export default defineComponent({
  name: 'AsyncShow',
  setup() {return new Promise((resolve) => {setTimeout(() => {
        return resolve({result: '这是申请后果',})
      }, 3000)
    })
  },
})
</script>

新建 Suspense.vue 组件

<template>
  <div>
    <Suspense>
      <template #default>
        <!-- 申请胜利展现 -->
        <async-show />
      </template>
      <template #fallback>
        <!-- 申请中和失败展现 -->
        <h2>Promise Loading...(申请中)</h2>
      </template>
    </Suspense>
  </div>
</template>

<script lang="ts">
import {defineComponent} from 'vue'
import AsyncShow from './AsyncShow.vue'

export default defineComponent({
  components: {AsyncShow,},
  setup() {//},
})
</script>

Vue3.x 生命周期变动

被替换

  • beforeCreate -> setup()
  • created -> setup()

重命名

生命周期办法后面都对立加了 ondestroy 被改名成 Unmount 更贴切也对应Mount

  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

新增

新增的以下 2 个不便调试 debug 的回调钩子:

  • onRenderTracked:状态跟踪,它会跟踪页面上所有响应式变量和办法的状态,也就是咱们用 return 返回去的值,他都会跟踪。只有页面有 update 的状况,他就会跟踪,而后生成一个 event 对象,咱们通过 event 对象来查找程序的问题所在。
  • onRenderTriggered:状态触发,它不会跟踪每一个值,而是给你变动值的信息,并且新值和旧值都会给你明确的展现进去。

新旧生命周期谁先运行?

  • 在 Vue2.x 中通过补丁模式引入 Composition API,进行 Vue2.x 和 Vue3.x 的回调函数混用时:Vue2.x 的回调函数会绝对先执行,比方:mounted 优先于 onMounted。
  • 在 Vue3.x 中,为了兼容 Vue2.x 的语法,所有旧的生命周期函数失去保留(除了 beforeDestroy 和 destroyed)。当生命周期混合应用时:Vue3.x 的生命周期绝对优先于 Vue2.x 的执行,比方:onMounted 比 mounted 先执行。

结语

临时就写这么多了,并不齐全,但也能大略对 Vue3 有个初步理解了。

本文案例代码:vue3-tutorial

参考

  • vue2.x/React/vue3.x 简略横评(4)
  • vue3 composition api
  • Vue3.0 所采纳的 Composition Api 与 Vue2.x 应用的 Options Api 有什么不同?
退出移动版