做项目必读的vue3基础知识

1.响应式

1.1 两者实现原理

  • vue2 利用es5的 Object.defineProperty() 对数据进行劫持结合发布订阅模式来实现
  • vue3 利用es6的 proxy 对数据代理,通过 reactive() 函数给每一个对象都包一层 proxy,通过 proxy 监听属性的变化,从而实现对数据的监控

1.2 vue2响应式缺陷

缺陷

对象新增、删除属性没有响应式数组新增、删除元素没有响应式;通过下标修改某个元素没有响应式;通过.length改变数组长度没有响应式。只有实例创建时 data 中有的数据实例创建后才是响应式的,给已创建好的 vue 实例 data 对象中添加属性时,数据虽然会更新,但视图不会更新,不具有响应式

解决

  • 使用 this.$forceUpdate() 强制更新视图和数据(不推荐)
  • 使用具有响应式的函数来操作对象:
[Vue | this].$set(object,key,value),实例中添加响应式属性;
[Vue | this].$delete(object,key),实例中删除属性;
Object.assign(),将多个对象属性合并到目标对象中,具有响应式;
Object.freeze(),将对象冻结,防止任何改变。使得对象成为只读,无法添加、删除或更新;
Object.keys(),返回对象的所有属性;
Object.values(),返回对象的所有属性值;
Object.entries(),返回对象的所有键值对;
  • 使用具有响应式的函数来操作数组:
pop(),尾部删除元素;
push(),尾部添加元素;
unshift(),首部添加元素;
shift(),首部删除元素;
sort(),排序;
reverse(),翻转;
splice(index[必填,位置],howmany[必填,要删除的数量],item1...itemx[可选,向数组中添加的新元素])

【补充】清空对象,置空数组操作

  • 清空对象
this.form = {
    
    }
this.$refs.form.resetFields()
this.form.name = ""
  • 置空数组
this.arrayList = [] 
this.arrayList.splice(0,this.arrayList.length)
// this.arrayList.length = 0  不具有响应式,无法实现

1.3 vue3响应式优势

  • proxy性能整体上优于Object.defineProperty
  • vue3支持更多数据类型的劫持(vue2只支持Object、Array;vue3支持Object、Array、Map、WeakMap、Set、WeakSet)
  • vue3支持更多时机来进行依赖收集和触发通知(vue2只在get时进行依赖收集,vue3在get/has/iterate时进行依赖收集;vue2只在set时触发通知,vue3在set/add/delete/clear时触发通知),所以vue2中的响应式缺陷vue3可以实现
  • vue3做到了“精准数据”的数据劫持(vue2会把整个data进行递归数据劫持,而vue3只有在用到某个对象时,才进行数据劫持,所以响应式更快并且占内存更小)
  • vue3的依赖收集器更容易维护(vue3监听和操作的是原生数组;vue2是通过重写的方法实现对数组的监控)

2.生命周期

vue2 vue3 说明
beforeCreate setup() 组件创建之前,执行初始化任务
created setup() 组件创建完成,访问数据、获取接口数据
beforeMount onBeforeMount 组件挂载之前
mounted onMounted 组件挂载完成,DOM已创建,访问数据或DOM元素,访问子组件
beforeUpdate onBeforeUpdate 未更新,获取更新前所有状态
updated onUpdated 已更新,获取更新后所有状态
beforeDestroy onBeforeUnmount 组件销毁之前,清空定时器,取消订阅消息
destroyed onUnmounted 组件销毁之后
activated onActivated keep-alive包含,组件被激活时
deactivated onDeactivated keep-alive包含,发生组件切换,组件消失时

2.1 初始化

  • vue2 一般在 created、mounted 中初始化
  • vue3 可以直接在 setup 中,或者放在 onBeforeMount、onMounted 中初始化
<script setup>
const getList = () => {
    
    }

getList()

onMounted(() => {
    
    
  getList()
}),

onBeforeMount(() => {
    
    
  getList()
}),
</script>

2.2 解除绑定

  • vue2 中操作:
