一个案例:Vue2组件化开发组件从入门到入土

1. 环境搭建

1.1. 创建项目

npm install -g @vue/cli


vue create vue_study_todolist

1.2. 清空项目代码

清楚HelloWorld.Vue代码中的内容。

1.3. 启动空项目

在这里插入图片描述

1.4 项目目标

项目组件实现以下效果

在这里插入图片描述

2. 组件拆分代码

Vue是一个基于组件的框架,允许您将界面拆分成小的、可重用的组件。每个组件都可以包含自己的模板、样式和逻辑,从而提高了代码的可维护性和重用性。

组件通常通过props和events来进行通信。Props允许父组件向子组件传递数据,而子组件则通过events将数据的变化通知给父组件。这种单向数据流的模式使得应用程序的数据流更加可控和预测。

在Vue中,您可以在组件之间传递函数,以实现更高级的交互和数据处理。这可以通过props来实现。您可以将一个函数作为prop传递给子组件,然后子组件可以调用这个函数来触发特定的操作。

例如,假设您有一个父组件和一个子组件,父组件传递了一个函数给子组件作为prop。子组件可以在某个事件发生时调用这个函数,从而通知父组件进行某些处理。

2.1. 组件拆分第一版

2.1.1. App.vue代码


<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :addTodo="addTodo"/>
        <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
        <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
      </div>
    </div>
  </div>
</template>

<script>
  import MyHeader from './components/MyHeader'
  import MyList from './components/MyList'
  import MyFooter from './components/MyFooter.vue'

  export default {
    name:'App',
    components:{MyHeader,MyList,MyFooter},
    data() {
      return {
        //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
        todos:[
          {id:'001',title:'抽烟',done:true},
          {id:'002',title:'喝酒',done:false},
          {id:'003',title:'开车',done:true}
        ]
      }
    },

    methods: {
      //添加一个todo
      addTodo(todoObj){
        this.todos.unshift(todoObj)
      },

      //勾选or取消勾选一个todo
      checkTodo(id){
        this.todos.forEach((todo)=>{
          if(todo.id === id) todo.done = !todo.done
        })
      },

      //删除一个todo
      deleteTodo(id){
        this.todos = this.todos.filter( todo => todo.id !== id )
      },

      //全选or取消全选
      checkAllTodo(done){
        this.todos.forEach((todo)=>{
          todo.done = done
        })
      },

      //清除所有已经完成的todo
      clearAllTodo(){
        this.todos = this.todos.filter((todo)=>{
          return !todo.done
        })
      }
    }
  }
</script>

<style>
  /*base*/
  body {
    background: #fff;
  }
  .btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }

  .btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
  }

  .btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
  }

  .btn:focus {
    outline: none;
  }

  .todo-container {
    width: 600px;
    margin: 0 auto;
  }

  .todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
  }
</style>

2.1.2. 各个子组件代码

2.1.2.1. MyHeader.vue 代码

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
    </div>
</template>
  

<script>
    import {nanoid} from 'nanoid'
    export default {
        name:'MyHeader',
        //接收从App传递过来的addTodo
        props:['addTodo'],
        data() {
            return {
                //收集用户输入的title
                title:''
            }
        },
        methods: {
            add(){
                //校验数据
                if(!this.title.trim()) return alert('输入不能为空')
                //将用户的输入包装成一个todo对象
                const todoObj = {id:nanoid(),title:this.title,done:false}
                //通知App组件去添加一个todo对象
                this.addTodo(todoObj)
                //清空输入
                this.title = ''
            }
        },
    }
</script>


<style scoped>
    /*header*/
    .todo-header input {
        width: 560px;
        height: 28px;
        font-size: 14px;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 4px 7px;
    }

    .todo-header input:focus {
        outline: none;
        border-color: rgba(82, 168, 236, 0.8);
        box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    }
</style>

2.1.2.2. MyList.vue 代码

