學習 Vue.js: Todo 小樣元件版

zhangbao發表於2017-07-07

上一篇 中寫了一個 Todo 小樣,但 Vue.js 提供的便利不只有這些,還有元件化。下面將之前的 Todo 小樣重寫成可重複利用的元件,結果在 codepen.io 上可看到。

註冊元件

Vue.js 使用 Vue.component 註冊全域性元件,下面註冊元件 todo-items

Vue.component('todo-items', {
    template: '#todo-items-template',
    props: ['initialTodos'],
    data() {
        return {
            todos: this.initialTodos ? [].concat(this.initialTodos) : [],
            newTodo: { title: '', completed: false }
        }
    },
    methods: {
        add() {
            if (! this.newTodo.title.trim()) { return ; }
            this.todos.push({
                title: this.newTodo.title,
                completed: this.newTodo.completed
            });
            this.newTodo.title = '';
        },
        destroy(index) {
            this.todos.splice(index, 1);
        },
        toggleDone(index) {
            this.todos[index].completed = !this.todos[index].completed;
        },
        up(index) { 
            if (index - 1 < 0) { return ; }
            var temp = this.todos[index];
            this.todos[index] = this.todos[index-1];
            this.todos[index-1] = temp;
            this.todos = [].concat(this.todos);
        },
        down(index) {
            if (index + 1 >= this.todos.length) { return ; }
            var temp = this.todos[index];
            this.todos[index] = this.todos[index+1];
            this.todos[index+1] = temp;
            this.todos = [].concat(this.todos);
        }
    }
});

元件中 data 資料是使用函式形式返回的物件,是為了每個元件例項都有獨一無二 data 物件。

寫模板程式碼

元件 todo-items 使用的 #todo-items-template 處的模板程式碼,就是之前的 Todos 的 HTML 程式碼,用 type="text/x-template 的 script 標籤包圍。

<script type="text/x-template" id="todo-items-template">
    <div class="panel panel-default">
        <div class="panel-heading text-center">
            計劃要做的事情,共 {{ todos.length }} 件
        </div>
        <div class="panel-body">
            <div class="list-groups">
                <a class="list-group-item" v-bind:class="{ 'completed': todo.completed }" v-for="(todo, index) in todos">
                    {{ todo.title }}
                    <button class="btn btn-xs btn-danger pull-right" v-on:click='destroy(index)'title="刪除">✘</button>
                    <button class="btn btn-xs btn-info pull-right" v-on:click='down(index)'title="下移">↓</button>
                    <button class="btn btn-xs btn-info pull-right" v-on:click='up(index)'title="上移">↑</button>
                    <button class="btn btn-xs pull-right" v-on:click='toggleDone(index)' v-bind:class="[todo.completed ? 'btn-success': '']" v-bind:title="[todo.completed ? '點選,標記為未完成': '點選,標記為已完成']">✔</button>
                </a>
            </div>
        </div>
        <div class="panel-footer">
            <form v-on:submit.prevent="add">
                <div class="form-group">
                    <input type="text" class="form-control text-center" v-model="newTodo.title">
                </div>
                <button class="btn btn-default btn-block" type="submit">新增</button>
            </form>
        </div>
    </div>
</script>

使用元件

<div id="app" class="container">
    <div class="row">
        <div class="col-md-6">
            <todo-items v-bind:initial-todos="initialTodos"></todo-items>
        </div>
        <div class="col-md-6">
            <todo-items v-bind:initial-todos="initialTodos"></todo-items>
        </div>
    </div>
</div>
new Vue({
    el: '#app',
    data: {
        initialTodos: [
            {title: '吃早飯', completed: false},
            {title: '吃午飯', completed: false},
            {title: '吃晚飯', completed: false}
        ]
    }
});

注意到,兩個元件例項,都賦予了同一個 initialTodos 物件。為了保證每個元件裡的 todos 物件是獨一無二的,所以元件內部 todos 資料都使用了 [].concat(this.initialTodos) 返回的新陣列;否則同一個 Vue 例項下的元件的 todos 資料,會因為引用了同一個物件,行為變一樣了。

相關文章