<script>
export default {
    
    
  mounted() {
    
    
    // 开启定时器
    let timer = setInterval(() => {
    
    
      console.log('---定时器在触发---')
    }, 1000)
   
   //这下面的代码不可以省略
    this.$on('hook:activated', () => {
    
    
      if (timer === null) {
    
     // 避免重复开启定时器
        timer = setInterval(() => {
    
    
          console.log('setInterval')
        }, 1000)
      }
    })

    this.$on('hook:deactivated', () => {
    
    
      clearInterval(timer)
      timer = null
    })
  }
}
<script>
  • vue3 中操作:
<script setup>
import {
    
     onBeforeUnmount, onDeactivated } from 'vue'

// 组件卸载前,对应 Vue2 的 beforeDestroy
onBeforeUnmount(() => {
    
    
    clearTimeout(timer)
    window.removeAddEventListener('...')
})

// 退出缓存组件,对应 Vue2 的 deactivated
onDeactivated(() => {
    
    
    clearTimeout(timer)
    window.removeAddEventListener('...')
})
</script>

3.this指向

  • vue2中可以调用this来指向当前实例,this上挂载了路由、状态管理、公共的组件、方法等可以访问、使用
  • 通过上面的生命周期可以看出来,vue3中setup()在解析其他组件选项(data、methods、computed 等都没解析)之前调用,在beforeCreate()之前执行,所以this指向undefined,vue3中不能通过this进行访问
  • vue3想要执行类似vue2调用this的用法可以进行如下操作:
<script setup>
import {
    
     getCurrentInstance } from "vue";

// proxy 为当前组件实例;global 为全局组件实例
const {
    
     proxy, appContext } = getCurrentInstance();
const global = appContext.config.globalProperties;
</script>

4.变量

4.1 ref

  • ref 定义基本类型生成 RefImpl 实例;定义复合类型生成 Proxy 实例
  • template 渲染直接使用,js中修改通过 .value 调用
const count = ref(0)
const user = ref({
    
    
    name:'falcon',
    age:20
})

const addCount = () => count.value++
const addAge = () => user.value.age++

4.2 reactive

  • reactive 只能定义对象类型的数据,生成 Proxy 实例
  • template、js中可以直接调用
  • shallowReactive 生成非递归响应数据,只监听第一层数据的变化
const stu = reactive({
    
    
    name:'falcon',
    major:'Chinese',
    score:80
})

const addScore = () => stu.score++

4.3 转化响应式

  • toRef(),单个转化为响应式
  • toRefs(),多个转化为响应式
  • unref(),是 val = isRef(val) ? val.value : val 的语法糖;如果参数是一个ref就返回其 value,否则返回参数本身

【注】针对一个响应式对象(reactive封装)的prop(属性)创建一个ref,且保持响应式

const stu = reactive({
    
    
    name:'falcon',
    age:20,
    major:'Chinese',
    score:80
})
const age = toRef(stu,'age')
const {
    
    name,major,score} = toRefs(stu)

4.4 只读

  • readonly,创建只读对象(递归只读)
  • isReadonly,判断是否是readonly对象
  • shallowReadonly,只对最外层响应式只读,深层次不转换
let status = readonly(true);
const changeStatus = () => (status = !status);

let info = reactive({
    
    
  username: "falcon",
  password: "123456",
  role: {
    
    
    roleId: 123,
    roleName: "系统管理员",
  },
});
info = shallowReadonly(info);
const changeRole = () => {
    
    
  info.role.roleId++;
};

5.Fragment

  • vue2 中只能有一个根节点,因为vdom是一颗单根树,patch方法在遍历的时候从根节点开始,所以要求template只有一个根元素
  • vue3 中可以有多个根节点,因为如果template不只有一个根元素时,就会添加一个fragment组件将多个根组件包起来
<template>
  <div>demo1 text</div>
  <h2>h2 text</h2>
  <p>p text</p>
</template>

6.Teleport

  • teleport 瞬移组件,能将我们的元素移动到DOM中vue app之外的其他位置(有时用于页面需要弹框且弹框不影响布局的情况,相对于body进行定位)
<template>
  <div class="app-container">
    <el-button type="primary" @click="showToast">打开弹框</el-button>
  </div>
  <teleport to="body">
    <div v-if="visible" class="modal_class">
      A man who has not climbed the granted wall is not a true man
      <el-button
        style="width: 50%; margin-top: 20px"
        type="primary"
        @click="closeToast"
        >关闭弹框</el-button
      >
    </div>
  </teleport>