<template>
    <ul class="todo-main">
        <MyItem
            v-for="todoObj in todos"
            :key="todoObj.id"
            :todo="todoObj"
            :checkTodo="checkTodo"
            :deleteTodo="deleteTodo"
        />
    </ul>
</template>

<script>
    import MyItem from './MyItem'
    export default {
        name:'MyList',
        components:{MyItem},
        //声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
        props:['todos','checkTodo','deleteTodo']
    }
</script>

<style scoped>
    /*main*/
    .todo-main {
        margin-left: 0px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding: 0px;
    }
  
    .todo-empty {
        height: 40px;
        line-height: 40px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding-left: 5px;
        margin-top: 10px;
    }
</style>

2.1.2.3. MyItem.vue 代码

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
            <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
            <!-- <input type="checkbox" v-model="todo.done"/> -->
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    </li>
</template>

  
<script>
    export default {
        name:'MyItem',
        //声明接收todo、checkTodo、deleteTodo
        props:['todo','checkTodo','deleteTodo'],
        methods: {
            //勾选or取消勾选
            handleCheck(id){
                //通知App组件将对应的todo对象的done值取反
                this.checkTodo(id)
            },
            //删除
            handleDelete(id){
                if(confirm('确定删除吗?')){
                    //通知App组件将对应的todo对象删除
                    this.deleteTodo(id)
                }
            }
        },
    }
</script>

<style scoped>
    /*item*/
    li {
        list-style: none;
        height: 36px;
        line-height: 36px;
        padding: 0 5px;
        border-bottom: 1px solid #ddd;
    }

    li label {
        float: left;
        cursor: pointer;
    }

    li label li input {
        vertical-align: middle;
        margin-right: 6px;
        position: relative;
        top: -1px;
    }

    li button {
        float: right;
        display: none;
        margin-top: 3px;
    }

    li:before {
        content: initial;
    }

    li:last-child {
        border-bottom: none;
    }

	li:hover{
        background-color: #ddd;
    }

	li:hover button{
        display: block;
    }
</style>

2.1.2.4. MyFooter.vue 代码

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
            <input type="checkbox" v-model="isAll"/>
        </label>

        <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'MyFooter',
        props:['todos','checkAllTodo','clearAllTodo'],
        computed: {
            //总数
            total(){
                return this.todos.length
            },
            //已完成数
            doneTotal(){
                //此处使用reduce方法做条件统计
                /* const x = this.todos.reduce((pre,current)=>{
                    console.log('@',pre,current)
                    return pre + (current.done ? 1 : 0)
                },0) */
                //简写
                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
            },
            //控制全选框
            isAll:{
                //全选框是否勾选
                get(){
                    return this.doneTotal === this.total && this.total > 0
                },
                //isAll被修改时set被调用
                set(value){
                    this.checkAllTodo(value)
                }
            }
        },

        methods: {
            /* checkAll(e){
                this.checkAllTodo(e.target.checked)
            } */
            //清空所有已完成
            clearAll(){
                this.clearAllTodo()
            }
        },
    }
</script>

<style scoped>
    /*footer*/
    .todo-footer {
        height: 40px;
        line-height: 40px;
        padding-left: 6px;
        margin-top: 5px;
    }

    .todo-footer label {
        display: inline-block;
        margin-right: 20px;
        cursor: pointer;
    }

    .todo-footer label input {
        position: relative;
        top: -1px;
        vertical-align: middle;
        margin-right: 5px;
    }

    .todo-footer button {
        float: right;
        margin-top: 5px;
    }
</style>

2.1.3. 代码层次和流程流转示意图

2.1.3.1. 控件层次示意图

在这里插入图片描述

2.1.3.2. 控件初始化数据与事件绑定示意图

在这里插入图片描述

2.2. 事件总线版本

事件总线是一种模式,用于在Vue应用程序中实现组件之间的通信,即使这些组件没有直接的父子关系。您可以创建一个Vue实例,作为事件总线,用于触发和监听事件。

