一、使用Vite创建Vue3项目
在听尤大大一次直播分享中见其本人对Vite很是称赞,学习Vue3的过程中也想体验一下,决定用Vite创建项目。
1. 了解Vite
Vite官网:vitejs.cn/guide/
Vite官网描述为下一代前端开发与构建工具。相比较webpack,Vite基于原生的ESM,无需对文件打包,按需加载,启动开发服务器时间大大缩短,热重载(HMR)方面,文件修改后,也能迅速反馈到页面上,循环往复,节省的时间可以大大提高开发效率。
常用的打包方式:
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
注意点:
- 尽量不能和选项式API那种混用。选项式API的配置里(data和methods)可以访问到setup里的属性、方法,但在setup里不能访问到选项式API配置的。
- 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
- ref常用来定义基本数据类型,当用其定义对象类型数据,它的内部会自动通过 reactive 转为代理对象。而reactive用来定义对象类型数据
- ref定义的数据,操作需要.value,模版中不需要.value。reactive定义的数据操作和模板中使用都不需要.vaule
- 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. 响应式数据的判断
- isRef: 检查一个值是否为一个ref对象
- isReactive: 检查一个对象是否由 reactive 创建的响应式代理
- isReadonly: 检查一个对象是否由 readonly 创建的只读代理
- 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有两个插槽。default 和 fallback
#default 插槽里, 当组件被加载过来,就会展示组件里的内容
#fallback 插槽里,通常被放置默认内容,也就是组件未加载过来的占位内容。
在父组件中使用子组件:
先引入异步组件
在components里注册组件
引入的组件要为异步组件。
- 在 setup 中返回Promise
- 前文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,这样更清晰。