Basic knowledge of Vue3 that you must read when doing projects

1. Responsive

1.1 The two implementation principles

  • vue2 uses es5 Object.defineProperty()to hijack data and combine it with the publish and subscribe model to achieve
  • Vue3 uses es6's proxydata proxy to wrap a layer of proxy for each object through the reactive() function, and monitors changes in attributes through the proxy to monitor data.

1.2 vue2 responsive defects

defect

对象新增、删除属性没有响应式, 数组新增、删除元素没有响应式;通过下标修改某个元素没有响应式;通过.length改变数组长度没有响应式. Only the data instances in the data when the instance is created are responsive. When adding attributes to the created vue instance data object, although the data will be updated, the view will not be updated, and it is not responsive.

solve

  • Use this.$forceUpdate()to force updates to views and data (not recommended)
  • Use reactive functions to manipulate objects:
[Vue | this].$set(object,key,value),实例中添加响应式属性;
[Vue | this].$delete(object,key),实例中删除属性;
Object.assign(),将多个对象属性合并到目标对象中,具有响应式;
Object.freeze(),将对象冻结,防止任何改变。使得对象成为只读,无法添加、删除或更新;
Object.keys(),返回对象的所有属性;
Object.values(),返回对象的所有属性值;
Object.entries(),返回对象的所有键值对;
  • Use reactive functions to manipulate arrays:
pop(),尾部删除元素;
push(),尾部添加元素;
unshift(),首部添加元素;
shift(),首部删除元素;
sort(),排序;
reverse(),翻转;
splice(index[必填,位置],howmany[必填,要删除的数量],item1...itemx[可选,向数组中添加的新元素])

[Supplement] Clear objects and empty array operations

  • Clear object
this.form = {
    
    }
this.$refs.form.resetFields()
this.form.name = ""
  • Empty array
this.arrayList = [] 
this.arrayList.splice(0,this.arrayList.length)
// this.arrayList.length = 0  不具有响应式,无法实现

1.3 vue3 responsive advantages

  • proxy性能整体上优于Object.defineProperty
  • vue3支持更多数据类型的劫持(vue2 only supports Object and Array; vue3 supports Object, Array, Map, WeakMap, Set, and WeakSet)
  • vue3支持更多时机来进行依赖收集和触发通知(vue2 only collects dependencies when get, vue3 collects dependencies when get/has/iterate; vue2 only triggers notifications when set, vue3 triggers notifications when set/add/delete/clear),所以vue2中的响应式缺陷vue3可以实现
  • vue3做到了“精准数据”的数据劫持(vue2 will perform recursive data hijacking on the entire data, while vue3 will only perform data hijacking when a certain object is used, so it is more responsive and takes up less memory)
  • vue3的依赖收集器更容易维护(vue3 monitors and operates native arrays; vue2 monitors arrays through rewriting methods)

2. Life cycle

view2 view 3 illustrate
beforeCreate setup() Before component creation, perform initialization tasks
created setup() The component creation is completed, access data and obtain interface data
beforeMount onBeforeMount Before component mounting
mounted onMounted Component mounting is completed, DOM has been created, access data or DOM elements, access sub-components
beforeUpdate onBeforeUpdate Not updated, get all status before update
updated onUpdated Updated, get all updated status
beforeDestroy onBeforeUnmount Before the component is destroyed, clear the timer and unsubscribe from the message
destroyed onUnmounted After the component is destroyed
activated onActivated keep-alive contains, when the component is activated
deactivated onDeactivated keep-alive contains, when component switching occurs and the component disappears

2.1 Initialization

  • vue2 is generally initialized in created and mounted
  • vue3 can be initialized directly in setup, or in onBeforeMount or onMounted
<script setup>
const getList = () => {
    
    }

getList()

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

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

2.2 Unbinding

  • Operation in 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>
  • Operation in 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 oriented

  • In vue2, you can call this to point to the current instance. Routing, state management, public components, methods, etc. are mounted on this and can be accessed and used.
  • As can be seen from the above life cycle, vue3 is setup()called before parsing other component options (data, methods, computed, etc. are not parsed) and executed before beforeCreate(), so this points to undefined, and cannot be accessed through this in vue3
  • If vue3 wants to perform a usage similar to vue2 calling this, you can do the following:
