vue中如何实现组件通信

1. 父子组件通信

1. props和emits

我们最常见的组件通信就是父子组件数据通信。父子组件实现数据通信需要使用props和emit两个api。

在父组件中我们通过props将数据绑定给子组件,在子组件中我们可以通过props对象来收集到父组件传递的数据。

在子组件想要修改的props中的数据时,我们可以使用emit对象来触发一个自定义事件,在父组件中监听这个自定义事件并修改数据。(Vue中数据必须保证单向数据流,父组件的数据不能由子组件直接修改,而是通过通知父组件的收段让父组件来修改数据)

1.script setup实现

在script setup语法糖中,我们可以使用defineProps和defineEmits两个宏函数来创建props和emit对象。

html 复制代码
<script setup>
  // 创建props和emit对象
  const props = defineProps({
    message: {
      type: String,
      default: 'hello'
    }
  })
  const emit = defineEmits(['change'])
</script>

defineProps注意事项

  1. 这两个宏函数在script setup语法糖中会隐式导入,并不需要我们手动导入。

  2. defineProps函数接收一个对象或者数组作为参数:

    • 为对象时,该对象中的属性即为父组件绑定的props名称,属性值可以是一个该数据对应的类型构造函数或者是一个包含该数据类型的对象配置对象,如上代码。(这里的配置对象下面会详解)
    • 为数组时,该数组每一项均为对应的props名称字符串。
  3. 在3.5以后的版本中,如果父组件的传递的props是响应式的,那我们在子组件中接解构props获取的值仍然是响应式的。可以通过watch和watchEffect来监听props的变化。我们能在setup中访问一个props解构的值时,其本质上是自动为调用的props.变量名.

    js 复制代码
    const props = defineProps(['foo'])
    
    watchEffect(() => {
      // 在 3.5 之前只运行一次
      // 在 3.5+ 中在 "foo" prop 变化时重新执行
      console.log(foo)
    })
    watchEffect(() => {
      // `foo` 由编译器转换为 `props.foo`
      console.log(props.foo)
    })
  4. 在模板中我们可以使用小驼峰命名法(camelCase)为props命名,也可以是使用短横线命名法(kebab-case),但为了和 HTML保持一致,推荐使用短横线命名法。

  5. 上面我们说到的配置对象中,除了type属性外,还可以按照如下的方式编写:

    js 复制代码
    defineProps({
      // 1. 基础类型检查
      // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
      propA: Number,
    
      // 2. 多种可能的类型
      propB: [String, Number],
    
      // 3. 必传,且为 String 类型
      propC: {
        type: String,
        required: true
      },
    
      // 4.必传但可为 null 的字符串
      propD: {
        type: [String, null],
        required: true
      },
    
      // 5.Number 类型的默认值
      propE: {
        type: Number,
        default: 100
      },
    
      // 6.对象类型的默认值
      propF: {
        type: Object,
        // 对象或数组的默认值
        // 必须从一个工厂函数返回。
        // 该函数接收组件所接收到的原始 prop 作为参数。
        default(rawProps) {
          return { message: 'hello' }
        }
      },
    
      // 7.自定义类型校验函数
      // 在 3.4+ 中完整的 props 作为第二个参数传入
      propG: {
        validator(value, props) {
          // The value must match one of these strings
          return ['success', 'warning', 'danger'].includes(value)
        }
      },
    
      // 8.函数类型的默认值
      propH: {
        type: Function,
        // 不像对象或数组的默认,这不是一个
        // 工厂函数。这会是一个用来作为默认值的函数
        default() {
          return 'Default function'
        }
      }
    })

当 prop 的校验失败后,Vue 会抛出一个控制台警告 (在开发模式下)。

defineEmits注意事项

  1. 该函数接收一个数组作为参数,数据每一项是事件名称,函数返回一个emit实例。

  2. 该函数必须放在script setup的顶级作用作用域中才能使用

  3. 在触发事件时,我们需要使用emit函数触发一个自定义事件,并传递参数。

    js 复制代码
    const emit = defineEmits(['change'])
    emit('change', 'hello')
2.setup函数中实现