</template>

<script setup>
import {
    
     ref } from "vue";

const visible = ref(false);
const showToast = () => {
    
    
  visible.value = true;
};
const closeToast = () => {
    
    
  visible.value = false;
};
</script>

<style scoped>
.modal_class {
    
    
  position: absolute;
  width: 300px;
  height: 200px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border: 1px solid #ccc;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-content: center;
  padding: 30px;
}
</style>

7.Suspense

  • suspense 允许程序在等待一些异步组件时加载一些后备内容
  • 在异步加载请求网络数据时,使用suspense组件,可以很好的实现loading效果
  • #default 初始化模板组件;#fallback 异步请求中处理的ui
<template>
  <div class="app-container">
    <Suspense>
      <template #default>
        <SyncApi />
      </template>
      <template #fallback>
        <h3 style="color: blue">数据加载中...</h3>
      </template>
    </Suspense>
  </div>
</template>

<script setup>
import SyncApi from "./SyncApi.vue";
</script>
// SyncApi 组件内容
<template>
  <div v-for="(people, index) in peoples.results" :key="index">
    {
   
   { people.name }} {
   
   { people.birth_year }}
  </div>
</template>

<script setup>
const peoples = ref({
      
      
  results: [],
});
const headers = {
      
       "Content-Type": "application/json" };
const fetchPeoples = await fetch("https://swapi.dev/api/people", {
      
      
  headers,
});
peoples.value = await fetchPeoples.json();
</script>

8.组件

  • vue2 中组件引入后需在components中注册后才能使用
  • vue3 中组件引入后直接使用无需注册
<template>
  <Table />
</template>

<script setup>
import Table from "@/components/Table";
</script>

9.获取DOM

<template>
  <el-form ref="formRef"></el-form>
</template>

<script setup>
// 1. 变量名和 DOM 上的 ref 属性必须同名,自动形成绑定
const formRef = ref(null)
console.log(formRef.value)

// 2. 通过当前组件实例来获取DOM元素
const {
      
       proxy } = getCurrentInstance()
proxy.$refs.formRef.validate((valid) => {
      
       ... })
</script>

10.watch、watchEffect

10.1 watch

// vue2 中用法
watch:{
    
    
    // 第一种
    flag(newValue,oldValue){
    
    },
    
    // 第二种
    user:{
    
    
        handler(newValue,oldValue){
    
    },
        immediate:true,
        deep:true
    }
}
// vue3 中用法
<script setup>
const count = ref(0)
const status = ref(false)

// 监听一个
watch(count,(newValue,oldValue) => {
    
    })
// 监听多个
watch([count,status],([newCount,oldCount],[newStatus,oldStatus]) => {
    
    })

const user = reactive({
    
    
    name:'falcon',
    age:20,
    sex:'female',
    hobbies:[]
})

// 监听一个
watch(() => user.age,(newValue,oldValue) => {
    
    })
// 监听多个
watch([() => user.name,() => user.sex],(newValue,oldValue) => {
    
    })

// 添加配置参数
watch(() => user.hobbies,(newValue,oldValue)=> {
    
    },{
    
    
    immediate:true,
    deep:true,
    // 回调函数的执行时机,默认在组件更新之前执行,更新之后执行参数为‘post’
    flush:'pre'
})
</script>

10.2 watchEffect

// 正常情况组件销毁自动停止监听
watchEffect(() => {
    
    })

// 异步方式手动停止监听
const stopWatch = watch(() => user.hobbies,(newValue,oldValue)=>{
    
    },{
    
    deep:true})
setTimeout(() => {
    
    
    stopWatch()
},3000)

const stopWatchEffect = watchEffect(() => {
    
    })
setTimeout(() => {
    
    
    stopWatchEffect()
},3000)

10.3 两者区别

  • watch 对传入的一个或多个值进行监听,触发时会返回新值和旧值,且默认第一次不会执行
  • watchEffect 是传入一个立即执行函数,默认第一次会执行,且不需要传入监听的内容,会自动收集函数内的数据源作为依赖,当依赖发生变化时会重新执行函数(类似computed),并且不会返回旧值
  • 正常情况下,组件销毁/卸载后这两种方式都会停止监听,但是异步方式例如setTimeout里创建的监听需要手动停止

