前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在业务代码中常用到的Vue数据通信方式

在业务代码中常用到的Vue数据通信方式

作者头像
Maic
发布2022-07-28 12:31:51
5.1K0
发布2022-07-28 12:31:51
举报
文章被收录于专栏:Web技术学苑

​​

vue中数据流是单向的,通常父子组件通信props或者自定义事件,或者还有provide/inject,甚至借助第三方数据流方案vuex,在通常的项目中我们会高频用到哪些通信方案?

本文是笔者总结过往项目,在vue使用到的一些数据通信方案,希望在实际项目中有些帮助和思考。

正文开始...

我们先看下在vue中我能想到的数据通信方案

1、props父传子

2、自定义事件@event="query"

3、.sync修饰符

3、vuex跨组件通信

4、Vue.observable

5、provide/inject

6、EventBus

7、refs、parent

基于以上几点,笔者用一个实际的todolist来举证所有的通信方式

props 父组件传递子组件数据的接口通信
代码语言:javascript
复制
// todoList.vue
<template>
  <div class="todo-list">
      <h1>todo list</h1>
      <Search />
      <Content :dataList="dataList"/>
  </div>
</template>

<script>
import Search from './Search.vue';
import Content from './Content.vue';
export default {
  name: 'todo-list',
  components: {Search, Content},
  data() {
    return {
      dataList: [
        {
          title: 'vuejs',
          subTitle: 'vuejs is crazy'
        },
        {
          title: 'reactjs',
          subTitle: 'reactjs is beautify'
        }
      ]
    }
  },
  methods: {}
}
</script>

父组件以Index.vue为例,传入的子组件Content.vueprops就是:dataList="dataList"Content.vue中我们可以看到就是通过props上的dataList获取父组件数据的。

代码语言:javascript
复制
<!--Content.vue-->
<template>
    <div class="content">
          <template v-for="(item, index) in dataList">
              <h1 :key="index">{{item.title}}</h1>
              <h2 :key="item.subTitle">{{item.subTitle}}</h2>
          </template>
      </div>
</template>

<script>
export default {
  props: {
    dataList: {
      type: Array,
      default: () => []
    }
  }
}
</script>

子组件数据通过父组件传递,页面数据就显示出来了

自定义事件emit通信
代码语言:javascript
复制
...
<div class="todo-list">
      <h1>todo list</h1>
      <Search @handleAdd="handleAdd"/>
      <Content :dataList="dataList"/>
  </div>
<script>
 export default {
    name: 'todo-list',
    methods: {
      handleAdd(params) {
        this.dataList.push(params)
      }
    }
  }
</script>

我们看到在父组件中加入了@handleAdd自定义事件

Search.vue中我们引入对应逻辑

代码语言:javascript
复制
<!--Search.vue-->
<div class="search">
    <a-row type="flex" justify="center" >
        <a-col :span="4">
           <a-input placeholder="Basic usage" v-model="value" @pressEnter="handleAdd"></a-input> 
        </a-col>
        <a-col :span="2">
             <a-button type="dashed" @click="handleAdd">添加</a-button>
        </a-col>
    </a-row>
  </div>
