写点什么

对于 Vue3 和 Ts 的心得和思考

  • 2023-03-27
    北京
  • 本文字数:2890 字

    阅读完需:约 9 分钟

对于Vue3和Ts的心得和思考

作者:京东物流 吴云阔

1 前言

Vue3 已经正式发布了一段时间了,各种生态已经成熟。最近使用 taro+vue3 重构冷链的小程序,经过了一段时间的开发和使用,有了一些自己的思考。


总的来说,Vue3 无论是在底层原理还是在实际开发过程中,都有了很大的进步。


从源码层面来说,使用 Proxy 代替 Object.defineProperty 的 API,一个是代理的对象,一个是递归监控的属性,从而在性能上有了很大的进步,并且,也解决了对象动态属性增加、数组改变监听上的缺陷;对 diff 算法进行优化,增加了静态标记,大大提高了 Vue 的执行效率;还有静态提升、事件监听缓存等一系列提升效率的手段。


从应用层面来说,主要的改变是将 option API 改成了 composition API(组合式 API),在业务中抛弃 data、methods、生命周期函数隔离开的开发方式,使代码相对于业务有更强的聚合性,在代码开发、代码阅读、代码维护方面对于开发者都是更加友好。


对于 typescript 有了更好的支持,我们知道,对于大型的前端项目来说,使用 typescript 的类型校验,能使前端项目有更强的健壮性,这也使得 Vue3 对于大型项目的开发提供了更强的质量保证。

2 组合式 API

所谓的组合式 API,将 Vue2 中的 data、methods、生命周期、数据监听等 option,都封装成钩子函数,然后组合到 setup 函数中,其核心就在于 setup 函数。setup 函数存在的意义,就是为了使用这些新增的组合式 API,并且这些 API 只能在 setup 函数中使用。


setup 函数执行的时机是,props 初始化之后,beforeCreate 函数执行之前,所以在执行 setup 时,还没有初始化 Vue 实例,因此在 setup 中不能使用 this 对象。setup 函数的返回值会被注入到 Vue 实例中,供 Vue 组件使用,所以任何数据想在 Vue 组件的模板中使用,必须在 setup 函数中 return 出去。


组合式 API 的组合,体现在两个层面。第一层的意思是,将某一业务相关数据和处理逻辑放到一起,这是一种关注点的聚合,更方便我们编写代码、处理业务逻辑,并且能更聚焦业务逻辑,更方便我们看代码。第二层面的意思,当某个组件的业务逻辑足够复杂,setup 中的代码足够大的情况下,我们可以在 setup 内部,进一步提取相关的一块业务,使代码逻辑更加清晰,做到了进一步的聚合作用。


如下面代码所示,将业务代码块 A 抽出来,则代码块 A 中 return 出来的数据就可以在组件中使用:


  1. // 组件

  2. import functionA from 'A'

  3. export default defineComponent({

  4. name: 'componentName',

  5. setup() {

  6. ...functionA()

  7. }

  8. })

  9. // 代码块A

  10. export default () => {

  11. return {

  12. a: 1

  13. }

  14. }

3 响应式 API

在 Vue3 中响应式 API,主要体现在 ref 和 reactive 两个函数。对于响应式 API,想说两个问题,第一个是为什么要增加响应式 API,第二个是响应式 API 函数 ref 和 reactive 的异同点。

3.1 为什么增加响应式 API

在 Vue2 中所有数据都写在 data 的 option 中,data 中的数据都是响应式的,这样产生的一个问题是,有些常量数据本身不需要监听,从而造成了资源的浪费。所以在 Vue3 中增加了响应式 API,只需要对需要动态更新 dom 的数据进行响应式,不需要动态更新 dom 的数据不进行响应式处理,从很大程度上节省了资源。这里我觉得需要注意的是,写代码的时候一定要仔细思考一下,哪些数据需要进行响应式绑定,哪些数据不需要进行响应式绑定,而不是一股脑的全给绑定上,这样即使代码逻辑不能很清晰易懂,并且也会影响执行效率(写惯了 Vue2 的同学需要注意)。

3.2 ref 和 reactive 的异同点

在了解了为什么要增加响应式 API 后,我们发现 Vue3 提供了两个响应式 API 函数,ref 和 reactive。为什么会提供两个 API 呢? 一个不就可以了吗?那么这两个 API 之间的区别是什么呢?