11.computed

  • 默认只改变数据的值,如果想改变后的值具有响应性,利用其set()方法
  • vue3.x中移除过滤器filter,建议使用computed
  • 回调函数必须return,结果是计算结果
  • 计算属性依赖的数据项发生变化时,重新计算,具有缓存性
  • 不能执行异步操作
const names = reactive({
    
    
    firstName:'',
    lastName:'',
    fullName:''
})

// 通过此种方式定义的fullName,想要修改的时候后台警告:Write operation failed: computed value is readonly;想要修改fullName,通过set()方法
const fullName = computed(() => {
    
    
  return names.firstName + " " + names.lastName;
});

const fullName = computed({
    
    
    get(){
    
    
        return names.firstName + " " + names.lastName
    },
    set(value){
    
    
        
    }
})

12.可组合函数

  • vue3 中组合式api使用hooks代替 vue2 中的mixin。hooks 约定用驼峰命名法,并以“use”作为开头;将可复用的功能抽离为外部js文件;引用时将定义的属性和方法响应式地解构暴露出来;避免了vue2的碎片化,实现低内聚高耦合
  • 传统mixin的缺陷:
    • mixin可能会引起命名冲突和重复代码,后期很难维护
    • mixin可能会导致组件之间的依赖关系不清楚,不好追溯源
  • mixin生命周期函数:先执行mixin中生命周期函数;后执行组件内部代码mixin中的data数据和组件中的data数据冲突时,组件中的data数据会覆盖mixin中数据
// useCount.js 
const useCount = (initValue = 1) => {
    
    
    const count = ref(initValue)
    
    const increase = (delta) => {
    
    
        if(typeof delta !== 'undefined'){
    
    
            count.value += delta
        }else{
    
    
            count.value++
        }
    }
    
    const multiple = computed(() => count.value * 2)
    
    const decrease = (delta) => {
    
    
        if(typeof delta !== 'undefined'){
    
    
            count.value -= delta
        }else{
    
    
            count.value--
        }
    }
    
    const reset = () => count.value = initValue 
    
    return {
    
    
        count,
        multiple,
        increase,
        decrease,
        reset
    }
}

export default useCount
<template>
    <p>{
   
   {count}}</p>
    <p>{
   
   {multiple}}</p>
    <el-button @click="addCount">count++</el-button>
    <el-button @click="subCount">count--</el-button>
    <el-button @click="resetCount">reset</el-button>
</template>

<script setup>
import useCount from "@/hooks/useCount"
const {
      
      count,multiple,increase,decrease,reset} = useCount(10)
const addCount = () => increase()
const subCount = () => decrease()
const resetCount = () => reset()
</script>

13.懒加载组件

// Demo.vue
<template>
    <div>异步加载组件的内容</div>
</template>
// ErrorComponent.vue
<template>
    <div>Warning:组件加载异常</div>
</template>
// LoadingComponent.vue
<template>
    <div>组件正在加载...<div>
</template>
<template>
    <AsyncDemo />
</template>

<script setup>
import LoadingComponent from './LoadingComponent.vue'
import ErrorComponent from './ErrorComponent.vue'

const time = (t,callback = () => {
      
      }) => {
      
      
    return new Promise((resolve) => {
      
      
        setTimeout(() => {
      
      
            callback()
            resolve()
        },t)
    })
}

const AsyncDemo = defineAsyncComponent({
      
      
    // 要加载的组件
    loader:() => {
      
      
        return new Promise((resolve) => {
      
      
            async function(){
      
      
                await time(3000)
                const res = await import("./Demo.vue")
                resolve(res)
            }
        })
    },
    // 加载异步组件时使用的组件
    loadingComponent:LoadingComponent,
    // 加载失败时使用的组件
    errorComponent:ErrorComponent,
    // 加载延迟(在显示loadingComponent之前的延迟),默认200
    delay:0,
    // 超时显示组件错误,默认永不超时
    timeout:5000
})
</script>

