Vue2的16种传参通信方式

前言

先直入主题列出有哪些传参方式,下面再通过事例一一讲解。

props(父传子)
$emitv-on (子传父)
EventBus (兄弟传参)
.syncupdate: (父子双向)
v-model (父子双向)
ref
$children$parent
$attrs$listeners (爷孙双向)
provideinject (多层传参)
Vuex (全局)
Vue.prototype (全局)
路由
浏览器缓存 (全局)
window (全局)
$root (顶层)
slot(父传子)

一、props(父传子)

思路简述:父组件直接用冒号 :绑定变量,然后子组件通过 props接收父组件传过来的内容。

父组件代码: 核心代码在第3行,直接用:message="message"传参。

<template>
  <div>
    <child  :message="message" />
  </div>
</template>

<script>
import child  from './child .vue';
export default {
  components: {
    child 
  },
  data() {
    return {
      message: '这是父组件传过去的'
    };
  }
};
</script>

子组件代码:props接收消息后可以直接使用,如下第3行和第16行中直接使用

注意: props有两种接收方法,如下第9行注释的就是简写用法,此用法不能设置默认值。

<template>
  <div>
    <p>接收到的消息: {{ message }}</p>
  </div>
</template>

<script>
  export default {
    //props:['message'],
    props: {
      message: {
        type: String,
        default: '', // 这里能设置默认值,如果父组件没有传参,默认值会生效
      },
    },
    mounted() {
      console.log(this.message);
    },
  };
</script>

注意: 此传参方式是单向的,即子组件接收到父组件的数据后,是不能直接修改props接收的数据,否则会直接报错。

二、$emitv-on (子传父)

思路简述: 子组件通过$emit触发父组件的指定方法并且在此方法中携带任意参数,父组件通过在被触发的方法中拿到携带的参数完成子传父。

语法:$emit(方法名,参数)

子组件代码: 核心代码在第11行,触发方法并且携带参数。

<template>
  <div>
    <button @click="sendParent">点击发送数据给父组件</button>
  </div>
</template>

<script>
  export default {
    methods: {
      sendParent() {
        this.$emit('my-event', '这是传递的参数');
      },
    },
  };
</script>

父组件代码: 核心代码在第3行触发事件,获取到子组件的传参。

<template>
  <div>
    <child v-on:my-event="childEvent" />
    // 或者用@简写代替v-on
    <child @my-event="childEvent" />
  </div>
</template>

<script>
  import child from './child.vue';
  export default {
    components: {
      child,
    },
    methods: {
      childEvent(item) {
        console.log('接收到子组件的传参:', item);
      },
    },
  };
</script>

三、EventBus (兄弟传参)

思路简述: 先创建一个全局的事件总线Bus(可以随意命名),并挂载在Vue.prototype上。

然后兄弟组件A通过$emit发送参数,兄弟组件B通过$on接收参数。

  • 有两种使用方法,下面分别讲解。

**方法一:**直接挂载全局事件总线,全局直接使用不需要额外引入。

先在项目的入口文件中(main.jsmain.ts)创建全局事件Bus并且挂载在Vue.prototype*

import Vue from 'vue';

const Bus = new Vue();

Vue.prototype.$Bus = Bus;

兄弟组件A代码: 通过this.$Bus.$emit(方法名,参数)发送参数。

<template>
  <div>
    <button @click="sendSibling">点击发送内容给兄弟组件</button>
  </div>
</template>

<script>
  export default {
    methods: {
      sendSibling() {
        this.$Bus.$emit('my-event', '参数');
      },
    },
  };
</script>

兄弟组件B代码: 通过this.$Bus.$on(对应$emit的方法,本地方法)触发本地的方法,从而接收参数。

<template>
  <div> 我是兄弟组件B </div>
</template>