通过事件总线,您可以在任何组件中触发和监听事件,实现了组件之间的解耦和通信。请注意,事件总线在大型应用中可能会导致事件管理变得复杂,所以需要慎重使用。

2.2.1. main.js 代码

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'

//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
    el:'#app',
    render: h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this
    },
})

2.2.2. App.vue 代码

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader'
    import MyList from './components/MyList'
    import MyFooter from './components/MyFooter.vue'

    export default {
        name:'App',
        components:{MyHeader,MyList,MyFooter},
        data() {
            return {
                //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods: {
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },

			//删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter( todo => todo.id !== id )
            },

            //全选or取消全选
            checkAllTodo(done){
                this.todos.forEach((todo)=>{
                    todo.done = done
                })
            },

            //清除所有已经完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter((todo)=>{
                    return !todo.done
                })
            }
        },

        watch: {
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        },

        mounted() {
            this.$bus.$on('checkTodo',this.checkTodo)
            this.$bus.$on('deleteTodo',this.deleteTodo)
        },
        beforeDestroy() {
            this.$bus.$off('checkTodo')
            this.$bus.$off('deleteTodo')
        },
    }
</script>
  

<style>
    /*base*/
    body {
        background: #fff;
    }

    .btn {
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
        border-radius: 4px;
    }

    .btn-danger {
        color: #fff;
        background-color: #da4f49;
        border: 1px solid #bd362f;
    }

    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }

    .btn:focus {
        outline: none;
    }

    .todo-container {
        width: 600px;
        margin: 0 auto;
    }

    .todo-container .todo-wrap {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
</style>

2.2.3. 各个子组件代码

2.2.3.1. MyHeader.vue 代码

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
    </div>
</template>
  

<script>
    import {nanoid} from 'nanoid'
    export default {
        name:'MyHeader',
        data() {
            return {
                //收集用户输入的title
                title:''
            }
        },
        methods: {
            add(){
                //校验数据
                if(!this.title.trim()) return alert('输入不能为空')
                //将用户的输入包装成一个todo对象
                const todoObj = {id:nanoid(),title:this.title,done:false}
                //通知App组件去添加一个todo对象
                this.$emit('addTodo',todoObj,1,2,3)
                //清空输入
                this.title = ''
            }
        },
    }
</script>

<style scoped>
    /*header*/
    .todo-header input {
        width: 560px;
        height: 28px;
        font-size: 14px;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 4px 7px;
    }

    .todo-header input:focus {
        outline: none;
        border-color: rgba(82, 168, 236, 0.8);
        box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    }
</style>

2.2.3.2. MyList代码

<template>
    <ul class="todo-main">
        <MyItem
            v-for="todoObj in todos"
            :key="todoObj.id"
            :todo="todoObj"
        />
    </ul>
</template>

<script>
    import MyItem from './MyItem'
  
    export default {
        name:'MyList',
        components:{MyItem},
        //声明接收App传递过来的数据
        props:['todos']
    }
</script>

<style scoped>
    /*main*/
    .todo-main {
        margin-left: 0px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding: 0px;
    }

    .todo-empty {
        height: 40px;
        line-height: 40px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding-left: 5px;
        margin-top: 10px;
    }
</style>

2.2.3.3. MyItem.vue 代码

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
            <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
            <!-- <input type="checkbox" v-model="todo.done"/> -->
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    </li>
</template>

<script>
    export default {
        name:'MyItem',
        //声明接收todo
        props:['todo'],
        methods: {
            //勾选or取消勾选
            handleCheck(id){
                //通知App组件将对应的todo对象的done值取反
                // this.checkTodo(id)
                this.$bus.$emit('checkTodo',id)
            },

            //删除
            handleDelete(id){
                if(confirm('确定删除吗?')){
                    //通知App组件将对应的todo对象删除
                    // this.deleteTodo(id)
                    this.$bus.$emit('deleteTodo',id)
                }
            }
        },
    }
</script>

<style scoped>
    /*item*/
    li {
        list-style: none;
        height: 36px;
        line-height: 36px;
        padding: 0 5px;
        border-bottom: 1px solid #ddd;
    }

    li label {
        float: left;
        cursor: pointer;
    }

    li label li input {
        vertical-align: middle;
        margin-right: 6px;
        position: relative;
        top: -1px;
    }

    li button {
        float: right;
        display: none;
        margin-top: 3px;
    }

    li:before {
        content: initial;
    }

    li:last-child {
        border-bottom: none;
    }

    li:hover{
        background-color: #ddd;
    }

    li:hover button{
        display: block;
    }
</style>

2.2.3.4. MyFooter.vue 代码

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'MyFooter',
        props:['todos'],
        computed: {
            //总数
            total(){
                return this.todos.length
            },
            //已完成数
            doneTotal(){
                //此处使用reduce方法做条件统计
                /* const x = this.todos.reduce((pre,current)=>{
                    console.log('@',pre,current)
                    return pre + (current.done ? 1 : 0)
                },0) */
                //简写
                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
            },

            //控制全选框
            isAll:{
                //全选框是否勾选
                get(){
                    return this.doneTotal === this.total && this.total > 0
                },
                //isAll被修改时set被调用
                set(value){
                    // this.checkAllTodo(value)
                    this.$emit('checkAllTodo',value)
                }
            }
        },
        methods: {
            /* checkAll(e){
                this.checkAllTodo(e.target.checked)
            } */
            //清空所有已完成
            clearAll(){
                // this.clearAllTodo()
                this.$emit('clearAllTodo')
            }
        },
    }
