浅学vue3

469 阅读12分钟

一、使用Vite创建Vue3项目

在听尤大大一次直播分享中见其本人对Vite很是称赞,学习Vue3的过程中也想体验一下,决定用Vite创建项目。

1. 了解Vite

Vite官网:vitejs.cn/guide/

Vite官网描述为下一代前端开发与构建工具。相比较webpack,Vite基于原生的ESM,无需对文件打包,按需加载,启动开发服务器时间大大缩短,热重载(HMR)方面,文件修改后,也能迅速反馈到页面上,循环往复,节省的时间可以大大提高开发效率。

常用的打包方式:

bundler.37740380.png

Vite打包方式:

如图可见,Vite会在浏览器发出请求后按需编译,不再等待整个应用编译完成。现有工具会在所有文件模块打包就绪之后,开启服务,再供页面访问。

在打包方面,Vite是用rollup进行打包的

2. 创建项目

npm init @vitejs/app

然后按照后续提示创建项目。

接下来按照命令行指示,安装依赖,启动服务,基本项目就创建好了。

二、开始Vue3

1. 分析目录结构

Vite生成的Vue3目录结构和vue-cli生成的Vue2项目基本一致,入口文件同样为main.js。Vue3入口引入的不再是Vue的构造函数,而是名为createApp的工厂函数,若继续Vue2里边的写法,import Vue from 'vue' 打印出来的Vue则为undefined。也就是说Vue2里边的写法并不被兼容。creatApp(App)相当于Vue2里定义的vm,且会更轻量。

2、Vue的响应式原理

1. Vue2.x的响应式

