正文
Vue2直接把整个Vue都导入,例如Vue2的 main.js 文件中的代码:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App)
}).$mount('#app')
而vue3是按需导入api
import { createApp } from 'vue';
import App from './App.vue'
createApp(App).mount('#app')
1.setup
setup 函数是 Composition API 的入口函数,我们的变量、方法都是在该函数里定义的,来看一下使用方法:
<template>
<div id="app">
<p>{{ number }}</p>
<button @click="add">增加</button>
</div>
</template>
<script>
// 1. 从 vue 中引入 ref 函数
import {ref} from 'vue'
export default {
name: 'App',
setup() {
// 2. 用 ref 函数包装一个响应式变量 number
let number = ref(0)
// 3. 设定一个方法
function add() {
// number是被ref函数包装过了的,其值保存在.value中
number.value ++
}
// 4. 将 number 和 add 返回出去,供template中使用
return {number, add}
}
}
</script>
上述代码中用到了 ref 函数,下面会详细讲解,在这里你只需要理解它的作用是包装一个响应式的数据即可,并且你可以将 ref 函数包装过的变量看作是Vue2 data 中的变量,这样就简单实现了一个点击按钮数字加1的功能.
2.生命周期
<template>
<div id="app"></div>
</template>
<script>
// 1. 从 vue 中引入 多个生命周期函数
import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted} from 'vue'
export default {
name: 'App',
setup() {
onBeforeMount(() => {
// 在挂载前执行某些代码
})
onMounted(() => {
// 在挂载后执行某些代码
})
onBeforeUpdate(() => {
// 在更新前前执行某些代码
})
onUpdated(() => {
// 在更新后执行某些代码
})
onBeforeUnmount(() => {
// 在组件销毁前执行某些代码
})
unMounted(() => {
// 在组件销毁后执行某些代码
})
return {}
}
}
</script>
3.reactive
reactive 方法是用来创建一个响应式的数据对象。
<template>
<div id="app">
<!-- 4. 访问响应式数据对象中的 count -->
{{ state.count }}
</div>
</template>
<script>
// 1. 从 vue 中导入 reactive
import {reactive} from 'vue'
export default {
name: 'App',
setup() {
// 2. 创建响应式的数据对象
const state = reactive({count: 3})
// 3. 将响应式数据对象state return 出去,供template使用
return {state}
}
}
</script>
4.ref
ref 就是通过 reactive 包装了一个对象 ,然后是将值传给该对象中的 value 属性,这也就解释了为什么每次访问时我们都需要加上 .value
<script>
import {ref, reactive} from 'vue'
export default {
name: 'App',
setup() {
const obj = {count: 3}
const state1 = ref(obj)
const state2 = reactive(obj)
console.log(state1)
console.log(state2)
}
}
</script>
打印结果:
注意: 这里指的 .value 是在 setup 函数中访问 ref 包装后的对象时才需要加的,在 template 模板中访问时是不需要的,因为在编译时,会自动识别其是否为 ref 包装过的
ref 和 reactive的选择:
①.基本类型值(String 、Nmuber 、Boolean 等)或单值对象(类似像 {count: 3} 这样只有一个属性值的对象)使用 ref
②引用类型值(Object 、Array)使用 reactive
5.toRef
toRef 是将某个对象中的某个值转化为响应式数据,其接收两个参数,第一个参数为 obj 对象;第二个参数为对象中的属性名
<script>
// 1. 导入 toRef
import {toRef} from 'vue'
export default {
setup() {
const obj = {count: 3}
// 2. 将 obj 对象中属性count的值转化为响应式数据
const state = toRef(obj, 'count')
// 3. 将toRef包装过的数据对象返回供template使用
return {state}
}
}
</script>
但其实表面上看上去 toRef 这个API好像非常的没用,因为这个功能也可以用 ref 实现,代码如下:
<script>
// 1. 导入 toRef
import {toRef} from 'vue'
export default {
setup() {
const obj = {count: 3}
// 2. 将 obj 对象中属性count的值转化为响应式数据
const state = toRef(obj, 'count')
// 3. 将toRef包装过的数据对象返回供template使用
return {state}
}
}
</script>
直接总结说明:
ref不改变原始值,会更新视图;
toRef 改变原始值,不会更新视图
6.toRefs
其作用就是将传入的对象里所有的属性的值都转化为响应式数据对象,该函数支持一个参数,即 obj 对象
<script>
import {toRefs} from 'vue'
export default {
setup() {
const obj = {
name: '前端印象',
age: 22,
gender: 0
}
const state = toRefs(obj)
console.log(state)
}
}
</script>
7.shallowReactive
这是一个用于性能优化的API。
其实将 obj 作为参数传递给 reactive 生成响应式数据对象时,若 obj 的层级不止一层,那么会将每一层都用 Proxy 包装一次,我们来验证一下:
<script>
import {reactive} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = reactive(obj)
console.log(state)
console.log(state.first)
console.log(state.first.second)
}
}
</script>
设想一下如果一个对象层级比较深,那么每一层都用 Proxy 包装后,对于性能是非常不友好的。
接下来我们再来看看 shallowReactive:
<script>
import {shallowReactive} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowReactive(obj)
console.log(state)
console.log(state.first)
console.log(state.first.second)
}
}
</script>
结果非常的明了了,只有第一层被 Proxy 处理了,也就是说只有修改第一层的值时,才会响应式更新,代码如下:
<template>
<p>{{ state.a }}</p>
<p>{{ state.first.b }}</p>
<p>{{ state.first.second.c }}</p>
<button @click="change1">改变1</button>
<button @click="change2">改变2</button>
</template>
<script>
import {shallowReactive} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowReactive(obj)
function change1() {
state.a = 7
}
function change2() {
state.first.b = 8
state.first.second.c = 9
console.log(state);
}
return {state}
}
}
// 这里说一下执行结果:
// 首先我们点击了第二个按钮,改变了第二层的 b 和第三层的 c,虽然值发生了改变,但是视图却没有进行更新;
// 当我们点击了第一个按钮,改变了第一层的 a 时,整个视图进行了更新;
// 由此可说明,shallowReactive 监听了第一层属性的值,一旦发生改变,则更新视图。
</script>
8.shallowRef
这是一个浅层的 ref,与 shallowReactive 一样是拿来做性能优化的。
shallowReactive 是监听对象第一层的数据变化用于驱动视图更新,那么 shallowRef 则是监听 .value 的值的变化来更新视图的。
<template>
<p>{{ state.a }}</p>
<p>{{ state.first.b }}</p>
<p>{{ state.first.second.c }}</p>
<button @click="change1">改变1</button>
<button @click="change2">改变2</button>
</template>
<script>
import {shallowRef} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowRef(obj)
console.log(state);
function change1() {
// 直接将state.value重新赋值
state.value = {
a: 7,
first: {
b: 8,
second: {
c: 9
}
}
}
}
function change2() {
state.value.first.b = 8
state.value.first.second.c = 9
console.log(state);
}
return {state, change1, change2}
}
}
</script>
state的打印结果:
我们先点击了第二个按钮,发现数据确实被改变了,但是视图并没随之更新;
于是点击了第一个按钮,即将整个 .value 重新赋值了,视图就立马更新了
这么一看,未免也太过麻烦了,改个数据还要重新赋值,不要担心,此时我们可以用到另一个API,叫做 triggerRef ,调用它就可以立马更新视图,其接收一个参数 state ,即需要更新的 ref 对象
我们来使用一下:
<template>
<p>{{ state.a }}</p>
<p>{{ state.first.b }}</p>
<p>{{ state.first.second.c }}</p>
<button @click="change">改变</button>
</template>
<script>
import {shallowRef, triggerRef} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowRef(obj)
console.log(state);
function change() {
state.value.first.b = 8
state.value.first.second.c = 9
// 修改值后立即驱动视图更新
triggerRef(state)
console.log(state);
}
return {state, change}
}
}
</script>
可以看到,我们没有给 .value 重新赋值,只是在修改值后,调用了 triggerRef 就实现了视图的更新。
9.toRaw
toRaw 方法是用于获取 ref 或 reactive 对象的原始数据的。
<template>
<p>{{ state.name }}</p>
<p>{{ state.age }}</p>
<button @click="change">改变</button>
</template>
<script>
import {reactive} from 'vue'
export default {
setup() {
const obj = {
name: '前端印象',
age: 22
}
const state = reactive(obj)
function change() {
state.age = 90
console.log(obj); // 打印原始数据obj
console.log(state); // 打印 reactive对象
}
return {state, change}
}
}
</script>
我们改变了 reactive 对象中的数据,于是看到原始数据 obj 和被 reactive 包装过的对象的值都发生了变化,由此我们可以看出,这两者是一个引用关系
那么此时我们就想了,那如果直接改变原始数据 obj 的值,会怎么样呢?答案是: reactive 的值也会跟着改变,但是视图不会更新
由此可见,当我们想修改数据,但不想让视图更新时,可以选择直接修改原始数据上的值,因此需要先获取到原始数据,我们可以使用 Vue3 提供的 toRaw 方法
toRaw 接收一个参数,即 ref 对象或 reactive 对象
<script>
import {reactive, toRaw} from 'vue'
export default {
setup() {
const obj = {
name: '前端印象',
age: 22
}
const state = reactive(obj)
const raw = toRaw(state)
console.log(obj === raw) // true
}
}
</script>
上述代码就证明了 toRaw 方法从 reactive 对象中获取到的是原始数据,因此我们就可以很方便的通过修改原始数据的值而不更新视图来做一些性能优化了。
注意: 补充一句,当 toRaw 方法接收的参数是 ref 对象时,需要加上 .value 才能获取到原始数据对象
10.markRaw
markRaw 方法可以将原始数据标记为非响应式的,即使用 ref 或 reactive 将其包装,仍无法实现数据响应式,其接收一个参数,即原始数据,并返回被标记后的数据。
<template>
<p>{{ state.name }}</p>
<p>{{ state.age }}</p>
<button @click="change">改变</button>
</template>
<script>
import {reactive, markRaw} from 'vue'
export default {
setup() {
const obj = {
name: '前端印象',
age: 22
}
// 通过markRaw标记原始数据obj, 使其数据更新不再被追踪
const raw = markRaw(obj)
// 试图用reactive包装raw, 使其变成响应式数据
const state = reactive(raw)
function change() {
state.age = 90
console.log(state);
}
return {state, change}
}
}
</script>
我们来看一下在被 markRaw 方法处理过后的数据是否还能被 reactive 包装成响应式数据:
从图中可以看到,即使我们修改了值也不会更新视图了,即没有实现数据响应式。
11.provide && inject
假设这有三个组件,分别是 A.vue 、B.vue 、C.vue,其中 B.vue 是 A.vue 的子组件,C.vue 是 B.vue 的子组件。
// A.vue
<script>
import {provide} from 'vue'
export default {
setup() {
const obj= {
name: '前端印象',
age: 22
}
// 向子组件以及子孙组件传递名为info的数据
provide('info', obj)
}
}
</script>
// B.vue
<script>
import {inject} from 'vue'
export default {
setup() {
// 接收A.vue传递过来的数据
inject('info') // {name: '前端印象', age: 22}
}
}
</script>
// C.vue
<script>
import {inject} from 'vue'
export default {
setup() {
// 接收A.vue传递过来的数据
inject('info') // {name: '前端印象', age: 22}
}
}
</script>
12.watch && watchEffect
①监听 ref 类型:
<script>
import {ref, watch} from 'vue'
export default {
setup() {
const state = ref(0)
watch(state, (newValue, oldValue) => {
console.log(`原值为${oldValue}`)
console.log(`新值为${newValue}`)
/* 1秒后打印结果:
原值为0
新值为1
*/
})
// 1秒后将state值+1
setTimeout(() => {
state.value ++
}, 1000)
}
}
</script>
②监听 reactive 类型:
<script>
import {reactive, watch} from 'vue'
export default {
setup() {
const state = reactive({count: 0})
watch(() => state.count, (newValue, oldValue) => {
console.log(`原值为${oldValue}`)
console.log(`新值为${newValue}`)
/* 1秒后打印结果:
原值为0
新值为1
*/
})
// 1秒后将state.count的值+1
setTimeout(() => {
state.count ++
}, 1000)
}
}
</script>
③监听多个值:
<script>
import {reactive, watch} from 'vue'
export default {
setup() {
const state = reactive({ count: 0, name: 'zs' })
watch(
[() => state.count, () => state.name],
([newCount, newName], [oldvCount, oldvName]) => {
console.log(oldvCount) // 旧的 count 值
console.log(newCount) // 新的 count 值
console.log(oldName) // 旧的 name 值
console.log(newvName) // 新的 name 值
}
)
setTimeout(() => {
state.count ++
state.name = 'ls'
}, 1000)
}
}
</script>
④立即执行和深度监听
let sum = ref(0);
let msg = ref("hello");
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
watch([sum,msg], (newVal, oldVal) => {
console.log("sum或msg的值变了", "newVal===", newVal, "oldVal===", oldVal);
},{immediate: true, ,deep:true});
补充: watch方法会返回一个stop方法,若想要停止监听,便可直接执行该stop函数
13.getCurrentInstance
这里我直接将项目中用到的getCurrentInstance拿出来展示。比如我想全局封装一个信息确定提示框。
第一步,封装好提示框组件:
/**
* 信息确定提示框
*/
import { ElMessageBox } from 'element-plus'
export default function myConfirm(text: string) {
return new Promise((resolve, reject) => {
ElMessageBox.confirm(text,'系统提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
resolve(true)
})
.catch(() => {
reject(false)
})
}).catch(() => {
return false
})
}
第二步,main.ts中引入工具类
第三步,hooks里封装实例
import { getCurrentInstance,ComponentInternalInstance } from "vue";
// 通过 getCurrentInstance 方法获取当前组件实例
export default function userInstance() {
const { proxy, appContext } = getCurrentInstance() as ComponentInternalInstance
const global = appContext.config.globalProperties
return {
proxy,
global
}
}
第四步,引用实例
import userInstance from '@/hooks/userInstance'
export default function userDept(getDeptList,searchParams:ListParam) {
const { global, proxy } = userInstance()
// 删除
const deleteBtn = async (id: number) => {
// console.log(global)
// console.log(proxy)
let params = { id: id }
const confirm = await global.$myConfirm('确定删除该数据吗?')
console.log(confirm)
if(confirm) {
// 执行删除的操作
let res = await deleteDeptApi(params)
// console.log(res)
if(res && res.code === StatusCode.Success) {
global.$message({
message: res.msg,
type: 'success'
})
getDeptList()
}
}
}
// 保存(更新,新增)
const saveBtn = async (params: AddDeptModel) => {
console.log('父组件取到值了,保存===')
console.log(params)
// type: 0新增 1编辑
let res: Result
if(params.type === EditType.ADD) {
res = await addDeptApi(params)
} else {
res = await editDeptApi(params)
}
if(res && res.code === StatusCode.Success) {
global.$message({
message: res.msg,
type: 'success'
})
// 刷新表格
getDeptList()
}
}
return {
deleteBtn,
saveBtn
}
}
再比如,我想做表单验证,怎么弄?
方法1: 不推荐
setup() {
const {ctx} = getCurrentInstance();
console.log(ctx,"属性1")
//表单查询方法
const submitForm = (formName) =>{
ctx.$refs[formName].validate(valid => {
if (valid) {
ruleForm.pageNum = 1;
getTableData();
} else {
console.log("error submit!!");
return false;
}
});
}
}
方法2:推荐此用法,才能在你项目正式上线版本正常运行,避免线上报错问题
解决:用proxy代替ctx。在结构的时候直接将proxy解构出来
setup() {
let {proxy} = getCurrentInstance();
console.log(proxy,"属性2");
//表单查询方法
const submitForm = (formName) =>{
proxy.$refs[formName].validate(valid => {
if (valid) {
ruleForm.pageNum = 1;
getTableData();
} else {
console.log("error submit!!");
return false;
}
});
}
}
14.useStore
// store 文件夹下的 index.js
import Vuex from 'vuex'
const store = Vuex.createStore({
state: {
name: '前端印象',
age: 22
},
mutations: {
……
},
……
})
// example.vue
<script>
// 从 vuex 中导入 useStore 方法
import {useStore} from 'vuex'
export default {
setup() {
// 获取 vuex 实例
const store = useStore()
console.log(store)
}
}
</script>
15.获取标签元素
<template>
<div>
<div ref="el">div元素</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
// 创建一个DOM引用,名称必须与元素的ref属性名相同
const el = ref(null)
// 在挂载后才能通过 el 获取到目标元素
onMounted(() => {
el.value.innerHTML = '内容被修改'
})
// 把创建的引用 return 出去
return {el}
}
}
</script>