<script>
  export default {
    created() {
      this.$Bus.$on('my-event', this.handleMessage);
    },
    beforeDestroy() {
      this.$Bus.$off('my-event', this.handleMessage);
    },
    methods: {
      handleMessage(message) {
        console.log('来自兄弟的参数:', message);
      },
    },
  };
</script>

注意 : 如上第10-12行所示,在组件销毁前要在 beforeDestroy 生命周期中使用$off移除移除$on的事件监听器,防止避免内存泄漏影响性能。如下所示

方法二: 不创建全局事件总线,单独开一个文件,哪里需要就哪里引用。

创建一个单独文件命名为Bus.js(可以自由命名)

import Vue from "vue"

export default new Vue()

兄弟组件A代码: 先引入Bus.js文件,然后通过Bus.$emit(方法名,参数)发送参数。

<template>
  <div>
    <button @click="sendSibling">点击发送内容给兄弟组件</button>
  </div>
</template>

<script>
  import Bus from './Bus.js';
  export default {
    methods: {
      sendSibling() {
        Bus.$emit('my-event', '参数');
      },
    },
  };
</script>

兄弟组件B代码: 先引入Bus.js文件,然后通过Bus.$on(对应$emit的方法,本地方法)触发本地的方法,从而接收参数。同样也需要使用$off销毁事件监听。

<template>
  <div> 我是兄弟组件B </div>
</template>

<script>
  import Bus from './Bus.js';
  export default {
    created() {
      Bus.$on('my-event', this.handleMessage);
    },
    beforeDestroy() {
      Bus.$off('my-event', this.handleMessage);
    },
    methods: {
      handleMessage(message) {
        console.log('来自兄弟的参数:', message);
      },
    },
  };
</script>

四、.syncupdate: (父子双向)

思路简述: .sync其实是一个语法糖, 配合子组件用 this.$emit('update:绑定的属性名', 方法)修改父组件属性, 能解决 props只能单向传递的问题。

父组件代码: 核心代码在第3行,比普通的父传子多使用了.sync修饰符。

<template>
  <div>
    <chile :myprop.sync="myData" />
  </div>
</template>

<script>
import chile from './chile.vue';

export default {
  components: {
    chile
  },
  data() {
    return {
      myData: '父组件数据'
    };
  }
};
</script>

子组件代码: 核心代码是第14行,通过this.$emit同步修改父组件内容。

<template>
  <div>
    <button @click="updateData">点击子修改父传过来的数据</button>
  </div>
</template>

<script>
  export default {
    props: {
      myprop: String,
    },
    methods: {
      updateData() {
        this.$emit('update:myprop', 新内容);
      },
    },
  };
</script>

注意: 使用.sync修饰符时,this.$emit里面总是以update:开头,后面接要修改的属性名称。

五、v-model (父子双向)

思路简述:v-model最常用于表单,它其实是一个语法糖,并且和上面.sync有点类似。v-model本质上是v-bind:value@input组件效果。通过v-bind:value绑定数据父传子,通过@input触发对应事件子传父从而实现双向绑定。

父组件代码: 直接用v-model绑定要传给子组件的参数,当子组件触发input事件时父组件myData会同步更新。

<template>
  <div>
    <child v-model="myData" />
  </div>
</template>

<script>
import child from './child.vue';

export default {
  components: {
    child
  },
  data() {
    return {
      myData: '天天鸭'
    };
  }
};
</script>

子组件代码:input输入框的内容发生变化时,就会触发@input事件,然后this.$emit同步修改父组件的值

<template>
  <div>
    <input :value="childData" @input="handleChange" />
  </div>
</template>

<script>
export default {
  model: {
    prop: 'myProp',
    event: 'input'
  },
  props: {
    myProp: String
  },
  data() {
    return {
      childData: this.myProp
    };
  },
  methods: {
    handleChange(event) {
      this.childData = event.target.value;
      this.$emit('input', this.childData);
    }
  }
};
</script>

注意: 在子组件当中,必须要定义model来指定props和事件名称(名称默认为input)。

