vue3(一)

146 阅读4分钟

这是我参与8月更文挑战的第7天,活动详情查看: 8月更文挑战

这是一个趋势~~~~

所以,为了一系列,,,,,不得不学一下。学习新东西,过程还是蛮有意思的,毕竟,吹牛还是用得上的。。。

动机:

类型支持更好

利于 tree-shaking

API简化、一致性:render函数,sync修饰符,指令定义等

复用性:composition api

性能优化:响应式、编译优化

扩展性:自定义渲染器

1、可以先cdn引入,体验下。可参考官方文档 v3.vuejs.org/

<div id="app">    <h1>{{title}}</h1>  </div>  <script src="https://unpkg.com/vue@next"></script>  <script>    const { createApp } = Vue    // new Vue    // 函数式:类型支持ts    const app = createApp({      // 统一api,一致性      data(){        return {          title: '嘿嘿,vue3!'        }      }    }).mount('#app')  </script>

好处:

1、函数式,对类型支持很好。对ts支持很好,对比用options会产生很多this,减少this的使用;

摇树优化,即 Tree Shaking;

通过函数调用,知道程序中用到了哪些依赖,打包的时候把这些用到的函数打包进去。那些在源码中存在,但是却没有调用的代码就像树叶一样摇掉。这样打包体积减小。消灭静态方法。多个实例之间不会相互污染。

2、api简化,一致性

首次挂载做3件事:

1、根组件实例化;

2、初始化根组件;setupComponent函数,类似vue2中 this._init()

3、安装渲染函数(render函数)副作用

(ps: 副作用:如果对一个响应式数据有依赖,数据发生变化,会再次将这个副作用函数执行一遍,即:安装副作用。effect(fn): 添加副作用函数,这样里面的相关的响应式数据如果发生变化,那么fn会再次执行。)

compile 在我们无感知的情况下,做到了按需更新;

所以编译优化,就是代码在打包上线之前进行的优化,vue中把template转化成render函数,期间的源码大家去vue-next里搜baseCompile

const ast = baseParse(template) // 把template解析成ast 也就是一棵树
transform(ast,option)           // 优化,比如标记和转化vue的特定语法
return genrate(ast)             // 生成的render 函数

vue3.0亮点优势:

Performance : 重写了虚拟Dom的实现、编译模板的优化、更⾼效的组件初始

化。性能更⽐Vue 2.0强

Tree shaking support : 可以将⽆⽤模块“剪辑”,仅打包需要的,vue功能按

需引⼊

Composition API : 组合API,可与现有的 Options API⼀起使⽤,混⼊(mixin)

将不再作为推荐使⽤

Fragment, Teleport, Suspense : “碎⽚”,Teleport即Protal传送⻔,“悬

念”,⽀持async setup()

Better TypeScript support : ⽤TypeScript编写的库,可以享受到⾃动的类

型定义提示,提供类型检查,⾃动补全等功能

Custom Renderer API : 暴露了⾃定义渲染API

Composition API 详解

⼀、组件初始⼊⼝ setup

新的组件选项。作为在组件内使⽤ Composition API 的⼊⼝。

调⽤时机:创建组件实例,然后初始化 props ,紧接着就调⽤setup 函数。在

created 之前执⾏。

<script>
import { ref, reactive } from 'vue'
export default {
 setup(props, ctx) {
const count = ref(0)
const object = reactive({ foo: 'bar' })
// 暴露给模板
return {
 count,
 object
 }
 }
 }
</script>

参数说明

props : 接收props 数据

ctx(或者context):组件上下⽂对象,包含⼀些常⽤的属性,在vue2.x 中

通过this访问的

ctx.attrs、ctx.slots、ctx.parent、ctx.root、ctx.emit、ctx.refs

注:

1、this 在 setup() 中不可⽤

2、props 对象是响应式的, watchEffect 或 watch 会观察和响应 props 的

更新

3、不要解构 props 对象,那样会使其失去响应性。不要在组件内修改props

⼆、响应式系统 API

reactive: 创建响应式数据对象,等同于 2.x 的 Vue.observable()

setup() {
 const state = reactive({ message: 'Hello Vue3!!' })
 msgReverse = () => {
state.message = state.message.split('').reverse().join('')
 }
 return {
state,
 msgReverse
 }
}

响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 Proxy 实现。

ref 和 isRef: 创建⼀个响应式的 ref 对象拥有⼀个指向内部值的单⼀属性

.value

const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

computed: 创建计算属性

1、传⼊⼀个 getter 函数,返回⼀个默认不可⼿动修改的ref对象

const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误!

2、传⼊⼀个拥有 get 和 set 函数的对象,创建⼀个可⼿动修改的计算状态

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 和 watchEffct : 创建数据watch监听

1、watchEffect,如果响应性的属性有变更,就会触发这个函数。

watchEffect(() => console.log(count.value, 'count.value'))
// const stopWatchEffect = watchEffect(() =>
console.log(count.value, 'count.value'))
// stopWatchEffect()

2、watch,等效2.0⾥⾯的this.$watch

a、侦听单个数据源

watch(count, (val, oldVal) => {
console.log(val, 'val')
console.log(oldVal, 'oldVal')
console.log(count.value, 'count.value')
})
// const stop = watch(count, (val, oldVal) => console.log(val,
'val'))
// stop()

b、侦听多个数据源

watch([count, state], ([countVal, msgVal], [countOldVal,
msgOldVal]) => {
console.log(countVal, 'countVal')
console.log(msgVal, 'msgVal')
})

c、两者⽐较