14.插槽

  • 具名插槽使用方式不同:vue2 中使用slot='插槽名称',vue3 中使用v-slot:插槽名称
  • 作用域插槽使用方式不同:vue2 中在父组件中使用slot-scope="data"从子组件获取数据,vue3 中在父组件中使用#data或者#default="{data}"获取
<template>
    <div>
        <!-- 默认 -->
        <slot />
        <!-- 具名 -->
        <slot name="slotName" />
        <!-- 作用域 -->
        <slot :data="user" name="propsSlot" />
    </div>
</template>

<script>
const user = reactive({
      
      
    name:'falcon',
    age:20
})
</script>
<template>
    <Son>
        <template #default><div>默认插槽内容</div></template>
        <template #slotName><div>具名插槽内容</div></template>
        <template #propsSlot="scope">
            <div>
                作用域插槽内容:name,{
   
   {scope.data.name}};age,{
   
   {scope.data.age}}
            </div>
        </template>
    </Son>
</template>

<script setup>
import Son from './Son.vue'
<script>

15.自定义指令

  • 全局自定义指令在main.js中定义
  • 局部自定义指令在当前组件中定义
// main.js
app.directive("focus",{
    
    
    mounted(el,bingings,vnode,preVnode){
    
    
        el.focus()
    }
})
<template>
    <div>
        <input type="text" v-focus />
    </div>
</template>

<script setup>
const vFocus = {
      
      
    mounted:(el) => el.focus()
}
</script>

16.v-model

  • vue2 中.syncv-model都是语法糖,都可以实现父子组件中数据的双向通信
  • vue2两种格式差别:v-model="num",:num.sync="num";v-model:@input+value,:num.sync:@update:num
  • vue2中v-model只能用一次,.sync可以有多个
  • vue3中取消了 .sync,合并到v-model,vue3中v-model可以有多个
<template>
    <p>name:{
   
   {name}}</p>
    <p>age:{
   
   {age}}</p>
    <Son v-model:name="name" v-model:age="age" />
</template>

<script setup>
import Son from './Son.vue'

const user = reactive({
      
      
    name:'falcon',
    age:20
})

const {
      
      name,age} = toRefs(user)
</script>
<template>
    <input type="text" :value="name" @input="onNameInput" />
    <input type="number" :value="age" @change="onAgeInput" />
</template>

<script setup>
defineProps({
      
      
    name:{
      
      
        type:String,
        default:() => ""
    },
    age:{
      
      
        type:String,
        default:() => ""
    }
})

const emit = defineEmits(["update:name"],["update:age"])

const onNameInput = (e) => emit("update:name",e.target.value)
const onAgeInput = (e) => emit("update:age",e.target.value)
</script>

17.v-if/v-for

  • 不建议 v-for 与 v-if 一起使用
  • vue2 中优先级v-for 高于 v-if。如果执行过滤列表项操作,配合computed;如果条件判断来显隐循环列表,将v-if提前,包裹v-for
  • vue3 中优先级v-if 高于 v-for
<template>
    <div v-if="flag">
        <div v-for="item in dataList" :key="item.id">{
   
   {item.id}} - {
   
   {item.label}}</div>
    </div>
</template>

<script setup>
const flag = ref(true)
const dataList = reactive([
    {
      
      
        id:1,
        label:'list-01'
    },
    {
      
      
        id:2,
        label:'list-02'
    }
])
</script>

18.v-bind

  • vue2 中单独声明优先,并且重复定义会出发出警告
  • vue3 中绑定值是否生效遵循就近原则
<template>
    <div>
        <input type="text" v-bind:disabled="false" :disabled="disabled" />
        <input type="text" :disabled="disabled" v-bind:disabled="false" />
    </div>
</template>

<script setup>
const disabled = ref(true)
</script>

19.组件通信

19.1 props/$emit

父组件传值,子组件通过props接受;子组件想改变父组件中数值,通过$emit调用父组件中方法

  • 父组件
<template>
  <Child :count="count" :name="name" :age="age" @add="add" @sub="sub" />
</template>

<script setup>
import Child from "./Child.vue";

const count = ref(0);
const user = reactive({
      
      
  name: "falcon",
  age: 20,
});

const add = () => count.value++;
const sub = () => count.value--;

