vue3与vue2区别(对vue3的了解)
(1) 性能更高 ① 相对于vue2而言 底层响应原理转换为proxy ② 虚拟 DOM 的算法进行了优化
(2) 体积更小 删除了一些不常用的api ① 过滤器 ② EventBus ③ 代码支持按需导入 ④配合Webpack打包工具支持 TreeShaking
(3) 对TS的支持更好 源码就是用TS重写的
(4) Composition API ① 能把相同功能的数据和业务逻辑组合到一起,代码更容易复用和维护。
② 更适合大型项目
(5) 新特性 Fragment Teleport Suspense
Vite 创建项目
对比 Webpack 和 Vite 如下。
Webpack:会将所有模块提前编译、打包,不管这个模块是否被用到,随着项目越来越大,打包启动速度自然越来越慢。
Vite:瞬间开启一个服务,并不会先编译所有文件,当浏览器用到某个文件时,Vite 服务会收到请求然后编译后响应到客户端。
创建项目命令
npm create vite
# or
yarn create vite
vite快捷创建
# 创建普通 Vue 项目
yarn create vite vite-demo --template vue
# 创建基于 TS 模板的 Vue 项目
yarn create vite vite-demo-ts --template vue-ts
编写 Vue 应用
在main.js中
// 1. 导入 createApp 函数,不再是曾经的 Vue 了
// 2. 编写一个根组件 App.vue,导入进来
// 3. 基于根组件创建应用实例,类似 Vue2 的 vm,但比 vm 更轻量
// 4. 挂载到 index.html 的 #app 容器
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
// Vue2: new Vue()、new VueRouter()、new Vuex.Store()
// Vue3: createApp()、createRouter()、createStore()
在app.vue中
<template>
<div>
我是根组件
</div>
</template>
<script>
export default {
setup () {
return {}
}
}
</script>
<style lang="scss" scoped>
</style>
vue2和vue3的优缺点
Vue2
-
优点:易于学习和使用,写代码的位置已经约定好。
-
缺点:对于大型项目,不利于代码的复用、不利于管理和维护。
vue3
-
优点:可以把同一功能的数据和业务逻辑组织到一起,方便复用和维护。
-
缺点:需要有良好的代码组织和拆分能力,相对没有 Vue2 容易上手。
setup 入口函数
setup是Vue3中新增的组件配置项,作为组合API的入口函数
执行机制 在实例创建前调用 甚至早于beforecreate
所以在setup中无法获取到data里面的数据,和methods里面的方法 且vue3中this指向undefined
虽然 Vue2 中的 data 和 methods 配置项虽然在 Vue3 中也能使用,但不建议了,建议数据和方法都写在 setup 函数中,并通过 return 进行返回可在模版中直接使用(一般情况下 setup 不能为异步函数)
面试题 setup 内部一定返回一个对象吗?不一定,其实也可以返回一个渲染函数
<template>
<h1>name:{{name}}</h1>
</template>
<script>
export default {
beforeCreate(){
console.log('#2 beforecreate')
},
setup(){
// 执行时机 比beforecreate 早
//注意点:走到这的时候实例还没创建,意味着不能访问data数据和methods方法
console.log('#1 setup')
console.log(this) // this指向为undefined
const name ='12121'
// 使用数据 :定义的数据如果想要在模板中使用,必须通过return 出去
return {
//setup中的 return 返回必须是一个对象吗还可以是渲染函数
name,
}
}
}
</script>
小结
-
setup 的执行时机是什么?
-
setup 中的 this 指向是什么?
-
想在模板中使用 setup 中定义的数据,该怎么做?
reactive
reactive可以将复杂数据类型包装成响应式对象
案例
<template>
<div>
<input type="text" v-model="user.id"/>
<input type="text" v-model="user.name"/>
<button @click="add">提交</button>
<ul>
<li v-for="(item,index) in data.arr" :key="item.id" @click="del(index)">{{item.name}}</li>
</ul>
</div>
</template>
<script>
import {reactive} from 'vue'
const useDelFn=()=>{
const data = reactive({
arr: [
{
id: 0,
name: 'ifer',
},
{
id: 1,
name: 'elser',
},
{
id: 2,
name: 'xxx',
},
],
})
const del=(index)=>{
data.arr.splice(index,1)
}
return {data,del}
}
const useAddFn=(data)=>{
// 1. 收集数据(通过 v-model)
const user=reactive({
id:'',
name:''
})
// 2. 把收集到的数据怼到 data.arr 里面
const add =()=>{
data.arr.push({
id:user.id,
name:user.name,
})
// 3.清空表单
user.id=''
user.name=''
}
return {user,add}
}
export default {
setup() {
const {data,del}=useDelFn()
// 添加数据,不要忘记把 data 传递到 useAddFn 里面供使用
const {user,add}=useAddFn(data)
return {data,del,user,add}
},
}
</script>
抽离函数 拆分文件
新建文件夹 hooks 新建文件index.vue
export导出函数
在app.vue中导入函数
<template>
<div>
<input type="text" v-model="user.id"/>
<input type="text" v-model="user.name"/>
<button @click="add">提交</button>
<ul>
<li v-for="(item,index) in data.arr" :key="item.id" @click="del(index)">{{item.name}}</li>
</ul>
</div>
</template>
<script>
import {useDelFn,useAddFn} from './hooks/index'
export default {
setup() {
const {data,del}=useDelFn()
const {user,add}=useAddFn(data)
return {data,del,user,add}
},
}
</script>
ref
ref将简单数据类型和复杂数据类型包装成响应式对象
修改ref包裹的数据类型时必须加.value
可以通过isRef 来判断数据类型的包裹是否为ref
修改和操作ref包裹的数据时必须加 .value 但是在模板中不能加.value因为他自动添加有.value
ref简单包裹数据类型
<template>
<div>
<p>count:{{ count }}</p>
<button @click="add">点我加一</button>
<!-- 在模板内不需要加.value -->
<button @click="count++">加一</button>
</div>
</template>
<script>
import { ref, isRef } from 'vue'
export default {
setup() {
const count = ref(0)
// isRef判断某个数据是否是ref
console.log(isRef(count));
const add = () => {
// 修改ref类型包裹的数据时必须要加.value
count.value++
}
return { count, add }
}
}
</script>
ref包裹复杂数据类型
<template>
<div>
<div>name: {{ data && data.name }}</div>
<!-- ?. ES2021 新增的操作符,可选链,表示只有 data 存在的情况下才去往后取 name,如果 data 不存在(null、undefined),就直接整体返回 undefined -->
<div>name: {{ data?.name }}</div>
</div>
</template>
<script>
import { ref } from 'vue'
// ref可以包裹复杂数据类型为响应式
export default {
setup() {
// const data = ref(null)
const data = ref({
name: '❤❤❤'
})
setTimeout(() => {
data.value = {
name: '❤❤'
}
}, 3000)
setTimeout(() => {
data.value = {
name: '❤'
}
}, 5000)
return { data }
}
}
</script>
如何选择什么时候使用ref什么时候使用reactive
当你明确知道要包裹的数据是一个对象的时候,可以使用reactive,Vue3.2 之后,更推荐使用 ref,性能得到了很大的提升
toRef
toRef 函数的作用:转换响应式对象中某个属性为单独响应式数据,并且转换后的值和之前是关联的(ref 函数也可以转换,但值非关联)
toRef的使用
<template>
<div>
<p>name{{ name }}</p>
<p>age{{ age }}</p>
<button @click="updateName">updateName</button>
</div>
</template>
<script>
import { reactive, toRef, } from 'vue'
export default {
setup() {
// 需求在模板中渲染名字和年龄
const obj = reactive({
name: '张三',
age: 10,
address: '河南',
sex: '男'
})
// #问题 1:模板中使用太麻烦了,每次都要 obj.,重复!
// #问题 2:明明模板中只用到了 name 和 age,你却把整个 obj 都导出了,没必要(性能不好)
// 把响应式对象 obj 中的 name 转成了响应式的 ref 数据
const name = toRef(obj, 'name')
const age = toRef(obj, 'age')
// const name =ref(obj,'name')
// const age =ref(obj,'age')
// ref转换后的name 和原来的obj是非关联的,意味着对原来数据的修改视图不会更新
const updateName = () => {
// 不要忘记.value
name.value = "李四"
// toRef 转换后的 name 和原来的 obj 是关联的,意味着对原来数据的修改也会影响转换后的 name
// obj.name = 'elser'
}
return { name, age, updateName }
}
}
</script>
当需要渲染所有时
toRefs
<template>
<div>
<p>name: {{ name }}</p>
<p>age: {{ age }}</p>
<p>address: {{ address }}</p>
<p>sex: {{ sex }}</p>
<button @click="updateName">update name</button>
</div>
</template>
<script>
import { reactive, toRef, toRefs } from 'vue'
// 需求:在模板中渲染 name、age、address、sex...。
export default {
setup() {
const obj = reactive({
name: 'ifer',
age: 10,
address: '河南',
sex: '男',
})
// 这儿写起来太麻烦了
/* const name = toRef(obj, 'name')
const age = toRef(obj, 'age')
const address = toRef(obj, 'address')
const sex = toRef(obj, 'sex') */
// toRefs 可以把响应式对象中的所有属性变成单独的响应式 ref 对象
const { name, age, address, sex } = toRefs(obj)
const updateName = () => {
name.value = 'elser'
}
return {name,age,address,sex,updateName}
},
}
</script>
简写方法//最佳实践
<template>
<div class="container">
<h2>{{ name }} {{ age }}</h2>
<button @click="updateName">修改数据</button>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
name: 'App',
setup() {
const obj = reactive({
name: 'ifer',
age: 10,
})
const updateName = () => {
obj.name = 'xxx'
obj.age = 18
}
return { ...toRefs(obj), updateName }
},
}
</script>
computed计算属性
案例 拼接姓氏 和名字
获取数据是会主动调用get( )方法拿去结果
<template>
<div>
<p>firstName: {{ firstName }}</p>
<p>lastName: {{ lastName }}</p>
<p>fullName: {{ fullName }}</p>
</div>
</template>
<script>
import { reactive, computed, toRefs } from 'vue'
export default {
setup() {
const person = reactive({
firstName: '张',
lastName: '小二',
})
/* person.fullName = computed(() => {
// computed 基于依赖进行缓存,产生一个新结果
// watch 监听数据的变化,根据变化的后的新值进行副作用相关的处理(发请求,操作 DOM,操作本地数据...)
return person.firstName + ' ' + person.lastName
}) */
// computed 其实还可以接受一个对象
// 和上面的等价
person.fullName = computed({
get() {
// 获取数据的时候会主动调用 get 函数,拿到结果
return person.firstName + ' ' + person.lastName
},
})
return {
...toRefs(person),
}
},
}
</script>
computed的完整用法
通过v-model双向绑定 和set方法修改姓氏和名字并实现 数据的更新视图更新
<template>
<div>
<p>firstName{{firstName}}</p>
<p>lastName{{lastName}}</p>
<!-- <p>fullName{{fullName}}</p> -->
<p>
<input v-model="fullName"/>
</p>
</div>
</template>
<script>
import{reactive,computed, toRefs} from 'vue'
export default {
setup () {
const person =reactive({
firstName:'张',
lastName:'小二'
})
person.fullName=computed({
// 获取数据的时候会主动调用 get 函数,拿到结果
get(){
return person.firstName +' '+ person.lastName
},
set(newValue){
// 设置数据的时候会主动调用 set 函数,newValue 就是修改后的新值
const arr =newValue.split(' ')
person.firstName=arr[0]
person.lastName=arr[1]
}
})
return {
// toRefs批量转化为响应式对象
// ...toRefs (所有项)
...toRefs(person)
}
}
}
</script>
watch监听
1.watch监听reactive
监听reactive默认为深度监听 监听不能修改(配置无效)
监听的值是一个 reactive包裹,那么就符合下面特点
深度监听(配置无效)
其实是监听的 是内部数据的变化
监听对象的时候 newValue 和 oldValue 是全等的。
注意1 监听reactive默认为深度监听 监听不能修改(配置无效) 监听对象的时候 newValue 和 oldValue 是全等的。
<template>
<p>{{ obj.hobby.eat }}</p>
<button @click="obj.hobby.eat = '面条'">click</button>
</template>
<script>
import { watch, reactive } from 'vue'
export default {
name: 'App',
setup() {
const obj = reactive({
name: 'ifer',
hobby: {
eat: '西瓜',
},
})
// watch reactive 类型的数据,强制是开启了深度监听,没法配置
watch(
obj,
(newValue, oldValue) => {
// 此时 newValue 和 oldValue 新值和旧值是相等的
console.log(newValue === oldValue) // true
},
{
deep: false, // 配置无效
}
)
return { obj }
},
}
</script>
注意 2:reactive 的【内部对象】也是一个 reactive 类型的数据。
注意3.watch监听的是reactive的内置数据 ,对 reactive 自身的修改则不会触发监听 ,但是可以改变视图数据
<template>
<p>{{ obj.hobby.eat }}</p>
<!-- 直接修改 obj 等于一个新对象,不能被监听到,视图也不会更新 -->
<button @click="obj={hobby:{eat:'面条'}}">click</button>
</template>
<script>
import { watch, reactive } from 'vue'
export default {
name: 'App',
setup() {
const obj = reactive({
name: 'ifer',
hobby: {
eat: '西瓜',
},
})
// watch reactive 类型的数据,强制是开启了深度监听,没法配置
watch(obj,(newValue, oldValue) => {
// 此时 newValue 和 oldValue 新值和旧值是相等的
// console.log(newValue === oldValue) // true
console.log(1) // 不会打印
},
)
return { obj }
},
}
</script>
watch监听ref
监听ref.value默认为浅监听 只会监听一层 监听可以修改(配置无效)
当监听一个ref值时,触发后面的回调函数是不需要加.value的
当监听多个ref值时需要加.value
立即触发监听通过 immediate: true
<template>
<p>age: {{ age }} num: {{ num }}</p>
<button @click="handleClick">click</button>
</template>
<script>
import { watch, ref } from 'vue'
export default {
name: 'App',
setup() {
const age = ref(18)
const num = ref(0)
const handleClick = () => {
age.value++
num.value++
}
// 数组里面是 ref 数据
watch(
[age, num],
(newValue, oldValue) => {
console.log(newValue, oldValue)
},
{
immediate: true,
}
)
return { age, num, handleClick }
},
}
</script>
评论 ( 0 )
注意:监听ref.value 默认是浅监听的,默认只会监听一层
如果ref内置数据的修改也想要触发监听时可以通过
方法一 开启深度监听
watch(
obj,
(newValue, oldValue) => {
console.log(newValue)
},
{
deep: true,
}
)
方法二 修改 ref.value 表示只修改 外面的一层 直接修改自身
<template>
<p>{{ obj.hobby.eat }}</p>
<button @click="obj = { hobby: { eat: '面条' } }">修改 obj.hobby.eat</button>
<button @click="handleClick">修改 obj.hobby.eat</button>
</template>
<script>
import { watch, ref } from 'vue'
export default {
name: 'App',
setup() {
const obj = ref({
hobby: {
eat: '西瓜',
},
})
// 注意:默认是浅监听的,默认只会监听最外面一层(obj.value)
watch(obj, (newValue, oldValue) => {
console.log(newValue)
console.log(oldValue)
})
const handleClick = () => {
obj.value = { hobby: { eat: '热干面' } }
}
return { obj, handleClick }
},
}
</script>
方法三 监听 ref.value 因为obj.value也是一个reactive
// 如果 ref 包裹的是一个复杂数据类型, 其实内部还是借助 reactive 实现的,obj.value 是一个 reactive 类型的数据
console.log(isReactive(obj.value)) // true
// 其实监听的是一个 reactive,就符合下面特点
// 1. 开启深度监听(配置无效)
// 2. 监听的是 obj.value 内部数据的变化
watch(obj.value, (newValue, oldValue) => {
console.log(newValue)
})
const obj=ref(对象数据) watch(obj,回调) 此时只能监听最外面一层
const obj=ref(对象数据) watch(obj.value,回调) 对obj.value相当于reactive数据 因此具有监听reactive的特点
const obj=ref(对象数据) watch(obj.value,回调{deep:true}) 开启深度监听 此时既有ref数据特点也能监听内部数据
思维导图如下
watch监听普通值
/watch 的第一个参数可以是一个函数,函数的返回值是一个普通值,就表示监听这个普通值(字符串)
如果要监听普通值,可以通过函数返回这个普通值
utton @click="obj.hobby.eat = '面条'">修改 obj</button>
</template>
<script>
import { watch, reactive } from 'vue'
export default {
name: 'App',
setup() {
const obj = reactive({
hobby: {
eat: '西瓜',
a: 1,
b: 2,
c: 3,
},
})
// 浪费
/* watch(obj, () => {
console.log('1')
}) */
// 有点浪费
/* watch(obj.hobby, () => {
console.log('1')
}) */
// 浏览器爆出警告 function, a ref, a reactive object, or an array of these types.
// 监听的参数只能是 function、ref、reactive、array[function、ref、reactive]
// 语法错误
/* watch(obj.hobby.eat, () => {
console.log(1)
}) */
// watch 的第一个参数可以是一个函数,函数的返回值是一个普通值,就表示监听这个普通值(字符串)
// 如果要监听普通值,可以通过函数返回这个普通值
watch(
() => obj.hobby.eat,
() => {
console.log(1)
}
)
watch(obj, () => {
console.log(1)
})
watch(obj.hobby, () => {
console.log(1)
})
return { obj }
},
}
</script>
生命周期
-
Vue3(组合 API)常用的生命周期钩子有 7 个,可以多次使用同一个钩子,执行顺序和书写顺序相同。
-
setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted。
<template>
<p>{{ state.msg }}</p>
<button @click="state.msg = 'xxx'">update msg</button>
</template>
<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, reactive } from 'vue'
export default {
name: 'HelloWorld',
setup() {
const state = reactive({
msg: 'Hello World',
})
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
onUnmounted(() => {
console.log('onUnmounted')
})
return {
state,
}
},
}
</script>