六、ref

思路讲解: ref主要用来访问子组件的方法和属性,是直接操纵DOM的方式。主要用法是在子组件上绑定一个ref,然后父组件用this.$refs直接访问子组件的方法

父组件代码: 子组件上用ref="refChild"绑定一个ref,然后用this.$refs.refChild获取到子组件实例,能获取到子组件属性和操纵子组件方法。

<template>
  <div>
    <child ref="refChild" />
  </div>
</template>

<script>
  import child from './child.vue';
  export default {
    components: {
      child,
    },
    mounted() {
      let childObj = this.$refs.refChild;
      console.log(childObj.name); // 直接获取到子组件的属性内容 打印出来:天天鸭
      childObj.childMethod('参数'); // 触发子组件的方法
    },
  };
</script>

子组件代码:

<template>
  <div></div>
</template>

<script>
  export default {
    data() {
      return {
        name: '天天鸭',
      };
    },
    methods: {
      childMethod(val) {
        console.log(val);
      },
    },
  };
</script>

七、$children$parent

简述: $children$parentVue用于访问子组件实例和父组件实例的特殊属性。其中$children能获取所有子组件实例但不能获取孙子的,而$parent获取当前组件的父组件实例。

父组件代码: 直接使用this.$children即可获取。

注意: 获取到的实例可能为空,因此需要判空。并且如果有多个子组件时返回的是一个数组,所以需要通过下标确认对应的子组件数据。

<template>
  <div>
    <child />
    <button @click="getChildMethod">调用子组件方法</button>
  </div>
</template>

<script>
  import child from './child.vue';
  export default {
    components: {
      child,
    },
    methods: {
      getChildMethod() {
       // 判空,然后用下标获取
        if (this.$children.length > 0) {
          this.$children[0].childMethod();  // 使用子组件方法
          this.$children[0].name;    // 使用子组件属性
        }
      },
    },
  };
</script>

子组件代码: 类似地,父组件也是同样用法,但区别是返回的不是数组而且一个对象,能直接使用。

<template>
  <div></div>
</template>

<script>
  export default {
    mounted(){
        this.$parent.parMethod()
        this.$parent.name
    }
  };
</script>

八、$attrs$listeners (爷孙双向)

简述: $attrs$listeners相当于是使用在父亲组件上的一个中转站。 $attrs用于将props外的数据从爷组件传递给孙组件的,而$listeners用于从孙组件中触发爷组件中事件达到传参效果。

下面把$attrs$listeners分开讲解更易于理解。

$attrs使用流程代码:

(1)爷组件代码 : 类似父传子,正常用冒号绑定属性传参。

<GrandParent :name=name></GrandParent>

(2)父组件代码$attrs作用在父组件,意思是把props之外属性全部传递给到孙子。

注意:如果这里父组件用 props接收了 name属性,那么用 $attrs无法传递到孙子组件,因为只能传递 props之外属性。

<Parent v-bind="$attrs"></Parent>

(3)孙组件代码 :类似父传子,正常用popos接收爷组件传过来的参数。

<template>
  <div> 爷组件传来的:{{ name }} </div>
</template>
<script>
  export default {
    props: {
      name: {
        default: String,
      },
    },
  };
</script>

$listeners使用流程代码:

(1)孙组件代码 直接this.$emit类似子传父

<template>
  <div>
    <el-button @click="update">点击孙传爷</el-button>
  </div>
</template>

<script>
  export default {
    methods: {
      update() {
        this.$emit('my-event', '孙传给爷的数据');
      },
    },
  };
</script>

(2)父组件代码: $listeners作用在父组件。

<Parent v-bind="$listeners"></Parent>

(3)爷组件代码: 类似子传父中的父组件,触发对应孙子组件this.$emit中的my-event事件接收到参数。

<template>
  <div>
    <Parent @my-event="getMyEvent" />
  </div>
