经过了漫长的迭代,2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)使用了Typescript
进行了大规模的重构,带来了Composition API RFC
版本,类似React Hook
一样的写Vue
,可以自定义自己的hooks
TypeScript
支持不友好(所有属性都放在了this
对象上,难以推倒组件的数据类型)API
挂载在Vue
对象的原型上,难以实现TreeShaking
。dom
渲染开发支持不友好CompositionAPI
。受ReactHook
启发jsx
Template
不支持多个根标签打包大小减少41%
初次渲染快55%, 更新渲染快133%
内存减少54%
…
使用Proxy代替defineProperty实现响应式
重写虚拟DOM的实现和Tree-Shaking
…
Composition API(组合API)
setup()
ref()
reactive()、shallowReactive()
isRef()
toRefs()
readonly()、isReadonly()、shallowReadonly()
computed()
watch()
LifeCycle Hooks(新的生命周期)
Template refs
globalProperties
Suspense
Provide/Inject
…
新的内置组件
其他改变
官方文档
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
## 升级一 npm i -g @vue/cli to update!
## 升级二 npm uninstall vue-cli -g (先卸载后安装)
npm install -g @vue/cli
## 创建
vue create v3-ts-team
## 启动
cd vue_test
npm run serve
一些选择:
?Check the features needed for your project:
Choose Vue version(选择VUE版本)
Babel(JavaScript 编译器,可将代码转换为向后兼容)
TypeScript(编程语言,大型项目建议使用)
Progressive Web App (PWA) Support-APP使用
Router(路由)
Vuex(Vuex)
CSS Pre-processors(css预处理)
Linter / Formatter(代码风格/格式化)
Unit Testing(单元测试)
E2E Testing(e2e测试)
vue官方文档 vite官网
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
官方文档
setup
函数会在 beforeCreate
、created
之前执行, vue3
也是取消了这两个钩子,统一用setup
代替, 该函数相当于一个生命周期函数,vue
中过去的data
,methods
,watch
等全部都用对应的新增api
写在setup()
函数中setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
context.attrs
// 插槽 (非响应式对象,等同于 $slots)
context.slots
// 触发事件 (方法,等同于 $emit)
context.emit
// 暴露公共 property (函数)
context.expose
return {}
}
props
: 用来接收 props
数据, props
是响应式的,当传入新的 props
时,它将被更新。context
用来定义上下文, 上下文对象中包含了一些有用的属性,这些属性在 vue 2.x 中需要通过 this
才能访问到, 在 setup()
函数中无法访问到 this
,是个 undefined
context
是一个普通的 JavaScript
对象,也就是说,它不是响应式的,这意味着你可以安全地对 context
使用 ES6
解构。return {}
, 返回响应式数据, 模版中需要使用的函数注意: 因为
props
是响应式的, 你不能使用 ES6 解构,它会消除 prop 的响应性。不过你可以使用如下的方式去处理
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({
setup(props, context) {
const { title } = toRefs(props)
console.log(title.value)
return {}
}
});
</script>
如果 title
是可选的 prop,则传入的 props
中可能没有 title
。在这种情况下,toRefs
将不会为 title
创建一个 ref 。你需要使用 toRef
替代它:
<script lang="ts">
import { defineComponent, reactive, toRef, toRefs } from 'vue';
export default defineComponent({
setup(props, context) {
const { title } = toRef(props, 'title')
console.log(title.value)
return {}
}
});
</script>
const xxx = ref(initValue)
xxx.value
{{xxx}}
Object.defineProperty()
的get
与set
完成的。reactive
函数。<template>
<div class="mine">
{{count}} // 10
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref<number>(10)
// 在js 中获取ref 中定义的值, 需要通过value属性
console.log(count.value);
return {
count
}
}
});
</script>
ref
函数)const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)reactive()
函数接收一个普通对象,返回一个响应式的数据对象, 相当于 Vue 2.x
中的 Vue.observable()
API,响应式转换是“深层”的——它影响所有嵌套属性。基于proxy来实现,想要使用创建的响应式数据也很简单,创建出来之后,在setup
中return
出去,直接在template
中调用即可
{{state.name}} // test
注意: 该 API 返回一个响应式的对象状态。该响应式转换是“深度转换”——它会影响传递对象的所有嵌套 property。
在 reactive
对象中访问 ref
创建的响应式数据
<template>
<div class="mine">
{{count}} -{{t}} // 10 -100
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
export default defineComponent({
setup() {
const count = ref<number>(10)
const obj = reactive({
t: 100,
count
})
// 通过reactive 来获取ref 的值时,不需要使用.value属性, ref 将被自动解包
console.log(obj.count); // 10
console.log(obj.count === count.value); // true
// count 改变时,更新 `obj.count
count.value = 12
console.log(count.value) // 12
console.log(obj.count) // 12
// 反之,修改obj 的count 值 ,ref 也会更新
obj.count = 20
console.log(obj.count) // 20
console.log(count.value) // 20
return {
...toRefs(obj)
}
}
});
</script>
reactive
将解包所有深层的 refs
,同时维持 ref 的响应性。当将 ref
分配给 reactive
property 时,ref 将被自动解包
实现原理:
对象类型:通过Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
存在问题:
Proxy
Reflect
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
reactive
转为代理对象。Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。.value
,读取数据时模板中直接读取不需要.value
。.value
。setup执行的时机
setup的参数
this.$attrs
。this.$slots
。this.$emit
。与Vue2.x中computed配置功能一致
创建只读的计算属性
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
setup(props, context) {
const age = ref(18)
// 根据 age 的值,创建一个响应式的计算属性 readOnlyAge,它会根据依赖的 ref 自动计算并返回一个新的 ref
const readOnlyAge = computed(() => age.value++) // 19
return {
age,
readOnlyAge
}
}
});
</script>
通过set()、get()方法创建一个可读可写的计算属性
<script lang="ts">
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
setup(props, context) {
const age = ref<number>(18)
const computedAge = computed({
get: () => age.value + 1,
set: value => age.value + value
})
// 为计算属性赋值的操作,会触发 set 函数, 触发 set 函数后,age 的值会被更新
age.value = 100
return {
age,
computedAge
}
}
});
</script>
两个小“坑”:
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
```ts
```
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
interface Person {
name: string,
age: number
}
export default defineComponent({
setup(props, context) {
const age = ref<number>(10);
watch(age, () => console.log(age.value)); // 100
// 修改age 时会触发watch 的回调, 打印变更后的值
age.value = 100
return {
age
}
}
});
</script>
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
interface Person {
name: string,
age: number
}
export default defineComponent({
setup(props, context) {
const state = reactive<Person>({ name: 'vue', age: 10 })
watch(
[() => state.age, () => state.name],
([newName, newAge], [oldName, oldAge]) => {
console.log(newName);
console.log(newAge);
console.log(oldName);
console.log(oldAge);
}
)
// 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
state.age = 100
state.name = 'vue3'
return {
...toRefs(state)
}
}
});
</script>
在 setup()
函数内创建的 watch
监视,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监视,可以调用 watch()
函数的返回值即可, 如下:
<script lang="ts">
import { set } from 'lodash';
import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
interface Person {
name: string,
age: number
}
export default defineComponent({
setup(props, context) {
const state = reactive<Person>({ name: 'vue', age: 10 })
const stop = watch(
[() => state.age, () => state.name],
([newName, newAge], [oldName, oldAge]) => {
console.log(newName);
console.log(newAge);
console.log(oldName);
console.log(oldAge);
}
)
// 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
state.age = 100
state.name = 'vue3'
setTimeout(()=> {
stop()
// 此时修改时, 不会触发watch 回调
state.age = 1000
state.name = 'vue3-'
}, 1000) // 1秒之后讲取消watch的监听
return {
...toRefs(state)
}
}
});
</script>
watch的套路是:既要指明监视的属性,也要指明监视的回调。
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
watchEffect有点像computed:
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
```html
vue2.x的生命周期
vue3.0的生命周期
```
beforeDestroy
改名为 beforeUnmount
destroyed
改名为 unmounted
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
类似于vue2.x中的mixin。
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
Vue2.x useCount.ts
实现:
import { ref, Ref, computed } from "vue";
type CountResultProps = {
count: Ref<number>;
multiple: Ref<number>;
increase: (delta?: number) => void;
decrease: (delta?: number) => void;
};
export default function useCount(initValue = 1): CountResultProps {
const count = ref(initValue);
const increase = (delta?: number): void => {
if (typeof delta !== "undefined") {
count.value += delta;
} else {
count.value += 1;
}
};
const multiple = computed(() => count.value * 2);
const decrease = (delta?: number): void => {
if (typeof delta !== "undefined") {
count.value -= delta;
} else {
count.value -= 1;
}
};
return {
count,
multiple,
increase,
decrease,
};
}
使用useCount
这个 hook
:
count: {{ count }}
倍数: {{ multiple }}
作用:可以将 reactive()
创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref()
类型的响应式数据
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
{{name}} // test
{{age}} // 18
shallowReactive:创建一个响应式代理,它跟踪其自身属性的响应性shallowReactive
生成非递归响应数据,只处理对象最外层属性的响应式,但不执行嵌套对象的深层响应式转换 (暴露原始值)(浅响应式)。
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
什么时候使用?
<script lang="ts">
import { shallowReactive } from "vue";
export default defineComponent({
setup() {
const test = shallowReactive({ num: 1, creator: { name: "撒点了儿" } });
console.log(test);
test.creator.name = "掘金";
return {
test
};
},
});
</script>
readonly: 传入ref
或 reactive
对象,并返回一个原始对象的只读代理,对象内部任何嵌套的属性也都是只读的、 并且是递归只读,即让一个响应式数据变为只读的(深只读)。
shallowReadonly: 作用只处理对象最外层属性的响应式(浅响应式)的只读,但不执行嵌套对象的深度只读转换 (暴露原始值),即让一个响应式数据变为只读的(浅只读)。
应用场景: 不希望数据被修改时。
<script lang="ts">
import { readonly, reactive } from "vue";
export default defineComponent({
setup() {
const test = reactive({ num: 1 });
const testOnly = readonly(test);
console.log(test);
console.log(testOnly);
test.num = 110;
// 此时运行会提示 Set operation on key "num" failed: target is readonly.
// 而num 依然是原来的值,将无法修改成功
testOnly.num = 120;
// 使用isReadonly() 检查对象是否是只读对象
console.log(isReadonly(testOnly)); // true
console.log(isReadonly(test)); // false
// 需要注意的是: testOnly 值会随着 test 值变化
return {
test,
testOnly,
};
},
});
</script>
readonly
和const
有什么区别const
是赋值保护,使用const
定义的变量,该变量不能重新赋值。但如果const
赋值的是对象,那么对象里面的东西是可以改的。原因是const
定义的变量不能改说的是,对象对应的那个地址不能改变readonly
是属性保护,不能给属性重新赋值reactive
生成的响应式对象转为普通对象。作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
实现防抖效果:
{{keyword}}
作用:实现祖与后代组件间通信
套路:父组件有一个 provide
选项来提供数据,后代组件有一个 inject
选项来开始使用这些数据
具体写法:
setup(){
......
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
// 父组件
// 子组件
{{ msg }}
{{ provideData }}
name
:参数名称
value
:属性的值
<script lang="ts">
import { provide, reactive, ref } from "vue";
import HelloWorldVue from "./components/HelloWorld.vue";
export default defineComponent({
name: "App",
components: {
HelloWorld: HelloWorldVue,
},
setup() {
const age = ref(18);
provide("provideData", {
age,
data: reactive({ name: "撒点了儿" }),
});
},
});
</script>
<script lang="ts">
import { inject } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
msg: String,
},
setup() {
const provideData = inject("provideData");
console.log(provideData);
return {
provideData,
};
},
});
</script>
如果要确保通过 provide 传递的数据不会被 inject 的组件更改,我们建议对提供者的 property 使用 readonly。
reactive
创建的响应式代理readonly
创建的只读代理reactive
或者 readonly
方法创建的代理使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
什么是Teleport?—— Teleport
是一种能够将我们的组件html结构移动到指定位置的技术。
我是一个弹窗
React
的 Suspense
组件
React.lazy
接受一个函数,这个函数需要动态调用 import()
。它必须返回一个 Promise
,该 Promise
需要 resolve
一个 default export
的 React
组件。
import React, { Suspense } from 'react';
const myComponent = React.lazy(() => import('./Component'));
function MyComponent() {
return (
Loading... }>
Vue3
也新增了 React.lazy
类似功能的 defineAsyncComponent
函数,处理动态引入(的组件)。defineAsyncComponent
可以接受返回承诺的工厂函数。当你从服务器检索到组件定义时,应该调用Promise
的解析回调。你还可以调用reject(reason)
来指示负载已经失败
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
app.component('async-component', AsyncComp)
Vue3 也新增了 Suspense 组件:
Loading ...
Vue 2.x 有许多全局 API 和配置。
例如:注册全局组件、注册全局指令等。
//注册全局组件
Vue.component('MyButton', {
data: () => ({
count: 0
}),
template: ''
})
//注册全局指令
Vue.directive('focus', {
inserted: el => el.focus()
}
Vue3.0中对这些API做出了调整:
将全局的API,即:Vue.xxx
调整到应用实例(app
)上
2.x 全局 API(Vue ) |
3.x 实例 API (app ) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
data选项应始终被声明为一个函数。
过度类名的更改:
Vue2.x写法
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
Vue3.x写法
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes
移除v-on.native
修饰符
父组件中绑定事件
子组件中声明自定义事件
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
…
{{name}}
{{count}}
- {{item.name}}