Vue3使用经验总结

434 阅读4分钟

Composition API

setup

setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API的入口。

setup 参数

使用setup时,它接受两个参数:

  1. props: 组件传入的属性
  2. context

setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。 错误代码示例, 这段代码会让 props 不再支持响应式:

// demo.vue
export default defineComponent ({
    setup(props, context) {
        const { name } = props
        console.log(name)
    },
})

在开发中我们想要使用解构,还能保持props的响应式,可以采用toRefssetup接受的第二个参数contextcontext中就提供了this中最常用的三个属性:attrsslotemit,分别对应 Vue2.x 中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。

响应式数据

  • ref:可传入任意类型的值并返回一个响应式且可改变的ref对象。ref对象拥有一个指向内部值的单一属性.value,改变值的时候必须使用其value属性
  • reactive:接受一个普通对象然后返回该普通对象的响应式代理。等同于2.x的Vue.obserable()

简写之:reactive负责复杂数据结构,ref可以把基本的数据结构包装成响应式

reactive

<template>
  <div>
    <h2>{{state.count}}</h2>
    <button @click="add">计算</button>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  setup(){
    // 响应式变量声明 reactive负责复杂数据结构,
    const state = reactive({
      count: 1
    });
    function add() {
      state.count++;
    }
    return { state, add};
  }
};
</script>

ref

<template>
  <div>
    <h2>{{state.count}}</h2>
    <h3>{{num}}</h3>
    <button @click="add">计算</button>
  </div>
</template>
<script>
import { reactive, ref } from "vue";
export default {

  setup(){
    const state = reactive({
      count: 1
    });
    const num = ref(0);
    function add() {
      state.count++;
      num.value+=2
    }
    return { state, add, num };
  }
};
</script>

toRef 和 toRefs

创建一个 ref 对象,其value值指向另一个对象中的某个属性。

语法:const name = toRef(person,'name')

应用: 要将响应式对象中的某个属性单独提供给外部使用时。

扩展:toRefs 与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

<template>
  <div>
    <h1>姓名:{{person.name}}</h1>
    <h2>年龄:{{person.age}}</h2>
    <h3>喜欢的水果:{{person.likeFood.fruits.apple}}</h3>
  </div>
</template>
 
<script>
import {reactive} from 'vue'
export default {
  name: "App",
  setup() {
    // 定义了一段数据
    let person = reactive({
      name: '张三',
      age: 18,
      likeFood: {
        fruits:{
          apple: '苹果'
        }
      }
    })
    // 将数据返回出去
    return {
      person,
    }
  }
};
</script>

我们可以看到在模板中的使用的数据 都是person.xxx 、person.xxx。 person一直出现在插值语法中看起来并不整洁

使用toRef 

首先引入toRef方法 然后在返回的时候调用toRef并传入要返回的对象里面的哪个值,就可以实现响应式数据了

// 引入toRef方法
import {reactive, toRef} from 'vue'
 
// 返回的时候调用toRef方法将要返回的对象和属性传递给toRef方法
return {
  name: toRef(person, "name"),
  age: toRef(person, "age"),
  apple: toRef(person.likeFood.fruits, "apple")
}

这样就可以实现数据响应了 

使用toRefs

引入toRefs方法 我们还可以使用toRefs来包装多个ref数据  解构之后返回 模板中也可以使用

<template>
  <div>
    <h1>姓名:{{name}}</h1>
    <h2>年龄:{{age}}</h2>
    <h3>喜欢的水果:{{likeFood.fruits.apple}}</h3>
  </div>
</template>
...
 
return {
   ...toRefs(person)   // 将对象解构后返回
}
...

computed

传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。

import { reactive, ref, computed } from "vue";
export default {

  setup() {
    // 1.响应式变量声明 reactive负责复杂数据结构,
    const state = reactive({
      count: 1
    });
    // 2.ref可以把基本的数据结构包装成响应式
    const num = ref(0);
    // 3.创建只读的计算属性
    const computedEven1 = computed(() => state.count % 2);
    // 4.创建可读可写的计算属性
    const computedEven2 = computed({
      get:()=>{
        return state.count % 2;
      },
      set: newVal=>{
        state.count = newVal;
      }
    })

    // 事件的声明
    function add() {
      state.count++;
      num.value += 2;
    }

    function handleClick() {
      computedEven2.value = 10;
    }



    return { state, add, num, computedEven1,computedEven2,handleClick };
  }
};

watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const num = ref(0)

watchEffect(() => console.log(count.value))
// -> 打印出 0

setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)

1.停止监听

隐式停止

当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止

显示停止

在一些情况下,也可以显示调用返回值来停止侦听

const stop = watchEffect(()=>{
  /*...*/
})
//停止侦听
stop()

2.清除副作用

有时候副作用函数会执行一些异步的副作用,这些响应需要在其失效时来清除(即完成之前状态已改变了)。可以在侦听副作用传入的函数中接受一个onInvalidate函数作为参数,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

  • 副作用即将重新执行时
  • 侦听器被停止(如果在setup()或生命周期钩子函数中使用了watchEffect,则在卸载组件时)

官网的例子:

watchEffect((onInvalidate) => {
  const token = performAsyncOperation(id.value)
  onInvalidate(() => {
    // id 改变时 或 停止侦听时
    // 取消之前的异步操作
    token.cancel()
  })
})