</template>

<script>
  import Parent from './Parent.vue';
  export default {
    components: {
      Parent,
    },
    methods: {
      getMyEvent(val) {
        console.log('爷组件接收到的数据:', val);
      },
    },
  };
</script>

这里感觉算两种(爷传孙与孙传爷)传参方式了,但由于都是类似中转站效果,所以放一起说比较好理解。

九、provideinject (多层传参)

简述: provideinject无论多少层组件都能传参。顶层组件通过provide传参,下面所有组件都能用inject接收,而且子组件也能通过方法给顶层组件传参。

顶层组件代码: 核心代码在第8行的provide()中,可以传递常量、变量和方法。

<template>
  <div> </div>
</template>

<script>
  export default {
    provide() {
      return {
        name: '天天鸭',
        age: this.age,
        myMethod: this.myMethod,
      };
    },
    data() {
      return {
        age: '18',
      };
    },
    methods: {
      myMethod(data) {
        console.log('收到来自某个孙子的数据:', data);
      },
    },
  };
</script>

子孙组件代码: 核心代码在第10行接收参数, 除了能接收顶层参数外,还能通过参考第13行的用法,通过顶层给到的方法传参给顶层组件。

<template>
  <div>
    <el-button @click="myMethod('我是孙子组件')">我是孙子组件</el-button>
    这是顶层传下来的参数: {{ name }}
  </div>
</template>

<script>
  export default {
    inject: ['name', 'age', 'myMethod'],
    methods: {
      myMethod(data) {
        this.myMethod(data); // 传参给顶层祖先组件
      },
    },
  };
</script>

十、Vuex (全局)

有针对性写过对应的文章,可以直接跳转细看:对比学习vuex和pinia用法

十一、Vue.prototype (全局)

简述 :能用Vue.prototype把任何属性和方法挂载在Vue实例了,让所有Vue实例共用。

(1)挂载属性 直接往Vue.prototype挂载即可

Vue.prototype.$testName = '天天鸭';

(2)挂载方法 直接往Vue.prototype挂载即可

Vue.prototype.$testMethod = function(val) {
  console.log(val);
};

调用: 直接在任何页面用this调用

this.$appName;

this.$testMethod('参数');

十二、浏览器缓存

简述: localStoragesessionStorage:主要是浏览器用来持久化存储的,这算是用的不多,但也是必用的一种通信方式。两者区别如下

sessionStorage(临时存储):最大空间 5M,为每一个数据源维持一个存储区域,但只在浏览器打开期间存在,关闭后数据会不会消失,包括页面重新加载。
localStorage(长期存储):最大空间 5M,与 sessionStorage 一样,但是哪怕浏览器关闭后,数据依然会一直存在,除非手动删除。

具体用法如下所示:

// 存储
sessionStorage.setItem('key', 'value');
localStorage.setItem('key', 'value');

// 获取
let valueFromSessionStorage = sessionStorage.getItem('key');
let valueFromLocalStorage = localStorage.getItem('key');

// 删除
sessionStorage.removeItem('key');
localStorage.removeItem('key');

// 清空所有
sessionStorage.clear();
localStorage.clear();

注意: 存储的数据只能是字符串形式,因此如果要存储对象或者数组,则需要使用JSON.stringify来转换后再存储,读取后用JSON.parse还原。

十三、window (全局)

简述: 直接用语法 window.age = '18' 定义然后全局通用即可。(此方式是存放在内存刷新会清空)

注意 :在 Vue 应用中,虽然可以直接将属性挂载到 window 对象上实现全局通用,但并推荐,因为这可能会出现命名冲突、导致代码难以维护

添加属性和方法:直接定义,可以是属性也可以是对象

window.Obj = { test: '挂载对象' }
window.name = '天天鸭'

使用:

console.log( window.Obj); 
console.log( window.name);  

十四、路由

