Vue 案例:ToDoList

Vue案例:ToDoList

ToDoList

要求

  • 目标:
    • 显示一个输入框,一个按钮
    • 在输入框中输入内容,点击按钮即在页面中显示出该内容,之后输入框恢复空白
  • 若要完全理解这个小demo,需要一些前提条件:
    • vue基础指令
    • 初步理解JavaScript中的作用域以及this关键字
    • Vue全局组件以及局部组件的用法

最普通的方式

<div id="app">
    <input type="text" v-model="inputValue"/>  <!-- v-model实现双向数据绑定 -->
    <button type="button" @click="buttonClick">添加</button>  <!-- @click触发点击事件 -->
    <ul>
        <li v-for="item in list">{{ item }}</li>  <!-- v-for绑定数据来循环渲染 -->
    </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>  <!-- 引入Vue -->
<script type="text/javascript">
    var app = new Vue({  //创建一个新的实例对象
        el: '#app',  //el:element 的简写,用来指定Vue应用程序接管的元素(包括所有的子元素)
        data: {  //data:data就是Vue实例中的数据
            list: [],  //对应到v-for指令的数据
            inputValue: ''  //对应到v-model的数据绑定
        },
        methods: {  //方法
            buttonClick: function(){  //对应到@click的方法
                //将input框中的内容添加到list中,this指向的是这个app实例
                this.list.push(this.inputValue);
                //添加完之后清空input框
                this.inputValue = '';
            }
        }
    })
</script>

全局组件实现

<div id="app">
    <input type="text" v-model="inputValue"/>  <!-- v-model实现双向数据绑定 -->
    <button type="button" @click="buttonClick">添加</button>  <!-- @click触发点击事件 -->
    <ul>
        <!-- 使用组件 --> <!-- 注意命名规则,全局组件的TodoItem在这里要写成todo-item -->
        <todo-item v-bind:content='item' v-for='item in list'></todo-item>
    </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>  <!-- 引入Vue -->
<script type="text/javascript">
    //用Vue中的component方法用来定义一个全局组件
    Vue.component('TodoItem', {  //组件名为TodoItem
        props: ['content'],  //来自父组件的传值,即要渲染的content值
        template: '<li>{{content}}</li>',  //HTML模板,在其中仍然用插值表达式来显示content值
    })
    var app = new Vue({  //创建一个新的实例对象
        el: '#app',  //el:element 的简写,用来指定Vue应用程序接管的元素(包括所有的子元素)
        data: {  //data:data就是Vue实例中的数据
            list: [],  //对应到v-for指令的数据
            inputValue: ''  //对应到v-model的数据绑定
        },
        methods: {  //方法
            buttonClick: function(){  //对应到@click的方法
                //将input框中的内容添加到list中,this指向的是这个app实例
                this.list.push(this.inputValue);
                //添加完之后清空input框
                this.inputValue = '';
            }
        }
    })
</script>
  • 其实,对比我们之前用最普通的方式,这里无非就是把之前的<li v-for="item in list">{{ item }}</li>
    换成了<todo-item v-bind:content='item' v-for='item in list'></todo-item>
    然后定义了一个全局的组件TodoItem
  • 那么,组件与普通方式之间,分别对应着什么呢?
    • 我们用Vue.component方法定义了一个叫TodoItem的全局组件,它的template属性的值,就是用来替换<li>{{ item }}</li>
    • 注册组件之后我们就可以使用啦,我们在div中直接用<todo-item></todo-item>即可
    • 另外,为了替换掉普通方法中的v-for='item in list'
      我们在组件中使用了v-bind:content='item'以及v-for='item in list'
    • 最后一步:组件传值,我们现在只有v-bind:connentcontent,还没传值呢,所以我们用props属性来接受父组件的传值(也就是这里的input框中的输入,另外,实际上这里的Vue实例最外层就是父组件)
  • 来梳理一下整个过程:
    • input框中输入内容,并点击添加,触发buttonClick()data.list添加input框中的内容
    • v-foritem遍历了data.list,并将值通过v-bind:content传给<todo-item></todo-item>标签
    • 最后用插值表达式将content打印出来

局部组件实现

<div id="app">
    <input type="text" v-model="inputValue"/>  <!-- v-model实现双向数据绑定 -->
    <button type="button" @click="buttonClick">添加</button>  <!-- @click触发点击事件 -->
    <ul>
        <!-- 注意命名,这里的名称以Vue实例中的components的属性名为准 -->
        <todo v-bind:content='item' v-for='item in list'></todo>  <!-- 使用组件 -->
    </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>  <!-- 引入Vue -->