1、watchEffect 是⽴即执⾏,watch 为懒执⾏,仅在侦听的源变更才执⾏
回调
2、watch 可访问侦听状态变化前后的值,更加明确状态的改变

v-modle: 数据双向绑定

三、⽣命周期钩⼦函数

可以直接导⼊ onXXX ⼀类的函数来注册⽣命周期钩⼦

import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
 setup() {
 onMounted(() => {
console.log('mounted!')
 })
 onUpdated(() => {
console.log('updated!')
 })
 onUnmounted(() => {
console.log('unmounted!')
 })
 }
}

⽣命周期钩⼦函数依赖于内部的全局状态来定位当前组件实例,只能在

setup() 期间同步使⽤。

与 2.x 版本⽣命周期对⽐映射

四、模板 Refs

1. 在 setup()中创建⼀个ref()对象并返回;

2. 在⻚⾯上为元素添加ref属性,并设置属性值与创建的ref对象名称相同;

3. 当前⻚⾯渲染完成后(onMounted),可通过该ref对象获取到⻚⾯中对应的dom

元素。

<template> <div ref="root"></div>
</template> <script>
import { ref, onMounted } from 'vue'
export default {
 setup() {
const root = ref(null)
 onMounted(() => {
// 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
console.log(root.value) // <div/>
 })
return {
 root,
 }
 },
 }
</script>

五、依赖注⼊ provide 和 inject

在祖先组件中使⽤provide()函数向下传递数据;在后代组件中使⽤inject()函数获取

上层传递传递过来的数据,两个函数只能在 setup()函数中使⽤

// ⽗组件
<template> <div class="hello"> <button @click="colorRef='red'"></button> <button @click="colorRef='yellow'"></button>
</div>
</template> <script>
import { provide, ref} from 'vue'
export default {
 setup(){
// 向后代传数据 不会响应式更新
 provide('color','yellow');
// 传递响应式的数据
let colorRef = ref('red')
 provide('colorRef',colorRef);
return{
 colorRef
 }
 }
}
</script>

//⼦或孙⼦组件
<template> <div class="grandson"> <h1 :style="{color:color}">孙组件1</h1> <h1 :style="{color:colorRef}">孙组件1</h1>
</div>
</template> <script>
import { inject } from 'vue'
export default {
 setup() {
const color = inject('color')
const colorRef = inject('colorRef')
return {
 color,
 colorRef
 }
 }
}
</script>

六、公共代码的复⽤(mixin的替代⽅案)

  • mixin 的缺陷

1. mixin把公共的逻辑抽取到⼀个独⽴⽂件,当项⽬过于复杂,mixin中的代码和

外部的代码存在命名冲突的时候会被覆盖,相同的⽣命周期函数也会被覆盖,

代码难以维护,容易出现bug。

2. 存在多个mixin 的时候,容易互相影响,来源不清晰,

  • 代码示例

    // useResizeMin.js import { ref, onMounted, onUnmounted } from 'vue' export default () => { const width = ref(window.innerWidth) // 默认值 const height = ref(window.innerHeight) // 默认值 const onUpdate = () => { width.value = window.innerWidth height.value = window.innerHeight } onMounted(() => { window.addEventListener('resize', onUpdate) }) onUnmounted(() => { window.removeEventListener('resize', onUpdate) }) return { width, height } } // 组件内使⽤ // 这样就很清晰width这些数据是从哪⾥维护的,且不会冲突其变量 const { width, height } = useResizeMin()

附加⼀:插件开发: vue2 很多插件都是向 this 注⼊property, 例如Vue-Router 注

⼊ this.routethis.route 和 this.router ,Vuex 注⼊ this.$store 。⽽vue3 不再使

⽤ this ,插件将在内部利⽤ provide 和 inject 并暴露⼀个组合函数。 以vuex为

例,插件内部代码:

const StoreSymbol = Symbol()
export function provideStore(store) {
 provide(StoreSymbol, store)
}
export function useStore() {
const store = inject(StoreSymbol)
if (!store) {
// 抛出错误,不提供 store
 }
return store
}

// vue-router 
function useRouter() {
return vue.inject(routerKey);
 }
function useRoute() {
return vue.inject(routeLocationKey);
 }
// vuex 
function useStore (key) {
if ( key === void 0 ) key = null;
return vue.inject(key !== null ? key : storeKey)
}

开发者在项⽬中使⽤:

// 在根组件中提供 store
const App = {
setup() {
 provideStore(store)
 },
}
const Child = {
setup() {
const store = useStore()
// 使⽤ store
 },
}

附加⼆:Ref 和 Reactive 的选择

1. 区分开普通的基本类型 和 响应式值的引⽤;

2. 读写 ref 的操作⽐普通值更冗余,需要访问 .value ;

3. 通过 reactive 创建的对象不能被解构或展开,会失去响应性;

4. 最佳实践需要在项⽬中不断尝试。

现有vue2 项⽬逐步迁移vue3

  • 安装 @vue/composition-api

    // 安装npm 包依赖 npm i @vue/composition-api // 在项⽬⼊⼝ main.js 或者 main.ts 引⼊ import CompositionApi from '@vue/composition-api' Vue.use(CompositionApi)

  • setup 中可使⽤的函数与vue3 基本类似

  • vue-router 和 vuex 的使⽤

    const { router,router, store } = ctx.root const route = computed(() => ctx.root.route)//通过计算属性,获取vuex值的变更letavatar=computed(()=>route) // 通过计算属性,获取vuex 值的变更 let avatar = computed(() => store.getters.avatar) let name = computed(() => $store.getters.name)