使用vite创建vue项目
- 什么是vite?
- 新一代前端构建工具
- 优势如下:
- 开发环境中,无须打包操作,可快速的冷启动
- 清亮快速的热重载
- 真正的按需编译,不在等待整个应用编译完成
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
- 注意,vite初始化后需要你手动安装一下node_modules
vue3中的根标签可以不止一个
vue2就不行
setup
- vue3中一个新的配置项,值为一个函数
- setup是所有Composition API(组合式API)表演的舞台
- 组件中所用到的:数据,方法等等,均要配置在setup
- setup函数的两种返回值
- 若返回一个对象,则对象中的属性,方法在模板中均可以直接使用
- 若返回一个渲染函数,则可以自定义熏染内容
- 注意点:
- 尽量不要与vue2.0配置混用
- vue2.0配置(data,methos,computed…)中可以访问到setup中的属性,方法
- 但在setpup中不能访问到vue2配置(data,methods,computed)
- 如果有重名,setup优先
- setup不能是一个async函数,因为返回值不再是return对象,而是promise,模板看不到return对象中的属性
vue3向下兼容
vue3中也可以按照vue2的方式来写,vue3是向下兼容的
<template>
<div class="index">
index页面
name:
{{ name }}
age:
{{ age }}
<br>
<router-link to="/test1">test1</router-link>
</div>
</template>
<script>
export default {
name: "Index",
// 测试只是测试函数,并不考虑响应式
setup () {
let name = "张三"
let age = 18
// 方法
function sayHello () {
}
// 返回对象
return {
name,
age,
sayHello
}
}
}
</script>
这里面的returb也可以这样写:
<template>
<div class="index">
index页面
name:
{{ name }}
age:
{{ age }}
<br>
<router-link to="/test1">test1</router-link>
</div>
</template>
<script>
import { h } from "vue"
export default {
name: "Index",
// 测试只是测试函数,并不考虑响应式
setup () {
let name = "张三"
let age = 18
// 方法
function sayHello () {
}
// 返回渲染函数
// 此时你页面中写什么都不重要了,直接渲染下面这里写的东西
return () => {
return h('h1', '白马')
}
}
}
</script>
而此时,页面中的内容只剩下return h('h1', '白马')
这里指定渲染的内容了
关于你可以在vue3中按照vue2那样写
<template>
<div class="test2">
<button @click="test1">点击测试</button>
</div>
</template>
<script>
export default {
name: "test2",
data () {
return {
name2:'ls'
}
},
created () {
},
methods: {
test1 () {
console.log(this.name)
console.log(this.name2)
}
},
setup () {
const name = 'zs'
return {
name
}
}
}
</script>
像是上面的代码,可以访问到setup中的变量
- 注意,setup不可以使用async修饰
因为如果被async修饰了,那么它的返回值就不再是对象了,而是一个被promise包裹的对象,而你的模板不认识这个返回值
ref
当初在使用vue2的时候,使用ref是为了给元素打标识之类的作用
在vue3中ref是个函数,原来的没有废掉
非响应式数据:
像是下面一样,定义了非响应式的数据:
<template>
<div class="ref">
<h5>一个人的信息:</h5>
<h5>姓名:{{ name }}</h5>
<h5>年龄:{{ age }}</h5>
<button @click="changeInfo">修改数据</button>
</div>
</template>
<script>
export default {
setup () {
let age = 24
let name = 'zs'
const changeInfo = () => {
name = '李四'
age = 50
console.log(name,age)
}
return {
age,
name,
changeInfo
}
}
}
</script>
这里的按钮点击了,在控制台输出的值会更改,但是页面中的值并没有发生改变,因为此时他不是响应式数据
虽然这里不是响应式数据了,但是也没有一些多余的getter,setter,当你想要使用响应式数据的时候,可以进行按需引入ref来使用
例如下面的:
<template>
<div class="ref">
<h5>一个人的信息:</h5>
<h5>姓名:{{ name }}</h5>
<h5>年龄:{{ age }}</h5>
<h5>爱好:{{ hobby }}</h5>
<button @click="changeInfo">修改数据</button>
<button @click="changeHobby">修改爱好</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
let age = 24
let name = 'zs'
let hobby = ref('唱跳rap,篮球')
const changeInfo = () => {
name = '李四'
age = 50
console.log(name, age)
}
const changeHobby = () => {
hobby.value = '学习'
}
return {
age,
name,
changeInfo,
hobby,
changeHobby
}
}
}
</script>
此时如果你直接输出一下hobby这个响应式对象,可以发现它是:
{
"__v_isShallow": false,
"dep": {
"w": 0,
"n": 0
},
"__v_isRef": true,
"_rawValue": "学习",
"_value": "学习"
}
这样的
而这里的RefImpl
的意思是引用的实现
标准的称呼为引用实现对象
,或是引用对象
,简称ref对象
在vue3的模板中,解析html部分的{{}}
内容时,它会自动.value
ref中的对象会使用proxy来包装
例如下面的代码:
setup () {
let job = ref({
salary: 30,
work: 'black man'
})
const getObj = () => {
console.log(job.value)
}
getObj()
}
这里输出的对象是:
![[Pasted image 20230603141346.png]]
这里的对象包装方式不同了
像是refimpl的实例对象,是通过getter,setter实现响应式的
ref处理对象的时候,会将里面的对象使用proxy来包装
对象类型的数据,内部会求助vue3中的一个新函数:reactive
reactive函数
作用:定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
语法:const 代理对象 = reactive(源对象)
接收一个对象(或数据),返回一个代理对象(proxy对象)
reactive定义的响应式数据是深层次的
内部基于es6的proxy实现,通过代理对象操作源对象内部数据进行操作
reactive函数处理不了基本类型
用reactive处理对象要比ref简单,像是下面这个:
setup () {
let job = ref({
salary: 30,
work: 'black man'
})
let company=reactive({
name:'武汉轻工大学',
year:4,
numbers:9000
})
const getObj = () => {
console.log(job.value)
console.log("company:")
console.log(company)
console.log(company.name)
}
getObj()
}
直接.
属性名即可,不用在.value之后再.
属性名
vue3中的响应式原理
vue2的响应式:
实现原理:
- 对象类型:
- 通过
Object.defineProperty
对属性的读取,修改进行拦截(数据劫持) - 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)
- 通过
Object.defineProperty(data,'count',{
get(){},
set(){}
})
- 存在问题:
- 新增属性,删除属性,界面不会更新
像是下面的代码在vue2环境下跑:
- 新增属性,删除属性,界面不会更新
<template>
<div class="index">
<h5>
测试vue2中为对象新增/删除属性是否能被监听到
</h5>
<div class="list-column">
<span>
name:{{ person.name }}
</span>
<span> age:{{ person.age }}
</span>
<span> gender:{{ person.gender }}
</span>
<span> school:{{ person.school }}
</span>
<span v-if="person.sclary">
sclary:{{ person.sclary }}
</span>
</div>
<br> <button @click="delAttr()">删除school属性</button>
<button @click="addAttr()">添加sclary属性</button>
</div>
</template>
<script>
export default {
name: "Index",
data () {
return {
person: {
name: '椎间孔',
age: 22,
gender: '大三',
school: '武汉轻工大学'
}
}
},
methods: {
delAttr () {
delete this.person.school
console.log('删除之后的对象:')
console.log(this.person)
},
addAttr () {
this.person.sclary = 3000
console.log('添加之后的对象:')
console.log(this.person)
}
}
}
</script>
依次点击删除和添加:
![[Pasted image 20230603170440.png]]
对象是发生了改变,但是页面中没有改变
- 监听不到,那么你可以使用
this.$set
来设置:
this.$set(this.person,'sclary','3000')
那么此时添加属性,页面上也会有响应的
- 实现同样的目的也可以使用vue.set:
import Vue from 'vue'
// 使用它之前要引入
Vue.set(this.person, 'sclary', '3000')
- 实现响应式的对象删除属性:
Vue.set(this.person, 'sclary', '3000')
// 下面这个:
this.$delete(this.person, 'school')
// 或者:
Vue.delete(this.person,'school')
- 对象中的数组实现响应式:
<script>
import Vue from 'vue'
export default {
name: "Index",
data () {
return {
person: {
name: '椎间孔',
age: 22,
gender: '大三',
school: '武汉轻工大学',
hobby: ['唱', '跳', 'tap', '篮球']
}
}
},
methods: {
change () {
// 下面两种方式都可以
// this.$set(this.person.hobby, 0, '逛街')
// Vue.set(this.person.hobby, 0, '洗黑钱')
// 当然你也可以使用数组的方法来修改:
this.person.hobby.splice(0,1,'洗钱')
}
}
}
</script>
vue3的响应式:
实现原理:
- 通过proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写,属性的添加,属性的删除等
- 通过Reflect(反射):对被代理对象的属性进行操作
- MDN文档中描述的Proxy与Reflect:
实现一个proxy的响应式对象:
const person = {
name: 'zs',
age: 18
}
const p = new Proxy(person, {
get (target, propName) {
console.log('有人读取了')
return target[propName]
},
set (target, propName, value) {
console.log(`将${ propName }修改为${ value }`)
return target[propName] = value
}
})
在控制台尝试使用:
![[Pasted image 20230604000442.png]]
![[Pasted image 20230604000624.png]]
但是像上面的代码,捕获到了他的更新和读取,并没有捕获到它的删除和新增属性
其实proxy也可以监听删除:
const person = {
name: 'zs',
age: 18
}
const p = new Proxy(person, {
get (target, propName) {
console.log('有人读取了')
return target[propName]
},
set (target, propName, value) {
console.log(`将${ propName }修改为${ value }`)
target[propName] = value
},
deleteProperty (target, p) {
console.log(`删除属性${p}`)
delete target[p]
}
})
![[Pasted image 20230604002915.png]]
要注意,delete target[p]
这里的删除是有返回值的,其实把这里的删除返回即可:
deleteProperty (target, p) {
console.log(`删除属性${p}`)
return delete target[p]
}
关于proxy的增加属性:
-
get
是有人读取某个属性时会被调用 -
set
有人修改属性,或追加
属性时会被调用 -
deleteProperty
删除属性时调用
虽然proxy可以监听到增删改查,但是vue3不是这样做的
![[Pasted image 20230604003421.png]]
使用两种方式实现对象的映射
let obj = { a: 1, b: 2 }
/* console.log('使用Object.defineProperties来实现映射:')
Object.defineProperties(obj, 'c', { get () { return 3 } }) Object.defineProperties(obj, 'c', { get () { return 4 } })*/
console.log('使用reflect来实现映射')
const x1 = Reflect.defineProperty(obj, 'c', {
get () {
return 3
}
})
console.log(x1)
const x2 = Reflect.defineProperty(obj, 'c', {
get () {
return 4
}
})
console.log(x2)
第一种方式使用Object.defineProperties
重复覆盖属性会报错,此时可以使用try,catrch来捕获,但是为了程序的壮行这样是否有些麻烦了
所以可以使用Reflect.defineProperty
来进行映射,它重复覆盖并不会出错,而且在每一次覆盖是都会有一个为boolean的返回值
vue3中响应式对象的雏形:
let person = {
name: 'zs',
age: 24,
gender: '大三',
school: '武汉轻工大学'
}
const p = new Proxy(person, {
get (target, propName) {
console.log(`有人获取了${ propName }`)
return Reflect.get(target, propName)
},
set (target, propName, value) {
console.log(`有人修改了${ propName }`)
Reflect.set(target, propName, value)
},
deleteProperty (target, propName) {
console.log(`有人删除了p身上的${ propName }`)
return Reflect.deleteProperty(target, propName)
}})
reactive对比ref
-
从定义数据角度对比:
ref用来定义:基本类型数据
reactive用来定义:对象(或数组)类型数据
备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive
转为代理对象 -
从原理角度对比:
ref通过Object.defineProperty()
的get
与set
来实现响应式(数据劫持)
reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据 -
从使用角度对比:
ref定义的数据:操作数据需要.value
,读取数据时,模板中直接读取不需要.value
reactive定义的数据:操作数据与读取数据:均不需要.value
关于vue2中的props
如果你像是在下面的子组件中不写props:
父组件:
<template>
<div class="index">
<chindren msg="你好" about="关于信息"></chindren>
</div>
</template>
<script>
import Vue from 'vue'
import Chindren from '@/components/chindren.vue'
export default {
name: "Index",
components: { Chindren },
}
}
</script>
子组件:
<template>
<div class="lim">
</div></template>
<script>
export default {
name: "chindren",
mounted () {
console.log(this)
}
}
</script>
注意,此时在子组件的props中是没有定义的
,但是在页面的控制台可以看到输出的this:
![[Pasted image 20230605234406.png]]
在vm的$attrs
上挂载了传入的值
到这里你可能想着,不定义props,直接传过来多方便,
但是,如果不定义props的话,你是无法限制传入的类型
关于vue2的slot
在子组件中不去定义slot:
父组件:
<chindren msg="你好" about="关于信息">
<h5>这是一个slot的内容</h5>
</chindren>
子组件:
<template>
<div class="lim">
</div>
</template>
<script>
export default {
name: "chindren",
mounted () {
console.log(this)
}
}
</script>
控制台中:
![[Pasted image 20230605234843.png]]
slot是存在的
使用多个插槽:
父组件:
<chindren msg="你好" about="关于信息">
<template slot="name">
<h5>张三</h5>
</template>
<template slot="gender">
<h5>gender:5</h5>
</template>
</chindren>
子组件:
<template>
<div class="lim">
<slot name="name"></slot>
<slot name="gender"></slot>
</div>
</template>
<script>
export default {
name: "chindren",
mounted () {
console.log(this)
}
}
</script>
setup的两个注意点:
-
setup的执行时机:
在beforeCreate之前执行一次,this是undefined -
setup的参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
context:上下文对象attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this. a t t r s s l o t s : 收到的插槽内容 , 相当于 t h i s . attrs slots:收到的插槽内容,相当于this. attrsslots:收到的插槽内容,相当于this.slota
emit:分发自定义事件的函数,相当于this.$emit
例如在下面的例子中,父组件向子组件传值,但是子组件并没有定义props:
父组件:
<template>
<div class="lim">
你好
<demo msg="来自setup的信息" index="20"></demo>
</div>
</template>
<script>
import Demo from './demo.vue'
export default {
name: "setup",
components: { Demo }
}
</script>
子组件:
<template>
<div>
子组件的内容
</div>
</template>
<script>
export default {
name: "demo",
setup (props) {
console.log('setup props:')
console.log(props)
}
}
</script>
控制台输出:
![[Pasted image 20230611142633.png]]
如果此时在子组件中定义了props:
<template>
<div>
子组件的内容
</div>
</template>
<script>
export default {
name: "demo",
props:['msg','index'],
setup (props) {
console.log('setup props:')
console.log(props)
}
}
</script>
此时setup中接收的第一个参数输出:
![[Pasted image 20230611143321.png]]
注意,此时它也是响应式对象
那么关于传入的其他形参:
父组件:
<template>
<div class="lim">
你好
<demo @click="hello" msg="来自setup的信息" index="20">
<slot>这是第一个slot</slot>
<template slot="qwe">
自定义插槽
</template>
<template v-slot:two>
自定义插槽的第二个插槽
</template>
</demo>
</div>
</template>
<script>
import Demo from './demo.vue'
export default {
name: "setup",
components: { Demo },
setup () {
const hello = () => {
console.log('hello world')
}
return {
hello
}
}
}
</script>
子组件:
<template>
<div>
子组件的内容
<button @click="log">测试触发子组件事件</button>
</div>
</template>
<script>
export default {
name: "demo",
props: ['msg', 'index'],
emits: ['hello'],
setup (props, context) {
console.log('setup props:')
console.log(props)
console.log('context.attrs:')
// vue2中的$attrs
console.log(context.attrs)
console.log('context.emit:')
// 插槽
console.log('context.slots:')
console.log(context.slots)
const log = () => {
console.log('触发了子组件的click')
}
return {
log
}
},
}
</script>
在控制台中查看:
![[Pasted image 20230611144611.png]]
计算属性
<template>
<div class="computed">
<div class="inputLim">
性:
<el-input v-model="lastName" placeholder="Please input"/>
</div>
<br> <div class="inputLim">
名:
<el-input v-model="firstName" placeholder="Please input"/>
</div>
<br>
<div class="inputLim">
名字:
<el-input v-model="fullName" placeholder="Please input"/>
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
const lastName = ref('')
const firstName = ref('')
// 简写,没有考虑计算属性被修改的情况
/*const fullName = computed(() => {
return lastName.value + ' ' + firstName.value})*/
// 完整写法
const fullName = computed({
get () {
return lastName.value + ' ' + firstName.value
},
set (val) {
console.log(val)
}})
</script>
<style scoped lang="less">
.computed {
display: flex;
flex-direction: row;
.inputLim {
display: flex;
flex-direction: row;
}
}
</style>
![[Pasted image 20230611162743.png]]
watch属性
一个简单的使用:
<template>
<div class="watch">
<h2>sum:{{ sum }}</h2>
<button @click="sum++">add</button>
<br> <h2>当前的信息为:{{ msg }}</h2>
<button @click="msg+='!'">msg改变</button>
<br> <h2> 姓名:{{ person.name }}
年龄:{{ person.age }}
学校:{{ person.school }}
薪资:{{ person.job.salary }}
</h2>
<el-text> 注意在vue3中只要对象使用active包裹,那么不管多深,都是可以监听到的
</el-text>
<div class="btns">
<el-button @click="person.name+='~'">修改名字</el-button>
<el-button @click="person.age++">修改年龄</el-button>
<el-button @click="person.school+='school'">修改学校</el-button>
<el-button @click="person.job.salary++">薪资增加</el-button>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
let sum = ref(0)
let msg = ref("你好")
let person = reactive({
name: 'zs',
age: 24,
school: '武汉',
job: {
salary: '3000'
}
})
// 监视ref所定义的数据
watch(sum, (newVal) => {
console.log('sum发生改变')
})
// 监视ref所定义的多个数据
watch([sum, msg], (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// 配置一上来就监听
watch(sum, (newVal) => {
console.log(newVal)
}, { immediate: true })
// 监听reactive 定义的数据,
// 如果监听使用reactive 定义的数据,那么在获取oldVal上会有问题,无法正确地获取oldVal,目前无法解决
// 但如果你使用ref来定义一个对象,它走的还是reactive的逻辑,也是行不通的
watch(person, (newVal, oldVal) => {
console.log("newVal:")
console.log(newVal)
console.log("oldVal:")
console.log(oldVal)
})
</script>
vue3中无法正确监听到对象中watch函数中的oldVal
像是上面代码中,监听persojn的修改中,修改学校字段,此时在控制台查看:
![[Pasted image 20230612232234.png]]
注意,不论你是用ref还是reactive包裹对象,他都是无法监听到oldVal的,这是目前vue3中的问题
当你使用ref包裹对象时,它会自动使用reactive来包裹对象的
监听多个
let sum = ref(0)
let msg = ref("你好")
watch([sum, msg], (newVal, oldVal) => {
console.log(newVal, oldVal)
})
在vue2中watch为配置对象,只能调用一次,而vue3中 ,watch为函数,可以调用多次
监听中的配置项
一上来就监听一次:
watch(sum, (newVal) => {
console.log(newVal)
}, { immediate: true })
vue3中可以监听嵌套很深的对象
强制开启了deep,你关闭了也没用文章来源:https://www.toymoban.com/news/detail-480886.html
监听对象中的某一属性
let person = reactive({
name: 'zs',
age: 24,
school: '武汉',
job: {
salary: '3000'
}
})
watch(() => person.name, (newVal, oldVal) => {
console.log('值发生改变了')
console.log(newVal)
})
![[Pasted image 20230612235713.png]]
此时你修改其它字段是不会被监听到文章来源地址https://www.toymoban.com/news/detail-480886.html
到了这里,关于vue3对比vue2的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!