vue进阶——组件之间通信

Vue进阶——组件之间的通信

​ 在vue中组件是可复用的Vue实例,本质上是一个对象。而随着项目的不断增大,总是会有很多重复的模块,而把这些重复的模块提取出来封装成共用的组件。这无疑是大大的减少代码量与页面逻辑。当封装成组件时,如何给组件内变量赋值取值,这又成为了新的问题。这时候就需要了解几种组件之间的通信,更好的实现组件之间的信息的传递。

一、使用props属性:适用于父子组件之间的通信

在父组件中调用子组件,在组件中以自定义属性的方式给子组件传值,子组件中使用props接收父组件的值;

<template name="父组件">
  <div>
    <!-- 使用自定义事件的方式传递给子组件监听事件 -->
    <PageA :contA="contA" :textA="textA" @changeText="changeText" :objA="objA" />
    <Button type="success" @click="changeText">父组件</Button>
  </div>
</template>
<script>
import PageA from "@/components/pageA";
export default {
  components: {
    PageA
  },
  name: 'home',
  data() {
    return {
      contA: '父组件contA',
      textA: 5555,
      objA: {name: "哈哈"}
    }
  },
  methods: {
    // 父组件改变textA的值
    changeText(){
      this.textA++;
    }
  }
}
</script>

<template name="子组件">
  <div>
    <h2>{{contA}}--{{textA}}</h2>
    <Button type="success" @click="changeText">textA++</Button>
    <div @click="objChange">{{objA.name}}</div>
  </div>
</template>
<script>
export default {
  name: 'pageA',
  // 第一种props以数组的方式接收
  // props: ['contA','textA'],
  // 第二种props以对象形式接收可以给默认值
  props: {
    contA: {
      type: String,
      required: false, // 是否必须
      default: '默认内容'
    },
     status: {
      type: String,
      required: true, 
      validator: function (value) {
        return [
          'error', 
          'success',
          'fail',
        ].indexOf(value) !== -1
      } // 验证传递过来的是否是数组其中一项
  	},
    textA: {
      type: Number,
     	required: true,
      default: 77777
    },
    objA: {
      type: Object,
      default: {}
    }
  },
  methods: {
    changeText(){
      // 第一种改变父组件值:通知父组件改变变量
      this.$emit('changeText');
    },
    objChange(){
      // 第二种改变父组件的值:此时可以改变父组件里面的值,因为父组件将objA的内存地址传递过来了,父子组件同用一个objA的内存地址,所以这里改变父组件的值。(请根据需求谨慎使用)
      this.objA.name = "张三";
    }
  }
}
</script>
props属性总结:
  1. 父组件通过自定义属性方式传值给子组件,子组件使用props接收;
  2. props接收可以数组或对象的形式;
  3. 父组件传递的值是基本类型时,子组件不能直接改变父组件的值,需要使用自定义方法通知父组件更改;
  4. 父组件传递的值是引用类型时,子组件可以直接改变父组件的值,因为此时父子共用一个内存地址;

二、创建Vue实例作为事件中心 e m i t / emit/ emit/on事件监听器: 适用兄弟之间传值,稍微复杂一些情况建议使用下面的vuex;

​ 通过创建一个空的Vue实例作为事件中心,const EventVue = new Vue(), 发送方利用Event. e m i t ( ′ 事 件 名 称 ′ , 传 递 的 值 ) , 接 收 方 利 用 E v e n t . emit('事件名称',传递的值),接收方利用Event. emit(,)Event.on(‘事件名称’,事件处理函数); 注意:接收事件要放到created或之前触发监听;

<template name="兄弟A">
  <div>
  </div>
</template>
<script>
import Event from "@/utils/vm";
export default {
  name: 'pageA',
  data() {
    return {
      name: "a"
    }
  },
  methods: {
    send(){
      Event.$emit('global',this.name);
    }
  }
}
</script>

<template name="兄弟B">
  <div>
    <h2>{{name}}</h2>
  </div>