</script>

<style scoped>
    /*footer*/
    .todo-footer {
        height: 40px;
        line-height: 40px;
        padding-left: 6px;
        margin-top: 5px;
    }

    .todo-footer label {
        display: inline-block;
        margin-right: 20px;
        cursor: pointer;
    }

    .todo-footer label input {
        position: relative;
        top: -1px;
        vertical-align: middle;
        margin-right: 5px;
    }

    .todo-footer button {
        float: right;
        margin-top: 5px;
    }
</style>

2.2.4. 代码流程流转示意图

在这里插入图片描述

2.3. 发布订阅模式版本

pubsub-js 是一个JavaScript库,提供了发布-订阅模式的实现,用于在应用程序中实现组件之间的解耦通信。它允许不同的组件在不直接相互关联的情况下进行通信。这个库提供了一个简单的方式来发送消息并订阅事件,从而使应用程序的代码更加模块化和可维护。

导入pubsub-js

2.3.1. main.js 代码

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
    el:'#app',
    render: h => h(App),
})

2.3.2. App.vue 代码

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'

	import MyHeader from './components/MyHeader'
    import MyList from './components/MyList'
    import MyFooter from './components/MyFooter'

    export default {
        name:'App',
        components:{MyHeader,MyList,MyFooter},
        data() {
            return {
                //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods: {
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            
            //删除一个todo
            deleteTodo(_,id){
                this.todos = this.todos.filter( todo => todo.id !== id )
            },
            
            //全选or取消全选
            checkAllTodo(done){
                this.todos.forEach((todo)=>{
                    todo.done = done
                })
            },

            //清除所有已经完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter((todo)=>{
                    return !todo.done
                })
            }
        },

        watch: {
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        },

        mounted() {
            this.addTodoPubId = pubsub.subscribe('addTodo',this.addTodo)
            this.checkTodoPubId = pubsub.subscribe('checkTodo',this.checkTodo)
            this.deleteTodoPubId = pubsub.subscribe('deleteTodo',this.deleteTodo)
            this.checkAllTodoPubId = pubsub.subscribe('checkTodo',this.checkAllTodo)
            this.clearAllTodoPubId = pubsub.subscribe('deleteTodo',this.clearAllTodo)
        },

		beforeDestroy() {
            pubsub.unsubscribe(this.addTodoPubId)
            pubsub.unsubscribe(this.checkTodoPubId)
            pubsub.unsubscribe(this.deleteTodoPubId)
            pubsub.unsubscribe(this.checkAllTodoPubId)
            pubsub.unsubscribe(this.clearAllTodoPubId)
        },
    }
