前文指路:
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零基础教程》