const {
      
       name, age } = toRefs(user);
</script>
  • 子组件
<template>
  <p>接受到的参数为:name,{
   
   { name }},age,{
   
   { age }},count,{
   
   { count }}</p>
  <el-button type="primary" size="small" @click="add">count++</el-button>
  <el-button type="primary" size="small" @click="sub">count--</el-button>
</template>

<script setup>
defineProps({
      
      
  name: {
      
      
    type: String,
    default: () => "",
  },
  age: {
      
      
    type: Number,
    default: () => 0,
  },
  count: {
      
      
    type: Number,
    default: () => 0,
  },
});

const emits = defineEmits(["add", "sub"]);

const add = () => emits("add");
const sub = () => emits("sub");
</script>

19.2 attrs

传递属性或方法给子组件下级组件,传递子组件中没有被props定义的属性,传递子组件中没有被emits定义的方法

  • 父组件
<template>
  <Child :count="count" :name="name" :age="age" @add="add" @sub="sub" />
</template>

<script setup>
import Child from "./Child.vue";

const count = ref(0);
const user = reactive({
      
      
  name: "falcon",
  age: 20,
});

const add = () => count.value++;
const sub = () => count.value--;

const {
      
       name, age } = toRefs(user);
</script>
  • 子组件
<template>
  <p>子组件接收:{
   
   { count }}</p>
  <el-button type="primary" size="small" @click="add">count++</el-button>
  <GrandChild v-bind="$attrs" />
</template>

<script setup>
import GrandChild from "./GrandChild.vue";

defineProps({
      
      
  count: {
      
      
    type: Number,
    default: () => 0,
  },
});

const emits = defineEmits(["add"]);

const add = () => emits("add");
</script>
  • 孙组件
<template>
  <p>孙组件接受:name,{
   
   { name }},age,{
   
   { age }}</p>
  <el-button type="primary" size="small" @click="sub">count--</el-button>
</template>

<script setup>
defineProps({
      
      
  name: {
      
      
    type: String,
    default: () => "",
  },
  age: {
      
      
    type: Number,
    default: () => 0,
  },
});

const emits = defineEmits(["sub"]);

const sub = () => emits("sub");
</script>

19.3 v-model

  • 父组件
<template>
  <Child v-model:name="name" v-model:count="count" v-model:salary="salary" />
</template>

<script setup>
import Child from "./Child.vue";

const name = ref("falcon");
const count = ref(0);
const salary = ref(3000);
</script>
  • 子组件
<template>
  <p>
    子组件接受到的v-model参数:name,{
   
   { name }},count,{
   
   { count }},salary,{
   
   {
      salary
    }}
  </p>
  <el-button type="primary" size="small" @click="changeCount"
    >count++</el-button
  >
  <el-button type="primary" size="small" @click="changeSalary"
    >salary1000+</el-button
  >
</template>

<script setup>
const props = defineProps({
      
      
  name: {
      
      
    type: String,
    default: () => "",
  },
  count: {
      
      
    type: Number,
    default: () => "",
  },
  salary: {
      
      
    type: Number,
    default: () => "",
  },
});

const emits = defineEmits(["update:count", "update:salary"]);
const changeCount = () => emits("update:count", props.count + 1);
const changeSalary = () => emits("update:salary", props.salary + 1000);
</script>

19.4 ref/expose

通过ref获取指定的DOM元素或组件,结合defineExpose暴露出来的属性和方法实现通信

  • 父组件
<template>
  <div>title:{
   
   { title }}</div>
  <Child ref="child" />
  <el-button type="primary" size="small" @click="add">count++</el-button>
  <el-button type="primary" size="small" @click="sub">count--</el-button>
  <el-button type="primary" size="small" @click="receive"
    >receive msg</el-button
  >
</template>

<script setup>
import Child from "./Child.vue";

const child = ref(null);
const title = ref("暂无数据");
const add = () => child.value.add();
const sub = () => child.value.sub();
const receive = () => (title.value = child.value.msg);
</script>
  • 子组件
<template>
  <p>子组件:count,{
   
   { count }}</p>
</template>

<script setup>
const count = ref(0);
const msg = "expose message";
const add = () => count.value++;
const sub = () => count.value--;

