Vue脚手架搭建项目TodoList-Vue组件之间的消息通信

Vue脚手架-TodoList案例(Vue组件间消息通信)

最近在学习尚硅谷的Vue教程,通过TodoList案例的演示贯穿学习了组件化的思路,以及学习Vue 组件间消息通信的方式。

Vue脚手架初始化
第一步:使用npm安装

npm install -g @vue/cli

第二步:切换到你要创建项目的目录,然后使用命令创建项目

vue create xxxx

第三步:启动项目

npm run serve

配置淘宝镜像:

npm config set registry https://registry.npm.taobao.org

在项目文件中有vue.config.js可以设置相关配置(例如关闭语法检查):

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false  //关闭语法检查
})

没有显示该文件自行新建

组件化编程思路

如图:
在这里插入图片描述
Vue实例对象管理App,App组件管理所有的子组件,并且app组件为了所有组件都能使用数据,所以数据模型(todos-model)放在了app中,其相应的方法也在app中创建。

<script>
import TodoHeader from "./components/TodoHeader";
import TodoList from "./components/TodoList";
import TodoFooter from "./components/TodoFooter";

export default {
  name: "App",
  data() {
    return {
      todos: [
        {
          id: "001",
          title: "DayDayCoding",
          done: false,
        }
      ],
    };
  },
  methods: {
    /**
     * 添加一个todo
     */
    addTodo(todo){
        this.todos.unshift(todo)
    },
    /**
     * 勾选修改Todo.done
     */
    handleCheck(id) {
      this.todos.forEach((todo) => {
        if(todo.id === id){
          todo.done = !todo.done
        }
      })
    },
    /**
     * 删除一个todo
     */
    deleteTodo(id){
      this.todos = this.todos.filter((el)=>{
        return el.id !== id
      })
    },
    /**
     * 全选/不选
     */
    checkAll(isAll){
      this.todos.forEach((todo)=>{
         todo.done = isAll
      })
    },
    /**
     * 删除全部已完成的任务
     */
    clearAll(){
      if(confirm('确定删除全部已完成的任务吗?')){
        this.todos = this.todos.filter((todo)=>{
          return !todo.done
        })
      }
    }
  },
  components: {
    TodoHeader,
    TodoList,
    TodoFooter,
  },
};
</script>

组件间通信的方式

一、父组件传输数据到子组件
(一)
props属性传输数据、方法到子组件,子组件接收数据和方法并且直接使用,只能父组件到子组件,不能反向以及不能跨越父子关系传递

示例:

//App.vue
...
<TodoList :todos="todos" :handleCheck="handleCheck" :deleteTodo="deleteTodo"></TodoList>
...
//TodoList.vue
...
export default {
	props:['todos','handleCheck','deleteTodo'],
    components:{
        TodoItem
    }
}
...
//TodoItem.vue
...
<script>
export default {
    name:'TodoItem',
	props:['todo','handleCheck','deleteTodo'],
	methods: {
		changeCheck(id){
			this.handleCheck(id)
		},
		handleDelete(id){
			if(confirm('确定删除这个任务吗?')){
				this.deleteTodo(id)
			}
		}
	},
}
</script>

这里为了子组件获取todos这个数据模型使用props,同时为了TodoList的子组件TodoItem能够使用,在此进行了传递。

props:['name'] //用于接收数据和方法 而在标签上使用:name="data"是将数据动态绑定到name身上使得props接收

二、子组件 ===> 父组件
(一)使用自定义组件方法

为组件标签自定义事件,通过触发该事件实现子组件到父组件的数据传递
使用v-on:事件名=“触发的函数” 或者 @事件名=“触发的函数”

优点:不需要使用props接收与传递

//自定义组件事件(App.vue)
<TodoList :todos="todos" @getList="getList" ></TodoList>
...
methods:{
	getList(){
		console.log('getList(data)被调用了')
	}
}
...
//TodoList.vue
<button @click='handleGetList'></button>
...
methods:{
	//触发自定义事件getList(data)
	handleGetList(){
		this.$emit('getList',this.data)
	}
}

(二)使用ref属性

在组件标签上使用ref属性,可以通过this.refs.属性名 获取组件实例对象然后触发其定义的方法
示例:

//自定义组件事件(App.vue)
<TodoList ref="todolist" ></TodoList>
...
mounted:{
	//使用钩子,当组件挂载完毕之后绑定一个自定义事件
	//这里getList代表自定义事件名,this.getList()是绑定的具体方法
	this.$refs.todolist.$on('getList',this.getList)
}
methods:{
	getList(){
		console.log('getList(data)被调用了')
	}
}
...
//TodoList.vue
<button @click='handleGetList'></button>
...
methods:{
	//触发自定义事件getList(data)
	handleGetList(){
		this.$emit('getList',this.data)
	}
}