</template>
<script>
export default {
  name: 'pageB',
  data(){
    return {
      name: "888"
    }
  },
  created(){
    Event.$on('global',name=>{
      this.name = name;
    });
  }beforeDestroy(){
    // 关闭事件监听器
    Event.$off('global');
  }
}
</script>

e m i t / emit/ emit/on事件监听器总结:

  1. 首先必须要创建一个空的vue实例,通信的两个组件必须事件名一样。不同的组件通信事件名不能相同;
  2. 传递的数据属于单向数据流,并不是响应式的;
  3. 出于性能考虑可以在组件卸载前关闭事件监听器;
  4. 假如只需要监听一次事件时,可以使用Event.$once('事件名',处理函数);

三、vuex状态管理:适用于大型项目中的数据存储,从而实现页面中的通信;

​ 这里直接是使用vuex的方法,详细的vuex在之前笔记中,vue进阶——vuex状态管理。下面单纯的介绍一下用法:将多个页面需要的数据封装到一个 store 子仓库中,当需要的时候在仓库中拿去就好,利用 mutations 同步改变值;

// vuex仓库代码
<script>
 // 这里是子仓库,记得要在主仓库中的modules中挂载
export default {
  namespaced: true,
  state() {
    return {
      number: 22
    }
  },
  // 同步
  mutations: {
    SET_REDUCE(state) {
      state.number--
    },
    SET_ADD(state) {
      state.number++
    }
  }
}
</script>

<template>
  <div>
    <h2>vuex状态管理通信</h2>
    <div>组件A</div>
    <BaseStep class="base-step" :data="number" @reduce="SET_REDUCE" @add="SET_ADD" />
  </div>
</template>

<script>
import { mapMutations, mapState } from "vuex";
export default {
  // 利用计算器属性拿到store仓库数据
  computed: {
    ...mapState({
      number: state => state.Msg.number
    })
  },
  methods: {
    // 辅助方法映射出同步方法
    ...mapMutations("Msg", ["SET_REDUCE", "SET_ADD"]),
  }
};
</script>
// 组件B与上面一样把仓库先导出进组件,可以使用组件同步方法与数据
vuex状态管理通信总结:
  1. 这种情况适合大型项目,嵌套层级比较复杂的页面进行通信;
  2. 首先要在需要在store仓库中创建变量,在需要的组件中导入使用,同步方法看情况导入;
  3. 需要注意:在web页面中页面刷新后会使store变为初始值,利用本地存储保持状态的保持

四、ref、$parent和$children: 适用于父子组件之间通信

​ ref表示一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。 p a r e n t 与 parent与 parentchildren都属于vue实例方法;获取对应的父组件或子组件的实例和方法。

// 获取组件的父组件的组件实例,同时可以使用父组件的变量与方法
console.log(this.$parent);
// 获取组件的所有子组件的实例,返回的子组件实例数组利用唯一值_uid区分,也可以使用子组件的变量与方法
console.log(this.$children);
// 假设在组件A中引用的组件B,给组件B的标签中ref名,就可以获取组件B的实例
console.log(this.$refs.pageB);
总结:
  1. 获取$children时,在父组件的created生命周期之后获取,因为此时子组件还未完全渲染完,无法获取到所有子组件实例;具体与父子组件生命周期有关;
  2. 如果组件没有父组件时,$parent返回的组件实例就为本组件实例;

五、provide/inject:适用层级嵌套比较深的组件之间通信

​ 在vue2.2.0中新增的方法,祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。跨级组件之间建立了一种联系;

// 祖先组件中
  data() {
    return {
      objProvide: {
        id: 333
      }
    };
  },
  provide() {
    return {
      // 这里之所以绑定一个对象,主要是将内存地址传递给祖孙组件,这样数据就可以变成响应式
      objProvide: this.objProvide
    };
  },
 
 // 祖孙组件中接收值的方式可以效仿props
  // inject: ["objProvide"],
  inject: {
    objProvide: {
      type: Object,
      default: {}
    }
  },