生命周期钩子

image.png

Vue3 通信使用写法

1. props

// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2"></child>
<script>
import child from "./child.vue"
import { ref, reactive } from "vue"
export default {
    data(){
        return {
            msg1:"这是传级子组件的信息1"
        }
    },
    setup(){
        // 创建一个响应式数据
        
        // 写法一 适用于基础类型  ref 还有其他用处,下面章节有介绍
        const msg2 = ref("这是传级子组件的信息2")
        
        // 写法二 适用于复杂类型,如数组、对象
        const msg2 = reactive(["这是传级子组件的信息2"])
        
        return {
            msg2
        }
    }
}
</script>

// Child.vue 接收
<script>
export default {
  props: ["msg1", "msg2"],// 如果这行不写,下面就接收不到
  setup(props) {
    console.log(props) // { msg1:"这是传给子组件的信息1", msg2:"这是传给子组件的信息2" }
  },
}
</script>

2. $emit

// Child.vue 派发
<template>
    // 写法一
    <button @click="emit('myClick')">按钮</buttom>
    // 写法二
    <button @click="handleClick">按钮</buttom>
</template>
<script setup>
    
    // 适用于Vue3.2版本 不需要引入
    // import { defineEmits } from "vue"
    // 对应写法一
    const emit = defineEmits(["myClick","myClick2"])
    // 对应写法二
    const handleClick = ()=>{
        emit("myClick", "这是发送给父组件的信息")
    }
  
</script>

// Parent.vue 响应
<template>
    <child @myClick="onMyClick"></child>
</template>
<script setup>
    import child from "./child.vue"
    const onMyClick = (msg) => {
        console.log(msg) // 这是父组件收到的信息
    }
</script>

3. expose / ref

父组件获取子组件的属性或者调用子组件方法

<script setup>
    // 适用于Vue3.2版本, 不需要引入
    // import { defineExpose } from "vue"
    defineExpose({
        childName: "这是子组件的属性",
        someMethod(){
            console.log("这是子组件的方法")
        }
    })
</script>

// Parent.vue  注意 ref="comp"
<template>
    <child ref="comp"></child>
    <button @click="handlerClick">按钮</button>
</template>
<script setup>
    import child from "./child.vue"
    import { ref } from "vue"
    const comp = ref(null)
    const handlerClick = () => {
        console.log(comp.value.childName) // 获取子组件对外暴露的属性
        comp.value.someMethod() // 调用子组件对外暴露的方法
    }
</script>

4. attrs

attrs:包含父作用域里除 class 和 style 除外的非 props 属性集合

// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2" title="3333"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const msg1 = ref("1111")
    const msg2 = ref("2222")
</script>

// Child.vue 接收
<script setup>
    import { defineProps, useContext, useAttrs } from "vue"
    // Vue3.2版本不需要引入 defineProps,直接用
    const props = defineProps({
        msg1: String
    })
 
    const attrs = useAttrs()
    console.log(attrs) // { msg2:"2222", title: "3333" }
</script>

5. v-model

可以支持多个数据双向绑定

<child v-model:key="key" v-model:value="value"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const key = ref("1111")
    const value = ref("2222")
</script>

// Child.vue
<template>
    <button @click="handlerClick">按钮</button>
</template>
<script setup>
    // Vue3.2版本,不需要引入
    // import { defineEmits } from "vue"
    const emit = defineEmits(["key","value"])
    
    // 用法
    const handlerClick = () => {
        emit("update:key", "新的key")
        emit("update:value", "新的value")
    }
</script>

6. provide / inject

provide / inject 为依赖注入

provide:可以让我们指定想要提供给后代组件的数据

inject:在任何后代组件中接收想要添加在这个组件上的数据,不管组件嵌套多深都可以直接拿来用

// Parent.vue
<script setup>
    import { provide } from "vue"
    provide("name", "沐华")
</script>

// Child.vue
<script setup>
    import { inject } from "vue"
    const name = inject("name")
    console.log(name) // 沐华
</script>

7. Vuex

// store/index.js
import { createStore } from "vuex"
export default createStore({
    state:{ count: 1 },
    getters:{
        getCount: state => state.count
    },
    mutations:{
        add(state){
            state.count++
        }
    }
})

// main.js
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store"
createApp(App).use(store).mount("#app")

// Page.vue
// 方法一 直接使用
<template>
    <div>{{ $store.state.count }}</div>
    <button @click="$store.commit('add')">按钮</button>
</template>

// 方法二 获取
<script setup>
    import { useStore, computed } from "vuex"
    const store = useStore()
    console.log(store.state.count) // 1

    const count = computed(()=>store.state.count) // 响应式,会随着vuex数据改变而改变
    console.log(count) // 1 
</script>

8. mitt

Vue3 中没有了 EventBus 跨组件通信,但是现在有了一个替代的方案 mitt.js,原理还是 EventBus

先安装 npm i mitt -S

然后像以前封装 bus 一样,封装一下

mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt

然后两个组件之间通信的使用

// 组件 A
<script setup>
import mitt from './mitt'
const handleClick = () => {
    mitt.emit('handleChange')
}
</script>

// 组件 B 
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
    mitt.off('handleChange',someMethed)
})
</script>