<script setup>
import {
    
     getCurrentInstance } from "vue";

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

4.Variables

4.1 ref

  • ref defines a 基本类型generated RefImplinstance; defines a 复合类型generated Proxyinstance
  • Template is used directly for rendering, and modified in js by .valuecalling
const count = ref(0)
const user = ref({
    
    
    name:'falcon',
    age:20
})

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

4.2 reactive

  • reactive can only define data of object type and generate Proxyinstances
  • Can be called directly in template and js
  • shallowReactiveGenerate non-recursive response data and only monitor changes in the first layer of data
const stu = reactive({
    
    
    name:'falcon',
    major:'Chinese',
    score:80
})

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

4.3 Conversion responsiveness

  • toRef(), single converted to responsive
  • toRefs(), convert multiple into responsive
  • unref(), is val = isRef(val) ? val.value : valthe syntactic sugar of ; if the parameter is a ref, return its value, otherwise return the parameter itself

[Note] Create a ref for the prop (property) of a responsive object (reactive encapsulation) and keep it responsive

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

4.4 Read only

  • readonly, create a read-only object (recursive read-only)
  • isReadonly, determine whether it is a readonly object
  • shallowReadonly, only read-only for the outermost responsive layer, and no conversion is performed on the deeper layers.
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

  • In vue2 只能有一个根节点, because vdom is a single-root tree, the patch method starts from the root node when traversing, so the template is required to have only one root element.
  • In vue3 可以有多个根节点, because if the template has more than one root element, a fragment component will be added to wrap multiple root components.
<template>
  <div>demo1 text</div>
  <h2>h2 text</h2>
  <p>p text</p>
</template>

