You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
16 KiB
Markdown

2 years ago
# 03 | 新特性初探Vue 3新特性
你好,我是大圣。
在上一讲我们跟着小圣通过实现一个清单应用扭转了思路认识到jQuery的开发思路和 Vue开发思路的区别。想要用好 Vue首先就是专注数据本身的操作。
而我们的主人公小圣并没有满足这些内容他跑到社区逛了一圈想知道自己从哪个版本开始学习Vue更合适。这就引出了今天的话题相比 Vue 2Vue 3 的优势是什么,以及 Vue 3 到底有哪些新特性值得我们学习。
## Vue 2 的核心模块和历史遗留问题
先看一看Vue 2。从下图你能看到Vue 2 是一个响应式驱动的、内置虚拟DOM、组件化、用在浏览器开发并且有一个运行时把这些模块很好地管理起来的框架。
![图片](https://static001.geekbang.org/resource/image/df/a2/df099da509445a941d129eb9696935a2.jpg?wh=1661x957)
Vue 2 能把上面所说的这些模块很好地管理起来看起来已经足够好了。不过事实真的如此么聪明的你估计已经猜到了Vue 2 还是有缺陷的,所以后面才会升级迭代。
我在下面列举了一些Vue 2 常见的缺陷,你可以对照你的实际开发经验,看看是否也遇到过这些问题:
首先从开发维护的角度看Vue 2 是使用Flow.js来做类型校验。但现在Flow.js已经停止维护了整个社区都在全面使用TypeScript来构建基础库Vue团队也不例外。
然后从社区的二次开发难度来说Vue 2 内部运行时是直接执行浏览器API的。但这样就会在Vue 2的跨端方案中带来问题要么直接进入 Vue 源码中,和 Vue 一起维护比如Vue 2 中你就能见到Weex的文件夹。
要么是要直接改为复制一份全部Vue的代码把浏览器API换成客户端或者小程序的。比如mpvue就是这么做的但是Vue后续的更新就很难享受到。
最后从我们普通开发者的角度来说,**Vue 2响应式并不是真正意义上的代理而是基于Object.defineProperty() 实现的**。对于Object.defineProperty() 这个API的细节我们在后面讲源码时会讲到现在你只需要知道这个API并不是代理而是对某个属性进行拦截所以有很多缺陷比如删除数据就无法监听需要 $delete 等 API 辅助才能监听到。
**并且Option API在组织代码较多组件的时候不易维护。**对于Option API 来说所有的methods、computed都在一个对象里配置这对小应用来说还好。但代码超过300行的时候新增或者修改一个功能就需要不停地在datamethods里跳转写代码我称之为上下反复横跳。
## 从七个方面了解Vue 3新特性
前面这些问题并不是Vue 2 有意为之大部分是发展的过程中碰见的。Vue 3 就是继承了 Vue 2 具有的响应式、虚拟DOM组件化等所有优秀的特点并且全部重新设计解决了这些历史包袱的新框架是一个拥抱未来的前端框架。
接下来我们就来具体看看 Vue 3 新特性我将分成7个具体方面向你展开介绍。其中响应式系统、Composition API组合语法、新的组件和Vite是你需要重视的自定义渲染器这方面的知识你想用Vue开发跨端应用时会用到如果你想对Vue源码作出贡献RFC机制你也需要好好研究并且得对TypeScript重构有很好的经验。
### RFC机制
Vue 3的第一个新特性和代码无关而是Vue团队开发的工作方式。
关于 Vue 的新语法或者新功能的讨论都会先在GitHub上公开征求意见邀请社区所有的人一起讨论 你随时可以打开这个项目,我把链接放在[这里](https://github.com/vuejs/rfcs)。Vue 3 正在讨论中的新需求,任何人都可以围观、参与讨论和尝试实现。
这个改变让Vue社区更加有活力不管是课程后面会提到的<script setup> Vue 3 ref APIVueVue
Vue很长一段时间都是尤雨溪一个人维护感慨尤雨溪战斗力的同时社区也有很多人对Vue的稳定性提出质疑。后来尤雨溪吸纳了社区的人并成立了Core Team。Vue 3 在此基础之上更进一步全面拥抱社区任何对Vue感兴趣的人都可以参与新特性的讨论。
![图片](https://static001.geekbang.org/resource/image/61/68/61bb976a8165ef2e2f177d9e51c8bd68.png?wh=1534x1094)
RFC的引入让Vue生态更加开放在开发方式的新特性之外我们搞技术的还是要回归代码下面我来说说Vue 3 在代码层面所做的具体优化。
### 响应式系统
Vue 2 的响应式机制是基于Object.defineProperty()这个API实现的此外Vue还使用了Proxy这两者看起来都像是对数据的读写进行拦截但是defineProperty是拦截具体某个属性Proxy才是真正的“代理”。
怎么理解这两者的区别呢我们首先看defineProperty这个APIdefineProperty的使用要明确地写在代码里下面是示例代码
```xml
Object.defineProperty(obj, 'title', {
  get() {},
  set() {},
})
```
当项目里“读取obj.title”和“修改obj.title”的时候被defineProperty拦截但defineProperty对不存在的属性无法拦截所以 Vue 2 中所有数据必须要在data里声明。
而且如果title是一个数组的时候对数组的操作并不会改变obj.title的指向虽然我们可以通过拦截.push等操作实现部分功能但是对数组的长度的修改等操作还是无法实现拦截所以还需要额外的$set等API。
而Proxy这个API就是真正的代理了我们先看它的用法
```xml
new Proxy(obj, {
get() { },
set() { },
})
```
需要注意的是,虽然 Proxy 拦截obj这个数据但obj具体是什么属性Proxy则不关心统一都拦截了。而且Proxy还可以监听更多的数据格式比如Set、Map这是 Vue 2 做不到的。
当然Proxy存在一些兼容性问题这也是为什么Vue 3 不兼容IE11以下的浏览器的原因还好现在IE用的人不多了。
**更重要的是我觉得Proxy代表一种方向就是框架会越来越多的拥抱浏览器的新特性。**在Proxy普及之前我们是没有办法完整的监听一个JavaScript对象的变化只能使用Object.defineProperty()去实现一部分功能。
前端框架利用浏览器的新特性来完善自己,才会让前端这个生态更繁荣,抛弃旧的浏览器是早晚的事。
这里你掌握Proxy的优势就可以了具体的使用我们后面会自己手写一个帮助你深入理解。
### 自定义渲染器
Vue 2 内部所有的模块都是揉在一起的这样做会导致不好扩展的问题刚才我也提到了这一点。Vue 3 是怎么解决这个问题的呢那就是拆包使用最近流行的monorepo管理方式响应式、编译和运行时全部独立了变成下图所示的模样
![图片](https://static001.geekbang.org/resource/image/95/0c/9573fb8b18cb694fe9959b82742ecb0c.jpg?wh=1444x824)
**我们能看到在Vue 3 的组织架构中,响应式独立了出来。**而Vue 2 的响应式只服务于VueVue 3 的响应式就和Vue解耦了你甚至可以在Node.js和React中使用响应式。
渲染的逻辑也拆成了**平台无关渲染逻辑**和**浏览器渲染API**两部分 。
在这个架构下Node的一些库甚至React都可以依赖响应式。在任何时候如果你希望数据被修改了之后能通知你你都可以单独依赖Vue 3 的响应式。
那么在你想使用Vue 3 开发小程序、开发canvas小游戏以及开发客户端的时候就不用全部fork Vue的代码只需要实现平台的渲染逻辑就可以。
![图片](https://static001.geekbang.org/resource/image/27/f6/2742614d6d43134084835a44079313f6.jpg?wh=1920x939)
就像动画片《战神金刚》五个机器人可以独立执行任务但关键时刻高呼一声“我来组成头部”就可以合体从而发挥整体的作用。Vue 3 也是一样响应式、编译和运行时几部分组合在一起就是运行在浏览器端的Vue 3每个模块又都可以独立扩展出新的功能。
### 全部模块使用TypeScript重构
由于小圣之前只是用JavaScript来构建他的前端项目而JavaScript是弱类型的语言。类型系统带来的好处以后我再跟他细说现在只是笼统地告诉他**类型系统带来了更方便的提示,并且让我们的代码能够更健壮**。
我们还是结合例子来看看在下面这段代码中我们首先定义了name这个变量在定义的时候标记的是一个字符串因而后面给它赋值时赋值为数字就会报错。
之后,我们定义一个类型 Person里面的变量name是字符串类型变量age是数字类型。违反这个设置的数据就报错这在多人协同和长期维护的项目里带来的收益是巨大的因为这样可以使错误的代码在编译阶段就被发现从而避免程序上线运行后可能会发生的更大的异常错误。
```xml
let name:string = '我是个靓仔'
name = 1 // 报错
interface Person {
name: string;
age: number;
}
let me:Person = {
name:'靓仔圣',
age:18
}
me.age = '整条街' // 报错
```
所以大部分开源的框架都会引入类型系统来对JavaScript进行限制。这样做的原因就是我们前面提到的两点**第一点是,类型系统带来了更方便的提示;第二点是,类型系统让代码更健壮**。
Vue 2 那个时代基本只有两个技术选型Facebook家的Flow.js和微软家的TypeScript。Vue 2选Flow.js没问题但是现在Flow.js被抛弃了。Vue 3 选择了TypeScriptTypeScript官方也对使用TypeScript开发Vue 3 项目的团队也更加友好。
### Composition API 组合语法
Composition API 是Vue 3 中我最喜欢的一个特性我们也叫它组合API。
先举个Vue 2 中的简单例子一个累加器并且还有一个计算属性显示累加器乘以2的结果。
```xml
<div id="app">
<h1 @click="add">{{count}} * 2 = {{double}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
let App = {
data(){
return {
count:1
}
},
methods:{
add(){
this.count++
}
},
computed:{
double(){
return this.count*2
}
}
}
Vue.createApp(App).mount('#app')
</script>
```
在Vue 3 中除了上面这种这个写法我们还可以采用下方的写法新增一个setup配置
```xml
<div id="app">
<h1 @click="add">{{state.count}} * 2 = {{double}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const {reactive,computed} = Vue
let App = {
setup(){
const state = reactive({
count:1
})
function add(){
state.count++
}
const double = computed(()=>state.count*2)
return {state,add,double}
}
}
Vue.createApp(App).mount('#app')
</script>
```
使用Composition API后代码看起来很烦琐没有Vue 2 中 Options API 的写法简单好懂但Options API的写法也有几个很严重的问题
* 由于所有数据都挂载在this之上因而Options API 的写法对TypeScript的类型推导很不友好并且这样也不好做Tree-shaking清理代码。
* 新增功能基本都得修改data、method等配置并且代码上300行之后会经常上下反复横跳开发很痛苦。
* 代码不好复用Vue 2 的组件很难抽离通用逻辑只能使用mixin还会带来命名冲突的问题。
我们使用Composition API 后,虽然看起来烦琐了一些,但是带来了诸多好处:
* 所有API都是import 引入的现在我们的例子还没有工程化后续会加入。用到的功能都import进来对Tree-shaking很友好我的例子里没用到功能打包的时候会被清理掉 ,减小包的大小。
* 不再上下反复横跳我们可以把一个功能模块的methods、data都放在一起书写维护更轻松。
* 代码方便复用可以把一个功能所有的methods、data封装在一个独立的函数里复用代码非常容易。
* Composotion API 新增的return等语句在实际项目中使用<script setup>特性可以清除, 我们后续项目中都会用到这样的操作。
**Composition API对我们开发Vue项目起到了巨大的帮助。**下面这个示例图很好地说明了问题每一个功能模块的代码颜色一样左边是Options API一个功能的代码零散的分布在datamethods等配置内维护起来很麻烦而右边的Compositon API就不一样了每个功能模块都在一起维护。
![](https://static001.geekbang.org/resource/image/a0/5f/a0010538b40e48fc5fc68b0eed2b025f.jpg?wh=3220x2046)
其实还可以更进一步如果每个颜色块代码我们都拆分出去一个函数我们就会写出类似上面右侧风格的代码每个数据来源都清晰可见而且每个功能函数都可以在各个地方复用。对于Vue 3 采用的这种代码风格,小圣同学直呼清爽。
### 新的组件
Vue 3 还内置了Fragment、Teleport 和 Suspense三个新组件。这个倒不难项目中用到的时候会详细剖析现在你只需要这仨是啥就行以及它们的用途即可
* Fragment: Vue 3 组件不再要求有一个唯一的根节点清除了很多无用的占位div。
* Teleport: 允许组件渲染在别的元素内,主要开发弹窗组件的时候特别有用。
* Suspense: 异步组件,更方便开发有异步请求的组件。
### 新一代工程化工具Vite
Vite不在Vue 3 的代码包内和Vue也不是强绑定Vite的竞品是Webpack而且按照现在的趋势看使用率超过Webpack也是早晚的事。
Vite主要提升的是开发的体验Webpack等工程化工具的原理就是根据你的import依赖逻辑形成一个依赖图然后调用对应的处理工具把整个项目打包后放在内存里再启动调试。
由于要预打包所以复杂项目的开发启动调试环境需要3分钟都很常见Vite就是为了解决这个时间资源的消耗问题出现的。
你可能不知道现代浏览器已经默认支持了ES6的import语法Vite就是基于这个原理来实现的。具体来说在调试环境下我们不需要全部预打包只是把你首页依赖的文件依次通过网络请求去获取整个开发体验得到巨大提升做到了复杂项目的秒级调试和热更新。
下图展示了Webpack的工作原理Webpack要把所有路由的依赖打包后才能开始调试。
![图片](https://static001.geekbang.org/resource/image/d4/ba/d471d1f14abeaf4b091ddf5fb83e3eba.jpg?wh=1920x913)
而下图所示的是Vite的工作原理一开始就可以准备联调然后根据首页的依赖模块再去按需加载这样启动调试所需要的资源会大大减少。
![图片](https://static001.geekbang.org/resource/image/8b/4c/8b726d2b8a09b045874340504a04414c.jpg?wh=1920x960)
后面我交给小圣的项目也主要用Vite作为工程化工具你之后会详细地在实战中了解第四部分我还会带着小圣手写一个mini版Vite让我们拭目以待吧
## 总结
今天学习的重点是Vue 3 主要的新特性,我们再来对这些特征做一个回顾:
* 新的RFC机制也让我们所有人都可以参与Vue新语法的讨论。
* 工程化工具Vite带来了更丝滑的调试体验。
* 对于产品的最终效果来看Vue 3 性能更高,体积更小。
* 对于普通开发者来说Composition API 组合语法带来了更好的组织代码的形式。全新的响应式系统基于Proxy也可以独立使用。
* Vue 3 内置了新的Fragment、Teleport和Suspense等组件。
* 对于Vue的二次开发来说自定义渲染器让我们开发跨端应用时更加得心应手。
* 对于Vue的源码维护者全部的模块使用TypeScript重构能够带来更好的可维护性。
简而言之Vue 3 带给我们的就是更快、更强且更易于扩展的开发体验,我们也可以用下面这个图来做个总结:
![](https://static001.geekbang.org/resource/image/cc/d0/cc47460b1f9441d843bff6d37777a8d0.jpg?wh=3059x1664)
## 思考题
你喜欢 Vue 3 的哪一个新特性呢?你可以谈谈你的看法。
欢迎你在留言区跟我互动讨论,也推荐你把这一讲分享给你自己的朋友、同事。