defineExpose({
      
      
  msg,
  add,
  sub,
});
</script>

19.5 provide/inject

祖先向下级传递参数,无论层级多深,都可以传递

  • 父组件
<template>
  <Child />
</template>

<script setup>
import Child from "./Child.vue";

const user = reactive({
      
      
  name: "falcon",
  age: 20,
});
provide("user", user);
</script>
  • 子组件
<template>
  <p>子组件接受:name,{
   
   { user.name }}</p>
  <GrandChild />
</template>

<script setup>
import GrandChild from "./GrandChild.vue";

const user = inject("user");
</script>
  • 孙组件
<template>
  <p>孙组件接受:age,{
   
   { user.age }}</p>
</template>

<script setup>
const user = inject("user");
</script>

19.6 mixin

不建议使用,建议使用可组合函数完成组件间通信和复用

  • mixin.js
const count = ref(20);
const name = ref("falcon");

const addCount = () => count.value++;
const subCount = () => count.value--;

export default {
    
     count, name, addCount, subCount };
  • 组件使用
<template>
  <p>name:{
   
   { name }},count:{
   
   { count }}</p>
  <el-button @click="addCount" type="primary" size="small">count++</el-button>
  <el-button @click="subCount" type="primary" size="small">count--</el-button>
</template>

<script setup>
import mixins from "./mixin";
const {
      
       count, name, addCount, subCount } = mixins;
</script>

19.7 mitt

vue3 中废除api:$on$once$off;不再支持Event Bus,选用替代方案mitt.js,原理还是Event Bus

  • bus.js
import mitt from 'mitt';
export default mitt()
  • 父组件
<template>
  <Brother1 />
  <Brother2 />
</template>

<script setup>
import Brother1 from "./Brother1.vue";
import Brother2 from "./Brother2.vue";
</script>
  • 兄弟组件1
<template>
    <p>brother1 发送事件</p>
    <el-button type="primary" size="small" @click="handleClick">发送事件</el-button>
</template>

<script setup>
import mybus from './bus.js';

const handleClick = () => {
      
      
    mybus.emit("title",{
      
      title:"hello world"});
    mybus.emit("user",{
      
      user:{
      
      name:"falcon",age:20}})
}
</script>
  • 兄弟组件2
<template>
    <p>brother2 接受事件</p>
    <p>title:{
   
   {title}}</p>
    <p>user:name,{
   
   {name}};age,{
   
   {age}}</p>
</template>

<script setup>
import mybus from './bus.js'

const title = ref("")
const user = reactive({
      
      
    name:"",
    age:null
})

mybus.on("title",(data) => {
      
      
    title.value = data.title
})
mybus.on("user",(data) => {
      
      
    user.name = data.user.name
    user.age = data.user.age 
})
</script>

20.状态管理 pinia

pinia 是 vue 的存储库,允许跨组件/跨页面共享状态。具有以下优点:

  • 轻量,约1kb
  • 去除Mutation,Actions支持同步和异步
  • 无需手动注册store,store仅在需要时才自动注册
  • 没有模块嵌套,store之间可以自由使用
  • 支持模块热更新

20.1 创建

import {
    
     createPinia } from 'pinia'

const store = createPinia()
export default store

20.2 定义

// 引入store定义函数
import {
    
     defineStore } from 'pinia'

// 定义store实例并导出
// 第一个参数,字符串类型,唯一不可重复,作为库id来区分不同库
// 第二个参数,以对象形式配置存储库的state、getters、actions

export const useStore = defineStore('useCount',{
    
    
    /**
        state,存储全局状态
        必须是箭头函数:为了在服务器端渲染的时候避免交叉请求导致数据状态污染
    */
    state:() => {
    
    
        return {
    
    
            count:0
        }
    },
    /**
        getters,封装计算属性
        具有缓存功能,类似于computed;需要传入state才能拿到数据;不能传递任何参数,但是可以返回一个函数接受任何参数
    */
    getters:{
    
    
        doubleCount:(state) => state.count * 2,
        powCount(){
    
    
            return this.doubleCount ** 2
        }
    },
    /**
        actions,编辑业务逻辑
        类似于methods,支持同步和异步;获取state里的数据不需要传入直接使用this
    */
    actions:{
    
    
        addCount(){
    
    
            this.count++
        },
        subCount(){
    
    
            this.count--
        }
    },
    /**
        配置数据持久化需要进行的操作
    */
    persist:{
    
    }
})