</script>

<style>
    /*base*/
    body {
        background: #fff;
    }
    .btn {
        display: inline-block;
        padding: 4px 12px;
        margin-bottom: 0;
        font-size: 14px;
        line-height: 20px;
        text-align: center;
        vertical-align: middle;
        cursor: pointer;
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
        border-radius: 4px;
    }
    .btn-danger {
        color: #fff;
        background-color: #da4f49;
        border: 1px solid #bd362f;
    }
    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }
    .btn:focus {
        outline: none;
    }
    .todo-container {
        width: 600px;
        margin: 0 auto;
    }
    .todo-container .todo-wrap {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
    }
</style>

2.3.3. 各个子组件代码

2.3.3.1. MyHeader.vue 代码

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'
    import {nanoid} from 'nanoid'
    export default {
        name:'MyHeader',
        data() {
            return {
                //收集用户输入的title
                title:''
            }
        },

        methods: {
            add(){
                //校验数据
                if(!this.title.trim()) return alert('输入不能为空')
                //将用户的输入包装成一个todo对象
                const todoObj = {id:nanoid(),title:this.title,done:false}
                //通知App组件去添加一个todo对象
                //this.$emit('addTodo',todoObj,1,2,3)
                pubsub.publish('addTodo',todoObj)
                //清空输入
                this.title = ''
            }
        },
    }
</script>

<style scoped>
    /*header*/
    .todo-header input {
        width: 560px;
        height: 28px;
        font-size: 14px;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 4px 7px;
    }

    .todo-header input:focus {
        outline: none;
        border-color: rgba(82, 168, 236, 0.8);
        box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    }
</style>

2.3.3.2. MyList.vue 代码

<template>
    <ul class="todo-main">
        <MyItem
            v-for="todoObj in todos"
            :key="todoObj.id"
            :todo="todoObj"
        />
    </ul>
</template>

<script>
    import MyItem from './MyItem'

	export default {
        name:'MyList',
        components:{MyItem},
        //声明接收App传递过来的数据
        props:['todos']
    }
</script>

<style scoped>
    /*main*/
    .todo-main {
        margin-left: 0px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding: 0px;
    }

    .todo-empty {
        height: 40px;
        line-height: 40px;
        border: 1px solid #ddd;
        border-radius: 2px;
        padding-left: 5px;
        margin-top: 10px;
    }
</style>

2.3.3.3. MyItem.vue 代码

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
            <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
            <!-- <input type="checkbox" v-model="todo.done"/> -->
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    </li>
</template>

<script>
    import pubsub from 'pubsub-js'
    export default {
        name:'MyItem',
        //声明接收todo
        props:['todo'],
        methods: {
            //勾选or取消勾选
            handleCheck(id){
                //通知App组件将对应的todo对象的done值取反
                // this.checkTodo(id)
                //this.$bus.$emit('checkTodo',id)
                pubsub.publish('checkTodo',id)
            },
            //删除
            handleDelete(id){
                if(confirm('确定删除吗?')){
                    //通知App组件将对应的todo对象删除
                    // this.deleteTodo(id)
                    // this.$bus.$emit('deleteTodo',id)
                    pubsub.publish('deleteTodo',id)
                }
            }
        },
    }
</script>

<style scoped>
    /*item*/
    li {
        list-style: none;
        height: 36px;
        line-height: 36px;
        padding: 0 5px;
        border-bottom: 1px solid #ddd;
    }

    li label {
        float: left;
        cursor: pointer;
    }

    li label li input {
        vertical-align: middle;
        margin-right: 6px;
        position: relative;
        top: -1px;
    }
  
    li button {
        float: right;
        display: none;
        margin-top: 3px;
    }

    li:before {
        content: initial;
    }

	li:last-child {
        border-bottom: none;
    }

    li:hover{
        background-color: #ddd;
    }

    li:hover button{
        display: block;
    }