总结:如何使得父组件接收到子组件的数据?

  1. 巧妙地利用props可以传递方法的性质,将父组件方法传递到子组件,子组件在调用父组件的方法时传入子组件的数据,父组件方法中就接收到了子组件的数据。
  2. 最好使用自定义组件事件的方式
  3. 当父组件==>子组件 为数据时使用props,而一般传方法给子组件是父组件从子组件那里调用方法获取数据,因此就使用自定义组件事件的方法

props缺点:这个方法好在父到子之前的通信,但是对于跨越父子关系的组件间的通信显得有点冗余,而兄弟关系(并行关系)则需要一边通过父组件方法接受子组件的数据再传给另一边,就复杂了。

组件标签如何使用原生事件?

组件标签使用原生事件需要使用原生事件名.native

<TodoItem @click.native='getItem'></TodoItem>

二、建立全局事件总线实现任意两个组件间通信

在这里插入图片描述
简单的了解全局事件总线的原理:
当App中的任意一个组件想要与另外一个组件通信时,右上角与App平行的一个可用的实例对象(X)将需要通信的组件的事件绑定在自身,然后目标组件去触发X绑定的事件,将数据返回,实现组间通信。这样一来,不管组件之间什么关系,都可以只通过绑定事件到X以及去对应组件触发事件就可以实现通信了。关键是如何去找到这个具体的可用的X。

X:1. 所有组件能用 2.能够调用 $on $emit $off 方法

Vue中存在的重要关系:

VueComponent.prototype._proto_===Vue.prototype

这说明Vue中的组件实例对象能够访问Vue原型上的属性与方法
于是这里可以通过将vm(Vue实例对象)给到 Vue.prototype属性,具备X的能力。在创建Vue实例的过程当中:有 beforeCreate() created()
beforeMounted() mounted()
beforeUpdate() updated()
beforeDestroy destroy()
八个生命周期,在beforeCreated()中建立全局事件总线($bus)而在每个组件的beforeDestroy()中取消事件的绑定

代码:

new Vue({
  beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线,$bus(这是自定义的名字,this是当前的Vue实例对象)
	},
  render: h => h(App),
}).$mount('#app')

应用场景:在上面的案例中当App组件想要与TodoItem组件通信需要经过TodoList传递数据,而使用全局事件总线,就可以直接传递。

适用于这种多跨越的关系以及并行的兄弟组件关系。

代码实现:
A组件(App)

...
<TodoList :todos="todos" :handleCheck="handleCheck" :deleteTodo="deleteTodo"></TodoList>
...
<script>
...
export default {
  name: "App",
  components: {
    TodoHeader,
    TodoList,
    TodoFooter,
  },
  data() {
    return {
      todos:JSON.parse(localStorage.getItem('todos')) || []
    }
  },
  methods: {
   
    /**
     * 勾选修改Todo.done
     */
    handleCheck(id) {
      this.todos.forEach((todo) => {
        if(todo.id === id){
          todo.done = !todo.done
        }
      })
    },
    /**
     * 删除一个todo
     */
    deleteTodo(id){
      this.todos = this.todos.filter((el)=>{
        return el.id !== id
      })
    }
  },
  watch:{
    todos:{
      deep:true,
      handler(val){
        localStorage.setItem('todos',JSON.stringify(val))
      }
    }
  },
  beforeDestroy(){
    this.$bus.off(['handleCheck','deleteTodo'])
  }
};
</script>

B组件(TodoList 的子组件 TodoItem)

<template>
 ...
<input type="checkbox" :checked="todo.done" @change="changeCheck(todo.id)"/>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
...
</template>

<script>
export default {
    name:'TodoItem',
	props:['todo'],
	methods: {
		changeCheck(id){
			this.$bus.$emit('handleCheck',id)
		},
		handleDelete(id){
			if(confirm('确定删除这个任务吗?')){
				this.$bus.$emit('deleteTodo',id)
			}
		}
	},
}
</script>

三、使用消息订阅与发布实现组件间简单的通信(Vue中用的不多)

推荐使用一个外部js库:pubsub-js

代码实例:
组件A订阅组件B的消息实现通信

//组件A
<script>
import pubsub from 'pubsub-js'
export default {
    ...
    props:['todos']
	methods: {
		...
	},
	mounted(){
		//参数:消息名,绑定的回调函数(参数:消息名,数据)
		//该方法返回一个Id用于解除消息订阅
const this.pubId = pubsub.subscribe('handleCheck',(msgName,id) => {
     		 this.todos.forEach((todo) => {
      		 if(todo.id === id){
        		  todo.done = !todo.done
       		 }
      		})
		})
	},
	beforeDestroy(){
		//解除消息订阅
		pubsub.unsubscribe(this.pubId)
	}
}
</script>

组件B

<script>
import pubsub from 'pubsub-js'
export default {
    ...
	methods: {
		//组件B向组件A发布消息
		handleSubsribe(data) {
     		 pubsub.publish('handleCheck',data)
   		},
	}
	...
}
</script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值