本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Vue3.0新特性
- Composition Api (最核心)
- v-model更改
- v-for的key节点上的使用情况更改
- v-if和v-for对同一元素的优先级更高
- ref内部v-for不再注册引用数组
- 功能组件只能使用普通函数创建
- 异步组件需要使用
defineAsyncComponent创建方法 - 所有插槽都通过
$slots - 在
destroyed生命周期的选项已更名为unmounted - 在
beforeDestroy生命周期的选项已更名为beforeUnmount - ...
Vue3.0优缺点
优点:
- 将Vue内部的绝大部分api对外暴露,使Vue具备开发大型项目的能力,例如compile编译api等
- webpack的treeshaking(tree shaking 是 DCE 的一种方式,它可以在打包时忽略没有用到的代码。)支持度友好
- 使用Proxy进行响应式变量定义,性能提高1.2~2倍
- ssr快了2~3倍
- 可在Vue2.0中单独使用composition-api插件,或者直接用它开发插件
- 对typescript支持更加友好
- 面向未来:对于尤雨溪最近创新的vite开发服务器(舍弃webpack、底层为Koa框架的高性能开发服务器),直接使用的Vue3.0语法
缺点:
- vue3将不再支持IE11,Vue 在 2.X 版本仍然支持 IE11,如果你想使用类似 Vue 3 的新特性,可以等等 Vue 2.7 版本。这次的 RFC 宣布,将会对 2.7 版本做向后兼容,移植 3.x 的部分新功能,以保证两个版本之间相似的开发体验。
- 对于习惯了Vue2.0开发模式的开发者来说,增加了心智负担,对开发者代码组织能力有体验
同时也是能力提升的机会吧,特别喜欢Vue作者的而设计初心:让开发者随着框架一起成长
体验Vue3.0的四种姿势
现在来说,体验Vue3.0有四种姿势 传送门
- 通过CDN:
- 通过 Codepen 的浏览器 playground
- 脚手架 Vite:
npm init vite-app hello-vue3 # OR yarn create vite-app hello-vue3
尤大开发的新工具vite,下一代前端开发与构建工具,原来是利用浏览器现在已经支持ES6的import;遇到import会发送一个http请求去加载对应的文件,vite拦截这些请求,做预编译,就省去了webpack冗长的打包事件,提升开发体验。
- 脚手架 vue-cli
npm install -g @vue/cli # OR yarn global add @vue/cli
vue create hello-vue3
# select vue 3 preset
全局API
新的全局api:createApp
调用createApp返回一个应用实例,这是Vue3.0的新概念:
打开src/main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
应用程序实例暴露当前全局 API 的子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上app上,以下是当前全局 API 及其相应实例 API 的表:
| 2.x 全局 API | 3.x 实例 API (app) |
|---|---|
| Vue.config | app.config |
| Vue.config.productionTip | removed 已移除 |
| Vue.config.ignoredElements | app.config.isCustomElement |
| Vue.component | app.component |
| Vue.directive | app.directive |
| Vue.mixin | app.mixin |
| Vue.use | app.use |
composition API学习
setup
setup函数是一个新的组件选项。作为组件内使用Composition API的入口点
创建组件实例,然后初始化props,紧接着调用setup函数。它会在beforeCreate钩子之前调用。
setup返回一个对象。则对象的所有属性(它是响应式的数据)都可以直接在模板中使用。相当于vue2.0中data函数返回的对象。
App.vue
<script>
export default {
setup () {
return {}
}
}
</script>
响应式数据
- ref:可传入任意类型的值并返回一个响应式且可改变的ref对象。ref对象拥有一个指向内部值的单一属性.value,改变值的时候必须使用其value属性
- reactive:接受一个普通对象然后返回该普通对象的响应式代理。等同于2.x的Vue.obserable()
简写之:reactive负责复杂数据结构,ref可以把基本的数据结构包装成响应式
reactive
<template>
<div>
<h2>{{state.count}}</h2>
<button @click="add">计算</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup(){
// 响应式变量声明 reactive负责复杂数据结构,
const state = reactive({
count: 1
});
function add() {
state.count++;
}
return { state, add};
}
};
</script>
ref
<template>
<div>
<h2>{{state.count}}</h2>
<h3>{{num}}</h3>
<button @click="add">计算</button>
</div>
</template>
<script>
import { reactive, ref } from "vue";
export default {
setup(){
const state = reactive({
count: 1
});
const num = ref(0);
function add() {
state.count++;
num.value+=2
}
return { state, add, num };
}
};
</script>
ref包装的num,模板里可以直接用,但js中修改的时候操作.value属性。
toRefs
将响应式对象转换为普通对象,其中结果对象的每个property都是指向原始对象相应的property
从合成函数返回响应式对象时,toRefs非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解/扩散:
useFeatureX.js
import {reactive} from 'vue';
export function userFeatureX(){
const state = reactive({
foo: 1,
bar: 2
})
// 逻辑运行状态
// 返回时转换为ref
return state;
}
App.vue
import {toRefs} from 'vue'
export default {
setup(){
const state = useFeatureX();
return {
...toRefs(state)
}
}
}
computed
传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
import { reactive, ref, computed } from "vue";
export default {
setup() {
// 1.响应式变量声明 reactive负责复杂数据结构,
const state = reactive({
count: 1
});
// 2.ref可以把基本的数据结构包装成响应式
const num = ref(0);
// 3.创建只读的计算属性
const computedEven1 = computed(() => state.count % 2);
// 4.创建可读可写的计算属性
const computedEven2 = computed({
get:()=>{
return state.count % 2;
},
set: newVal=>{
state.count = newVal;
}
})
// 事件的声明
function add() {
state.count++;
num.value += 2;
}
function handleClick() {
computedEven2.value = 10;
}
return { state, add, num, computedEven1,computedEven2,handleClick };
}
};
watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const num = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
- 停止监听
隐式停止
当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止
显示停止
在一些情况下,也可以显示调用返回值来停止侦听
const stop = watchEffect(()=>{
/*...*/
})
//停止侦听
stop()
- 清除副作用
有时候副作用函数会执行一些异步的副作用,这些响应需要在其失效时来清除(即完成之前状态已改变了)。可以在侦听副作用传入的函数中接受一个onInvalidate函数作为参数,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止(如果在setup()或生命周期钩子函数中使用了watchEffect,则在卸载组件时)
官网的例子:
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id 改变时 或 停止侦听时
// 取消之前的异步操作
token.cancel()
})
})
案例:实现对用户输入“防抖”效果
<template>
<div>
<input type="text"
v-model="keyword">
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
setup() {
const keyword = ref('')
const asyncPrint = val => {
return setTimeout(() => {
console.log('user input: ', val)
}, 1000)
}
watchEffect(
onInvalidate => {
//用户输入的时间间隔小于1秒,都会立刻清除掉定时,不输入结果。正因为这个,实现了用户防抖的功能,只在用户输入时间间隔大于1秒,才做打印
const timer = asyncPrint(keyword.value)
onInvalidate(() => clearTimeout(timer))
console.log('keyword change: ', keyword.value)
},
// flush: 'pre' watch() 和 watchEffect() 在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新。
//flush: 'post' 选项来定义,这将在 DOM 更新后运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。
{
flush: 'post' // 默认'pre',同步'sync','pre'组件更新之前
}
)
return {
keyword
}
}
}
// 实现对用户输入“防抖”效果
</script>
watch
watch API 完全等效于 2.x this.$watch (以及 watch 中相应的选项)。watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
watch()接收的第一个参数被称作"数据源",它可以是:
- 一个返回任意值的getter函数
- 一个包装对象(可以是ref也可以是reactive包装的对象)
- 一个包含上述两种数据源的数组
第二个参数是回调函数。回调函数只有当数据源发生变动时才会被触发:
- 侦听单个数据源
const state = reactive({count: 1});
//侦听一个reactive定义的数据,修改count值时会触发 watch的回调
watch(()=>state.count,(newCount,oldCount)=>{
console.log('newCount:',newCount);
console.log('oldCount:',oldCount);
})
//侦听一个ref
const num = ref(0);
watch(num,(newNum,oldNum)=>{
console.log('newNum:',newNum);
console.log('oldNum:',oldNum);
})
- 侦听多个数据源(数组)
const state = reactive({count: 1});
const num = ref(0);
// 监听一个数组
watch([()=>state.count,num],([newCount,newNum],[oldCount,oldNum])=>{
console.log('new:',newCount,newNum);
console.log('old:',oldCount,oldNum);
})
- 侦听复杂的嵌套对象 我们实际开发中,复杂数据随处可见, 比如:
const state = reactive({
person: {
name: '张三',
fav: ['帅哥','美女','音乐']
},
});
watch(
() => state.person,
(newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
},
{ deep: true }, // 立即监听
);
如果不使用第三个参数
deep:true, 是无法监听到数据变化的。 前面我们提到,默认情况下,watch是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?其实使用也很简单, 给第三个参数中设置immediate: true即可 同时,watch和watchEffect在停止侦听,清除副作用(相应地onInvalidate会作为回调的第三个参数传入)等方面行为一致。
<template>
<div>
<input type="text"
v-model="keyword">
</div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
setup() {
const keyword = ref('')
const asyncPrint = val => {
return setTimeout(() => {
console.log('user input: ', val)
})
}
watch(
keyword,
(newVal, oldVal, onCleanUp) => {
const timer = asyncPrint(keyword)
onCleanUp(() => clearTimeout(timer))
},
{
lazy: true // 默认false,即初始监听回调函数执行了
}
)
return {
keyword
}
}
}
</script>