</style>

2.3.3.4. MyFooter.vue 代码

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'
    export default {
        name:'MyFooter',
        props:['todos'],
        computed: {
            //总数
            total(){
                return this.todos.length
            },
            //已完成数
            doneTotal(){
                //此处使用reduce方法做条件统计
                /* const x = this.todos.reduce((pre,current)=>{
                    console.log('@',pre,current)
                    return pre + (current.done ? 1 : 0)
                },0) */
                //简写
                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
            },

            //控制全选框
            isAll:{
                //全选框是否勾选
                get(){
                    return this.doneTotal === this.total && this.total > 0
                },
                //isAll被修改时set被调用
                set(value){
                    // this.checkAllTodo(value)
                    // this.$emit('checkAllTodo',value)
                    pubsub.publish('checkAllTodo',id)
                }
            }
        },
        methods: {
            /* checkAll(e){
                this.checkAllTodo(e.target.checked)
            } */
            //清空所有已完成
            clearAll(){
                // this.clearAllTodo()
                // this.$emit('clearAllTodo')
                pubsub.publish('clearAllTodo')
            }
        },
    }
</script>

<style scoped>
    /*footer*/
    .todo-footer {
        height: 40px;
        line-height: 40px;
        padding-left: 6px;
        margin-top: 5px;
    }
  
    .todo-footer label {
        display: inline-block;
        margin-right: 20px;
        cursor: pointer;
    }

    .todo-footer label input {
        position: relative;
        top: -1px;
        vertical-align: middle;
        margin-right: 5px;
    }

    .todo-footer button {
        float: right;
        margin-top: 5px;
    }
</style>

2.3.4. 代码流程流转示意图

在这里插入图片描述

3. 发布订阅模式和事件总线之间的区别 优劣

发布-订阅模式和事件总线都是用于实现组件之间的通信的模式,但它们在一些方面有所不同。下面是它们之间的区别以及各自的优劣势:

发布-订阅模式:

定义:

发布-订阅模式是一种模式,其中有一个调度中心(通常称为“发布者”),负责管理订阅者(也称为“订阅者”)。订阅者可以订阅感兴趣的事件,而发布者发布这些事件。当事件被发布时,订阅者会收到通知并执行相关操作。

优势:

  • 解耦性强:发布者和订阅者之间没有直接关联,从而实现了松耦合。
  • 可以有多个订阅者:多个订阅者可以同时订阅同一个事件,实现了多对多的通信。
  • 灵活性:允许动态添加和移除订阅者,以及在不影响其他部分的情况下修改事件的处理逻辑。

劣势:

  • 调试和追踪:当订阅者数量增加时,跟踪事件的流向和处理变得更加复杂。
  • 维护困难:如果不恰当地使用,可能导致事件的处理分散和难以维护。

事件总线:

定义:

事件总线是一个中央的通信渠道,用于在不同组件之间传递消息。在Vue中,通常是通过创建一个Vue实例作为事件总线,允许组件通过该实例来发布和订阅事件。

优势:

  • 简单易用:Vue的事件总线机制非常简单,适用于小规模的组件通信。
  • Vue内置:作为Vue框架的一部分,事件总线是原生支持的,无需额外的库。
  • 组件间通信:可以方便地在任何两个组件之间进行通信,而不考虑它们的层级关系。

劣势:

  • 限制:事件总线适用于小型应用,但对于大型应用,可能会导致事件处理逻辑分散且难以追踪。
  • 耦合性:事件总线可能导致组件之间的耦合性增加,因为事件可以被任何组件订阅,难以管理。

结论:

发布-订阅模式和事件总线都有各自的优劣势,最适合的解决方案取决于您的应用程序的规模和需求。对于小型应用,事件总线可能更加简便,但对于大型应用,您可能需要考虑更结构化的通信方案,如Vuex状态管理、单向数据流等,以避免通信变得混乱和难以维护。在使用这些模式时,始终要考虑代码的清晰性、可维护性和可扩展性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/72581.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

