一步一步学习Vue(十一)

本篇继续学习vuex,还是以实例为主;我们以一步一步学Vue(四)中讲述的例子为基础,对其改造,基于vuex重构一遍,这是原始的代码:

todolist.js

; (function () {
    var list = [];
    var Todo = (function () {
        var id = 1;
        return function (title, desc) {
            this.title = title;
            this.desc = desc;
            this.id = id++;
        }
    })();
    /**
     * 搜索组件
     */
    var SearchBar = {
        template: `
        <div class="row toolbar">
            <div class="col-md-8">
                keyword:
                <input type="text" v-model="keyword" />
                <input type="button" @click="search()" value="search" class="btn btn-primary"  />
            </div>
        </div>
    `,
        data: function () {
            return {
                keyword: ''
            }
        },
        methods: {
            search: function () {
                this.$emit('onsearch', this.keyword);
            }
        }

    }
    /**
     * 表单组件
     */
    var TodoForm = {
        template: `
     <div class="col-md-6">
        <div class="form-inline">
            <label for="title" class="control-label col-md-4">title:</label>
            <input type="hidden" v-bind:value="todo.id" />
            <input type="text" v-model="todo.title" class="form-control col-md-8">
        </div>
        <div class="form-inline">
            <label for="desc" class="control-label col-md-4">desc</label>
            <input type="text" v-model="todo.desc" class="form-control col-md-8">
        </div>
        <div class="form-inline">
            <input type="button" value="OK" v-on:click="ok()" class="btn btn-primary offset-md-10"  />
        </div>
    </div>
    `,
        props: ['initItem'],

        computed: {
            todo: function () {
                return { id: this.initItem.id, title: this.initItem.title, desc: this.initItem.desc };
            }
        },

        methods: {
            ok: function () {
                this.$emit('onsave', this.todo);

            }
        }

    }
    /**
     * 列表项组件
     */
    var TodoItem = {
        template: `
     <tr>
        <th>{{todo.id}}</th>
        <td>{{todo.title}}</td>
        <td>{{todo.desc}}</td>
        <td>
            <input type="button" value="remove" @click="remove()" class="btn btn-danger" />
            <input type="button" value="edit" @click="edit()" class="btn btn-info" />
        </td>
    </tr>
    `,
        props: ['todo'],
        methods: {
            edit: function () {
                console.log(this.todo);
                this.$emit('onedit', this.todo.id);
            },
            remove: function () {
                this.$emit('onremove', this.todo.id);
            }
        }
    }
    /**
     * 列表组件
     */
    var TodoList = {
        template: `
    <div class="col-md-6">
        <table class="table table-bordered">
            <tr>
                <th></th>
                <th>title</th>
                <th>desc</th>
                <th></th>
            </tr>
            <todo-item  v-for="item in items" :todo="item" :key="item.id"  @onedit="edit($event)" @onremove="remove($event)"></todo-item>
        </table>
    </div>
    `,
        props: ['items'],
        components: {
            'todo-item': TodoItem
        },
        methods: {
            edit: function ($e) {
                this.$emit('onedit', $e);
            },
            remove: function ($e) {
                this.$emit('onremove', $e);
            }
        }
    }
    /**
     * 容器组件
     * 说明:容器组件包括三个字组件
     */
    var TodoContainer = {
        template: `
        <div class="container">
            <search-bar @onsearch="search($event)"></search-bar>
            <div class="row">
                <todo-list :items="items" @onremove="remove($event)" @onedit="edit($event)"></todo-list>            
                <todo-form :init-item="initItem" @onsave="save($event)" ></todo-form>
            </div>
        </div>
    `,
        data: function () {
            return {
                /**
                 * Todolist数据列表
                 * 说明:通过props传入到Todolist组件中,让组件进行渲染
                 */
                items: [],
                /**
                 * TodoForm初始化数据
                 * 说明:由于TodoForm包括两种操作:新增和编辑;新增操作无需处理,编辑操作需要进行数据绑定,这里通过传入initItem属性进行编辑时数据的初始化
                 * 如果传入的值为空,说明为新增操作,由initItem参数的Id是否为空,来确认是更新保存还是新增保存
                 */
                initItem: {
                    title: '',
                    desc: '',
                    id: ''
                }
            }
        },
        components: {
            'search-bar': SearchBar,/**SearchBar组件注册 */
            'todo-form': TodoForm,/**TodoForm组件注册 */
            'todo-list': TodoList/**TodoList组件注册 */
        },
        methods: {
            /**
             * 模拟保存数据方法
             * 辅助方法
             */
            _mock_save: function (lst) {
                list = lst;
            },
            /**
             * 根据id查询对象
             * 辅助方法
             */
            findById: function (id) {
                return this.items.filter(v => v.id === id)[0] || {};
            },
            /**
             * 查询方法
             * 由SearchBar组件触发
             */
            search: function ($e) {
                this.items = list.filter(v => v.title.indexOf($e) !== -1);
            },
            /**
             * 保存方法
             * 响应新增和更新操作,由TodoForm组件触发
             */
            save: function ($e) {
                //id存在则为编辑保存
                if (this.initItem.id) {
                    var o = this.findById($e.id);
                    o.title = $e.title;
                    o.desc = $e.desc;
                } else {
                    this.items.push(new Todo($e.title, $e.desc));
                }

                this.initItem = { id: '', title: '', desc: '' };

                this._mock_save(this.items);
            },
           /**
            * 删除方法
            * 响应删除按钮操作
            * 由TodoItem组件触发
            */
            remove: function ($e) {
                this.items = this.items.filter(v => v.id !== $e);
                this._mock_save(this.items);
            },
            /**
             * 编辑按钮点击时,进行表单数据绑定
             */
            edit: function ($e) {
                this.initItem = this.findById($e);
            }
        }
    }

    var app = new Vue({
        el: '#app',
        components: {
            'todo-container': TodoContainer
        }
    });

})();