setup函数中我们可以通过setup函数的两个参数来获取到props和emit对象。

  1. setup 函数传递两个参数,第一个参数就是父组件传入的props对象,第二个参数一个上下文对象,该上下文对象后续还会说到。

  2. props对象还需要通过选项式的写法来声明,才能使用。选项的值与上面的defineProps函数接收的参数一致。

    js 复制代码
    export default {
      props: ['foo'],
      setup(props,context) {
        // setup() 接收 props 作为第一个参数
        console.log(props.foo)
      }
    }
  3. context上挂载了emit对象,该对象本质是一个函数,与使用defineEmits函数创建的emit对象一致。

3.在template模板中使用
  1. 前面两个api都可以在template中直接通过关键是调用。
  2. props和emit对象通过 p r o p s 和 props和 props和emit来调用。

注意

如果子组件注册的事件与原生事件相同,则子组件只会触发注册事件,不会再响应原生事件。

2. defineModel(3.2+)

相信大家都知道Vue3中的v-model指令本质上是props名为modelValue和emit事件名为update:modelValue的组合。

因此在Vue3.2 版本之后,官方直接将这个组合封装到了defineModel宏函数中,以后再为组件绑定v-mode时,我们只需要在子组件中通过defineModel函数来声明即可。减少了代码量。

同时可以实现父子组件的数据通信.

  1. 这个宏函数接收一个配置参数
参数 默认值 描述
required false 是否必传,如果为true,则父组件必须绑定v-model的值
defaultProp undefined 默认的值,当父组件传递的值为undefined时会使用
  1. 该函数返回一个ref对象,并且如果在父组件中使用这个子组件,为子组件绑定一个v-model时,这个v-model的值就会被绑定给这个ref对象的value属性。一但这个ref变化就会触发父组件的数据更新,父组件数据主动更新也会触发子组件的更新。(这个宏函数的本质就是modelValue与update:modelValue的进一步封装)

3. 组件实例

在Vue中我们可以通过变量来绑定组件实例,在特定的生命周期函数中我们就可以通过变量来获取到组件实例对象。因此,我们就可以拿到组件暴露出来的属性与方法,从而时间组件间的数据通信。
步骤

  1. 父组件通过ref绑定来获取到子组件实例

  2. 子组件主动暴露某些属性与方法

  3. 父组件通过变量获取到子组件的属性与方法

    html 复制代码
    <!-- 父组件 -->
     <script setup>
    import { useTemplateRef, onMounted } from 'vue'
    import Child from './Child.vue'
    
    const childRef = useTemplateRef('child')
    
    onMounted(() => {
      // childRef.value 将持有 <Child /> 的实例
    })
    </script>
    
    <template>
      <Child ref="child" />
    </template>
    
    
    <!-- 子组件 -->
    <script setup>
    import { ref } from 'vue'
    
    const a = 1
    const b = ref(2)
    
    // 像 defineExpose 这样的编译器宏不需要导入
    defineExpose({
      a,
      b
    })
    </script>

注意事项

  1. 在最新版本的Vue中(3.5+)我们可以使用useTemplateRef hook函数来创建一个ref对象,并通过ref绑定到组件上。在3.5之前可以直接使用ref函数来创建。
  2. 在子组件使用script setup语法糖时,默认的组件实例是不会暴露任何的属性与方法(子组件私有),因此如果需要在父组件中获取到子组件属性与方法就需要使用defineExpose函数来暴露。该函数接收一个作为参数,每一个属性就是一个暴露的属性名称。
  3. 如果不使用script setup语法糖,此时访问到的就是整个子组件实例对象,包括props,data等等。此时访问到的就是子组件中的this上下文。

2. 依赖注入

上面我们提到数据通信方法仅限于父子组件之间,如果两个组件嵌套层级过深,子级组件如果想要访问父级组件的数据,就需要一层一层的传递props,这样显然是不合理的。因此Vue提供了依赖注入的方式来实现跨组件通信。

1.provide(提供)

我们可以在数据提供的组件中通过provide函数来提供数据,该函数接收两个参数:

  1. 第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会用注入名来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。

  2. 第二个参数是提供的值,值可以是任意类型,包括响应式的状态.

    html 复制代码
    <script setup>
    import { provide } from 'vue'
    
    provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
    </script>
    
    <!--  如果不使用 <script setup>,请确保 provide() 是在 setup() 同步调用的: -->
    <script>
    import { provide } from 'vue'
    
    export default {
      setup() {
        provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
      }
    }
    </script>