任我行 CRM SQL注入漏洞复现(HW0day)

0x01 产品简介 任我行CRM&#xff08;Customer Relationship Management&#xff09;是一款专业的企业级CRM软件&#xff0c;旨在帮助企业有效管理客户关系、提升销售效率和提供个性化的客户服务。 0x02 漏洞概述 任我行 CRM SmsDataList 接口处存在SQL注入漏洞&#xff0c;未…

基于熵权法对Topsis模型的修正

由于层次分析法的最大缺点为&#xff1a;主观性太强&#xff0c;影响判断&#xff0c;对结果有很大影响&#xff0c;所以提出了熵权法修正。 变异程度方差/标准差。 如何度量信息量的大小&#xff1a; 把不可能的事情变成可能&#xff0c;这里面就有很多信息量。 概率越大&…

IC设计仿真云架构

对于IC仿真来说&#xff0c;最重要的是要安全、可维护、高性能的的HPC环境环境。 那么云上如何搭建起一套完整的IC仿真云环境呢&#xff1f; 这种架构应该长什么样子&#xff1f; 桌面虚拟化基础架构 将所有桌面虚拟机在数据中心进行托管并统一管理&#xff1b;同时用户能够…

HTML详解连载(5)

HTML详解连载&#xff08;5&#xff09; 专栏链接 [link](http://t.csdn.cn/xF0H3)下面进行专栏介绍 开始喽行高&#xff1a;设置多行文本的间距属性名属性值行高的测量方法 行高-垂直居中技巧 字体族属性名属性值示例扩展 font 复合属性使用场景复合属性示例注意 文本缩进属性…

挑战Open AI!!!马斯克宣布成立xAI.

北京时间7月13日凌晨&#xff0c;马斯克在Twitter上宣布&#xff1a;“xAI正式成立&#xff0c;去了解现实。”马斯克表示&#xff0c;推出xAI的原因是想要“了解宇宙的真实本质”。Ghat GPT横空出世已有半年&#xff0c;国内外“百模大战”愈演愈烈&#xff0c;AI大模型的现状…

Ajax-AJAX请求的不同发送方式

&#x1f954;&#xff1a;你一定能成为想要成为的人 发送AJAX请求不同方式 发送AJAX请求不同方式1、jQuery发送AJAX请求2、axios发送AJAX请求&#xff08;重点&#xff09;3、fetch发送AJAX请求 发送AJAX请求不同方式 1、jQuery发送AJAX请求 首先需要jquery的js文件&#xf…

集合Collection-List-ArrayList学习

一、集合 集合是数据容器。相较于数组集合具有以下几个特点&#xff1a; 数组一旦创建&#xff0c;长度不可改变。集合的长度会自动扩容。集合具有很多数组没有的功能函数API数组元素的存储特点单一&#xff0c;不同的集合有不同的存储特点。 1. Collection顶层接口 Collect…

用python来爬取某鱼的商品信息(2/2)

目录 上一篇文章 本章内容 设置浏览器为运行结束后不关闭&#xff08;可选&#xff09; 定位到搜索框的xpath地址 执行动作 获取cookie 保存为json文件 修改cookie的sameSite值并且导入cookie 导入cookie&#xff08;出错&#xff09; 导入cookie&#xff08;修改后&…

Jmeter(五) - 从入门到精通 - 创建网络计划实战和创建高级Web测试计划(详解教程)

1.简介 上一篇中已经将其的理论知识介绍了一下&#xff0c;这一篇就带着大家一步一步的把上一篇介绍的理论知识实践一下&#xff0c;然后再说一下如何创建高级web测试计划。 2.网络计划实战 通过上一篇的学习&#xff0c;将其分类为&#xff1a; &#xff08;1&#xff09;不需…

python -- 函数闭包

