Vue零基础教程|从前端框架到GIS开发系列课程(六)组合式API
前文指路:
Vue零基础教程|从前端框架到GIS开发系列课程(五)组件式开发
Vue零基础教程|从前端框架到GIS开发系列课程(四)计算属性与侦听器
Vue零基础教程|从前端框架到GIS开发系列课程(三)模板语法
Vue零基础教程|从前端框架到GIS开发系列课程(二)
0帧起手《Vue零基础教程》,从前端框架到GIS开发系列课程
什么是组合式API(了解)
从整体上划分, Vue分为如下几个核心模块
-
编译器.
-
渲染器.
-
响应式系统.
将模块里的每个功能解耦成一个一个函数,
每个函数实现特定的功能, 这些函数可以任意组合使用, 就叫做组合式API
比如:
-
reactive: 将普通对象转换成响应式对象
-
computed: 定义一个计算属性
-
watch: 定义一个侦听器
小结
使用一个函数实现一个特定的小功能, 再加这些函数任意组合, 实现更复杂的功能.
这些函数就叫做组合式API
为什么提出组合式API(了解)
1) Options API下代码的组织形式
使用Options API实现一个功能, 需要在不同的地方编写代码
-
状态(数据)在data中定义
-
方法在methods中定义
-
计算属性
-
...
当新添加一个功能时, 代码的组织会比较零散
2) Composition API下代码的组织形式
Party3
Vue响应式原理(理解)
1) 什么是响应式
当数据改变时, 引用数据的函数会自动重新执行
2) 手动完成响应过程
首先, 明确一个概念: 响应式是一个过程, 这个过程存在两个参与者: 一方触发, 另一方响应
比如说, 我们家小胖有时候不乖, 我会打他, 他会哭. 这里我就是触发者, 小胖就是响应者
同样, 所谓数据响应式的两个参与者
-
触发者: 数据
-
响应者: 引用数据的函数
当数据改变时, 引用数据的函数响应数据的改变, 重新执行
我们先手动完成响应过程
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script>
// 定义一个全局对象: `触发者`
const obj = { name: 'hello' }
// effect函数引用了obj.name, 这个函数就是 `响应者`
function effect() {
// 这里可以通过app拿到DOM对象
app.innerHTML = obj.name
}
effect()
// 当obj.name改变时, 手动执行effect函数, 完成响应过程
setTimeout(() => {
obj.name = 'brojie'
effect()
}, 1000)
</script>
</body>
</html>
为了方便, 我们把引用了数据的函数 叫做 副作用函数
3) 副作用函数
如果一个函数引用了外部的资源, 这个函数会受到外部资源改变的影响
我们就说这个函数存在副作用. 因此, 也把该函数叫做副作用函数
这里, 大家不要被这个陌生的名字吓唬住
所谓副作用函数就是引用了数据的函数或者说数据关联的函数
4) reactive()函数
在Vue3的响应式模块中, 给我们提供了一个API: reactive
reactive(): 将普通对象转换成响应式对象
如何理解响应式对象
如果一个对象的get和set过程被拦截, 并且经过自定义后与某个副作用函数建立了依赖关系.
这样的对象就被认为是具有响应式特性的. 即: 当数据改变时, 所依赖的函数会自动重新执行
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive } = Vue
// 将普通对象转换成响应式对象
const pState = reactive({ name: 'xiaoming', age: 20 })
console.log(pState)
</script>
</body>
</html>
5) effect()函数
响应式对象要跟副作用函数配合使用. 在Vue3中提供了一个API: effect
effect(): 注册副作用函数, 建立响应式对象和副作用函数之间的关系
如何建立依赖
-
定义一个函数, 这个函数中引用了响应式对象的属性
-
通过effect注册副作用函数
💡注意
effect()函数还有很多高级的用法, 这里我们先了解最基础的用法
-
接受一个函数作为参数
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect } = Vue
// 将普通对象转换成响应式对象
const pState = reactive({ name: 'xiaoming', age: 20 })
// 定义一个函数, 引用pState.name, 这个函数就是副作用函数
function e1() {
console.log('e1被执行...', pState.name)
}
// 通过effect注册
effect(e1)
// 当pState.name的值改变时, e1会自动重新执行
setTimeout(() => {
pState.name = 'xiaomei'
}, 1000)
</script>
</body>
</html>
说明
上述注册后, 只建立了pState.name => e1的对应关系
-
如果改变pState.age, e1不会重新执行
-
可以为pState.name注册多个副作用函数
示例1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect } = Vue
// 将普通对象转换成响应式对象
const pState = reactive({ name: 'xiaoming', age: 20 })
// 定义一个函数, 引用pState.name, 这个函数就是副作用函数
function e1() {
console.log('e1被执行...', pState.name)
}
// 通过effect注册
effect(e1)
setTimeout(() => {
// 当pState.age的值改变时, e1不会自动重新执行
pState.age = 30
}, 1000)
</script>
</body>
</html>
示例2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect } = Vue
// 将普通对象转换成响应式对象
const pState = reactive({ name: 'xiaoming', age: 20 })
// 定义一个函数, 引用pState.name, 这个函数就是副作用函数
function e1() {
console.log('e1被执行...', pState.name)
}
// 通过effect注册e1
effect(e1)
// 通过effect注册e2
effect(function e2() {
console.log('e2被执行...', pState.name)
})
setTimeout(() => {
// 当pState.name的值改变时, e1, e2都会自动重新执行
pState.name = 'xiaomei'
}, 1000)
</script>
</body>
</html>
6) 实现响应式流程
借助reactive和effect我们就可以实现vue最核心的响应式流程:
当数据改变时, 页面重新渲染
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { reactive, effect } = Vue
// 定义状态
const pMsg = reactive({ msg: 'hello' })
// 注册副作用函数
effect(() => {
app.innerHTML = pMsg.msg
})
// 改变状态
setTimeout(() => {
pMsg.msg = 'world'
}, 1000)
</script>
</body>
</html>
看到这里, 恭喜你, 已经掌握了最核心的原理🤝
💡 小结
-
响应式是一个过程, 存在触发者和响应者
-
数据的改变, 触发关联的副作用函数响应(重新执行)
-
分为两步
-
-
将普通对象转换成响应式对象
-
注册副作用函数, 建立依赖关系
-
Party4
reactive()函数详解(掌握)
通过前面的学习, 我们了解到reactive可以将一个普通对象转换成响应式对象.
那么, 接下来我们就详细研究一下这个函数.
研究函数主要从这样三个方面
-
输入, 也就是参数
-
作用, 做了什么
-
输出, 也就是返回值
1、参数: 只能是引用类型数据, 不能是值类型数据
2、作用: 创建传入对象的深层代理, 并返回代理后的对象
3、返回值: 一个Proxy代理对象
1) 深层代理
不管传入的对象存在多少层嵌套(对象套对象的情况), 每一层都具有响应性
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect } = Vue
const pState = reactive({
name: 'xiaoming',
age: 20,
gf: {
name: 'xiaomei',
city: {
name: 'wuhan',
},
},
})
effect(() => {
console.log(
`${pState.name}的女朋友叫${pState.gf.name}, 在${pState.gf.city.name}`
)
})
setTimeout(() => {
console.log('过了一段时间, 她去了beijing')
// 不管嵌套多少层, 都具有响应性
pState.gf.city.name = 'beijing'
}, 1000)
</script>
</body>
</html>
2) 重复代理
-
对同一个普通对象, 多次代理, 返回的结果唯一
-
对代理后的对象再次代理, 返回的结果唯一
以上, 可以理解为单例模式, reactive创建的代理对象只会存在一个
单例模式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect } = Vue
const state = { name: 'xiaoming' }
const p1 = reactive(state)
const p2 = reactive(state)
// 对同一个对象多次代理, 返回的结果唯一
console.log(p1 === p2) // true
const p3 = reactive(p1)
// 对代理后的对象, 再次代理, 返回的结果唯一
console.log(p3 === p1) // true
</script>
</body>
</html>
3) 局限性
-
传入参数只能是对象
-
解构或者赋值操作会丢失响应性
示例1
解构赋值后的变量没有响应性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { reactive, effect } = Vue
const pState = reactive({ name: 'xiaoming' })
// 对代理对象进行解构
let { name } = pState
effect(() => {
app.innerHTML = pState.name
})
setTimeout(() => {
name = 'xiaomei'
console.log('对解构后的name操作, 不会触发响应式')
}, 1000)
</script>
</body>
</html>
示例2
赋值操作丢失响应性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { reactive, effect } = Vue
let todos = reactive([])
effect(() => {
app.innerHTML = JSON.stringify(todos)
})
// 模拟向接口请求
setTimeout(() => {
// 将接口返回的数据赋值给todos, 导致todos丢失了响应性
todos = [
{ id: 1, content: 'todo-1' },
{ id: 2, content: 'todo-2' },
]
}, 1000)
</script>
</body>
</html>
Party5
ref()函数详解(重点)
1) ref的基本使用
由于reactive()存在一些局限性, 因此, Vue官方提供了另一个API(ref)定义响应式对象
-
可以传入任意类型数据
-
返回RefImpl
-
通过.value进行get和set操作
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { ref, effect } = Vue
// 创建值类型数据的响应式对象
const count = ref(0)
// 返回值, 是一个RefImpl
console.log(count)
effect(() => {
// 引用时, 需要使用.value
app.innerHTML = count.value
})
setInterval(() => {
// 赋值操作也需要使用.value
count.value++
}, 1000)
</script>
</body>
</html>
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { ref, effect } = Vue
const todos = ref([])
console.log(todos)
effect(() => {
// 引用时, 需要使用.value
app.innerHTML = JSON.stringify(todos.value)
})
setTimeout(() => {
todos.value = [
{ id: 1, content: 'todo-1' },
{ id: 2, content: 'todo-2' },
]
})
</script>
</body>
</html>
2) 响应性丢失问题
将reactive定义的代理对象赋值给其它变量时, 会出现响应性丢失问题
赋值主要有如下三种情况:
-
如果将reactive定义的代理对象的属性赋值给新的变量, 新变量会失去响应性
-
如果对reactive定义的代理对象进行展开操作. 展开后的变量会失去响应性
-
如果对reactive定义的代理对象进行解构操作. 解构后的变量会失去响应性
赋值操作
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive } = Vue
const obj = reactive({ foo: 1, bar: 2 })
// 将reactive创建的代理对象的属性赋值给一个新的变量foo
let foo = obj.foo // foo此时就是一个普通变量, 不具备响应性
effect(() => {
console.log('foo不具备响应性...', foo)
})
foo = 2
</script>
</body>
</html>
-
obj.foo表达式的返回值是1
-
相当于定义了一个普通变量foo, 而普通变量是不具备响应性的
展开操作
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive } = Vue
const obj = reactive({ foo: 1, bar: 2 })
// 展开运算符应用在proxy对象上, 会从语法层面遍历源对象的所有属性
// ...obj ===> foo:1, bar:2
const newObj = {
...obj,
}
// 此时的newObj是一个新的普通对象, 和obj之间不存在引用关系
console.log(newObj) // {foo:1, bar:2}
effect(() => {
console.log('newObj没有响应性...', newObj.foo)
})
// 改变newObj的属性值, 不会触发副作用函数的重新执行
// 此时, 我们就说newObj失去了响应性
newObj.foo = 2
</script>
</body>
</html>
解构操作
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive } = Vue
const obj = reactive({ foo: 1, bar: 2 })
// 对proxy对象进行解构操作后, foo和bar就是普通变量, 也失去了响应性
let { foo, bar } = obj
effect(() => {
console.log('foo不具备响应性', foo)
})
// 给变量foo赋值, 不会触发副作用函数重新执行
foo = 2
</script>
</body>
</html>
对proxy对象解构后, foo就是一个普通变量, 也失去了跟obj的引用关系.
因此, 对foo的修改不会触发副作用函数重新执行
3) toRef与toRefs
为了解决在赋值过程中响应丢失问题, Vue3提供了两个API
-
toRef: 解决赋值时响应丢失问题
-
toRefs: 解决展开, 解构时响应丢失问题
示例1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect, toRef, toRefs } = Vue
// obj是reactive创建的响应式数据(proxy代理对象)
const obj = reactive({ foo: 1, bar: 2 })
effect(() => {
console.log('obj.foo具有响应性:', obj.foo)
})
// 使用toRef定义, 取代基本赋值操作 foo = obj.foo
// 这里得到的foo是一个Ref对象
const foo = toRef(obj, 'foo')
effect(() => {
console.log('foo.value具有响应性:', foo.value)
})
</script>
</body>
</html>
示例2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect, toRef, toRefs } = Vue
// obj是reactive创建的响应式数据(proxy代理对象)
const obj = reactive({ foo: 1, bar: 2 })
// 使用toRefs解构赋值 取代 {foo, bar} = obj
const { foo, bar } = toRefs(obj)
effect(() => {
console.log('bar.value具有响应性', bar.value)
})
</script>
</body>
</html>
这里toRefs返回的结构如下, 这样解构出来的对象依然是Ref对象, 通过.value访问还是保持了响应性
{
foo: ref(1),
bar: ref(2),
}
4) reactive VS ref
✏️ 最佳实践
-
大部分(80%以上)情况推荐使用ref
-
处理本地关联数据时, 可以使用reactive
Party6
computed()函数详解(掌握)
1) 基本使用
📝计算属性computed()函数
-
参数: 函数/对象
-
作用: 创建一个计算属性
-
返回: 计算属性对象
示例1
const state = reactive({firstname: 'xiao', lastname: 'ming'})
// 接收一个副作用函数做为参数, 返回一个ref类型对象
const fullname = computed(() => {
return state.firstname + state.lastname
})
// 通过.value操作
console.log(fullname.value)
示例2
接收一个对象作为参数, 但是这种方式用的不多.
const state = reactive({firstname: 'xiao', lastname: 'ming'})
// 接收一个副作用函数做为参数, 返回一个ref类型对象
const fullname = computed({
get() {
return state.firstname + ' ' + state.lastname
},
set(newValue) {
[state.firstname, state.lastname] = newValue.split(' ')
}
})
// 通过.value操作
console.log(fullname.value)
2) 计算属性的特点
懒执行
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, computed } = Vue
const state = reactive({ firstname: 'xiao', lastname: 'ming' })
const fullname = computed(() => {
console.log('默认不执行, 只有当访问fullName.value时执行')
return state.firstname + state.lastname
})
setTimeout(() => {
fullname.value
}, 1000)
</script>
</body>
</html>
缓存
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="../node_modules/vue/dist/vue.global.js"></script>
<script>
const { reactive, computed } = Vue
const state = reactive({ firstname: 'xiao', lastname: 'ming' })
const fullname = computed(() => {
console.log('computed')
return state.firstname + state.lastname
})
console.log(fullname.value) // 初次访问时, 执行1次, 保存到缓存
console.log(fullname.value) // 再次访问, 直接返回缓存中的数据
</script>
</body>
</html>
3) effect的高级用法
effect函数的高级用法
-
lazy: 懒执行
-
scheduler: 自定义更新
lazy选项
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, effect } = Vue
const count = ref(0)
// effect 返回 run() 函数,
// 1. 加入lazy:true选项后, 不会自动调用副作用函数
// 2. 手动执行run()函数, 才会调用副作用函数, 建立依赖关系
const run = effect(
() => {
console.log('一开始不执行, 调用run才会执行', count.value)
},
{ lazy: true }
)
console.log(run)
</script>
</body>
</html>
scheduler选项
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, effect } = Vue
const count = ref(0)
effect(
() => {
console.log('第一次执行这里', count.value)
},
{
scheduler: () => {
console.log('更新时, 执行这里...')
},
}
)
</script>
</body>
</html>
4) 基于effect实现computed
基本实现
从computed()函数的使用上分析
computed()函数的参数是一个副作用函数, 依赖响应式对象
computed()函数跟注册副作用函数effect()类似. 接收一个副作用函数做为参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, effect } = Vue
function computed(fn) {
// 只考虑参数是fn的情况
const run = effect(fn)
return {
get value() {
return run()
},
}
}
const firstname = ref('xiao')
const lastname = ref('ming')
const fullname = computed(() => {
console.log('副作用函数被执行了...')
return firstname.value + lastname.value
})
</script>
</body>
</html>
实现懒执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, effect } = Vue
function computed(fn) {
// 只考虑参数是fn的情况
const run = effect(fn, { lazy: true })
return {
get value() {
return run()
},
}
}
const firstname = ref('xiao')
const lastname = ref('ming')
const fullname = computed(() => {
console.log(
'副作用函数被执行了, 值是:',
firstname.value + lastname.value
)
return firstname.value + lastname.value
})
</script>
</body>
</html>
实现缓存
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, effect } = Vue
function computed(fn) {
let cache
let dirty = true
// 只考虑参数是fn的情况
const run = effect(fn, {
lazy: true,
shecduler: () => {
dirty = true
},
})
return {
get value() {
if (dirty) {
cache = run()
dirty = false
}
return cache
},
}
}
const firstname = ref('xiao')
const lastname = ref('ming')
const fullname = computed(() => {
console.log(
'副作用函数被执行了, 值是:',
firstname.value + lastname.value
)
return firstname.value + lastname.value
})
</script>
</body>
</html>
更多详细实现, 详见: (设计与实现篇-响应式原理四.computed的实现)
Party7
watch()函数详解(掌握)
1) 基本使用
📝侦听器watch()函数
-
参数:
-
-
侦听的数据源:
-
可以是引用了响应式对象的副作用函数
-
响应式对象(ref, reactive, computed)
-
以上类型组成的数组
-
对应的回调: 当数据改变时, 执行的回调函数
-
选项:
-
immediate: 创建时立即执行回调
-
deep: 当数据是对象, 开启深度侦听
-
flush: 设置回调执行的时机
-
-
作用: 侦听数据源的改变, 当数据源改变时, 重新执行回调
-
返回: unwatch方法
示例1
// 1. 数据源是ref(响应式对象)
const count = ref(0)
watch(count, () => {
console.log('count的值改变了:', count.value)
})
示例2
// 2. 数据源是reactive对象(默认是深度侦听)
const stu = reactive({
name: 'xiaoming',
age: 20,
gf: {
name: 'xiaomei',
city: {
name: 'wuhan',
},
},
})
watch(stu, () => {
// 由于这种方式会递归遍历每一层属性, 效率不高, 一般不用
console.log('侦听整个reactive对象, 是深度侦听的')
})
由于侦听对象时会递归遍历每一层属性, 效率不高, 一般不用
一般情况下, 需要侦听的往往是某个具体的属性
此时, 我们需要包装一个函数, 在函数中引用该属性. 这个函数也是副作用函数
示例3
// 不能直接这样写. stu.age相当于读取出了具体的值
watch(stu.age, () => {
console.log('不会生效')
})
-
对于值类型, 不能直接侦听
-
对于对象类型, 依然可用
示例4
// 通过一个副作用函数包装, 在副作用函数中引用需要侦听的属性
watch(
() => stu.age,
() => {
console.log('会生效, 常用')
}
)
示例5
// 几乎不用
watch([count, () => stu.age], () => {
console.log('侦听的数据源是数组的情况')
})
2) 高级使用
-
只有当数据源改变时, 才会执行回调.
-
-
如果侦听值类型数据, 可以在回调中拿到新旧值
-
如果侦听对象类型数据, 在回调中新旧值相同, 都是新值
-
-
当通过getter返回一个对象时
-
-
只有当对象整体被替换时, 才会触发回调
-
需要设置deep: true, 强制开启深度监听
-
-
通过设置immediate: true 可以立即执行回调. 旧值为undefined
-
通过设置flush: post可以控制回调执行的时机, 在DOM重新渲染后执行, 可以在回调中拿到更新后的DOM对象
-
停止侦听
示例1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, reactive, watch } = Vue
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(oldValue, newValue)
})
// 2. 数据源是reactive对象(默认是深度侦听)
const stu = reactive({
name: 'xiaoming',
age: 20,
gf: {
name: 'xiaomei',
city: {
name: 'wuhan',
},
},
})
// 通过一个副作用函数包装, 在副作用函数中引用需要侦听的属性
watch(
() => stu.age,
(newValue, oldValue) => {
console.log(oldValue, newValue)
}
)
// 如果侦听对象类型, 新旧值相同, 都是新值
watch(stu, (newValue, oldValue) => {
console.log(oldValue, newValue)
})
</script>
</body>
</html>
示例2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, reactive, watch } = Vue
const stu = reactive({
name: 'xiaoming',
age: 20,
gf: {
name: 'xiaomei',
city: {
name: 'wuhan',
},
},
})
// 如果侦听的属性是一个对象, 只有当对象被替换时, 才触发回调
// 当改变stu.gf.name, 不会触发回调. 需要加deep: true
watch(
() => stu.gf,
(newValue, oldValue) => {
console.log(oldValue, newValue)
}
)
</script>
</body>
</html>
示例3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, reactive, watch } = Vue
const count = ref(0)
watch(
count,
(newValue, oldValue) => {
console.log(oldValue, newValue) // undefined 0
},
{
immediate: true,
}
)
</script>
</body>
</html>
示例4
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app">{{msg}}</div>
<script>
const { ref, reactive, watch, createApp } = Vue
createApp({
setup() {
const msg = ref('hello')
// watch(msg, () => {
// // 回调先执行, 渲染后执行
// console.log('此时的DOM是旧的', app.innerHTML)
// })
watch(
msg,
() => {
// 渲染先执行, 回调后执行
console.log('此时的DOM是新的', app.innerHTML)
},
{
flush: 'post',
}
)
setTimeout(() => {
msg.value = 'world'
}, 1000)
return {
msg,
}
},
}).mount('#app')
// watch()
</script>
</body>
</html>
示例5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { ref, reactive, watch } = Vue
const count = ref(0)
const unwatch = watch(
count,
(newValue, oldValue) => {
console.log(oldValue, newValue) // undefined 0
},
{
immediate: true,
}
)
// 在调用unwatch之后, 停止侦听数据的改变
</script>
</body>
</html>
3) 基于effect实现watch
实际上, watch也是由effect实现的. 这里我给出最基本用法的实现.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, effect } = Vue
const stu = reactive({
name: 'xiaoming',
age: 20,
gf: {
name: 'xiaomei',
city: {
name: 'wuhan',
},
},
})
function watch(source, callback) {
// 只考虑source是getter的情况
effect(source, {
scheduler: callback,
})
}
watch(
() => stu.age,
() => {
console.log('stu.age的值改变了...', stu.age)
}
)
</script>
</body>
</html>
4) watchEffect
watchEffect会自动收集参数函数中的依赖, 可以认为是对effect的直接封装.
当然会加入一些额外的参数选项
function watchEffect(fn) {
effect(fn)
}
3) watch VS watchEffect
-
watch可以更精确的控制侦听的属性, watchEffect是自动收集
-
watch可以拿到新旧值, watchEffect不行
-
watch的回调默认不执行, watchEffect的回调默认执行
Party8
其它函数(了解)
1) readonly()函数
readonly()
-
参数: 普通对象/reactive对象/ref对象
-
作用: 创建只读的响应式对象
-
返回: proxy对象
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.global.js"></script>
</head>
<body>
<script>
const { reactive, ref, readonly } = Vue
const count = ref(0)
const stu = reactive({ name: 'xiaoming', age: 20 })
const r_obj = readonly({ msg: 'hello' })
const r_count = readonly(count)
const r_stu = readonly(stu)
</script>
</body>
</html>
应用场景
父组件向子组件传对象时, 为了保证单向数据流(子组件不能修改父组件中的数据). 可以考虑在父组件中传递只读对象给子组件
2) shallow系列函数
创建浅层的代理, 即第一层具有响应性. 更深层不具有响应性
shallowReactive()
const sh_stu = shallowReactive({
name: 'xiaoming',
age: 20,
gf: { // 替换gf对象触发响应性
name: 'xiaomei', // 不具有响应性
city: {
name: 'wuhan' // 不具有响应性
}
}
})
3) 工具函数
函数名 | 参数 | 作用 | 返回值 | 说明 |
isRef | (数据) | 判断传入的参数是否为Ref类型 | true: 是Ref类型 false: 不是Ref类型 | 根据__v_isRef标识判断
|
isReactive | (数据) | 判断传入的参数是否由 reactive()或者shallowReactive()创建 | true: 是 false: 否 | |
isProxy | (数据) | 判断传入的参数是否由 reactive()或者shallowReactive() readonly()或者shallowReadonly() 创建 | true: 是 false: 否 | |
toRef | (对象, '属性', 默认值?) | 将reactive对象的某个属性转换为 ref类型对象 | ObjectRefImpl对象 | 属性是字符串 默认值可选 |
toRefs | (对象) | 将reactive对象转换成普通对象 但是每个属性值都是ref | 普通对象 | 传入参数必须是 reactive类型 |
需要vue教程资料,请加GIS小巫师,备注:《Vue零基础教程》