经过了漫长的迭代,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启发jsxTemplate 不支持多个根标签打包大小减少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
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eH3tDROQ-1663692950473)(C:\Users\huawei\AppData\Roaming\Typora\typora-user-images\image-20220810222758702.png)]](https://1000bd.com/contentImg/2024/09/13/99390f4fe34d42f5.png)
一些选择:
?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测试)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMIgkX1S-1663692950475)(C:\Users\huawei\AppData\Roaming\Typora\typora-user-images\image-20220810223604589.png)]](https://1000bd.com/contentImg/2024/09/13/13bc5449f8194f76.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vbEtqPdw-1663692950475)(C:\Users\huawei\AppData\Roaming\Typora\typora-user-images\image-20220810223958019.png)]](https://1000bd.com/contentImg/2024/09/13/41ca848ae38e30c9.png)
## 创建工程
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,是个 undefinedcontext 是一个普通的 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 () {}
})
存在问题:
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改名为 beforeUnmountdestroyed改名为 unmountedbeforeCreate===>setup()created=======>setup()beforeMount ===>onBeforeMountmounted=======>onMountedbeforeUpdate===>onBeforeUpdateupdated =======>onUpdatedbeforeUnmount ==>onBeforeUnmountunmounted =====>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}}