重要源码

components\MyItem.vue

<template>
    <li>
        <label>
            <!-- 默认勾选 
            <input type="checkbox" checked/>
            -->
            <!-- :checked="true" 表示 我们想给 input 添加一个 checked 选项,如果为true就勾选,否则不勾选。
            :checked="todo.done"  初始化谁勾,谁不勾。

            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            -->
            <!-- 另外一种方法: 绑定 change事件-->
            <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
            <!-- 如下代码也能实现功能,但是不推荐这种,Vue原则是:props 只能接收数据,不允许修改数据。因为如果修改了props,Vue没有监测到。
            <input type="checkbox" v-model="todo.done"/>
            -->

            <!--span>xxxxx</span-->
            <!-- 通过插值语法 展示 接收到的 todo对象-->
            <span v-show="!todo.isEdit">{{ todo.title}}</span>
            <!-- 089 结构也要准备好 @blur:绑定失去焦点-->
            <input type="text"
                    v-show="todo.isEdit" 
                    :value="todo.title" 
                    @blur="handleBlur(todo,$event)"
                    ref="inputTitle"
            >
        </label>

        <button class="btn btn-danger" @click="handDelete(todo.id)">删除</button>
        <!-- 089 -->
        <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
    </li>
</template>

<script>
    //引入库
    import PubSub from 'pubsub-js'

    export default {
        name:'MyItem',

        //声明接收 todo 对象
        //继续接收 MyList父组件 发来的数据checkTodo
        //props:['todo', "checkTodo", "deleteTodo"],
        //086
        props:['todo'],

        mounted(){
            console.log(this.todo)
        },

        methods: {
            //勾选or取消勾选
            handleCheck(id) {
                console.log(id)

                //勾选的时候,传id
                //通知 App.vue组件 将对应的 todo对象的 done值 取反
                //数据todo对象在哪里? 答: 在 App.vue。 我们有一句总结:数据在哪里,操作数据的方法就在哪里!
                //this.checkTodo(id) //把id传递给 父组件MyList,再间接给 爷组件App.vue

                //086
                this.$bus.$emit('checkTodo',id)
            },
            //删除
            handDelete(id){
                //弹窗 单击确定,就为真。单击取消就为假。
                if(confirm('确定删除吗?')){
                    console.log(id)

                    //删除的时候,传id
                    //this.deleteTodo(id)    //deleteTodo 此时叫 函数名

                    //086
                    //this.$bus.$emit('deleteTodo',id)    //deleteTodo 此时叫 事件名
                    //088
                    PubSub.publish('deleteTodo', id)    //deleteTodo 此时叫 消息名
                }
            },

            //089
            //编辑
            handleEdit(todo){
                //todo.isEdit = true
                //由于 todo 原来没有 idEdit 元素,所以如果上面这样这样生硬的添加进去会出问题。
                //需要调整为:
                //如果 todo 身上有 isEdit
                //if(todo.hasOwnProperty('isEdit')){
                //ESLint规则提示错误,调整为:
                if(Object.prototype.hasOwnProperty.call(todo, 'isEdit')){
                    console.log('todo身上已经有了 isEdit')
                    todo.isEdit = true        //这句话执行完,Vue并没有立刻解析模板. 而是需要等当前 handleEdit() 全部执行完才会去解析模板。

                } else {
                    console.log('todo身上此时还没有 isEdit')

                    //使用场景: todo身上原来没有isEdit,需要后来添加isEdit
                    this.$set(todo,'isEdit', true)    //响应式添加
                }
                console.log('handEdit',todo)


                //090 通过 ref 拿到 标签
                //this.$refs.inputTitle.focus()
                //顺序问题:如果之前被隐藏了,此时并不能获取焦点。
                //官方解决该问题:
                //.nextTick 指定回调函数,会在DOM节点更新完毕后再执行。
                this.$nextTick(function(){
                    this.$refs.inputTitle.focus()
                })
            },

            //失去焦点回调(真正执行修改逻辑)
            handleBlur(todo,e){
                todo.isEdit = false

                console.log('updateTodo',todo.id,e.target.value)
                //拿到最新的输入
                if(!e.target.value.trim()) return alert('输入不能为空')
                this.$bus.$emit('updateTodo',todo.id,e.target.value)
            }
        }
    }
</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 中的 button */
    li:hover button{
        display: block;
    }
</style>