简述: Vue在路由跳转时携带参数其实也很常用的方式,下面汇总一下三种路由传参。

(1)通过 params 传参 跳转页面用法:

this.$router.push({name:"index", params:{id}})

目标页面接收参数:

this.$route.params.id 

(2)通过 query 传参

跳转页面用法:有几种方式

this.$router.push({ name:"index", query:{id}})

this.$router.push({ path:"/index", query:{id}}) 

this.$router.push('/index?name='+obj.name+'&age='+obj.age)

目标页面接收参数:

this.$route.query.id

(3)通过动态路由传参

注意: 如果用动态路由传参需要对路由进行配置;并且参数会在 url 中显示。 如下所示,在path后面要配置:name/:age.

{ 
  path: '/about/:name/:age' ,  
  name: 'About', 
  component() => import('@/views/About.vue') 
}

跳转页面用法:

this.$router.push('/about/'+obj.name+'/'+obj.age) 

目标页面接收参数:

this.$route.params

十五、$root (顶层)

简述: 可以通过 $root 访问到整个 Vue 树的根实例,也就是可以使用 $root 来访问全局的属性或者修改全局的属性。

示例 :在main.js文件中定义一个globalName属性,可以全局使用。

import App from './App.vue';
import Vue from 'vue';

new Vue({
  el: '#app',
  render: h => h(App),
  data: {
    globalName: '天天鸭'
  }
});

在下层任意组件使用或者修改内容

<template>
  <div>
    {{ globalName }}
  </div>
</template>

<script>
export default {
  mounted() {
    this.$root.globalName = '修改数据';
  },
};
</script>

十六、slot(父传子)

简述: 通过插槽也是可以传递参数,这也是很多人忽略的一种方式。父组件可以通过插槽向子组件传递参数,然后子组件拿到参数进行渲染。

下面主要讲解具名插槽和默认插槽两种使用方式。

(1)具名插槽用法

子组件代码:slot定义一个名叫header的插槽。

<template>
  <div>
    <slot name="header"></slot>
  </div>
</template>

<script>
export default {
};
</script>

父组件代码:v-slot:header向子组件的header插槽传递内容。这边传递什么那边就会在对应区域显示什么。

<template>
  <div>
    <child>
        <template v-slot:header>
          <h1>这是插槽的内容</h1>
        </template>
    </child>
  </div>
</template>

<script>
import child from './child.vue';

export default {
  components: {
    child
  }
};
</script>

(2)无参数传递的默认插槽

子组件代码:slot定义插槽时不需要指定name名称。

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
};
</script>

**父组件代码:**不需要指定插槽名称,只要在组件中间填写内容就会渲染在默认插槽中。

<template>
  <div>
    <child>
      <p>默认插槽中的内容。</p>
    </child>
  </div>
</template>

<script>
import child from './child.vue';

export default {
  components: {
    child
  }
};
</script>
相关推荐
慢慢雨夜20 分钟前
uniapp 苹果安全域适配
java·前端·uni-app
凄凄迷人23 分钟前
前端基于Rust实现的Wasm进行图片压缩的技术文档
前端·rust·wasm·图片压缩
敲代码不忘补水25 分钟前
二十种编程语言庆祝中秋节
java·javascript·python·golang·html
我码玄黄32 分钟前
JS 的行为设计模式:策略、观察者与命令模式
javascript·设计模式·命令模式
史努比的大头37 分钟前
前端开发深入了解性能优化
前端
码农研究僧39 分钟前
Java或者前端 实现中文排序(调API的Demo)
java·前端·localecompare·中文排序·collator
营赢盈英1 小时前
OpenAI API key not working in my React App
javascript·ai·openai·reactjs·chatbot
吕永强1 小时前
HTML表单标签
前端·html·表单标签
范特西是只猫2 小时前
echarts map地图动态下钻,自定义标注,自定义tooltip弹窗【完整demo版本】
前端·javascript·echarts