/**
 * 
 * 
 * <div id="app">
 *     <todo-container></todo-container>
 * </app>
 */

index.html

<!DOCTYPE html>
<html lang="en">

<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>demo1</title>
    <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vuex/2.3.1/vuex.js"></script>

</head>

<body class="container">
    <div id="app">
       <todo-container></todo-container>
    </div>
    <script src="./todolist.js"></script>
</body>

</html>

注意要在html页面引入vuex的路径,我们这里直接使用cdn上的库,开始我们的重构

第一步:创建全局store

在vuex中,store是全局唯一的,我们在上一篇文章中也介绍了其基本创建方式,修改todolist.js,添加如下代码:

  var store=new Vuex.Store({
        state:{
            //TODO:状态
        },
        mutations:{
            //TODO:改变状态的方法
        }
    })

    var app = new Vue({
        store:store,
        el: '#app',
        components: {
            'todo-container': TodoContainer
        }
    });

上述代码表示store已经创建,并且注入到注册组件中了,在任何组件中都可以通过this.store来访问state和提交mutation,这里再简单说一下mutaiton,其实我们可以把mutation当成事件来理解,在store定义的时候,创建mutation,我们可以认为是mutation的注册,就如我们去注册普通的事件一样,内容都是key和value,其中key是事件的全局表示,value是事件的回调函数,类比mutation,定义是注册,模式还是func:function(){}的模式,在我们做commit(“mutation”)的时候相当于触发事件,这时候就会执行我们注册的回调函数。