对象类型:通过Object.defineProperty() 对属性读取/修改进行拦截(数据劫持

数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

\

2. Vue3.0的响应式

通过Proxy:拦截对象中任意属性的变化,包括属性的读写/添加/删除等

通过Reflect:对被代理对象属性进行操作

Proxy: es6.ruanyifeng.com/#docs/proxy

Reflect: es6.ruanyifeng.com/#docs/refle…

Proxy,字面翻译:代理。可以看作为目标对象代理做某些处理。也拥有get和set方法去读取和修改。

区别于 Object.defineProperty,它可以检测到属性的添加以及数组的变化,也使得Vue3里可以直接对某对象动态添加根级别的响应式属性,以及修改数组的长度/利用索引直接设置一个数组项,而无需用Vue.$set()去触发更新。

3、Vue的生命周期

1. Vue2.x的生命周期

2. Vue3.0的生命周期

Vue2.x和Vue3里的卸载前和卸载完毕的生命周期有差别。

4. 常用的组合式API

0. WHY组合式API

在项目中,不同页面我们已经做到了共用组件,但当组件多到一定数量,就需要共用代码了。而组合式API可以做到。

如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。

1. setup-组合式API入口

  • 组件中所用到的数据、方法等都要配置在setup里。它在组件被创建之前,props 被解析之后执行。
  • 如果返回对象,模板中可以直接使用。返回的任何内容都可以用于组件的其余部分

setup在beforeCreate之前执行,this拿不到,是undefined。也就是不能通过this去调用data/methods/props/computed里的变量或方法等相关内容了。

参数:

  • {Data} props
    • 值为对象 包含:组件外部传递过来,且组件内部声明接收了的属性
  • {SetupContext} context
    • attrs:值为对象,包含:从组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
    • slots:收到的插槽内容,相当于this.$slots
    • emit:分发自定义事件的函数,相当于this.$emit

注意点:

  1. 尽量不能和选项式API那种混用。选项式API的配置里(data和methods)可以访问到setup里的属性、方法,但在setup里不能访问到选项式API配置的。
  2. setup不能是async函数,因为返回值会变为promise,而非对象,模板拿不到要用到的属性。

模板里用到的数据,都要通过return传递出去,那有没有更方便的办法,不用return就可以直接用?

答案是有的。

Vue3为我们提供了一种语法糖的写法。

<script setup>

直接在 <script> 标签上添加 setup 属性即可。

这样写,在标签内部声明的顶层变量方法包括import引入的内容都可以直接在模版里使用。

<script setup> 中可以使用顶层 await。结果代码会被编译成 async setup()

2. 生命周期钩子

为了和选项式 API一样完整,组合式API里也可以注册生命周期钩子。

setup里用到生命周期,名称和选项式API生命周期有所差别。是onX类型的函数,在使用之前需要在Vue里引入。

3. 定义响应式数据

我们在setup函数里,定义的变量,直接return出来不是响应式数据(数据变,页面跟着渲染变化),需要用方法处理一下,让Vue知道要将此变量置为响应式数据。

- ref函数

ref是个方法,接受一个内部值(任意类型)并返回一个响应式且可变的 ref 对象。ref中如果传的是对象,则内部是通过reactive方法处理,最后形成了一个Proxy类型的对象

这个方法需要在顶部引入:import { ref } from 'vue'

注意⚠️:此ref方法和Vue2里的ref不一样,Vue2里的 ref 被用来给元素或子组件注册引用信息。引用信息可以通过父组件的$refs.xx拿到。用在dom上,代表这个dom元素;用在组件上,则引用指向这个组件的实例。Vue3里也保留了这个ref。

ref 对象具有指向内部值的单个 property .value。如图,单独打印ref返回的值是个RefImpl的对象。无法操作定义的响应值3。所以setup方法中操作数据需要通过.value去拿到值3。(模板中取该变量无需.value

那为什么定义一个响应式数据,偏偏要用.value去操作呢,满篇的.value有什么必要吗,像Vue2里直接拿着变量名称处理不是很好?

在官网中得到了解答:

将值封装在一个对象中,看似没有必要,但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的。这是因为在 JavaScript 中,Number 或 String 等基本类型是通过值而非引用传递的。

在任何值周围都有一个封装对象,这样我们就可以在整个应用中安全地传递它,而不必担心在某个地方失去它的响应性。

- reactive函数

前边提到,ref可以返回任意类型的变量的响应式副本,那reactive还有什么必要吗?

当然是有必要的。上一段对ref了解,他在操作该响应式变量的时候,需要.value去取值,那有没有一个方法,可以避开Number和String等基本类型,操作时候无需.value呢?答案是有的。也就是reactive函数。

返回对象的响应式副本(一个Proxy的代理对象,被代理者就是reactive中传入的对象),影响所有嵌套的属性,也就是这个对象层级不管多深,里边的数据都是响应式的。

reactive 将解包所有深层的 refs,同时维持 ref 的响应性。

- ref区别于reactive

  1. ref常用来定义基本数据类型,当用其定义对象类型数据,它的内部会自动通过 reactive 转为代理对象。而reactive用来定义对象类型数据
  2. ref定义的数据,操作需要.value,模版中不需要.value。reactive定义的数据操作和模板中使用都不需要.vaule
  1. ref是通过 Object.defineProperty()的get和set实现的响应式。reactive是通过 Proxy 实现数据劫持,并通过Reflect操作源对象内部的数据

- toRef

可以用来为源响应式对象上的某个属性新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。

const name == toRef(person, 'name')

- toRefs

在组合式函数里,想把响应式数据返回。一一返回太繁琐,这时候可以通过toRefs方法,对对象进行解构,解构出来的数据也是响应式的。

这时候data里的一级属性可以直接在模版里使用

- shallowRef

传入的是基本类型,则和ref无异

传入的对象类型,则不转为响应式数据

- shallowReactive

定义的响应式数据只考虑第一层的响应式(浅响应式)

- readonly

接收一个响应式数据,返回的不允许修改

- shallowReadonly

只考虑第一层数据,返回的不允许修改

- toRaw

返回reactive定义的响应式对象的原始对象。

假设此时有个对象为obj(原始对象),在创建原始对象obj的响应式副本时(const reactiveObj = reactive(obj)),obj仍然可以拿到,那么toRaw()把响应式对象转为原始对象还需要吗?明明obj近在咫尺,随时可用。答案为需要。我们知道在变更响应式副本reactiveObj时,原始对象obj的数据也会随之变化。

当我们需要原始对象时候,toRaw()就派上用场了。

- markRaw

标记一个对象,使其为非响应式对象

- customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制。

用于防抖等:

4. 计算属性与监视

- computed函数

computed需要从vue引入,import { ref, computed } from 'vue'

作为函数使用,内部接收一个函数,该函数可写为箭头函数。此方式返回的fullName不可更改。

内部也可接收一个具有 get 和 set 函数的对象,则可创建和修改值

- watch函数

watch也需要先从Vue引入,作为函数使用。

监听ref或者reactive定义的响应式数据,可以要监听变量传给watch的第一个参数,第二个参数接收一个函数,可以拿到最新改变的值。reactive定义的响应数据无法拿到oldValue,ref定义的对象类型的相应数据,第一个参数要加.value。

watch使用数组监听多个数据。

watch监听的数据不管多少层,都能监听。默认开启深度监听,且无法关闭

监听对象里某一个属性,要把第一个参数改为 函数式。同理,监听多个属性,也要写为数组形式

- watchEffect函数

不用指明监视哪个属性,回调里用到哪个属性发生了变化,就监视哪个属性。

有点儿像computed:

  • 但computed注重点算出来的值(回调函数的返回值),所以必须要写返回值
  • 而watchEffect更注重函数体,也就是中间过程,所以不用写返回值

5. Provide / Inject

实现祖孙组件间通信。父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据。

在组合式 API 中使用 provide/inject,两者都只能在当前活动实例的 setup() 期间调用。

  • 祖组件使用provide:

    a. 需要先引用provide

    import { provide } from 'vue'

    b. 在setup()期间使用provide方法暴露数据

    provide('name', 'value')

  • 后代组件使用inject:

    a. 需要引入inject

    import { inject } from 'vue'

    b. 在setup()期间使用inject方法获取provide暴露出来的数据

    const name = inject('name', 'defaultVal') // 第二个参数为默认值,可选

6. 响应式数据的判断

  1. isRef: 检查一个值是否为一个ref对象
  2. isReactive: 检查一个对象是否由 reactive 创建的响应式代理
  3. isReadonly: 检查一个对象是否由 readonly 创建的只读代理
  4. isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

三、新组件

1. Fragment(片段)

之前的vue需要一个根标签,vue3里不需要了。可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中。好处:减少标签层级,减少内存占用

2.x写法

3.x写法

2. teleport(瞬移)

Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件。

这将移动实际的 DOM 节点,而不是被销毁和重新创建,并且它还将保持任何组件实例的活动状态。所有有状态的 HTML 元素 (即播放的视频) 都将保持其状态。

假如一个按钮,点击弹出一个弹框供用户输入相关信息。弹框一般是fixed布局,显示在窗口中间。

有了teleport就可以通过参数 to 指定将teleport中包裹的元素移动到to参数传递到元素下。

注意⚠️: to 必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)

3. suspense(不确定)

该 组件提供了另一个方案,允许将等待过程提升到组件树中处理,而不是在单个组件中。

试验性

Suspense 是一个试验性的新特性,其 API 可能随时会发生变动。特此声明,以便社区能够为当前的实现提供反馈。

生产环境请勿使用。

suspense有两个插槽。defaultfallback

#default 插槽里, 当组件被加载过来,就会展示组件里的内容

#fallback 插槽里,通常被放置默认内容,也就是组件未加载过来的占位内容。

在父组件中使用子组件:

先引入异步组件

在components里注册组件

引入的组件要为异步组件。

  1. 在 setup 中返回Promise
  2. 前文4.1提到【setup不能是async函数,因为返回值会变为promise,而非对象】。有了suspense组件就可以做到了。

四、组合式API和optionsAPI的区别

vue2.x中的在data(),methods(),computed(),created()等项里配置内容等方式成为option API。它的特点就是清晰明了的配置项,虽然配置项清晰,也导致了页面里的各种功能的data,methods等配置会写在一起,假如某个功能逻辑需要改动,则需要改变data,methods....项的相关内容,滚动翻看查找要改动的地方,一旦页面逻辑复杂就会混乱。实际不够清晰明了。

组合式API则把各种功能用到的data,methods等归类,归到某个单独文件也就是hooks,在页面里use,这样更清晰。