代码语言:javascript
复制
// Search.vue
export default {
  name: 'search',
  data() {
    return {
      value: '',
      odd: 0
    }
  },
  methods: {
    handleAdd() {
      const {value: title} = this;
      if (title === '') {
        return;
      }
      this.odd = !this.odd;
      this.$emit('handleAdd', {
        title,
        subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`
      })
    }
  }
}

我们可以看到自定义事件子组件中就是这么给父组件通信的

代码语言:javascript
复制
...
this.$emit('handleAdd', {
        title,
        subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`
})
.sync实现props的双向数据通信

在vue中提供了.sync修饰符,本质上就是便捷处理props单向数据流,因为有时候我们想直接在子组件中修改props,但是vue中是会警告的,如果实现props类似的双向数据绑定,那么可以借用.sync修饰符,这点项目里设计弹框时经常有用。

同样是上面todolist的例子

代码语言:javascript
复制
<template>
  <div class="todo-list">
      <h1>todo list-sync</h1>
      <Search :dataList.sync="dataList"/>
      <Content :dataList="dataList"/>
  </div>
</template>
<script>
import Search from './Search.vue';
import Content from './Content.vue';
export default {
  name: 'todo-list',
  components: {Search, Content},
  data() {
    return {
      dataList: [
        {
          title: 'vuejs',
          subTitle: 'vuejs is crazy'
        },
        {
          title: 'reactjs',
          subTitle: 'reactjs is beautify'
        }
      ]
    }
  },
}
</script>

我们在看下Search.vue已经通过:dataList.sync="dataList"props上加了修饰符了

Search.vue中可以看到

代码语言:javascript
复制
...
<script>
export default {
  name: 'search',
  props: {
    dataList: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      value: '',
      odd: 0
    }
  },
  methods: {
    handleAdd() {
      const {value: title, dataList } = this;
      if (title === '') {
        return;
      }
      this.odd = !this.odd;
      const item = {
        title,
        subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`
      }
      this.$emit('update:dataList', dataList.concat(item))
    }
  }
}
</script>

注意我们在handleAdd方法中修改了我们是用以下这种方式去与父组件通信的,this.$emit('update:dataList', dataList.concat(item))

代码语言:javascript
复制
...
const {value: title, dataList } = this;
const item = {
        title,
        subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`
}
this.$emit('update:dataList', dataList.concat(item))

sync本质也是利用自定义事件通信,上面代码就是下面的简版,我们可以利用.sync修饰符实现props的双向数据绑定,因此在实际项目中可以用.sync修饰符简化业务代码,实际与下面代码等价

代码语言:javascript
复制
<Search :dataList="dataList" @update="update"/>
vuex

vuex在具体业务中基本上都有用,我们看下vuex是如何实现数据通信的,关于`vuex`[1]如何使用参考官方文档,这里不讲如何使用vuex,贴上关键代码

代码语言:javascript
复制
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const state = {
    dataList: [
      {
        title: 'vuejs',
        subTitle: 'vuejs is crazy'
      },
      {
        title: 'reactjs',
        subTitle: 'reactjs is beautify'
      }
    ]
};
const mutations = {
    handAdd(state, payload) {
      state.dataList = state.dataList.concat(payload)
    }
}
export const store = new Vuex.Store({
  state,
  mutations
})

然后在main.js中引入

代码语言:javascript
复制
// main.js
...
import {store} from '@/store/index';
...
/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  router,
  components: { App },
  template: '<App/>'
})

我们看下主页面路由页面,现在变成这样了,父组件没有任何props自定义事件,非常的干净。

代码语言:javascript
复制
<template>
  <div class="todo-list">
      <h1>todo list-vuex</h1>
      <Search />
      <Content/>
  </div>
</template>
<script>
import Search from './Search.vue';
import Content from './Content.vue';
export default {
  name: 'todo-list',
  components: {Search, Content}
}
</script>

然后看下Search.vueContent.vue组件

代码语言:javascript
复制
<!--Search.vue-->
<template>
  <div class="search">
    <a-row type="flex" justify="center" >
        <a-col :span="4">
           <a-input placeholder="Basic usage" v-model="value" @pressEnter="handleAdd"></a-input> 
        </a-col>
        <a-col :span="2">
             <a-button type="dashed" @click="handleAdd">添加</a-button>
        </a-col>
    </a-row>
  </div>
</template>
<script>
export default {
  name: 'search',
  data() {
    return {
      value: '',
      odd: 0
    }
  },
  methods: {
    handleAdd() {
      const {value: title } = this;
      if (title === '') {
        return;
      }
      this.odd = !this.odd;
      const item = {
        title,
        subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`
      }
      this.$store.commit('handAdd', item);
    }
  }
}
</script>

你会发现操作数据是用$store.commit('mutationName', data)这个vuex提供的同步操作去修改数据的。在Content.vue中就是直接从store中获取state就行了

代码语言:javascript
复制
<template>
    <div class="content">
          <template v-for="(item, index) in dataList">
              <h1 :key="index">{{item.title}}</h1>
              <h2 :key="item.subTitle">{{item.subTitle}}</h2>
          </template>
      </div>
</template>

<script>
export default {
  computed: {
    dataList() {
      return this.$store.state.dataList;
    }
  }
}
</script>

vuex的思想就是数据存储的一个仓库,数据共享,本质store也是一个单例模式,所有的状态数据以及事件挂载根实例上,然后所有组件都能访问和操作,但是​这么简单的功能引入一个状态管理工具貌似有点杀鸡用牛刀了,接下来我们用官方提供的跨组件方案。

Vue.observable

vue提供一个这样的一个最小跨组件通信方案,我们具体来看下,新建一个目录todoList-obsever/store/index.js,我们会借鉴vuex的一些思想,具体代码如下

代码语言:javascript
复制
// store/index.js
import Vue from 'vue';
const state = {
  dataList: [
    {
      title: 'vuejs',
      subTitle: 'vuejs is crazy'
    },
    {
      title: 'reactjs',
      subTitle: 'reactjs is beautify'
    }
  ],
  commit: {
    handAdd:(payload) => {
      state.dataList = state.dataList.concat(payload)
    },
    handleDelete(index) {
      state.dataList.splice(index, 1);
    }
  }
};
const mutations = {
  commit(actionName, payload) {
    if (Reflect.has(state.commit, actionName)) {
      state.commit[actionName](payload)
    }
  },
  dispatch(actionName, payload) {
    mutations.commit(actionName, payload);
  }
}
const store = {
  state,
  ...mutations,
}
export default Vue.observable(store);

然后在Content.vue

代码语言:javascript
复制
<template>
    <div class="content">
           <template v-for="(item, index) in dataList">
            <div :key="index" class="list">
              <h1 :key="index">{{ item.title }}</h1>
              <h2 :key="item.subTitle">{{ item.subTitle }}</h2>
              <a-button
                type="danger"
                class="del"
                :key="`${index}-${item.title}`"
                @click="handleDelete(index)"
                >删除</a-button
              >
            </div>
    </template>
      </div>
</template>
<script>
// 引入上面的store
import store from './store/index';
export default {
  computed: {
    dataList() {
      return store.state.dataList;
    }
  },
  methods: {
    handleDelete(index) {
      store.commit('handleDelete', index)
    }
  }
}
</script>
<style lang="scss">
.list {
  .del {
    position: relative;
    top:-70px;
    left: 160px;
  }
}
</style>

Search.vue

代码语言:javascript
复制
<template>
  <div class="search">
    <a-row type="flex" justify="center" >
        <a-col :span="4">
           <a-input placeholder="Basic usage" v-model="value" @pressEnter="handleAdd"></a-input> 
        </a-col>
        <a-col :span="2">
             <a-button type="dashed" @click="handleAdd">添加</a-button>
        </a-col>
    </a-row>
  </div>
</template>

<script>
  // 引入store
import store from './store/index';
export default {
  name: 'search',
  data() {
    return {
      value: '',
      odd: 0
    }
  },
  methods: {
    handleAdd() {
      const {value: title } = this;
      if (title === '') {
        return;
      }
      this.odd = !this.odd;
      const item = {
        title,
        subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`
      }
      store.commit('handAdd', item);
    }
  }
}
</script>

ok这种方式算是代替vuex的一种解决方案,是不是比vuex更简单呢,而且不用引入任何第三方库,因此在你的业务代码中可以用此来优化部分跨组件的数据通信。

provide / inject

这是一个父组件可以向子孙组件透传数据的一个属性,也就是意味着在所有子孙组件,能拿到父组件provide提供的数据,具体可以看下下面例子

代码语言:javascript
复制
<template>
  <div class="todo-list">
      <h1>todo list-provide</h1>
      <Search @handleAdd="handleAdd"/>
      <Content />
  </div>
</template>

<script>
import Search from './Search.vue';
import Content from './Content.vue';
export default {
  name: 'todo-list',
  components: {Search, Content},
  data() {
    return {
      dataList: [
        {
          title: 'vuejs',
          subTitle: 'vuejs is crazy'
        },
        {
          title: 'reactjs',
          subTitle: 'reactjs is beautify'
        }
      ]
    }
  },
  provide(){
     return {
        newDataList: this.dataList
     }
  },
  methods: {
    handleAdd(params) {
      this.dataList.push(params)
    }
  }
}
</script>

我们在Content.vue组件中发现

代码语言:javascript
复制
<template>
    <div class="content">
          <template v-for="(item, index) in newDataList">
              <h1 :key="index">{{item.title}}</h1>
              <h2 :key="item.subTitle">{{item.subTitle}}</h2>
          </template>
      </div>
</template>

<script>
export default {
  inject: ['newDataList'],
 
}
</script>

子组件就用inject: ['newDataList']来接收数据了。注意一点inject一定是要与provide组合使用,且必须是在父子组件,或者父孙,或者更深层的子组件中使用inject

EventBus 总线事件

这种方式平时业务上也会有用得到,特别是在表单验证中就会有

代码语言:javascript
复制
// utils/eventBus.js

export default class EventBus {
  constructor() {
    this.events = {}
  }
  on(name, fn) {
    if (!this.events[name]) {
      this.events[name] = [];
    }
    this.events[name].push(fn);
  }
  emit(name, ...payload) {
    this.events[name].forEach(v => {
      Reflect.apply(v, this, payload); // 执行回调函数
    })
  }
}

mian.js中挂载到prototype

代码语言:javascript
复制
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
import eventBus from '@/utils/eventBus';
import { store } from '@/store/index';
import App from './App'
import router from './router'
Vue.config.productionTip = false
Vue.use(Antd);

/* eslint-disable no-new */
Vue.prototype.$my_event = new eventBus;
new Vue({
  el: '#app',
  store,
  router,
  components: { App },
  template: '<App/>'
});

然后在具体路由上我们看下

代码语言:javascript
复制
<template>
  <div class="todo-list">
      <h1>todo list-event-bus</h1>
      <Search />
      <Content :dataList="dataList"/>
  </div>
</template>

<script>
import Search from './Search.vue';
import Content from './Content.vue';

export default {
  name: 'todo-list',
  components: {Search, Content},
  data() {
    return {
      dataList: [
        {
          title: 'vuejs',
          subTitle: 'vuejs is crazy'
        },
        {
          title: 'reactjs',
          subTitle: 'reactjs is beautify'
        }
      ]
    }
  },
  created() {
  // 添加事件
    this.$my_event.on('handleAdd', (payload) => {
        this.dataList.push(payload);
    })
  }
}
</script>

Search.vue中我们可以看到,我们是用 this.$my_event.emit去触发事件的

代码语言:javascript
复制
<template>
  <div class="search">
    <a-row type="flex" justify="center" >
        <a-col :span="4">
           <a-input placeholder="Basic usage" v-model="value" @pressEnter="handleAdd"></a-input> 
        </a-col>
        <a-col :span="2">
             <a-button type="dashed" @click="handleAdd">添加</a-button>
        </a-col>
    </a-row>
  </div>
</template>
<script>
export default {
  name: 'search',
  data() {
    return {
      value: '',
      odd: 0
    }
  },
  methods: {
    handleAdd() {
      const {value: title} = this;
      if (title === '') {
        return;
      }
      this.odd = !this.odd;
      this.$my_event.emit('handleAdd', { title,subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`});
    }
  }
}
</script>
<style>

</style>
$parent或者$refs访问父组件或者调用子组件方法

这是项目中比较常用粗暴的手段,用一段伪代码感受下就行,不太建议项目里用this.$parent操作

代码语言:javascript
复制
<template>
  <div class="todo-list">
      <h1>todo list-event-bus</h1>
      <Search ref="search"/>
      <Content :dataList="dataList"/>
  </div>
</template>
<script>
import Search from './Search.vue';
import Content from './Content.vue';

export default {
  name: 'todo-list',
  components: {Search, Content},
  data() {
    return {
      dataList: [
        {
          title: 'vuejs',
          subTitle: 'vuejs is crazy'
        },
        {
          title: 'reactjs',
          subTitle: 'reactjs is beautify'
        }
      ]
    }
  },
  mounted() {
   // 能直接调用子组件的数据或者方法
    console.log(this.$refs.search.value)
  }
}
</script>

Search.vue组件中也能调用父组件的方法

代码语言:javascript
复制
<template>
  <div class="search">
    <a-row type="flex" justify="center" >
        <a-col :span="4">
           <a-input placeholder="Basic usage" v-model="value" @pressEnter="handleAdd"></a-input> 
        </a-col>
        <a-col :span="2">
             <a-button type="dashed" @click="handleAdd">添加</a-button>
        </a-col>
    </a-row>
  </div>
</template>

<script>
export default {
  name: 'search',
  data() {
    return {
      value: '',
      odd: 0
    }
  },
  methods: {
    handleAdd() {
      // 访问父类的初始化数据
      console.log(this.$parent.dataList)
      const {value: title} = this;
      if (title === '') {
        return;
      }
      this.odd = !this.odd;
      this.$my_event.emit('handleAdd', { title,subTitle: `${title} is ${this.odd ? 'crazy' : 'beautify'}`});
    }
  }
}
</script>

最后把这个todo list demo完整的完善了一下,点击路由可以切换不同todolist了,具体可以参考code example代码

总结

1、用具体实例手撸一个todolist把所有vue中涵盖的通信方式props,自定义事件vuexvue.observableprovide/injecteventBus实践了一遍

2、明白vuex的本质,实现了Vue.observable跨组件通信​

3、了解事件总线的实现方式,在vue中可以使用emit与on方式实现事件总线

4、本文代码示例:code example[2]

参考资料

[1]vuex: https://v3.vuex.vuejs.org/zh/installation.html#%E7%9B%B4%E6%8E%A5%E4%B8%8B%E8%BD%BD-cdn-%E5%BC%95%E7%94%A8

[2]code example: https://github.com/maicFir/lessonNote/tree/master/vue/02-vue通信的几种方式/webpack

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Web技术学苑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • props 父组件传递子组件数据的接口通信
  • 自定义事件emit通信
  • .sync实现props的双向数据通信
  • vuex
  • Vue.observable
  • provide / inject
  • EventBus 总线事件
  • $parent或者$refs访问父组件或者调用子组件方法
  • 总结
  • 参考资料
相关产品与服务
事件总线
腾讯云事件总线(EventBridge)是一款安全,稳定,高效的云上事件连接器,作为流数据和事件的自动收集、处理、分发管道,通过可视化的配置,实现事件源(例如:Kafka,审计,数据库等)和目标对象(例如:CLS,SCF等)的快速连接,当前 EventBridge 已接入 100+ 云上服务,助力分布式事件驱动架构的快速构建。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档