6.Teleport

  • The teleport teleport component can move our elements to other locations outside the vue app in the DOM (sometimes used when the page requires a pop-up frame and the pop-up frame does not affect the layout, and is positioned relative to the 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 allows the program to load some fallback content while waiting for some asynchronous components
  • When asynchronously loading and requesting network data, using the suspend component can achieve a good loading effect.
  • #defaultInitialize template components; #fallbackUI processed in asynchronous requests
<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.Components

  • After components are introduced in vue2, they need to be registered in components before they can be used.
  • After the component is introduced in vue3, it can be used directly without registration.
<template>
  <Table />
</template>

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

9. Get 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 Differences between the two

  • watch 对传入的一个或多个值进行监听, will happen when triggered 返回新值和旧值, and默认第一次不会执行
  • watchEffect is to pass in an immediately executed function, 默认第一次会执行,且不需要传入监听的内容,会自动收集函数内的数据源作为依赖and when the dependencies change, the function will be re-executed (similar to computed), and不会返回旧值
  • Under normal circumstances, both methods will stop listening after the component is destroyed/uninstalled, but 异步方式for example, the listening created in setTimeout needs to be stopped manually.

11.computed

  • By default, only the value of the data is changed. If you want to change the value 具有响应性, use its set()method.
  • Filter is removed in vue3.x, it is recommended to use computed
  • 回调函数必须return, the result is the calculated result
  • Computed property dependent数据项发生变化时,重新计算,具有缓存性
  • 不能执行异步操作
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. Composable functions

  • Combined API in vue3 使用hooks代替 vue2 中的mixin. Hooks use camel case naming convention and start with "use"; extract reusable functions into external js files; deconstruct and expose defined properties and methods responsively when referencing; avoid the fragmentation of vue2, Achieve low cohesion and high coupling
  • Disadvantages of traditional mixins:
    • Mixins may cause naming conflicts and duplicate code, making it difficult to maintain later.
    • Mixins may cause unclear dependencies between components, making it difficult to trace the source
  • mixin life cycle function: 先执行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. Lazy loading of components

// 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.Slot

  • Named slots are used in different ways: used in vue2 slot='插槽名称'and used in vue3v-slot:插槽名称
  • Scope slots are used in different ways: in vue2, they are used in the parent component slot-scope="data"to obtain data from the child component, in vue3, they are used in the parent component #dataor #default="{data}"to get 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. Custom instructions

  • Global custom directives are defined in main.js
  • Local custom directives are defined in the current component
// 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

  • Both Vue2 .syncand v-modelZhong are syntax sugar, which can realize two-way communication of data in parent and child components.
  • The difference between the two formats of vue2: v-model="num", :num.sync="num";v-model:@input+value,:num.sync:@update:num
  • vue2 inv-model只能用一次,.sync可以有多个
  • .sync was canceled in vue3 and merged into 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. If the operation of filtering list items is performed, cooperate with computed; if the condition is judged to show or hide the circular list, advance v-if and wrap 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 单独声明优先, and repeated definitions will issue a warning
  • Whether the binding value in vue3 takes effect follows就近原则
<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. Component communication

19.1 props/$emit

The parent component passes the value, and the child component receives it through props; if the child component wants to change the value in the parent component, it calls the method in the parent component through $emit.

  • parent component
<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>
  • Subassembly
<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

Pass properties or methods to sub-components. Pass properties that are not defined by props in the sub-component. Pass methods that are not defined by ems in the sub-component.

  • parent component
<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>
  • Subassembly
<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>
  • Sun component
<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

  • parent component
<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>
  • Subassembly
<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

Obtain the specified DOM element or component through ref, and implement communication in combination with the properties and methods exposed by defineExpose.

  • parent component
<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>
  • Subassembly
<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

Ancestors pass parameters to subordinates, no matter how deep the hierarchy is, they can be passed

  • parent component
<template>
  <Child />
</template>

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

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

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

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

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

19.6 mixin

Not recommended. It is recommended to use composable functions to complete communication and reuse between components.

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

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

export default {
    
     count, name, addCount, subCount };
  • Component usage
<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

APIs are abolished in vue3: $on, $once, $off; Event Bus is no longer supported, and the alternative mitt.js is chosen. The principle is still Event Bus.

  • bus.js
import mitt from 'mitt';
export default mitt()
  • parent component
<template>
  <Brother1 />
  <Brother2 />
</template>

<script setup>
import Brother1 from "./Brother1.vue";
import Brother2 from "./Brother2.vue";
</script>
  • Brother component 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>
  • Brother component 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. State management pinia

pinia is a repository for vue that allows 跨组件/跨页面共享状态. Has the following advantages:

  • Lightweight, about 1kb
  • 去除Mutation,Actions支持同步和异步
  • No need to manually register the store,store仅在需要时才自动注册
  • There is no module nesting and can be used freely between stores.
  • 支持模块热更新

20.1 Create

import {
    
     createPinia } from 'pinia'

const store = createPinia()
export default store

20.2 Definition

// 引入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 Page usage

<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 Data persistence

Pinia's data is stored in memory, and the data will be lost after the page is refreshed; it can support extension plug-ins to achieve data persistence.

  • npm i pinia-plugin-persist,use sessionStorage by default
  • The configuration usage code is as follows:
persist:{
    
    
    enabled:true,
    strategies:[
        {
    
    
            storage:localStorage,
            paths:["num","user"]
        }
    ]
}

21.Routing

  • Query passes parameter configuration path, params passes parameter configuration name, andparams中配置path无效
  • The query parameters are displayed in the address bar, but the params parameters are not.
  • query传参刷新页面数据不会消失,params传参刷新页面数据消失
  • params can use dynamic parameters ("/path/:params"),动态参数会显示在地址栏中,且刷新页面数据不会消失
  • name is the name attribute defined in the route,严格区分大小写
  • Route jump: forward router.go(1), backward router.go(-1), refreshrouter.go(0)
  • Use Cases:
<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 accepts parameters
const route = useRoute()
console.log(route.query.name,route.query.age)
  • params accepts parameters
const route = useRoute()
console.log(route.params.name,route.params.age)
  • Dynamically passing parameters
const route = useRoute()
console.log(route.params.id)
  • Corresponding route
 {
    
    
  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 supplement

22.1 Style penetration

  • css >>> className,less /deep/ className, scss ::v-deep className
  • Use css in vue3::deep(className)

22.2 Bind variables

<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>

Guess you like

Origin blog.csdn.net/weixin_45506717/article/details/132883249