总结:
  1. this._provided 指向当前vue实例的provide对象,可以在祖先组件方法中再次更改
  2. 官方文档中说provide与inject绑定不是响应式的,但是传入可监听的对象时就可以实现响应式;
  3. 此方法用于高阶组件,无论多少层都会被子组件拿到;
  4. provide 选项应该是一个对象或返回一个对象的函数,inject 选项应该是:一个字符串数组,或 一个对象

六、$attrs/$listeners: 适用于多级嵌套的组件之间传递值

​ 这对方法是vue2.4新增的,假设A组件传给子组件B自定义属性时,B组件没有使用props接收,便可以使用this.$attrs拿到剩余属性值;在B组件中有C组件,C组件中可以使用this. a t t r s 拿 到 A 组 件 的 值 。 前 提 是 B 组 件 中 引 用 C 组 件 时 导 入 了 ‘ attrs拿到A组件的值。前提是B组件中引用C组件时导入了` attrsABCattrs`对象;

// 以下组件的引入与注册省略了,请注意
// A组件
<template>
 	<B :obj1="obj1" @cFn="cFn" />
</template>
<script>
		data(){
      return {
        obj1: {id: 2}
      }
    },
    methods: {
      cFn(){
        console.log("C组件中调用方法")
      }
    }
</script>

// B组件
<template>
 	<C  v-bind="$attrs" v-on="$listeners" />
</template>
<script>
	mouthed(){
    console.log(this.$attrs,"可以获取A组件中的obj1对象");
  }
</script>
// C组件
<template>
 	<h1 @click="cFn">点击改变A组件的obj1</h1>
</template>
<script>
	mouthed(){
    console.log(this.$attrs,"可以获取A组件中的obj1对象");
  },
  methods: {
    cFn(){
      // 触发A组件的方法
      this.$emit('cFn');
      // 这里也可以直接改变A组件的值,因为传递过来的是一个对象
      this.$attrs.obj1.id = 2222;
    }
  }
</script>
方法总结:
  1. 首先在最外层把需要传递的值传给下层组件,只能监听未被prop获取到的,如果没有就是空对象;
  2. 利用v-bind="$attrs"与v-on="$lienters"就未被监听的数组传递给祖孙后代组件;
  3. 如果传递值是一个对象可以直接改变里面的值,如果是基本类型就需要使用的自定义事件通知祖先组件进行更改;
  4. Vue2.4还新增了inheritAttrs属性,Boolean类型,默认值为true。如果是不想

组件有继承特性,就可以设置为false,这样子代$attrs就拿不到值;

七、本地缓存进行组件之间的通信:无法做到数据的响应式

​ 有时候我们在很多地方都会需要常量,这些都是不经常变化的,例如登录信息等。每次调用接口获取就比较麻烦,这时就可以采用缓存进行存储,组件中需要时直接在缓存里面获取就好。注意:敏感信息最好不要存储到浏览器上,不安全。

各服务端缓存大小限制:
  1. H5端为localStorage,浏览器限制5M大小,可以设置过期时间,sessionStorage存在回话期,浏览器关闭就清除;
  2. pp端为原生的plus.storage,无大小限制,不是缓存,是持久化的。
  3. 各个小程序端为其自带的storage api,数据存储生命周期跟小程序本身一致,即除用户主动删除或超过一定时间被自动清理,否则数据都一直可用。
  4. 微信小程序单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
  5. 支付宝小程序单条数据转换成字符串后,字符串长度最大200*1024。同一个支付宝用户,同一个小程序缓存总上限为10MB。(百度、字节小程序未知)
  6. 非App平台清空Storage会导致uni.getSystemInfo获取到的deviceId改变
// 原生app本地存储
plus.storage.setItem(key, value);
// uniapp本地存储
uni.setStroageSync(key,value);
// web端
localStorage.setItem(key,value);
// 小程序端
wx.setStroageSync(key,value);

你可能感兴趣的:(vue,vue.js,javascript)