教程: TodoMVC 与 director 路由

概述

mvc 通常与模型,视图,控制器三方配合。vue本身已偏向view渲染层,因此本例用localStorage 作数据持久化存储器,director路由库充当控制器,负责页面节点渲染分发调度,从而实现一个简易的todoMVC应用。

知识点

  • vuejs单页面的增删改查 TodoMVC
  • vue 自定义指令,观察函数,计算属性,方法使用
  • localStorage 对象 结合 JSON 的序列化与反序列化作为持久化层
  • director 无依赖轻量级的前端路由库,可在服务端/客户端/命令行路由

路由

  • 正则匹配路由,传参监听
  • director 基于hash的路由方式 如 /a/c 匹配的对应路径 index.php#/a/c
Router({
    '/(active|completed|all)/?': function (filter) {
        // 通过vue实例间接浸染视图
        app.visibility = filter
    }
}).init()

数据持久化

var STORAGE_KEY =  'pardon110'
var todoStorage = {
    fetch() {
        var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
        todos.forEach((todo, index) => todo.id = index);
        todoStorage.uid = todos.length
        return todos
    },
    save(todos) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
    }
}

效果图

TodoMVC 与 director 示例

源码

  • app.d.js
var STORAGE_KEY = 'pardon110'
// 存储器实例对象
var todoStorage = {
    fetch() {
        var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
        todos.forEach((todo, index) => todo.id = index);
        todoStorage.uid = todos.length
        return todos
    },
    save(todos) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
    }
}
// 可见性过滤
var filters = {
    all: todos => todos,
    active: todos => todos.filter(todo => !todo.completed),
    completed: todos => todos.filter(todo => todo.completed)
}

var app = new Vue({
    // 初始化应用状态
    data: {
        todos: todoStorage.fetch(),
        newTodo: '',
        editedTodo: null,
        visibility: 'all'
    },
    // 观察持久化存储
    watch: {
        todos: {
            // 注意,不应该使用箭头函数来定义 watcher 函数 
            // 箭头函数绑定了父级作用域的上下文, 其this 将不会按照期望指向 Vue 实例
            handler: todoStorage.save,
            // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
            deep: true
        }
    },
    // 计算属性
    computed: {
        filteredTodos() {
            return filters[this.visibility](this.todos)
        },
        remaining() {
            return filters.active(this.todos).length
        },
        // getter/setter
        allDone: {
            get() {
                return this.remaining === 0
            },
            set(value) {
                this.todos.forEach(todo => todo.completed = value)
            }
        }
    },

    // 视图过滤器,类型于管道用|
    filters: {
        // 复数转换
        pluralize: function (n) {
            return n === 1 ? 'item' : 'items'
        }
    },

    // 实现数据增删改逻辑,注意不直接操作Dom节点
    methods: {
        addTodo() {
            var value = this.newTodo && this.newTodo.trim()
            if (!value) {
                return
            }
            this.todos.push({
                id: todoStorage.uid++,
                title: value,
                completed: false
            })
            this.newTodo = ''
        },

        removeTodo(todo) {
            this.todos.splice(this.todos.indexOf(todo), 1)
        },

        // 编辑数据
        editTodo(todo) {
            // 缓存编辑前内容
            this.beforeEditCache = todo.title
            // 保留编辑后内容
            this.editedTodo = todo
        },
        doneEdit(todo) {
            if (!this.editedTodo) {
                return
            }
            this.editedTodo = null
            todo.title = todo.title.trim()
            if (!todo.title) {
                this.removeTodo(todo)
            }
        },
        cancelEdit(todo) {
            this.editedTodo = null
            todo.title = this.beforeEditCache
        },

        removeCompleted() {
            this.todos = filters.active(this.todos)
        }
    },

    // 自定义vue指令,在输入字段前实现聚集效果
    directives: {
        'todo-focus': function (el, binding) {
            if (binding.value) {
                el.focus()
            }
        }
    }
})

// 使用director库,注册路由
Router({
    '/(active|completed|all)/?': function (filter) {
        // 变更vue各应数据,渲染视图
        app.visibility = filter
    }
}).init()

// Vue实例挂载Dom节点
app.$mount('.todoapp')
  • index.html 文件
<!DOCTYPE html>
<html data-framework="vue">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Vue.js • TodoMVC</title>
    <link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@2.0.4/index.css">
    <!-- 前端路由库(支持spa应用) -->
    <script src="https://cdn.bootcss.com/Director/1.2.8/director.js"></script>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <section class="todoapp">
        <header class="header">
            <h1>todos</h1>
            <!-- 双向绑定添加 -->
            <input v-model="newTodo" placeholder="写点什么?" autofocus autocomplete="off" @keyup.enter="addTodo"
                class="new-todo">
        </header>

        <section class="main" v-show="todos.length" v-cloak>
            <!-- 全选/全不选切换 -->
            <input type="checkbox" class="toggle-all" v-model="allDone">
            <!-- 显示列表 -->
            <ul class="todo-list">
                <li v-for="todo in filteredTodos" :key="todo.id"
                    :class="{ completed: todo.completed, editing: todo == editedTodo }">
                    <div class="view">
                        <!-- 单项选中与非选中 -->
                        <input type="checkbox" class="toggle" v-model="todo.completed">
                         <!-- 双击条目编辑todo -->
                        <label @dblclick="editTodo(todo)">{{ todo.title }}</label>
                         <!-- 点击删除按钮 -->
                        <button class="destroy" @click="removeTodo(todo)"></button>
                    </div>
                    <input type="text" class="edit" v-model="todo.title" v-todo-focus="todo == editedTodo"
                        @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
                </li>
            </ul>
        </section>

        <footer class="footer" v-show="todos.length" v-cloak>
            <span class="todo-count">
                <strong> {{ remaining }}</strong>
                <!-- 过滤器管道 -->
                {{ remaining | pluralize }} left
            </span>

            <!--使用director库进行路由,自动分发调度,触发vue实例回调,间接渲染视图-->
            <ul class="filters">
                <li>
                    <a href="#/all" :class="{ selected: visibility == 'all'}">全部</a>
                    <a href="#/active" :class="{ selected: visibility == 'active'}">活跃</a>
                    <a href="#/completed" :class="{ selected: visibility == 'completed'}">回收站</a>
                </li>
            </ul>
            <button class="clear-completed" @click="removeCompleted" v-show="todos.length >  remaining">
                清空
            </button>
        </footer>
    </section>
    <footer class="info">
        <p>双击编辑一个todo应用</p>
        <p>Written by <a href="http://evanyou.me">Evan You</a></p>
        <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="app.d.js"></script>
</body>
</html>
本作品采用《CC 协议》,转载必须注明作者和本文链接
pardon110
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
开发者 @ 社科大
文章
134
粉丝
24
喜欢
101
收藏
55
排名:106
访问:8.9 万
私信
所有博文
社区赞助商