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

28次阅读

共计 8622 个字符,预计需要花费 22 分钟才能阅读完成。

本文同步公布在我的 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 有什么不同?

正文完
 0