Options API
- 包含一个描述组件选项(data, methods, props 等)
- options API 开发复杂组件,同一个功能逻辑的代码被拆分到不同选项
Composition API
- vue3.0 新增的 一组 API
- 一组 基于函数 的 API
- 可以更灵活的的 组织 ” 组件“ 逻辑
2 者 结构对比,如下【相同颜色代表相同功能模块】,
显然Composition API 可以更好的整理相同模块,使其看起来更加整洁 统一,
- vue2.x 响应式系统的核心是 defineProperty【初始化时,通过遍历data里的对象,使用 dedineProperty 把每个对象转化为getter和setter,从而使其变成响应式数据】
- vue 3.0 使用 Proxy(es6新属性)对象,重写响应式系统
优点:
- 可以监听动态 "新增" 的属性
- 可以监听 "删除" 的属性
- 可以监听 数组 的 ”索引“ 和 ”length“ 属性
vue2.x 中通过 "标记" "静态根节点" 来优化 diff 的过程
回顾vue2.x编译的过程:
- 模板首先要编译成 render函数【这个过程一般是在构建的时候完成的】
- 在编译的时候,会编译 ”静态根节点【必须是固定的】“ 和 ”静态节点“, 【静态根节点要求必须有一个静态子节点】
- 当组件的状态发生变化时,会通知 watcher ,触发 watcher 的 update
- 最终去执行虚拟dom的patch操作,遍历所有的虚拟节点找到差异,并更新到真实dom上
- diff 过程中,会去比较整个的虚拟DOM,先对比新旧DIV 以及它的属性,再对比它的子节点
总结:
- 在vue2.x 中它渲染的最小单位是 “组件”
- vue2.x 在 diff 过程中会 跳过 静态根节点【因为静态根节点的内容不会发生变化】,所以 vue2.x 通过 "标记" "静态根节点",优化了diff 过程,
- 在 vue 2.x 中 静态节点 还需要再进行 diff,这个过程 没有被优化
vue 3.0 中通过 "标记 和 提升" "所有的静态根节点",diff 时候只需要对比动态节点内容 【大大提升 diff 的性能】
- Fragments【片段,不是必须有一个固定的根节点,需要升级 vetur 插件】
- 静态升级【开启hoistStatic,将静态节点(内容是纯文本的节点),在初始化的时候创建(只创建一次),后面的render函数只需要直接调用,已经创建的vnode静态节点】
- Patch flag 【标记动态节点,对比时候只对比动态节点的值是否变化】
- 缓存事件处理函数【较少了不必要的更新操作】
- vue 3.0 移除了一些 不常用的 API 【 如:inline-template, filter 等】
- Tree-shaking 【vue3.0 对它的支持比较好】
内容拓展:
- Tree-shaking 依赖 ES Module【es6 模块化语法的静态结构:import 和 export】,通过编译阶段的静态分析,找到没有引入的模块,在打包的时候直接过滤掉,让打包后的体积更小
- vue3.0 在设计之初就考虑到了,内置的组件如:transition, keep-alive ;内置的指令v-module 等都是按需引入的
- vue3.0 的很多API 都是支持 Tree-shaking的,如果vue3.0 新增的一些API你没有使用,这部分代码是不会被打包的,只会打包你使用的API,但是 默认vue核心模块都会被打包
伴随 Vue 3.0 的推出, Vue3.0 的作者还开发了一个构建工具 Vite
Vite:这个单词来自于法语【快的意思】, 意味着 Vite 这个工具比过去 vue-cli 【基于webpack的】更快
彩蛋:vite多久后能干掉webpack?
知识回顾:浏览器使用 ES Module 的方式
- 现代浏览器都支持 ES Module 【IE不支持】
- 通过下面的方式加载模块【使用es module方式加载模块,默认开启严格模式 use strict 】
- 支持模块的 script ,并且是默认延迟加载
- 类似于 script 标签设置 defer
- 在(DOM树创建完毕)文档解析完成 之后,执行
- 在触发 DOMContentLoaded 事件 之前 执行
Document Hello World// 先执行这个
Vite的快:就是使用浏览器支持的ES Module方式,避免 开发环境下打包,从而提升开发速度
- Vite 在 开发模式下 不需要打包,可以直接运行
工作原理: 在开发模式下 ,Vite使用 浏览器原生支持的 ES Module 加载模块,【也是就:import导入模块,支持ES Module的现代浏览器,通过 的方式加载模块】,因为 Vite 不需要打包项目【因为把所有模块的请求都交给服务器来处理,在服务器端处理浏览器不能识别的模块,再把编译的结果返回给浏览器】, 因此Vite在开发模式下,打开页面是秒开的。
- Vue-CLI 开发模式下,必须 对 项目打包 才可以 运行
Vue-CLI在开发模式下,会先打包整个项目,如果项目比较大,打包会特别慢
- Vite 在 生产模式下 使用 Rollup 打包
Rollup是基于浏览器原生的ES Module 打包的,它不需要使用 babel 将 import 转换为require,以及相应的一些辅助函数,因此会比 webapck 打包的 体积更小【现代浏览器都已经支持ES Module的方式加载模块】
- Vue-CLI 生产模式下 使用 webpack 打包
- 快速冷启动 【因为不需要打包】
- 按需编译 【只有代码在当前需要加载时才会编译,不需要在开启整个开发服务器的时候,等待整个项目被打包,等到项目比较大时,该特征就会更明显 】
- 模块热更新【模块热更新的性能 和 模块总数无关,无论你有多少模块,HRM的速度始终比较快】
1: Vite 创建项目
npm init vite-app
cd npm install npm run dev 创建成功后的目录结构如下:
2:基于模板 创建项目【可以让它支持其他框架】
npm init vite-app --template react // react 是要使用的框架 npm init vite-app --template preact
不可以对响应式对象进行解构
const position = reactive({ x: 0, y: 0 })
因为: 如上position 调用reactive 方法将对象转换为process对象, 此时position是 process对象。
当访问 position 对象的 x 和 y 时候, 会调用代理对象中的 gettrer 拦击 收集依赖 ;
当 x, y 变化的时候,会调用 代理对象中的 setter 进行拦截, 触发 跟新;
const { x, y } = position
如上: 当解构 代理对象 position 时, 就相当于 定义了 2个变量 x 和 y 来接收 position.x 和 position.y 【基本类型赋值:就是把内存中的值复制一份】,所以这里的 x 和 y 就是基本类型变量,跟代理对象无关,当重新给 x 和 y 赋值的时候,也不会调用代理对象的 setter,无法 "触发更新" 的操作,所以我们不能对响应式对象进行解构。。。
知识拓展:
什么是process对象?
- process对象是一个Global全局对象,你可以在任何地方(所有模块中)都能使用它,而无需 require【如:process.env: 代表全局环境】
- 作用域是全局的
- 比如 export 也是在所有模块中都能使用,但是作用域是当前模块
实现原理:toRefs( 接收的参数必须是代理对象 ),它内部会创建一个新的对象,然后 遍历 这个传入的 代理对象 的 所有属性,然后把 所有属性的值 都转化为 响应式对象
function useMousePosition () { const position = reactive({ x: 0, y: 0 }) return toRefs(position) } const { x, y } = useMousePosition()
如上:toRefs 把 position 这个对象里面的所有属性的值,都转化为 响应式对象,然后挂载到新创建的对象上, 然后再把这个新创建的对象返回;
它内部会为 代理对象的每一个属性 创建一个具有value属性的对象,该对象是响应式对象,value属性具有getter 和 setter 属性, getter里面返回 代理对象 对应属性 的 值 ,setter中给 代理对象 赋值,所以返回的每一个属性都是响应式的
Document
x: {{ x }}
y: {{ y }}
实现原理:ref(参数) ,参数分为2种类型
- 对象类型:内部会调用 reactive 来转化为 响应式对象
- 基本数据类型:内部会 创建一个只有value属性的对象,该对象是响应式对象,该对象的 value 属性具有 getter 和 setter 属性, getter 里面收集依赖【返回 数据 的值】 ,setter中 触发更新【给 数据 赋值】
代码演示:
Document {{ count }}
计算属性 【简化模板中代码,可以缓存计算的结果,当数据变化后才会重新计算】
// 用法1
computed(()=> count.value + 1)
// 用法2
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
Document
未完成:{{ activeCount }}
watch: 监听响应式数据
watcheffect: 简化版的watch,也用来监听数据的变化【watchEffect 会返回一个用于停止这个监听的函数】
watch有3个参数:
- 参数1:要监听的数据
- 参数2:监听到数据变化后要执行的函数,这个函数有2个参数,分别是 新值 和 旧值
- 参数3:选项对象 【 deep 和 immediate 】
watch 的返回值
- 取消监听的函数
watcheffect 有1个参数
- 参数:接收一个函数作为参数,监听函数内响应式数据的变化
watchEffect与 watch 有什么不同?
- 第一点我们可以看到
watchEffect
不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而watch
只能监听指定的属性而做出变更(v3开始可以同时指定多个)。- 第二点就是 watch 可以获取到新值与旧值(更新前的值),而
watchEffect
是拿不到的。- 第三点是 watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与
computed
同理),而后收集到的依赖发生变化,这个回调才会再次执行,而 watch 不需要,因为他一开始就指定了依赖。
Document
请问一个 yes/no 的问题:
{{ answer }}
{{ count }}
- 添加待办事项
- 删除待办事项
- 编辑待办事项
- 切换待办事项
- 存储待办事项 【存在localstorege】
vue3.0 响应式回顾
- proxy 实现响应式监听【提升 和 标记 所有的静态跟节点】
- 多层属性嵌套,在访问属性过程,中处理下一级属性【通过reactive处理】
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听 数组索引 和 length属性
- 可以作为单独的模块使用
核心方法
- reactive,ref,toRefs,computed
- effect【是watch 和 watchEffect 底层的核心方法】
- track 【收集依赖的函数】
- trigger 【触发更新的函数】
代码演示
解决问题1:proxy的 set 和 deleteProperty中需要返回布尔类型的值
Document
解决问题2:Proxy 和 Reflect 中使用的 receiver
- 接受一个参数,判断这个参数是否是对象
- 创建拦截器对象handler,设置get、set,deleteProperty
- 返回Proxy对象
Document
====定义全局方法
// 1: 是否是对象的方法,返回布尔值
const isObject = val => val !== null && typeof val === 'object'
// 2:判断是否是多层属性嵌套
const convert = target => isObject(target)? reactive(target) : target
// 3: 判断对象中是否存在某个key
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)
export default function reactive (target) {
// 1: 接收一个参数,判断这个参数是否是对象
if (!isObject(target)) return target // 该值不是对象,则直接返回这个值
// 2: 创建拦截对象handler【proxy的 set 和 deleteProperty中需要返回布尔类型的值】
const handler = {
get (target, key, receiver) {
// 收集依赖
// 获取值
const result = Reflect.get(target, key, receiver)
// 判断result是否是多层属性嵌套,然后用reactive处理
return convert(result)
},
set (target, key, value, receiver) {
// 获取旧的值
const oldValue = Reflect.get(target, key, receiver)
let result = true
// 判断新旧值是否相同
if (!oldValue === value) {
// Reflect.set() 设置成功返回true, 失败返回false
result = Reflect.set(target, key, value, receiver)
// 触发更新
}
return result // 手动添加return
},
deleteProperty (target, key) {
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 触发更新
}
return result
}
}
return new Proxy(target, handler)
}
代码方法集合演示:【reactive】
知识拓展
get() 依赖收集 过程原理
当访问 代理对象的属性时,会执行该属性的get方法,在get方法中会 ”收集依赖“ 【在代理对象的get方法中会存储:target【目标代理对象】 ,key【目标对象对应的属性】, 回调函数 (该key属性改变后的回调函数) 】
set() 触发跟新 过程原理
当重新给属性赋值时,会执行属性对应的set方法,在set方法中会 ”触发更新“【就是找到依赖收集过程中存储的 属于 对应的 回调函数】,找到这个函数后,会立即执行
- new WeakMap()
- new Map()
- new Set()
代码方法集合演示:【effect,tract,trigger】
reactive vs ref
- ref 可以把基本数据类型数据,转成 响应式对象
- ref 返回的对象,重新赋值成对象,也是,响应式的
- reactive返回的对象,重新赋值 丢失 ”响应式“
- reactive返回的对象,不可解构
代码方法集合演示:【ref 】
代码方法集合演示:【toRefs】
代码方法集合演示:【computed】
// 1: 定义全局方法,是否是对象的方法,返回布尔值
const isObject = val => val !== null && typeof val === 'object'
// 2:判断是否是多层属性嵌套
const convert = target => isObject(target)? reactive(target) : target
// 3: 判断对象中是否存在某个key
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)
// =======【 reactive 】
export default function reactive (target) {
// 1: 接收一个参数,判断这个参数是否是对象
if (!isObject(target)) return target // 该值不是对象,则直接返回这个值
// 2: 创建拦截对象handler【proxy的 set 和 deleteProperty中需要返回布尔类型的值】
const handler = {
get (target, key, receiver) {
// 收集依赖
track(target, key)
// 获取值
const result = Reflect.get(target, key, receiver)
// 判断result是否是多层属性嵌套,然后用reactive处理
return convert(result)
},
set (target, key, value, receiver) {
// 获取旧的值
const oldValue = Reflect.get(target, key, receiver)
let result = true
// 判断新旧值是否相同
if (!oldValue === value) {
// Reflect.set() 设置成功返回true, 失败返回false
result = Reflect.set(target, key, value, receiver)
// 触发更新
trigger(target, key)
}
return result // 手动添加return
},
deleteProperty (target, key) {
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 触发更新
trigger(target, key)
}
return result
}
}
return new Proxy(target, handler)
}
// 全局的是否有依赖收集 标识
const activeEffect = null
export function effect (callback) {
activeEffect = callback
callback() // 访问响应式对象时候,触发的回调,收集依赖
activeEffect = null
}
let targetMap = new WeakMap()
// 收集依赖
export function track (target, key) {
// 如果没有依赖,则不需要收集
if (!activeEffect) return
// target:目标对象key, depsMap:目标对象target的value
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// key:目标对象的 属性名, dep:目标对象 的 属性 对应的 值value, 是集合【effect回调函数集合】
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
// 触发更新
export function trigger (target, key) {
// 获取目标对象
const depsMap = targetMap.get(target)
if (!depsMap) return
// 获取,目标对象下 对应属性的值
const dep = depsMap.get(key)
if (dep) {
// 遍历属性值dep 里的所有 effect集合
dep.forEach(effect => {
effect()
})
}
}
// =======【 ref 】
export function ref (raw) {
// 判断 rwa 是不是 ref创建的对象,是就直接返回,不做转换了
if (raw._v_isRef && isObject(raw)) {
return
}
let value = convert(raw)
// 1: 生成新的对象
const r = {
_v_isRef: true, // 是否是ref的标识
get value () { // 创建一个get 属性名字叫value
// 收集依赖
track(r, value)
return value
},
set value (newValue) { // 创建一个set 属性名字叫value
if (value !== newValue) {
raw = newValue
value = convert(raw)
// 触发更新
trigger(r, value)
}
}
}
// 2: 返回新的对象
return r
}
// =======【 toRefs】
// 作用:把reactive返会的每一个属性,转化为类似于ref返回的对象,这样就可以对reactive返回的对象进行解构
export function toRefs (proxy) {
// 设置一个新的对象遍历,并设置初始值
const ret = proxy instanceof Array ? new Array(proxy.length) : {}
for (const key in proxy) {
// 遍历代理对象,将代理对象的每一个属性,转化为响应式 对象,并挂载给新对象
ret[key] = toProxyRef(proxy, key)
}
return ret
}
export function toProxyRef (proxy, key) {
const r = {
_v_isRef: true,
get value () {
return proxy[key]
},
set value (newValue) {
if (newValue !== proxy[key]) {
proxy[key] = newValue
}
}
}
return r
}
// =======【 computed 】
export function computed (getter) {
const result = ref()
// effect 内部可以监听getter内部属性的变化
effect(() => result.value = getter)
return result
}