<script type="text/javascript">
    var TodoItem = {  //定义局部组件的名称为TodoItem
        props: ['content'],  //接受父组件传值
        template: '<li>{{ content }}</li>',  //HTML模板
    }
    var app = new Vue({  //创建一个新的实例对象
        el: '#app',  //el:element 的简写,用来指定Vue应用程序接管的元素(包括所有的子元素)
        components: {  //注册组件
            //定义时为TodoItem,现在在实例中另外取名为todo,使用时以后者为准
            todo: TodoItem,
        },
        data: {  //data:data就是Vue实例中的数据
            list: [],  //对应到v-for指令的数据
            inputValue: ''  //对应到v-model的数据绑定
        },
        methods: {  //方法
            buttonClick: function(){  //对应到@click的方法
                //将input框中的内容添加到list中,this指向的是这个app实例
                this.list.push(this.inputValue);
                //添加完之后清空input框
                this.inputValue = '';
            }
        }
    })
</script>
  • 其实这里的局部组件已经很容易掌握了,对比之前,全局组件是用Vue.component方法定义的,而局部组件直接将整个内容写成了一个 对象
  • 然后在Vue实例中,通过components属性,将该组件作为属性值注册了该组件,最后直接使用组件

增加一个功能

要求

  • 在之前的ToDoList基础上,要增加一项功能:
    • 点击一项内容,点击后即消失
    • 在输入框中回车即可添加一项
  • 知识前提:
    • 组件传值
    • JavaScript字符串方法
    • Vue按键修饰符

组件实现

<div id="app">
    <!-- 事件修饰符将回车事件keyup.enter指向buttonClick函数 -->
    <input type="text" v-model="inputValue" @keyup.enter="buttonClick"/>
    <button type="button" @click="buttonClick">添加</button>
    <ul>
        <todo-item v-bind:content = 'item' 
                   v-for = '(item, index) in list'
                   v-bind:index = 'index'
                   v-on:delete = 'parentDelete'>
        </todo-item>
    </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script type="text/javascript">
    Vue.component('TodoItem', {
        props: ['content', 'index'],  //接受来自父组件的传值
        //点击元素,触发子组件的childDelete事件
        template: '<li @click="childDelete">{{ content }}</li>',
        methods: {
            /*子组件向外触发一个名为delete的事件,这个事件将会被父组件监听,从而触发parentDelete
            子组件向父组件传的值不仅有事件名称delete,还有一个index来表示该元素的下标*/
            childDelete: function(){
                this.$emit('delete', this.index);  //this.index来自于props接受的值
            }
        }
    })
    var app = new Vue({
        el: '#app',
        data: {
            list: [],
            inputValue: ''
        },
        methods: {
            buttonClick: function(){
                this.list.push(this.inputValue);
                this.inputValue = '';
            },
            //父组件监听到子组件的delete事件被触发时
            parentDelete: function(index){ //接受子组件的传值,index来自于子组件的this.index
                this.list.splice(index, 1); //js字符串方法删除指定下标的元素
            }
        }
    })
</script>
  • 很明显,我们不该从上往下读代码,这样是自己为难自己,我们应该从功能的角度考虑.
  • 我们要实现的功能是:点击元素,让它消失,但是元素在子组件中,消失与否是受父组件的listv-for共同确定渲染的
  • 所以,我们的大概思路应该是:
    • 点击元素,让子组件将该元素的下标都传给父组件
    • 然后父组件删除在list中该下标的元素,即可实现整个过程
  • 所以,我们的关键操作是:
    • 在定义局部组件时,给template属性的HTML模板添加childDelete,点击时触发
    • 通过this.$emitthis.index传给父组件,但是我们需要为此绑定一个事件,以供父组件实时监听,我们命名为delete
    • 父组件通过v-on:delete监听到时,触发parentDelete来删除该元素
    • 但是parentDelete肯定需要知道该元素下标,所以我们在最开始就要把下标传给子组件,子组件再返回给parentDelete
  • 子组件与父组件之间的传值,个人感觉取决于什么时候用,比如这里的多个事件对应的多个传值,也就是说,我们在传值时务必弄清楚这个值所对应的事件是什么
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 2
wangchunbo

加油呀!

4年前 评论
Ozzie (楼主) 4年前

加油~我也刚刚开始学

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!