注意事项

  1. 该函数无论在那种写法下都需要显示的导入,且必须爱setup中才能使用。

  2. 该函数在Vue全局的app实例上默认是挂载的,因此我们可以通过app.provide来提供数据,这样所有的Vue组件均可以访问到该数据。(该方法也被称为应用级Provide)

    js 复制代码
    import { createApp } from 'vue'
    
    const app = createApp({})
    
    app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')

2. Inject (注入)

上面在父级组件中通过provide函数提供了数据,那么在子级组件中如何获取到该数据呢?Vue提供了inject函数来获取到父级组件提供的数据。

html 复制代码
<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

该函数接受两个参数:

  1. 第一个参数就是注入数据的名称,可以为字符串或 Symbol。
  2. 第二个参数是默认值,当没有任何的组件提供该数据时,会使用这个默认值。

注意事项

  1. 该函数无论在那种写法下都需要显示的导入,且必须爱setup中才能使用。

  2. 在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值。同时需要为该函数提供第三个参数并设置为true,代表第二个参数是一个工厂函数。

  3. 和响应式数据配合使用,这里可以注入一个ref响应式数据,通过在提供数据的时候为该数据提供一个修改的函数,作为一个对象的setter,这样就可以在子组件中修改该数据。

    html 复制代码
    <!-- 在供给方组件内 -->
    <script setup>
    import { provide, ref } from 'vue'
    
    const location = ref('North Pole')
    
    function updateLocation() {
      location.value = 'South Pole'
    }
    
    provide('location', {
      location,
      updateLocation
    })
    </script>
    html 复制代码
    <!-- 在注入方组件 -->
    <script setup>
    import { inject } from 'vue'
    
    const { location, updateLocation } = inject('location')
    </script>
    
    <template>
      <button @click="updateLocation">{{ location }}</button>
    </template>

3. 全局状态状态管理

如果我们的应用及其复杂,那么我们可能需要一个全局的状态管理来帮助我们更好的组织代码。

所谓的全局状态管理就是将应用中需要共享的数据抽取出来,在任意组件中都可以访问到这些数据。

1.VueX

VueX是一个专门为Vue.js设计的状态管理工具,它采用集中式存储管理应用的所有组件的状态。并且以响应的规则保证状态以一种可预测的方式发生变化。
基本使用

  1. 安装VueX

    bash 复制代码
    npm install vuex --save
  2. 创建store实例

    js 复制代码
    import { createStore } from 'vuex'
    const store = createStore({
      state() {
        return { count: 0 }
      },
      mutations: {
        increment(state) {
          state.count++
        }
      },
      actions: {},
      getters: {}
      modules: {}
    })
  3. 在Vue实例中注册store

    js 复制代码
    import { createApp } from 'vue'
    import App from './App.vue'
    import store from './store'
    const app = createApp(App)
    app.use(store)
    app.mount('#app')
  4. 在组件中访问store

    html 复制代码
    <template>
      <div>{{ count }}</div>
      <button @click="increment">+</button>
    </template>
    <script setup>
      import { useStore } from 'vuex'
      const store = useStore()
      </script>

注意事項:

  1. 虽然vuex仍然可以作为vue3的全局状态管理,但官方不在推荐使用vuex,而是推荐使用pinia。因此这里只是简单的介绍一下vuex的使用。

2.Pinia

pinia为Vue3官方推荐的状态管理工具,基本使用和vuex类似。

1. 基本使用
  1. 安装

    bash 复制代码
    npm install pinia --save
  2. 创建pinia实例

    js 复制代码
    import { createPinia } from 'pinia'
    const pinia = createPinia()
    export default pinia
  3. 创建store hook函数

    js 复制代码
    import { defineStore } from 'pinia'
    export const useStore = defineStore("main",{
      state: () => {
        return { count: 0 }
      },
      getters: {},
      actions: {}
    })
  4. 在Vue实例中注册pinia

    js 复制代码
    import { createApp } from 'vue'
    import App from './App.vue'
    import pinia from './pinia'
    const app = createApp(App)
    app.use(pinia)
    app.mount('#app')
  5. 在组件中访问store

    html 复制代码
    <template>
      <div>{{ count }}</div>
    </template>
    <script setup>
      import { useStore } from './store'
      const store = useStore()
    </script>
