六、Composition API
本章中,将会给大家全面介绍 Vue3 新提供的 Composition API 语法 ,帮助大家学会优雅合理的使用 新版本 Vue 语法 。在本章中,还通过小例子的形式帮助大家进行实操联系,确保大家能够学以致用,为之后的实战课程做好准备。
1.Setup 函数的使用
什么是 Composition API ? 当我们在 项目开发中 的时会遇到这种问题, data 中很多 变量 , methods 中很多 方法 ,当我们 想看一些代码的逻辑 时,需要上下文的来回查看 ,当 项目代码上百 / 千行的时,想看一些代码逻辑定位非常麻烦 ,下图 颜色相同 的 代码块 , 代表同一个功能的代码 ,可以看到 Options API(传统vue写法)一个功能代码分布在上、中、下,代码很零散,而 Composition API(vue3 新写法) 每个功能的代码都汇聚在一块 ,这样 查阅就会很方便可读性强 。
要想使用 Composition API ,首先要学习一个 函数 ,就是 Setup 函数 , Setup 函数 会在 created 实例被完全初始化之前 执行,它有 2 个参数 , 第1个参数参数 是 当前组件的 props , 第2个参数 是 上下文, Setup函数 必须要 return 返回一个值 , Setup 函数返回的值可以直接在 template 模板中使用 如下代码:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup 函数的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
return {
name: 'dell', // Options API 中data中的变量
handleClick(){ // Options API 中methods中的方法
alert(123)
}
}
}
})
const vm = app.mount('#root')
</script>
</html>
我们发现 Setup 这个 函数 ,它 return 的 所有内容 会 暴露在外部 ,我们在 template 模板 中可以直接 使用暴露出来的变量或方法 。
1.Setup 函数内调用外部方法与变量
Setup 函数 是 在实例被创建之前执行的一个函数 ,我们可能会想到,我们如果在 Setup 函数 中 调用 methods 中定义的方法 ,会怎样呢?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup 函数内调用外部方法与变量</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
alert('test')
}
},
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
this.test()
return {
name: 'dell', // data中的变量
handleClick(){ // methods中的方法
alert(123)
}
}
}
})
const vm = app.mount('#root')
</script>
</html>
会直接报错 this.test is not a function ,这是因为 Setup 函数 是 在实例被初始化之前就执行的一个函数 , 实例初始化之前 methods 并没有被放到 this 上面, 所以通过 this.test() 根本就无法调用这个 methods , 所以要记住, 在Setup 函数里面,不能使用 this 这样的关键词, this 里面实际上什么也没有 ,所以我们不要去这样写,用了 Setup 函数 之后,我们就 不会去用this这样语法,再去写任何的代码 了,后面这种 methods 的定义,我们都会换成新的 composition API 的语法来定义 。
2.外部方法(methods)、生命周期(mounted)中调用 Setup 函数
在 Setup 函数 中 我们无法调用外部的一些方法 ,或者一些模板,或者mounted这样的生命周期函数 ,但是 在外部的方法或者生命周期中,我们可以调用 Setup 这个方法 ,原因是 Setup 执行时,实例并没有被创建 ,但是 在实例创建后,Setup 已经被挂载到实例上了,所以在 mounted 或者 methods 中可以获取到 Setup ,如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>外部方法(methods)、生命周期(mounted)中调用 Setup 函数</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
console.log(this.$options.setup())
}
},
mounted(){
this.test()
},
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
return {
name: 'dell', // data中的变量
handleClick(){ // methods中的方法
alert(123)
}
}
}
})
const vm = app.mount('#root')
</script>
</html>
2.ref,reactive 响应式引用的用法和原理
ref 与 reactive 是 vue3 提供给我们 解决数据响应式问题的方法,下面我们将通过 2 个例子来讲解 ref 与 reactive 以及 readonly 、toRefs 具体用法。
- ref :解决 基本数据类型数据(基本类型:Boolean、String等等) 的 数据响应式问题
- reactive :解决 非基本数据类型数据(引用类型:Array、Object等等) 的 数据响应式问题
- readonly :数据 只读 。
- toRefs : 解决 解构赋值 的 数据响应式问题。
ref
需求 :我们在 setup 中定义了变量 name , 并且将 name 返回 return 出去,在页面的 template 模板 中使用 name, 2s 之后更改了变量 name , 我们 希望 template 模板上的 name 也随之更新 , 代码如下:
1.非响应式数据案例
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ref 响应式引用的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 声明定义变量name
let name = 'dell'
// 2s 后更改 name
setTimeout(() => {
name = 'lee'
}, 2000);
return {
name // data中的变量
}
}
})
const vm = app.mount('#root')
</script>
</html>
2.ref 响应式数据案例
上面的数据,无法更新,这是 因为 name 只是一个普通的变量并不会响应式的更新 ,如果想 响应式的更新 name ,使用 ref 可以解决 基本数据类型数据 的 数据响应式问题 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ref 响应式引用的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// ref 响应式的引用
// 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新
// ref 处理基础类型的数据
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入 composition API 的 ref
const { ref } = Vue
// 调用 ref 后,proxy, 'dell' 变成 proxy({value: 'dell'}) 这样的一个响应式引用
let name = ref('dell') // 值会存入到 name.value 中
// 2s 后更改 name
setTimeout(() => {
name.value = 'lee' // 修改 name.value 的值
}, 2000);
return {
name // data中的变量
}
}
})
const vm = app.mount('#root')
</script>
</html>
template 模板 中不需要写 name.value ,直接写 name 就行,因为 vue 在 做模板处理时,它会做一个转换,它如果知道 name 是通过 ref 返回的一个响应式引用,会自动的在底层帮我们调用 name.value
reactive
下面的代码是 非基本数据类型数据 的案例,也是 无法做到数据的响应式 。
1.非响应式数据案例
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>reactive 响应式引用的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 定义声明对象
const nameObj = { name: 'dell' }
// 2s 后更改 nameObj
setTimeout(() => {
nameObj.name = 'lee'
}, 2000);
return {
nameObj // data中的变量
}
}
})
const vm = app.mount('#root')
</script>
</html>
2.reactive 响应式数据案例
reactive 可以解决 非基本数据类型数据 的 数据响应式问题 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>reactive 响应式引用的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// reactive 响应式的引用
// 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新
// reactive 处理非基础类型的数据
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入 composition API 的 reactive
const { reactive } = Vue
// 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用
const nameObj = reactive({ name: 'dell' })
// 2s 后更改 nameObj
setTimeout(() => {
nameObj.name = 'lee'
}, 2000);
return {
nameObj // data中的变量
}
}
})
const vm = app.mount('#root')
</script>
</html>
上面是数据是 对象 ,也可以使用 数组 ,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>reactive 响应式引用的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// reactive 响应式的引用
// 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新
// reactive 处理非基础类型的数据
const app = Vue.createApp({
template: `
<div>{{nameObj[0]}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入 composition API 的 reactive
const { reactive } = Vue
const nameObj = reactive([123])
// 2s 后更改 nameObj
setTimeout(() => {
nameObj[0] = 456
}, 2000);
return {
nameObj // data中的变量
}
}
})
const vm = app.mount('#root')
</script>
</html>
readonly
假如我想让部分数据 不希望,数据被变更 ,变成 只读状态 , 我们可以使用 vue3 提供的 readonly 实现这个功能,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>readonly 的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// ref, reactive 响应式的引用
// 原理, 通过 proxy 对数据进行封装, 当数据变化时, 触发模板等内容的更新
// reactive 处理非基础类型的数据
const app = Vue.createApp({
template: `
<div>{{nameObj[0]}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入 composition API 的 reactive
const { reactive, readonly } = Vue
const nameObj = reactive([123])
// 复制一份数据
const copyNameObj = readonly(nameObj)
// 2s 后更改 nameObj 与copyNameObj
setTimeout(() => {
nameObj[0] = 456
copyNameObj[0] = 456
}, 2000);
return {
nameObj, // data中的变量
copyNameObj
}
}
})
const vm = app.mount('#root')
</script>
</html>
这样 2s 过后,浏览器控制台就会 抛出警告 。
toRefs
下面代码中在 setup 函数 中 return 一个对象
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toRefs 的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入 composition API 的 reactive
const { reactive } = Vue
// 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用
const nameObj = reactive({ name: 'dell' })
// 2s 后更改 nameObj
setTimeout(() => {
nameObj.name = 'lee'
}, 2000);
return {
nameObj // data中的变量
}
}
})
const vm = app.mount('#root')
</script>
</html>
有些人就会觉得这么写很麻烦,用 ES6 解构赋值 直接 return 一个 name ,像下面这样写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toRefs 的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入 composition API 的 reactive
const { reactive } = Vue
// 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用
const nameObj = reactive({ name: 'dell' })
// 2s 后更改 nameObj
setTimeout(() => {
nameObj.name = 'lee'
}, 2000);
// 解构赋值
const { name } = nameObj
// data中的变量
return { name }
}
})
const vm = app.mount('#root')
</script>
</html>
2s 后就会发现不生效 , 解构赋值 并没有生效,因为 解构只是把这个值导出去,这个值并不是响应式的 ,如果想做解构赋值 ,解决这个问题,需要使用 toRefs 才可以解决这个问题,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toRefs 的用法和原理</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入 composition API 的 reactive
const { reactive, toRefs } = Vue
// 调用 reactive 后,proxy, { name: 'dell' } 变成 proxy({ name: 'dell' }) 这样的一个响应式引用
const nameObj = reactive({ name: 'dell' })
// 2s 后更改 nameObj
setTimeout(() => {
nameObj.name = 'lee'
}, 2000);
// 解构赋值
// 调用 toRefs 后,它会把 proxy({ name: 'dell' }) 转换成 { name: proxy({value:'dell'}) }
// 实际上最终返回的是 { name: proxy({value:'dell'}) }
const { name } = toRefs(nameObj)
// data中的变量
return { name }
}
})
const vm = app.mount('#root')
</script>
</html>
总结
当我们学了 ref 、 reactive 这种新的 composition API 之后,我们完全 可以替代老的语法中 data 这种写法,比如说以前 定义 都在 data 中定义变量 ,如下:
data() {
return {
name: 'dell',
nameObj: {
name: 'dell'
}
}
}
用新的 composition API 语法来定义的话,完全没必要用 data ,用 ref 、 reactive 这种形式来写即可。
3.toRef 以及 context 参数
这章讲解一下 2 个新的知识点,一个是 composition API 中的 toRef API,另一个是我们之前讲过的 setup 函数 的 第2个参数 context 。
toRef
1.解构赋值响应式数据正确写法
我们之前通过 toRefs 来解决 解构赋值数据响应式问题 ,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toRef</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// toRef
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
const { reactive, toRefs } = Vue
const data = reactive({ name: 'dell' })
// 解构赋值
const { name } = toRefs(data)
// 2s 后修改name变量
setTimeout(() => {
name.value = 'lee'
}, 2000)
return { name }
}
})
const vm = app.mount('#root')
</script>
</html>
2.解构赋值响应式数据错误写法
如果在 解构赋值时 ,我们把 name 改成 age ,这时候我们就会发现,在一开始 data 中没有 age , 这时候 解构赋值时有 age ,就会发生错误 ,这是 因为 toRefs 这种语法,它从 data 这样一个响应式对象里去找数据时,如果找不到,它不会给 age 一个默认的响应式的引用,而是给 age 赋值为 undefined,这样的话, age 最开始不存在 data 中,它后面就不具备响应式,再改它,也无效果 ,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toRef</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// toRef
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
const { reactive, toRefs } = Vue
const data = reactive({ name: 'dell' })
// 解构赋值
const { age } = toRefs(data)
// 2s 后修改name变量
setTimeout(() => {
age.value = 'lee'
}, 2000)
return { age }
}
})
const vm = app.mount('#root')
</script>
</html>
3.toRef
如果想解决上面的问题,我们可以用 toRef ,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toRef</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// toRef
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
const { reactive, toRef } = Vue
const data = reactive({ name: 'dell' })
// 意思是从 data 中取 age ,如果能取到就用取到的值,
// 取不到就声明一个空的响应式数据
const age = toRef(data, 'age')
// 2s 后给 age 赋值
setTimeout(() => {
age.value = 'lee'
}, 2000)
// 导出 age ,这里必须是对象的形式,就像在 vue2.x 时 return {} 一样
return { age }
}
})
const vm = app.mount('#root')
</script>
</html>
建议一般情况不要用 toRef ,一般来说,如果后面代码中要用到 age ,不妨直接在 data 中提前定义一个空的 age ,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toRef</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// toRef
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
const { reactive, toRefs } = Vue
const data = reactive({ name: 'dell', age: '' })
// 2s 后给 age 赋值
setTimeout(() => {
data.age = 'lee'
}, 2000)
// 解构赋值
const { age } = toRefs(data)
// 导出 age ,这里必须是对象的形式,就像在 vue2.x 时 return {} 一样
return { age }
}
})
const vm = app.mount('#root')
</script>
</html>
Setup 的第二个参数 context
接下来我们讲一下 context , context 是个 对象, 它有 3 个 属性值
1.attrs
context 的第 1 个参数是 attrs(None-Props属性) ,下面用它写一个小例子:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup 的第二个参数 context </title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `<child style="color:red" />`
})
// 子组件
app.component('child', {
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入h函数
const { h } = Vue
const { attrs, slots, emit } = contex
// 使用render函数,并且使用 attrs
return () => {
return h('div', {
style: attrs.style
}, 'child')
}
}
})
const vm = app.mount('#root')
</script>
</html>
2.slots
context 的第 2 个参数是 slots , slots 是 父组件传递过来的插槽 ,在 vue2.x 版本中,我们使用 slots 都需要 this.$slots ,在新版 composition API 中可以使用 slots ,例子如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup 的第二个参数 context </title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `<child>parent</child>`
})
// 子组件
app.component('child', {
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
// 引入h函数
const { h } = Vue
const { attrs, slots, emit } = contex
// 使用render函数,并且使用 slots
return () => {
return h('div', {}, slots.default())
}
}
})
const vm = app.mount('#root')
</script>
</html>
3.emit
在旧版 vue2.x 版本中, 子组件 给 父组件传值 通过 this.$emit ,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup 的第二个参数 context </title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `<child @change="handleChange">parent</child>`,
methods: {
handleChange(){
alert('handleChange')
}
}
})
// 子组件
app.component('child', {
template: '<div @click="handleClick">123123</div>',
methods: {
handleClick(){
this.$emit('change')
}
}
})
const vm = app.mount('#root')
</script>
</html>
在新版 composition API 中这样写即可,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup 的第二个参数 context </title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
methods: {
handleChange(){
alert('handleChange')
}
},
template: `<child @change="handleChange">parent</child>`
})
// 子组件
app.component('child', {
template: '<div @click="handleClick">123123</div>',
/**
* created 实例被完全初始化之前
* @param {object} props - 当前组件的props
* @param {number} contex - 上下文
*/
setup(props, contex) {
const { attrs, slots, emit } = contex
// 定义方法
function handleClick(){
emit('change')
}
// 导出方法
return { handleClick }
}
})
const vm = app.mount('#root')
</script>
</html>
4.使用 Composition API 开发TodoList
下面用 composition API 实现了一个 TodoList 效果
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用 Composition API 开发TodoList</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
setup(){
const { ref, reactive } = Vue
const inputValue = ref('')
const list = reactive([])
const handleInputValueChange = (e) => {
inputValue.value = e.target.value
}
const handleSubmit = () => {
list.push(inputValue.value)
}
return {
list,
inputValue,
handleSubmit,
handleInputValueChange
}
},
template: `
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="handleSubmit">提交</button>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
`,
})
const vm = app.mount('#root')
</script>
</html>
封装TodoList
上面的写法虽然是完成了功能,但是 代码零散,其实我们可以做一个 封装 ,把 逻辑相同的代码进行归类封装
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用 Composition API 开发TodoList</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 关于 list 操作的内容进行了封装
const listRelativeEffect = () => {
const { reactive } = Vue
const list = reactive([])
const addItemToList = (item) => {
list.push(item)
}
return { list, addItemToList }
}
// 关于 inputValue 操作的内容进行了封装
const inputRelativeEffect = () => {
const { ref } = Vue
const inputValue = ref('')
const handleInputValueChange = (e) => {
inputValue.value = e.target.value
}
return { inputValue, handleInputValueChange }
}
const app = Vue.createApp({
setup(){
// 流程调度中转
const { list, addItemToList } = listRelativeEffect()
const { inputValue, handleInputValueChange } = inputRelativeEffect()
return {
list, addItemToList,
inputValue, handleInputValueChange
}
},
template: `
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="() => addItemToList(inputValue)">提交</button>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
`,
})
const vm = app.mount('#root')
</script>
</html>
上面的代码中,我们把对 list 操作的相关逻辑进行了封装 , input 相关的逻辑也进行了封装 ,代码中的 addItemToList 用 箭头函数处理逻辑 ,如果觉得不太好懂,也可以像下面这样封装一个函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用 Composition API 开发TodoList</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 关于 list 操作的内容进行了封装
const listRelativeEffect = () => {
const { reactive } = Vue
const list = reactive([])
const addItemToList = (item) => {
list.push(item)
}
return { list, addItemToList }
}
// 关于 inputValue 操作的内容进行了封装
const inputRelativeEffect = () => {
const { ref } = Vue
const inputValue = ref('')
const handleInputValueChange = (e) => {
inputValue.value = e.target.value
}
return { inputValue, handleInputValueChange }
}
const app = Vue.createApp({
setup(){
// 流程调度中转
const { list, addItemToList } = listRelativeEffect()
const { inputValue, handleInputValueChange } = inputRelativeEffect()
const handleSubmit = () => {
addItemToList(inputValue.value)
}
return {
list, addItemToList,
inputValue, handleInputValueChange,
handleSubmit
}
},
template: `
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="handleSubmit">提交</button>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
`,
})
const vm = app.mount('#root')
</script>
</html>
但是还是 建议使用箭头函数的方式,没必要单独封装一个函数 。
5.computed方法生成计算属性
下面讲解一下 composition API 的新版 computed计算属性 如何使用。
computed 普通用法
1.Vue2.x 版本 computed 使用方法
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>computed方法生成计算属性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// vue2.x 版本 computed 计算属性
const app = Vue.createApp({
data(){
return{
cd: 'cd'
}
},
computed: {
abc(){
return this.cd + 'efg'
}
},
template: `
<div>
{{ abc }}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
2.Vue 3 版本 computed 使用方法
在 composition API 中使用 computed 计算属性 ,只需要引入一个 computed 方法 ,在使用时候返回一个计算后的值即可。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>computed方法生成计算属性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// computed 计算属性
const app = Vue.createApp({
setup(){
const { ref, computed } = Vue
const count = ref(0)
// 点击事件
const handleClick = () => {
count.value += 1
}
// 计算属性
const countAddFive = computed(() => {
return count.value + 5
})
return { count, countAddFive, handleClick }
},
template: `
<div>
<span @click="handleClick">{{ count }}</span> -- {{ countAddFive }}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
或者可以写的复杂一些,用 get 方法来写。
computed 的 get set方法
1.get
get 实现的效果, 与上面的普通写法实现的效果是一样的 , get 表示获取返回的这个值。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>computed方法生成计算属性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// computed 计算属性
const app = Vue.createApp({
setup(){
const { ref, computed } = Vue
const count = ref(0)
// 点击事件
const handleClick = () => {
count.value += 1
}
// 计算属性
const countAddFive = computed({
get: () => {
return count.value + 5
}
})
return { count, countAddFive, handleClick }
},
template: `
<div>
<span @click="handleClick">{{ count }}</span> -- {{ countAddFive }}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
2.set
当计算属性被修改时,会触发 set 这个方法,如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>computed方法生成计算属性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// computed 计算属性
const app = Vue.createApp({
setup(){
const { ref, computed } = Vue
const count = ref(0)
// 点击事件
const handleClick = () => {
count.value += 1
}
// 计算属性
let countAddFive = computed({
get: () => {
return count.value + 5
},
set: () => {
count.value = 10
}
})
// 2s 后修改 countAddFive 计算属性
setTimeout(() => {
countAddFive.value = 100
}, 3000)
return { count, countAddFive, handleClick }
},
template: `
<div>
<span @click="handleClick">{{ count }}</span> -- {{ countAddFive }}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
computed 的 get set使用实例
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>computed方法生成计算属性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// computed 计算属性
const app = Vue.createApp({
setup(){
const { ref, computed } = Vue
const count = ref(0)
// 点击事件
const handleClick = () => {
count.value += 1
}
// 计算属性
let countAddFive = computed({
get: () => {
return count.value + 5
},
set: (param) => {
count.value = param - 5
}
})
// 2s 后修改 countAddFive 计算属性
setTimeout(() => {
countAddFive.value = 100
}, 3000)
return { count, countAddFive, handleClick }
},
template: `
<div>
<span @click="handleClick">{{ count }}</span> -- {{ countAddFive }}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
6.watch 和 watchEffect 的使用和差异性
我们讲一下在 composition API 中如何来使用 watch 和 watchEffect
watch 使用方法
1.监听基本数据
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
const app = Vue.createApp({
setup(){
const { ref, watch } = Vue
// 定义name属性
const name = ref('dell')
// 监听name属性
// 具备一定的惰性 lazy
// 参数可以拿到原始和当前值
watch(name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
})
return { name }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
2.监听对象或数组
-
1.错误写法示范
-
如果 监听 的是一个 reactive 这样的数据, watch 的 第 1 个参数 就 不可以 像下面这样写:
-
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
const app = Vue.createApp({
setup(){
const { reactive, watch, toRefs } = Vue
const nameObj = reactive({ name: 'dell' })
// 具备一定的惰性 lazy
// 参数可以拿到原始和当前值
watch(nameObj.name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
})
const { name } = toRefs(nameObj)
return { name }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
-
2.正确写法示范
-
监听 reactive 这样的数据,watch 的第一个参数,要用 箭头函数 的形式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
const app = Vue.createApp({
setup(){
const { reactive, watch, toRefs } = Vue
const nameObj = reactive({ name: 'dell' })
// 具备一定的惰性 lazy
// 参数可以拿到原始和当前值
watch(() => nameObj.name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
})
const { name } = toRefs(nameObj)
return { name }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
3.监听多个数据
像下面这个代码中,想要 同时监听 2 个数据 就要写 2 次 watch
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue
// 定义变量
const nameObj = reactive({
name: 'dell',
englishName: 'lee',
})
// 监听 nameObj.name
watch(
() => nameObj.name,
(currentValue, prevValue) => {
console.log(currentValue, prevValue)
},
)
// 监听 nameObj.englishName
watch(
() => nameObj.englishName,
(currentValue, prevValue) => {
console.log(currentValue, prevValue)
},
)
// 解构取值
const { name, englishName } = toRefs(nameObj)
return { name, englishName }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
<div>
EnglishName: <input v-model="englishName">
</div>
<div>
EnglishName is {{ englishName }}
</div>
</div>
`,
})
const vm = app.mount('#root')
</script>
</html>
上面的代码就会 显得特别麻烦,重复性的代码写了多次 ,那我们可以这样写,把 它们俩写在一起监听 ,在 watch 里可以接收一个数组 ,它的意思就是 这个数组中不管哪个值发生变化,我都会执行后面的函数,同样后面函数的参数也必须是数组的形式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
const app = Vue.createApp({
setup(){
const { reactive, watch, toRefs } = Vue
// 定义变量
const nameObj = reactive({
name: 'dell', englishName: 'lee'
})
// 监听 nameObj.name
watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng],[preName, preEng]) => {
console.log(curName, preName, '---', curEng, preEng)
})
// 解构取值
const { name, englishName } = toRefs(nameObj)
return { name, englishName }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
<div>
EnglishName: <input v-model="englishName">
</div>
<div>
EnglishName is {{ englishName }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
4.初始化执行一次监听
watch 也可以 默认执行一次监听 , watch 的 第 2 个参数 是个 对象 ,可以在里面进行 配置 immediate: true 即可,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
const app = Vue.createApp({
setup(){
const { reactive, watch, watchEffect, toRefs } = Vue
// 定义变量
const nameObj = reactive({
name: 'dell', englishName: 'lee'
})
// 监听 nameObj.name
watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng],[preName, preEng]) => {
console.log('watch', curName, preName, '---', curEng, preEng)
}, {
immediate: true
})
// 解构取值
const { name, englishName } = toRefs(nameObj)
return { name, englishName }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
<div>
EnglishName: <input v-model="englishName">
</div>
<div>
EnglishName is {{ englishName }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
watchEffect 使用方法
传统的 vue2.x 版本 watch 想实现 默认执行一次监听 ,就要写 immediate: true ,如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
const app = Vue.createApp({
data(){
return {
nameObj: {
name: 'dell',
englishName: 'lee'
}
}
},
watch: {
'nameObj.name' : {
immediate: true,
handler: function (oldValue, newValue) {
console.log(oldValue,)
}
}
},
template: `
<div>
<div>
Name: <input v-model="nameObj.name">
</div>
<div>
Name is {{ nameObj.name }}
</div>
<div>
EnglishName: <input v-model="nameObj.englishName">
</div>
<div>
EnglishName is {{ nameObj.englishName }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
而新版 composition API 的 watchEffect 方法,初始化就会执行一次 , 它会 自动检测方法内部使用的代码是否有变化 ,而且 不需要传递你要侦听的内容,它会自动感知内容变化,缺点:无法获取之前或当前的数据
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
// watchEffect 侦听器,偏向于 effect
const app = Vue.createApp({
setup(){
const { reactive, watch, watchEffect, toRefs } = Vue
// 定义变量
const nameObj = reactive({
name: 'dell', englishName: 'lee'
})
// 监听 nameObj.name
// watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng],[preName, preEng]) => {
// console.log('watch', curName, preName, '---', curEng, preEng)
// })
// 立即执行,没有惰性 immediate
// 不需要传递你要侦听的内容,自动会感知代码依赖,不需要传递很多参数,只要传递一个回调函数
// 不能获取之前数据的值
watchEffect(() => {
console.log(nameObj.name)
})
// 解构取值
const { name, englishName } = toRefs(nameObj)
return { name, englishName }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
<div>
EnglishName: <input v-model="englishName">
</div>
<div>
EnglishName is {{ englishName }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
结束 watch 或 watchEffect 监听
假如我有这样一个需求想 2s 后 结束监听 ,可以用 变量或者常量 把 watch 或 watchEffect 储存起来,然后 2s 后执行一下这个变量的方法即可结束监听,具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watch 和 watchEffect 的使用和差异性</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// watch 侦听器
// watchEffect 侦听器,偏向于 effect
const app = Vue.createApp({
setup(){
const { reactive, watch, watchEffect, toRefs } = Vue
// 定义变量
const nameObj = reactive({
name: 'dell', englishName: 'lee'
})
// 监听 nameObj.name
const stop1 = watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng],[preName, preEng]) => {
console.log('watch', curName, preName, '---', curEng, preEng)
setTimeout(() => {
stop1() // 结束监听
}, 2000)
})
// 立即执行,没有惰性 immediate
// 不需要传递你要侦听的内容,自动会感知代码依赖,不需要传递很多参数,只要传递一个回调函数
// 不能获取之前数据的值
const stop = watchEffect(() => {
console.log(nameObj.name)
setTimeout(() => {
stop() // 结束监听
}, 2000)
})
// 解构取值
const { name, englishName } = toRefs(nameObj)
return { name, englishName }
},
template: `
<div>
<div>
Name: <input v-model="name">
</div>
<div>
Name is {{ name }}
</div>
<div>
EnglishName: <input v-model="englishName">
</div>
<div>
EnglishName is {{ englishName }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
7.生命周期函数的新写法
vue2.x 版本中 生命周期写法,目前新的 composition API 中的 生命周期 都需要写在 setup函数 中,用 按需引入 的方式来使用,具体代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生命周期函数的新写法</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
setup(){
const {
ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted
} = Vue
const name = ref('dell')
// beforeMount 等同于 onBeforeMount
onBeforeMount(() => {
console.log('onBeforeMount')
})
// mounted 等同于 onMounted
onMounted(() => {
console.log('onMounted')
})
// beforeUpdate 等同于 onBeforeUpdate
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
// updated 等同于 onUpdated
onUpdated(() => {
console.log('onUpdated')
})
// beforeUnmount 等同于 onBeforeUnmount(当组件从页面v-if隐藏移除时会执行)
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
// unmounted 等同于 onUnmounted(组件从页面移除后执行)
onUnmounted(() => {
console.log('onUnmounted')
})
// 定义方法
const handleClick = () => {
name.value = 'lee'
}
return { name, handleClick }
},
template: `
<div @click="handleClick">{{name}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
composition API 中很多的 生命周期 与原来一样,不同的就是 名字有所改变而已。
弃用的生命周期
在 composition API 中有 beforeCreate 与 created 这 2 个生命周期函数我们是不能使用的 ,因为 setup 函数执行的时间点,在 beforeCreate 与 created 之间 ,所以这 2 个 生命周期 中要写的东西,直接写在 setup 中就可以了。
新的的生命周期
composition API 出了 2 个新的 生命周期函数 ,onRenderTracked 与 onRenderTriggered ,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生命周期函数的新写法</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
setup(){
const { ref, onRenderTracked, onRenderTriggered } = Vue
const name = ref('小明')
// 每次渲染后重新收集响应式依赖
onRenderTracked(() => {
console.log('onRenderTracked')
})
// 每次触发页面重新渲染时自动执行
onRenderTriggered(() => {
console.log('onRenderTriggered')
})
// 定义方法
const handleClick = () => {
name.value = 'lee'
}
return { name, handleClick }
},
template: `
<div @click="handleClick">{{name}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
8.Provide,Inject,模版 Ref 的用法
本章将讲解 Provide、Inject、Ref 在 composition API 中如何使用,但是我们说的这个Ref ,是 获取dom元素 的那个Ref ,不是 定义基本类型响应式数据 时用的那个 Ref 。
Provide Inject 的用法
composition API 解决了 Provide Inject 数据响应式的问题,下面看一下在 composition API 中如何使用 Provide Inject ,如下代码:
1.inject 默认值
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Provide、Inject的用法</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
setup(){
// 引入 provide
const { provide } = Vue;
// 向【后代】传递数据(格式:provide(属性名,值))
provide('name', 'dell')
return { }
},
template: `
<div>
<child />
</div>
`
})
// 子组件
app.component('child', {
setup(){
// 引入 inject
const { inject } = Vue;
// 获取【祖先】组件传的数据(格式:inject('属性名', '默认值'),如果 provide 没有 name 就使用默认值小明)
const name = inject('name', '小明')
// 返回数据
return { name }
},
template: '<div>{{ name }}</div>'
})
// 挂载
const vm = app.mount('#root')
</script>
</html>
上面代码中 inject 如果获取不到值,默认赋值为 inject 的第 2 个参数(默认值 )。
2.后代修改祖先值
Vue 是 单向数据流 的概念,子组件 不可以 修改 父组件 传入的 数据 。那使用 Provide、Inject 时,后代组件 想修改 祖先组件 Provide 传过来的值 ,该怎么修改呢?
2.1 错误示范
下面这种写法,虽然也可以修改数据,但是 不符合单向数据流的要求
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Provide、Inject的用法</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
setup(){
// 引入 provide
const { provide, ref } = Vue;
// 向【后代】传递数据
provide('name', ref('dell'))
return { }
},
template: `
<div>
<child />
</div>
`
})
// 子组件
app.component('child', {
setup(){
// 引入 inject
const { inject } = Vue;
// 获取【祖先】组件传的数据
const name = inject('name')
const handleClick = () => {
name.value = '小明'
}
// 返回数据
return { name, handleClick }
},
template: '<div @click="handleClick">{{ name }}</div>'
})
// 挂载
const vm = app.mount('#root')
</script>
</html>
2.2 正确写法
后代组件需要调用祖先组件的方法,然后在祖先组件中去修改数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Provide、Inject的用法</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
setup(){
// 引入 provide
const { provide, ref } = Vue;
const name = ref('dell')
// 向【后代】传递数据
provide('name', name)
// 向【后代】传递 changeName 方法
provide('changeName', (value) => {
name.value = value
})
return { }
},
template: `
<div>
<child />
</div>
`
})
// 子组件
app.component('child', {
setup(){
// 引入 inject
const { inject } = Vue;
// 获取【祖先】组件传的数据
const name = inject('name')
const changeName = inject('changeName')
const handleClick = () => {
changeName('小明')
}
// 返回数据
return { name, handleClick }
},
template: '<div @click="handleClick">{{ name }}</div>'
})
// 挂载
const vm = app.mount('#root')
</script>
</html>
3.搭配使用 readonly ,防止单向数据流被后代组件篡改
有许多时候,许多人不懂得 单向数据流的概念 ,还是会在 后代组件 中去修改 祖先组件 传入的值,为了解决这个问题,可以使用 readonly 把参数变成只读,来解决这个问题,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Provide、Inject的用法</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
setup(){
// 引入 provide
const { provide, ref, readonly } = Vue;
const name = ref('dell')
// 向【后代】传递数据
provide('name', readonly(name))
return { }
},
template: `
<div>
<child />
</div>
`
})
// 子组件
app.component('child', {
setup(){
// 引入 inject
const { inject } = Vue;
// 获取【祖先】组件传的数据
const name = inject('name')
const handleClick = () => {
name.value = '小明'
}
// 返回数据
return { name, handleClick }
},
template: '<div @click="handleClick">{{ name }}</div>'
})
// 挂载
const vm = app.mount('#root')
</script>
</html>
Ref 的用法
在 composition API 中,我们如何通过 ref 获取 dom 元素呢?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ref 的用法</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
setup(){
const { onMounted, ref } = Vue;
// 01. 名字与标签 ref 的名字相等,这是固定写法
const hello = ref(null)
onMounted(() => {
// 获取 ref 有 hello 的 dom 元素
console.log(hello.value)
})
// 02. 导出 hello
return { hello }
},
// 03. ref 起名为 hello
template: `
<div ref="hello">小明</div>
`
})
// 挂载
const vm = app.mount('#root')
</script>
</html>