20.3 页面使用

<template>
    <p>{
    
    {
    
    useStoreDemo.count}}<p>
    <p>{
    
    {
    
    useStoreDemo.doubleCount}}</p>
    <p>{
    
    {
    
    useStoreDemo.powCount}}</p>
    <el-button @click="toAdd">count++</el-button>
    <el-button @click="toSub">count--</el-button>
</template>

<script setup>
import {
    
    useStore} from '../store'
const useStoreDemo = useStore()

// 也可以解构出来想要使用的count,但直接解构不具有响应式,想要具有响应式,可以执行如下操作:
const {
    
    count} = storeToRefs(useStore())

const toAdd = () => useStoreDemo.addCount()
const toSub = () => useStoreDemo.subCount()
<script>

20.4 数据持久化

pinia的数据是存储在内存中的,页面刷新后数据会丢失;可以支持扩展插件,实现数据持久化

  • npm i pinia-plugin-persist,默认使用sessionStorage
  • 配置使用代码如下:
persist:{
    
    
    enabled:true,
    strategies:[
        {
    
    
            storage:localStorage,
            paths:["num","user"]
        }
    ]
}

21.路由

  • query传参配置path,params传参配置name,且params中配置path无效
  • query传参显示在地址栏,params传参不会
  • query传参刷新页面数据不会消失,params传参刷新页面数据消失
  • params可以使用动态参数(“/path/:params”),动态参数会显示在地址栏中,且刷新页面数据不会消失
  • name为路由中定义的name属性,严格区分大小写
  • 路由跳转:前进router.go(1)、后退router.go(-1)、刷新router.go(0)
  • 使用案例:
<template>
    <el-button @click="TransByQuery">通过query传参</el-button>
    <el-button @click="TransByParams">通过params传参</el-button>
    <el-button @click="TransByDynamic">动态传递参数</el-button>
</template>

<script setup>
const queryParams = reactive({
    
    
    name:'falcon',
    age:20
})

const id = ref('2023')

const router = useRouter()

const TransByQuery = () => {
    
    
    router.push({
    
    
        path:'/basic/querydemo'query:queryParams
    })
}
    
const TransByParams = () => {
    
    
    router.push({
    
    
        name:'ParamsDemo',
        params:queryParams
    })
}

const TransByDynamic = () => {
    
    
    router.push({
    
    
        name:'DynamicDemo',
        params:{
    
    id:id.value}
    })
}
<script>
  • query 接受参数
const route = useRoute()
console.log(route.query.name,route.query.age)
  • params 接受参数
const route = useRoute()
console.log(route.params.name,route.params.age)
  • 动态传递 接受参数
const route = useRoute()
console.log(route.params.id)
  • 相应的路由
 {
    
    
  name: "QueryDemo",
  path: "querydemo",
  redirect: null,
  component: "basic/userouter/querydemo",
  hidden: true,
  meta: {
    
    
    title: "query样例",
    icon: null,
  },
},
{
    
    
  name: "ParamsDemo",
  path: "paramsdemo",
  redirect: null,
  component: "basic/userouter/paramsdemo",
  hidden: true,
  meta: {
    
    
    title: "params样例",
    icon: null,
  },
},
{
    
    
  name: "DynamicDemo",
  path: "dynamicdemo/:id",
  redirect: null,
  component: "basic/userouter/dynamicdemo",
  hidden: true,
  meta: {
    
    
    title: "dynamic样例",
    icon: null,
  },
},

22.css补充

22.1 样式穿透

  • css >>> className,less /deep/ className, scss ::v-deep className
  • vue3中css使用::deep(className)

22.2 绑定变量

<template>
    <div class="name">falcon</div>
</template>

<script setup>
const str = ref('#f00')
</script>

<style lang="scss" scoped>
.name{
      
      
    background-color:v-bind(str)
}
</style>

猜你喜欢

转载自blog.csdn.net/weixin_45506717/article/details/132883249