第二步,我们把共享状态进行提取:

  var store=new Vuex.Store({
        state:{
            items:[] // todoContainer中items,
        

就下来,我们把所有改变state的方法,都通过注册mutation的方式来重构,在vuex中,一定要通过mutation来改变状态:

mutations: {
            search: function (state, payload) {
                state.items = list.filter(v => v.title.indexOf(payload.title) !== -1);
            },
            save: function (state, payload) {
                if (state.initItem.id) {
                    var o = list.filter(v => v.id === payload.id);
                    o.title = payload.title;
                    o.desc = payload.desc;
                    state.items=state.items.map(v=>{
                        if(v.id==payload.id){
                            return payload;
                        }
                        return v;
                    });
                    
                } else {
                    state.items.push(new Todo(payload.title, payload.desc));
                }

                list = state.items;
            },
            remove: function (state, payload) {
                state.items = state.items.filter(v => v.id !== payload.id);
            },
            edit: function (state, payload) {
                state.initItem = state.items.filter(v => v.id === payload.id)[0];
            }
        }

我们添加的上述几个mutations,包括search、save、remove、edit,由于在每一个组件中都可以访问到this.$store,那么我们就不用对事件一层一层的传递啦,我们只需要在需要调用的地方,commit对应的mutation即可,比如search操作就是在searchbar组件中,那么我们没必要传递到父组件中来触发,基于此,我们修改SearchBar组件:

 /**
     * 搜索组件
     */
    var SearchBar = {
        template: `
        <div class="row toolbar">
            <div class="col-md-8">
                keyword:
                <input type="text" v-model="keyword" />
                <input type="button" @click="search()" value="search" class="btn btn-primary"  />
            </div>
        </div>
    `,
        data: function () {
            return {
                keyword: ''
            }
        },
        methods: {
            search: function () {
                this.$store.commit("search", {
                    title: this.keyword
                });
            }
        }

    }

这里看起来没有简单好多,但是我们至少不用把我们的事件往上级送了,可以对比最初代码,同理对我们的其它组件都进行重构:

 /**
     * 表单组件
     */
    var TodoForm = {
        template: `
     <div class="col-md-6">
        <div class="form-inline">
            <label for="title" class="control-label col-md-4">title:</label>
            <input type="hidden" v-bind:value="todo.id" />
            <input type="text" v-model="todo.title" class="form-control col-md-8">
        </div>
        <div class="form-inline">
            <label for="desc" class="control-label col-md-4">desc</label>
            <input type="text" v-model="todo.desc" class="form-control col-md-8">
        </div>
        <div class="form-inline">
            <input type="button" value="OK" v-on:click="ok()" class="btn btn-primary offset-md-10"  />
        </div>
    </div>
    `,
        props: ['initItem'],

        computed: {
            todo: function () {
                return { id: this.initItem.id, title: this.initItem.title, desc: this.initItem.desc };
            }
        },

        methods: {
            ok: function () {
                this.$store.commit("save",this.todo);
            }
        }

    }
    /**
     * 列表项组件
     */
    var TodoItem = {
        template: `
     <tr>
        <th>{{todo.id}}</th>
        <td>{{todo.title}}</td>
        <td>{{todo.desc}}</td>
        <td>
            <input type="button" value="remove" @click="remove()" class="btn btn-danger" />
            <input type="button" value="edit" @click="edit()" class="btn btn-info" />
        </td>
    </tr>
    `,
        props: ['todo'],
        methods: {
            edit: function () {
               this.$store.commit('edit',this.todo);
            },
            remove: function () {
                this.$store.commit('remove',{id:this.todo.id});
            }
        }
    }
    /**
     * 列表组件
     */
    var TodoList = {
        template: `
    <div class="col-md-6">
        <table class="table table-bordered">
            <tr>
                <th></th>
                <th>title</th>
                <th>desc</th>
                <th></th>
            </tr>
            <todo-item  v-for="item in items" :todo="item" :key="item.id" ></todo-item>
        </table>
    </div>
    `,
        props: ['items'],
        components: {
            'todo-item': TodoItem
        }
        
    }
    /**
     * 容器组件
     * 说明:容器组件包括三个字组件
     */
    var TodoContainer = {
        template: `
        <div class="container">
            <search-bar></search-bar>
            <div class="row">
                <todo-list :items="items" ></todo-list>            
                <todo-form :init-item="initItem" ></todo-form>
            </div>
        </div>
    `,

        components: {
            'search-bar': SearchBar,/**SearchBar组件注册 */
            'todo-form': TodoForm,/**TodoForm组件注册 */
            'todo-list': TodoList/**TodoList组件注册 */
        },
        computed: {
            initItem: function () {
                return this.$store.state.initItem;
            },
            items: function () {
                return this.$store.state.items;
            }
        }
    }

首先看一下我们的TodoContainer组件,里面已经清爽了好多,原来所有的逻辑,所有的属性,都汇集在这里,现在每个组件的逻辑都是它自己负责,表单组件负责保存操作,所以在其中提交commit(“save”);todo组件负责编辑和删除,所以在其方法中封装了remove和edit的mutaiton的访问。至此,我们的代码可以正常运行,由于只是对前文demo的重构,这里不再贴出运行效果图。

小结,在store中定义的状态,是响应式的,对其中状态的改变会导致view的重新渲染,改变状态只能通过提交mutation。由于其状态的响应式,所以我们在访问时一般定义成计算属性,如TodoContainer组件中的initItem和items;一般来说,不是所有状态都要定义到vuex的store中,每个组件都会有自己私有状态,只有全局或者共享状态才适合定义在store中,所以在实际开发中,需要好好斟酌;本篇就到此为止,其实算是上篇的一个延伸,下一篇介绍Actions,会继续在本篇demo的基础上进行延伸,敬请期待。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏全栈之路

VUE之组件全局方法

全局方法其实是js自身就可以实现的方法,具体实现其实很简单, 比如加个日志显示组件:

683
来自专栏ytkah

dedecms获取当前文章所在栏目URL

  我们知道dedecms有一个面包屑导航的调用函数,{dede:field name='position'/},这个样式是固定的,有时要个性化一些的话需要修改...

2518
来自专栏GreenLeaves

WCF系列教程之WCF客户端异常处理

本文参考自:http://www.cnblogs.com/wangweimutou/p/4414393.html,纯属读书笔记,加深记忆 一、简介 当我们打开W...

1856
来自专栏小程序·云开发官方专栏

小程序页面管理与跳转

原文链接:https://godbasin.github.io/2018/09/08/wxapp-page-and-navigate/

831
来自专栏林德熙的博客

win10 uwp 异步转同步 使用的条件使用方法使用Task.Wait 时需要小心死锁

在本文开始,我必须告诉大家,这个方法可能立即死锁,所以使用的时候需要满足下面的条件

862
来自专栏布尔

Ext的组件模型印象

组件模型在Ext1.x中已经引入了,但在框架中并没有得到全面的整合。2.0以后组件得到了很大的提高和改进,成为了框架的里最基础的一个类。组件对象模型为组件的创建...

16610
来自专栏听雨堂

ASP.Net Web Page深入探讨

这篇文章经典,看过之后大受启发。值得一看!看来ASP.NET跟JSP其实是一样的,本质上没区别,ASP.NET能做到的JSP一样可以做到,反之亦然。只不过ASP...

1837
来自专栏魏琼东

基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET - 对象控制反转

     控制反转,即IOC(Inversion of Control),也叫反转模式,也称依赖注入DI(Dependency Injection)模式,关于此...

1888
来自专栏灯塔大数据

15 个 Android 通用流行框架大全

? 1 缓存 名称描述DiskLruCacheJava实现基于LRU的磁盘缓存 2 图片加载 名称描述Android Universal Image Lo...

2736
来自专栏大壮

关于自定义聊天功能(理论篇)然后如果有人需要代码和其他疑惑请留言。

1374

扫码关注云+社区