在使用层面,ref 绑定的数据,需要使用[data].value 进行数据更改。而 reactive 绑定的数据需要使用[data].[prpoerty]的方式进行数据更改。在使用场景方面,一般的,单个的普通数据,我们使用 ref 来定义响应式。而复杂数据,如:表单数据对象、某一模块的一组数据等,使用 reactive 来定义响应式。


那么,对象是不是必须用 reactive 来定义呢? 其实不是的,都可以。官方说法是:可以根据自身习惯使用不同的 API。其实,我觉得,他们是有各自的使用场景的,ref 更强调的是数据 Value 的改变,reactive 更强调的是数据中某一属性的改变。

4 treeShaking 思想

当 Javascript 项目达到一定体积时,将代码分成模块会更易于管理。但是,当这样做时,我们最终可能会导入实际上未使用的代码。Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法。


Vue3 使用了 tree shaking 的方法,将组件以及其所有的生命周期函数等方法进行分开,如果在组件中使用的代码将不会出现在最终的打包文件中,如此,会减少大大 Vue3 项目的打包体积。由此造成的一个结果就是,使用方法的不同。


4.1 生命周期函数的使用方法


  1. import { defineComponent, ref, onMounted } from 'vue';

  2. export default defineComponent({

  3. name: 'Gift',

  4. setup() {

  5. const counter = ref(0);

  6. onMounted(() => {

  7. // 处理业务,一般进行数据请求

  8. })

  9. return {

  10. counter

  11. }

  12. }

  13. })

4.2 Vuex 的使用方法

  1. import { useStore } from "vuex";

  2. import { defineComponent, ref, computed } from 'vue';

  3. export default defineComponent({

  4. name: 'Gift',

  5. setup() {

  6. const counter = ref(0);

  7. const store = useStore();

  8. const storeData = computed(() => store); // 配合computed,获取store的值。

  9. return {

  10. counter,

  11. storeData

  12. }

  13. }

  14. })

4.3 Router 的使用方法

  1. import { useStore } from "vuex";

  2. import { useRouter } from "vue-router";

  3. import { defineComponent, ref, computed } from 'vue';

  4. export default defineComponent({

  5. name: 'Gift',

  6. setup() {

  7. const counter = ref(0);

  8. const router = useRouter();

  9. const onClick = () => {

  10. router.push({ name: "AddGift" });

  11. }

  12. return {

  13. counter,

  14. onClick

  15. }

  16. }

  17. })

5 关于 Typescript 的使用

这一部分是关于 Ts 的内容,不过它与 Vue3 的开发息息相关。Vue3 整体是使用 Ts 写的,因此,开发 Vue3 的项目需要使用 Ts,所以,我们还是要了解 TS 的。


关于 Ts 的使用这里就不在细说了,在这里想说的的是,在实际业务场景中是如何组织 Ts 代码的。通过对 TS 的大量使用,我的一个体会是:Ts 的核心思维是先关注数据结构,在根据数据结构进行页面开发。而以前的前端开发模式是,先写页面,然后再关注数据。


比如说,我们要开发一个页面,我们可能需要先定义一些 interface。开发页面的时候我们要关注:页面数据的 interface、接口返回数据的类型、请求参数的类型等等。


下面是开发一个列表页面的例子:


  1. // 这是列表中每一项的数据类型

  2. interface IDataItem {

  3. id: string | number;

  4. name: string;

  5. desc: string;

  6. [key: string]: any;

  7. }

  8. // 接口返回值类型, 一般来说,我们不确定接口返回的数据的类型,因此使用泛型

  9. interface IRes<T> {

  10. code: number;

  11. msg: string;

  12. data: T

  13. }

  14. // 口返回数据类型定义

  15. interface IDataInfo {

  16. list: Array<IDataItem>;

  17. pageNum: number;

  18. pageSize: number;

  19. total: number;

  20. }

  21. // 请求

  22. export const getDatalist = (

  23. params: Record<string, any>

  24. ): Promise<IRes<IDataInfo>> => {

  25. return Http.get("/api/data/list", params);

  26. };


如上面代码,当我们的 interface 定义完成后,我们的页面数据基本都已清楚,直接写页面就会清晰很多,且出错概率会大大降低。

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
对于Vue3和Ts的心得和思考_Vue_京东科技开发者_InfoQ写作社区