setup
setup是Vue3中一个新的配置项,值为一个函数
。
setup的使用
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的
两种返回值
:
(1)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用
。
(一定要到返回模板中才能读取出)
(2)若返回一个渲染函数
:则可以自定义渲染内容。
如果返回的是渲染函数
,那么返回值会直接渲染在页面上
eg:返回一个对象:
App.vue
<template>
<h1>app组件</h1>
<h2>姓名{
{name}}</h2>
<h2>年龄{
{age}}</h2>
<button @click="sayHello">说话</button>
</template>
<script>
export default {
name: 'App',
setup() {
// 数据
let name = "yang"
let age = 18
// 方法
function sayHello() {
//注意哦,这里的name前面就无需添加this了
alert(`你好呀,我叫${
name}`)
}
return {
name,
age,
sayHello
}
}
}
</script>
eg2:
返回的是渲染函数
<template>
<h1>app组件</h1>
<h2>姓名{
{name}}</h2>
<h2>年龄{
{age}}</h2>
<button @click="sayHello">说话</button>
</template>
<script>
import {
h} from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = "yang"
let age = 18
// 方法
function sayHello() {
alert("你好呀")
}
// return {
// name,
// age,
// sayHello
// }
return () => h('h1','yang')
}
}
</script>
setup的注意点
- 尽量不要与Vue2.x配置混用
在Vue3中然然可以编写Vue2的配置(data、methos、computed…),并且和setup有如下关系:
Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
如果有重名, setup优先。 - setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
响应式数据的实现:ref和reactive
在vue2中data配置
中的属性会通过Object.defineProperty
原理最终包装成响应式数据_data。
vue3中为我们提供了两种包装响应式数据的方法:ref和reactive
ref函数
注意这里的ref和vue2中的ref不一样,这里是一个ref函数
。
ref的引入
上面使用setup包裹页面数据,但是这样编写出的数据不是响应式的,即数据改变页面不会被重新加载。从vue2中我们知道一个数据要实现响应式一定有对应的响应式得getter和setter方法,在vue3中通过ref函数
帮我们生成响应式的getter和setter。
ref的实现原理也是通过Object.defineProperty
实现的
ref的作用
定义一个响应式的数据
基本类型ref的使用
- 引入 :
import { ref } from 'vue'
- 格式:
const xxx = ref(数据)
(创建一个包含响应式数据的引用对象(reference对象,简称ref对象))
ref负责将传入的数据包装成一个带有响应式的getter和setter的对象
eg:
可以发现具体值在该RefImpl的.value
属性上 - 值的获取:
js中:变量名.value
模板中读取数据:不需要.value
,直接:<div>{ {变量名}}</div>
(因为vue3在模板中自动帮我们读取其中的.value)
例子:
<template>
<h1>app组件</h1>
<h2>姓名{
{ name }}</h2>
<h2>年龄{
{ age }}</h2>
<button @click="sayHello">hello</button>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {
ref } from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = ref('yang')
let age = ref(18)
// 方法
function sayHello() {
alert(`你好呀,我叫${
name.value}`)
}
function changeInfo() {
console.log(name)
name.value = 'cheng',
age.value = 20
}
return {
name,
age,
sayHello,
changeInfo
}
}
}
</script>
对象属性ref的使用
ref当然也可以包装对象,那就有一个问题。他是否会包装对象中的数据,让对象中的数据也成为响应式的,这样对象中的数据改变才能重新渲染页面。
答案是:对象中的数据vue也帮我们设置成了响应式的但是不是通过ref实现的,是通过Proxy
(ES6语法)实现的。(——vue3中封装了reactive函数
来实现Proxy
)
所以获取ref包装的对象的属性时: 对象.value.属性
(属性后面无需再加value了,属性不是用ref封装的)
eg:app.vue
<template>
<h1>app组件</h1>
<h2>姓名{
{ name }}</h2>
<h2>年龄{
{ age }}</h2>
<h2>工作种类{
{ job.type }}</h2>
<h2>薪资{
{ job.salary }}</h2>
<button @click="sayHello">hello</button>
<br />
<button @click="changeInfo">修改人的信息</button>
<button @click="changeJob">修改工作信息</button>
</template>
<script>
import {
ref } from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = ref('yang')
let age = ref(18)
let job = ref({
type: '前端工程师',
salary:'30k'
})
// 方法
function sayHello() {
alert(`你好呀,我叫${
name.value}`)
}
function changeInfo() {
console.log(name)
name.value = 'cheng',
age.value = 20
}
function changeJob() {
job.value.type = 'UI设计师'
job.value.salary = '100k'
}
return {
name,
age,
job,
sayHello,
changeInfo,
changeJob
}
}
}
</script>
小结
- 基本类型的数据:响应式依然是靠
object.defineProperty()
的get 与set
完成的。 - 对象类型的数据:最外层使用的是
object.defineProperty()
的get 与set
,内部“求助”了Vue3.0中的一个新函数——reactive函数
。
reactive函数
reactive作用
作用:定义一个对象类型
的响应式数据(基本类型不要用它,要用ref函数)
reactive原理
直接源数据封装成Proxy代理对象,Proxy代理对象是响应式的
对象属性reactive使用
语法: const 代理对象 = reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
通过代理对象操作源对象内部数据进行操作。
eg:
let job = reactive({
type: '前端工程师',
salary:'30k'
})
console.log(job)
例子:
app.vue
<template>
<h1>app组件</h1>
<h2>姓名{
{ name }}</h2>
<h2>年龄{
{ age }}</h2>
<h2>工作种类{
{ job.type }}</h2>
<h2>薪资{
{ job.salary }}</h2>
<h2>c:{
{ job.a.b.c }}</h2>
<button @click="sayHello">hello</button>
<br />
<button @click="changeInfo">修改人的信息</button>
<button @click="changeJob">修改工作信息</button>
</template>
<script>
import {
ref,reactive } from 'vue'
export default {
name: 'App',
setup() {
// 数据
let name = ref('yang')
let age = ref(18)
let job = reactive({
type: '前端工程师',
salary: '30k',
a:{
b: {
c:6
}
}
})
// 方法
function sayHello() {
alert(`你好呀,我叫${
name.value}`)
}
function changeInfo() {
console.log(name)
name.value = 'cheng',
age.value = 20
}
function changeJob() {
// console.log(job)
job.type = 'UI设计师'
job.salary = '100k'
job.a.b.c = 666
}
return {
name,
age,
job,
sayHello,
changeInfo,
changeJob
}
}
}
</script>
reactive定义的响应式数据是“深层次的”。
数组属性reactive使用
Proxy封装的数组,可以直接通过下标修改数据,同时实现响应式布局。
定义数据
let hobby = reactive(['吃饭', '睡觉', '打豆豆'])
修改数据
function changeHobby() {
hobby[0] = 'study'
}
当数组数据改变可以引起页面重新渲染
例子,reactive实现上述ref例子
app.vue:
<template>
<h1>app组件</h1>
<h2>姓名{
{ person.name }}</h2>
<h2>年龄{
{ person.age }}</h2>
<h2>工作种类{
{ person.job.type }}</h2>
<h2>薪资{
{ person.job.salary }}</h2>
<h2>c:{
{ person.job.a.b.c }}</h2>
<h3>hobby:{
{ person.hobby}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {
ref,reactive } from 'vue'
export default {
name: 'App',
setup() {
let person = reactive({
name: 'yang',
age: 18,
job: {
type: '前端工程师',
salary: '30k',
a: {
b: {
c: 6
}
}
},
hobby: ['吃饭', '睡觉', '打豆豆']
})
function changeInfo() {
person.name = 'cheng',
person.age = 18,
person.job.type = 'UI设计师'
person.job.salary = '100k'
person.job.a.b.c = 666
person.hobby[0] = 'study'
}
return {
person,
changeInfo
}
}
}
</script>
vue3的响应式原理
vue2的响应式原理
- 实现原理:
(1)对象类型:通过object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。
(2)数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
eg:对象类型
<script type='text/javascript'>
let person ={
name:'yang',
age:18
}
// vue2的响应式原理
let p ={
}
Object.defineProperty(p,'name',{
get(){
return person.name
},
set(value){
console.log("name被修改了,触发了响应式")
person.name = value
}
})
Object.defineProperty(p,'age',{
get(){
return person.age
},
set(value){
console.log("age被修改了,触发了响应式")
person.age = value
}
})
</script>
- 存在问题:
(1)新增属性、删除属性,界面不会更新。
但是vue2中也可以解决:
新增属性this.$set(this.person, 'sex','女')
删除属性this.$delete(this.person, 'sex'')
(2)直接通过下标修改数组,界面不会自动更新。
但是vue2中也可以解决:
修改数组hobby第0个元素:this.$set(this.person.hobby, 0,'学习')
修改数组hobby第0个元素:this.person.hobby.splice(0,1,'学习')
vue3的响应式原理
vue3的响应式优点
vue2中存在的问题在vue3中都解决了:
(1)新增属性、删除属性,界面会更新。
(2)直接通过下标修改数组,界面会自动更新。
eg:实现添加sex属性和删除name属性
<template>
<h1>app组件</h1>
<h2 v-show="person.name">姓名:{
{ person.name }}</h2>
<h2>年龄:{
{ person.age }}</h2>
<h2 v-show="person.sex">性别:{
{ person.sex }}</h2>
<h2>工作种类:{
{ person.job.type }}</h2>
<h2>薪资:{
{ person.job.salary }}</h2>
<h2>c:{
{ person.job.a.b.c }}</h2>
<h3>hobby:{
{ person.hobby}}</h3>
<button @click="changeInfo">修改人的信息</button>
<button @click="addsex">添加一个sex属性</button>
<button @click="deleteName">删除一个name属性</button>
</template>
<script>
import {
reactive } from 'vue'
export default {
name: 'App',
setup() {
//数据
let person = reactive({
name: 'yang',
age: 18,
job: {
type: '前端工程师',
salary: '30k',
a: {
b: {
c: 6
}
}
},
hobby: ['吃饭', '睡觉', '打豆豆']
})
// 方法
function changeInfo() {
person.name = 'cheng',
person.age = 18,
person.job.type = 'UI设计师'
person.job.salary = '100k'
person.job.a.b.c = 666
person.hobby[0] = 'study'
}
function addsex() {
person.sex = '女'
}
function deleteName (){
delete person.name
}
return {
person,
changeInfo,
addsex,
deleteName
}
}
}
</script>
vue3的响应式原理
通过Proxy
实现,Proxy
是es6中的语法,是window身上的一个内置函数window.Proxy
,可以直接使用
proxy
的意思是代理,
- 语法格式:
const p =new Proxy(person,{})
第一个参数:
可以使p映射person的操作,即p代理着person,当p的值发生变化时person的值也会发生变化(和Object.defineProperty相似之处),而且增删改
变化都可以被检测到(和Object.defineProperty不同之处,Object.defineProperty只能检测到改
的变化)。
——这就形成了数据代理,但是还没有完成响应式
第二个参数:
用于实现响应式,里面可以编写增删改
操作的响应式
<script type='text/javascript'>
let person ={
name:'yang',
age:18
}
// vue3的响应式原理
const p =new Proxy(person,{
// 读取p的属性的响应式
get(target,propName){
// target代表person源对象
// propName代表读取的属性名
console.log(`有人读取了person身上的${
propName}属性`)
// propName是一个变量需要使用数组形式读取
return target[propName]
},
// 修改p或给p追加属性时的响应式
set(target,propName,value){
console.log(`有人修改了了person身上的${
propName}属性,我要去修改页面了`)
target[propName] = value
},
// 删除p的属性时的响应式
deleteProperty(target,propName){
console.log(`有人删除了person身上的${
propName}属性,我要去修改页面了`)
return delete target[propName]
}
})
</script>
vue3的响应式原理——reflect
Reflect也是ES6新增的一个属性,在Window身上,可以直接调用。
- 作用:可以实现对对象属性的增删改查
let person ={
name:'yang',
age:18
}
// 读取
Reflect.get(person,"name")
// 修改
Reflect.set(person,"name","cheng")
// 添加
Reflect.set(person,"sex","男")
// 删除
Reflect.deleteProperty(person,"name")
- Reflect身上也有 defineProperty 属性:
let person ={
name:'yang',
age:18
}
Reflect.defineProperty(person,"school",{
get(){
return "nefu"
},
set(value){
person.school = value
}
})
//Object写法
/*Object.defineProperty(person,"school",{
get(){
return "nefu"
},
set(value){
person.school = value
}
})*/
Reflect.defineProperty 和 Object.defineProperty的区别:
Object.defineProperty对一个代理对象设置两个相同的属性会直接报错。
Reflect.defineProperty 对一个代理对象设置两个相同的属性不会报错,且以第一次设置的属性为准。
vue3的响应式原理的reflect写法(常用)
<script type='text/javascript'>
let person ={
name:'yang',
age:18
}
// vue3的响应式原理
const p =new Proxy(person,{
// 读取p的属性的响应式
get(target,propName){
// target代表person源对象
// propName代表读取的属性名
console.log(`有人读取了person身上的${
propName}属性`)
// propName是一个变量需要使用数组形式读取
return Reflect.get(target,propName)
},
// 修改p或给p追加属性时的响应式
set(target,propName,value){
console.log(`有人修改了了person身上的${
propName}属性,我要去修改页面了`)
Reflect.set(target,propName,value)
},
// 删除p的属性时的响应式
deleteProperty(target,propName){
console.log(`有人删除了person身上的${
propName}属性,我要去修改页面了`)
return Reflect.deleteProperty(target,propName)
}
})
</script>
vue3的响应式原理就是通过Proxy代理对象
和 Reflect反射对象
实现的
reactive和ref的区别
- 从定义数据角度对比:
ref
用来定义:基本类型
数据。reactive
用来定义对象(或数组)
类型数据。- 备注: ref也可以用来定义对象(或数组)类型数据,它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
object.defineProperty()
的get与set来实现响应式(数据劫持)。 - reactive通过使用
Proxy
来实现响应式(数据劫持),并通过Reflect
操作源对象内部的数据。
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,模板中读取数据时直接读取不需要.value 。 - reactive定义的数据:操作数据与读取数据均不需要.value 。
我们最常用的还是 reactive,如果需要处理基本类型
数据,直接封装成对象即可
setup的两个注意点
vue2知识回顾
- 传递属性props:vue2中使用props传递的是属性,父组件在组件标签中传递属性,子组件使用props接受属性。
父组件:
<div>
<Student name='张三' :age="20"/>
</div>
子组件:
props: ['name','age'],
正常情况下由props接收从父组件传递管来的参数,并且将参数放在组件实例对象上。但是如果不接收的话会存储在组件实例对象上的$attrs属性
上,如果接受了就不会存储在$attrs属性
上了。
- 插槽
子组件设置<slot></slot>
,父组件向里面传递html内容,在组件实例对象的$slots属性
上存储着父组件向子组件传递的html内容
setup执行的时机
在beforeCreate之前执行一次,this是undefined。
setup的参数
setup的参数:
props
:值为对象,包含组件外部传递过来
,且组件内部声明接收了
的属性。
组件内必须使用props属性接受了的数据,setup的 props参数才能获取到,并且setup的 props参数是一个代理对象
,即可以实现响应式。
props:['msg','school'],
setup(props) {
console.log("---setup----", props)
}
context
: 上下文对象(是一个普通的对象)
包含:
(1)attrs
:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于实例对象中的this.$attrs
。
组件中没有使用props声明接受的参数会存放在attrs中。
eg:
props:['msg'],//还传递了一个“school”参数
setup(props,context) {
console.log("---setup----", context.attrs)
}
(2)slots
:收到的插槽内容,相当于实例对象中this.$slots
。
(3)emit
:触发自定义事件的函数,相当于实例对象中this.$emit
。
在vue3中为组件绑定自定义事件需要使用emits配置声明一下为该组件绑定的自定义事件。emits:['hello']
setup中触发事件: context.emit('事件名',参数)
EG:
app.vue:(绑定事件)
<template>
<demo @hello="showHelloMsg" msg="hello" school="nefu"></demo>
</template>
<script>
import Demo from './components/Demo.vue'
export default {
name: 'App',
components: {
Demo },
setup() {
function showHelloMsg (value){
alert(`${
value}`)
}
return {
showHelloMsg
}
}
}
</script>
Demo.vue(触发事件)
<template>
<h1>Demo组件</h1>
<h2 v-show="person.name">姓名:{
{ person.name }}</h2>
<h2>年龄:{
{ person.age }}</h2>
<button @click="test">测试触发Hello事件</button>
</template>
<script>
import {
reactive } from 'vue'
export default {
name: 'App',
props: ['msg', 'school'],
emits: ['hello'],
setup(props,context) {
// console.log("---setup----", context.attrs)
console.log("---setup----", context.emit)
//数据
let person = reactive({
name: 'yang',
age: 18,
})
// 方法
function test (){
context.emit('hello',666)
}
return {
person,
test
}
}
}
</script>