setup是Vue3中一个新的配置项,值为一个函数
。
两种返回值
:模板中均可以直接使用
。渲染函数
:则可以自定义渲染内容。渲染函数
,那么返回值会直接渲染在页面上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>
<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>
在vue2中data配置
中的属性会通过Object.defineProperty
原理最终包装成响应式数据_data。
vue3中为我们提供了两种包装响应式数据的方法:ref和reactive
注意这里的ref和vue2中的ref不一样,这里是一个ref函数
。
上面使用setup包裹页面数据,但是这样编写出的数据不是响应式的,即数据改变页面不会被重新加载。从vue2中我们知道一个数据要实现响应式一定有对应的响应式得getter和setter方法,在vue3中通过ref函数
帮我们生成响应式的getter和setter。
ref的实现原理也是通过Object.defineProperty
实现的
定义一个响应式的数据
import { ref } from 'vue'
const xxx = ref(数据)
(创建一个包含响应式数据的引用对象(reference对象,简称ref对象)).value
属性上变量名.value
不需要.value
,直接:{{变量名}}
例子:
<template>
<h1>app组件h1>
<h2>姓名{{ name }}h2>
<h2>年龄{{ age }}h2>
<button @click="sayHello">hellobutton>
<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当然也可以包装对象,那就有一个问题。他是否会包装对象中的数据,让对象中的数据也成为响应式的,这样对象中的数据改变才能重新渲染页面。
答案是:对象中的数据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">hellobutton>
<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函数
。作用:定义一个对象类型
的响应式数据(基本类型不要用它,要用ref函数)
直接源数据封装成Proxy代理对象,Proxy代理对象是响应式的
语法: 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">hellobutton>
<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>
Proxy封装的数组,可以直接通过下标修改数据,同时实现响应式布局。
定义数据
let hobby = reactive(['吃饭', '睡觉', '打豆豆'])
修改数据
function changeHobby() {
hobby[0] = 'study'
}
当数组数据改变可以引起页面重新渲染
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>
object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 <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>
this.$set(this.person, 'sex','女')
this.$delete(this.person, 'sex'')
this.$set(this.person.hobby, 0,'学习')
this.person.hobby.splice(0,1,'学习')
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>
通过Proxy
实现,Proxy
是es6中的语法,是window身上的一个内置函数window.Proxy
,可以直接使用
proxy
的意思是代理,
const p =new Proxy(person,{})
增删改
变化都可以被检测到(和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>
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")
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 对一个代理对象设置两个相同的属性不会报错,且以第一次设置的属性为准。
<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反射对象
实现的
ref
用来定义:基本类型
数据。reactive
用来定义对象(或数组)
类型数据。reactive
转为代理对象。object.defineProperty()
的get与set来实现响应式(数据劫持)。Proxy
来实现响应式(数据劫持),并通过Reflect
操作源对象内部的数据。.value
,模板中读取数据时直接读取不需要.value 。我们最常用的还是 reactive,如果需要处理基本类型
数据,直接封装成对象即可
<div>
<Student name='张三' :age="20"/>
div>
子组件:
props: ['name','age'],
正常情况下由props接收从父组件传递管来的参数,并且将参数放在组件实例对象上。但是如果不接收的话会存储在组件实例对象上的$attrs属性
上,如果接受了就不会存储在$attrs属性
上了。
,父组件向里面传递html内容,在组件实例对象的$slots属性
上存储着父组件向子组件传递的html内容在beforeCreate之前执行一次,this是undefined。
setup的参数:
props
:值为对象,包含组件外部传递过来
,且组件内部声明接收了
的属性。代理对象
,即可以实现响应式。 props:['msg','school'],
setup(props) {
console.log("---setup----", props)
}
context
: 上下文对象(是一个普通的对象)attrs
:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于实例对象中的this.$attrs
。 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>