1. LEGB规则 L: local 是局部作用域 E: Enclosed 是嵌套函数的外层函数作用域 G: Global 全局作用域 B:Build-In 内置作用域 变量的使用权重&#xff1a;局部变量 > 外层作用域变量 > 全局变量 > 内置变量 下面代码执行后&#xff0c;x变量的值分别为多少&#xff1…

KafkaStream:Springboot中集成

1、在kafka-demo中创建配置类 配置kafka参数 package com.heima.kafkademo.config;import lombok.Data; import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.StreamsConfig; import org.springframework.boot.context.properties.Configu…

怎么做Tik Tok海外娱乐公会呢?新加坡市场怎么样?

一、为什么选择TikTok直播 1. 海外市场潜力巨大 • 自2016年始&#xff0c;多家直播平台陆续拓展至东南亚、中东、俄罗斯、日韩、欧美、拉美等地区。 • 海外市场作为直播发展新蓝海&#xff0c;2021年直播行业整申请cmxyci体规模达百亿美元&#xff0c;并维持高速增长。 &a…

【数据结构系列】链表

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

异常(下)Java常见异常,异常的使用原则

文章目录 前言一、Java常见异常 1.常见异常2.实例展示二、异常的使用原则总结 前言 该文介绍了Java的一些常见异常&#xff0c;并给出对应的例子进行解释。介绍异常的使用原则&#xff0c;即创建&#xff0c;抛出异常的编程规范。 一、Java常见异常 前要&#xff1a;Java API中…

sklearn机器学习库(一)sklearn中的决策树

sklearn机器学习库(一)sklearn中的决策树 sklearn中决策树的类都在”tree“这个模块之下。 tree.DecisionTreeClassifier分类树tree.DecisionTreeRegressor回归树tree.export_graphviz将生成的决策树导出为DOT格式&#xff0c;画图专用tree.export_text以文字形式输出树tree.…

Jmeter(六) - 从入门到精通 - 建立数据库测试计划(详解教程)

1.简介 在实际工作中&#xff0c;我们经常会听到数据库的性能和稳定性等等&#xff0c;这些有时候也需要测试工程师去评估和测试&#xff0c;因此这篇文章主要介绍了jmeter连接和创建数据库测试计划的过程,在文中通过示例和代码非常详细地介绍给大家&#xff0c;希望对各位小伙…

基于YOLOv8+PyQt5开发的行人过马路危险行为检测告警系统(附数据集和源码下载)

系列文章目录 文章目录 系列文章目录前言欢迎来到我的博客&#xff01;我很高兴能与大家分享关于基于YOLOv8的行人过马路危险行为检测告警系统的内容。 一、系统特点1. 采用最新最优秀的目标检测算法YOLOv82. 系统分别基于PyQt5开发了两种GUI图形界面&#xff0c;供大家学习使用…

consul安装启动流程

普通软件包安装 首先cd /opt &#xff0c;将安装包放到该目录下 下载consul安装包 进入consul官网找到自己开发平台对应的安装包下载 https://www.consul.io/downloads.html 或使用命令 wget https://releases.hashicorp.com/consul/1.6.2/consul_1.6.2_linux_amd64.zip (如果…

解决lldb调试时可能出现的personality set failed: Function not implemented

最近在尝试使用Visual Studio 2022远程连接Linux进行C/C的开发&#xff0c;由于CentOS风波不断&#xff0c;所以现在的开发基本上都是使用ubuntu了&#xff0c;但是目前VS2022有一些BUG&#xff0c;就是远程调试时&#xff0c;如果目标系统是ubuntu则会出现启动调试器很慢的问题…

js设置css变量控制页面一行展示指定个数的元素

前置知识&#xff1a; CSS变量之var()函数的应用——动态修改样式 & root的使用 flex相关知识 场景&#xff1a; 动态设置给父元素内子元素设置每行排列几个 通过 document.body.style.setProperty(--itemNum, 5)设置样式变量&#xff0c;然后通过给父元素设置display: f…
最新文章