2.pinia各部分解析

(1).pinia创建

  • 在vue中我们如果需要使用pinia,就需要创建一个pinia实例,然后将实例挂载到app上。
  • 这里的创建pinia实例需要使用createPinia函数,该函数会返回一个pinia实例。使用时需要从pinia导入这个函数。

(2).store hook函数

我们需要在pinia中存放数据就需要由store,pinia提供了hook函数方式来创建store。我们首选需要从pinia中导入defineStore函数,该函数接收两个参数。

  1. 第一个 参数是个字符串,用来定义store的名称。
  2. 第二个参数可以是一个对象,也可以是一个工厂函数。
    • 如果是对象,则该对象中需要定义state,getters,actions三个属性。其中state属性是一个函数,用来定义state数据,getters属性是一个对象,用来定义getters(计算属性),actions属性是一个对象,用来定义actions(方法)。(state定义的数据均为响应式数据)
    • 如果为函数,急需要在该函数中返回一个对象,该对象的属性就会被作为store的属性。这里返回的对象的属性值一般是响应式数据。可以通过ref,computed等来定义。如果属性值为ref对象,则会被自动解包。同时作为store的state存在,如果是computed对象,则会被自动处理为getters(也会自动解包)。如果是一个函数,则会被自动处理为action。
  3. defineStore函数会返回一个hook函数,该函数可以用来创建store实例。调用这个函数不需要传入任何参数,该函数会返回一个store实例。
  4. 因此我们一般把这些hook函数通过一个文件统一导出,便于在组件中导入使用。

(3).组件内部使用

我们将hook函数导入之后,直接通过无参调用,即可得到有个store实例,可以在组件中直接调用对应的属性获取到数据。

3. 数据持久化

pinia本身的数据是存于内存当中,当再次刷新页面时,数据就会丢失。如果我们需要将数据持久化,可以使用pinia的插件来实现。pinia-plugin-persist

  1. 安装pinia-plugin-persist

    bash 复制代码
    npm install pinia-plugin-persist --save
  2. 在pinia实例中注册插件

    js 复制代码
    import { createPinia } from 'pinia'
    import piniaPluginPersist from 'pinia-plugin-persist'
    const pinia = createPinia()
    pinia.use(piniaPluginPersist)
    export default pinia
  3. 在store中开启持久化

    js 复制代码
    import { defineStore } from 'pinia'
    export const useStore = defineStore("main",{
      state: () => {
        return { count: 0 }
      },
      getters: {},
      actions: {},
      persist: {
        enabled: true,
        strategies: [
          {
            key: 'main',
            storage: localStorage,
            paths: ['count']
          }
        ]
      }
    })
  • persist属性是一个对象,该对象中需要定义enabled和strategies两个属性。
  • enabled属性是一个布尔值,用来开启或关闭持久化。
  • strategies属性是一个数组,用来定义持久化的策略。该数组中可以定义多个策略,每个策略都是一个对象,该对象中需要定义key,storage和paths三个属性。
  • key属性是一个字符串,用来定义持久化的key。
  • storage属性是一个对象,用来定义持久化的存储方式。可以是localStorage,sessionStorage,cookie等。
  • paths属性是一个数组,用来定义需要持久化的属性。如果不定义,则默认持久化所有属性。
  • 如果需要持久化所有属性,则可以不定义paths属性。

如果defineStore函数中定义的是工厂函数,则该函数会接受第三个参数,该参数是一个对象,该对象中需要定义persist属性。属性的值和上面一样。同时persist属性可以简写,接收一个布尔值作为值,表示开启或关闭持久化。其缓存策略默认会使用localStorage。key默认会使用store的名称。paths默认会使用所有属性。

拓展: 这个插件通过指定保存策略的不同,也可以在uniapp中使用。

相关推荐
轻口味11 分钟前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王1 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀1 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef3 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6414 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云4 小时前
npm淘宝镜像
前端·npm·node.js