Vue+Webpack开发可复用的单页面富应用教程(技巧篇)

本文首发于TalkingCoder,一个有逼格的程序员社区。转载请注明出处和作者。

写在前面

本文为系列文章,总共分四节,建议按顺序阅读:

《Vue+Webpack使用规范》

《Vue+Webpack开发可复用的单页面富应用教程(配置篇)》

《Vue+Webpack开发可复用的单页面富应用教程(组件篇)》

《Vue+Webpack开发可复用的单页面富应用教程(技巧篇)》

在前两节中,我们介绍了在项目https://github.com/icarusion/vue-vueRouter-webpack中如何配置webpack以及使用vue的组件化开发SPA。通过前两节的介绍,现在应该可以轻松的写几个路由和组件了吧,并且在这个工程的基础上开发会省去很多事情,我们的重心应该在组件的开发上。随着项目的不断扩大和对vue的学习程度不同,多少会出现一些复杂的问题或者编程的最佳实践,本篇文章就来介绍一些笔者在几个项目中积累的一些经验谈。

山寨的Vuex

说到山寨,我们先看看正版的Vuex是个什么鬼。

当你的Vue项目比较大,或者说组件使用的比较深度(层级、复用性)的时候,状态、数据越来越共享化,如果把数据都存在组件中,会变的不太好维护,那么Vuex就是在这种情况下出现的。它借鉴于Flux和Redux的设计思想,用过React的话应该不会陌生,不过要比那些简单,而且转为Vue量身打造。

为什么需要山寨的Vuex

既然已经有正版的Vuex了,而且也不复杂,为什么需要山寨的vuex呢?我们先介绍一下什么是山寨的Vuex,其实这里有点概念化了,山寨的vuex并没有什么特殊,只是一个普通的js文件,为了达到类似的目的,我们将其命名为了vuex.js。它的结构大概是这样的:

exports.POILIST = {
    data : {
        list: []
    },
    clear : function() {
        this.data = {
            list: []
        };
    }
};
exports.MENU = {
    data: {
        list: [],
        select: {}
    },
    clear: function() {
        this.data = {
            list: [],
            select: {}
        };
    }
};

可以看到它对外导出了一些模块,模块的结构都有一个data,这个data里存储的就是我们需要共享的数据了,组件在通信时,不直接向上或向下传递数据,而是保存在vuex.js里,出于对象的引用关系,vue可以天然的实现共享数据的双向绑定,在使用时是这样的:

<template>
    <div>
        <li v-for="item in menu.list">{{ item }}</li>
        <p v-for="item in poi.list">{{ item.lng }},{{ item.lat }}</p>
    </div>
</template>
<script>
    import { POILIST, MENU } from '../config/vuex.js';
    export default {
        data() {
            return {
                menu: MENU.data,
                poi: POILIST.data
            }
        },
        methods: {
            set() {
                MENU.list.push('首页');
                POILIST.list.push({
                    lng: 124.1,
                    lat: 42.3
                });
            }
        }
    }
</script>

这种存储,可以理解为相当于存在全局,并且可以加一些事件来操作数据,比如我们demo的clear()用来清空数据,只不过这里是用函数来调用,也可以将其改为观察者模式,用事件派发的形式来修改数据,其实vuex正是这么玩的。

有了vuex.js,组件之间就可以直接保存到它里面,这对数据持久化、数据共享来说是一个简单粗暴的解决方案。它适用了不大的项目(当然在大型项目中它也没什么问题),或者不太想在当下接入vuex的架构,或者想简单而又优雅地维护一些全局状态和数据。

我们知道,JavaScript的对象是引用关系,也正是这个原因我们可以设计vuex.js,但也正是这个原因,如果你的数据不是对象,只是简单数组、数字、字符串等,是不能直接共享的,JS会复制来使用,所以我们在组件的data里,return的都是像MENU.data而不是MENU.data.list,在遍历时,才循环menu.list

到底用prop还是事件

上一篇文章介绍过,组件之间数据交互,主要有prop事件两种,在使用过程中发现,这两种方案有时候都可以达到同样的效果,比如下面的这个场景,父组件向子组件传递的一个数据id,子组件来判断这个id如果发生变化,就做一些事情。用prop是这么实现的:

<!-- 父组件 -->
<template>
    <child :id="id"></child>
</template>
<script>
    export default {
        data() {
            return {
                id: 1
            }
        },
        ready() {
            setTimeout(() => {
                this.id = 2;
            }, 1000);
        }
    }
</script>

<!-- child组件 -->
<template>
    
</template>
<script>
    export default {
        props: {
            id: {
                type: Number,
                default: 0
            }
        },
        data() {
            return {}
        },
        methods: {
            handler() { }
        },
        watch: {
            id() {
                this.handler();
            }
        }
    }
</script>

child组件通过watch来监听id的改变,如果用事件可以这样实现:

<!-- 父组件 -->
<template>
    <child :id="id"></child>
</template>
<script>
    export default {
        data() {
            return {
                id: 1
            }
        },
        ready() {
            setTimeout(() => {
                this.$broadcast('child-set-data', {
                    id: 2
                });
            }, 1000);
        }
    }
</script>

<!-- child组件 -->
<template>
    
</template>
<script>
    export default {
        props: {
            id: {
                type: Number,
                default: 0
            }
        },
        data() {
            return {}
        },
        methods: {
            handler() { }
        },
        events: {
            id() {
                this.handler();
            }
        }
    }
</script>

这次是通过events来接收事件,效果是一样的。既然两者都可以,那如何优雅的选择呢,笔者觉得可以在数据功能上进行区分。比如上面的场景,是由子组件child在知道id改变后,在它自己的作用域里去执行handler()方法,handler可能是一个需要ajax获取数据并渲染到child组件上的函数,这时候就推荐用watch来监听id的改变了。如果handler所对应的ajax任务是在父组件完成的,父组件需要将拿到的一堆json数据传给child时,这时可以通过事件来传递。不过说到底,还是看使用者的习惯了,这两者都是可以的,只不过事件更多的是来触发外部环境的action。

管好组件的一亩三分地

用好prop的sync

上篇文章介绍过,组件间可以通过prop传递数据,而且使用sync还可以双向绑定:

<child :id.sync="id"></child>

这样id就是一个双向的了,子组件修改后,父组件也会修改,但这样做有时候会分不清到底是谁改的,也很可能会不小心就修改了父组件的值,所以在使用时要特别注意。

验证prop

有时为了省事,在写组件时就将props直接写一个数组了,但如果你写的组件是给别人用的,那还是建议对每个prop都进行严格的验证,确保他人正确使用。具体验证方法可查看文档

子组件不该干涉父组件的状态

子组件可以通过$parent访问到父组件,也就是说它有权利去修改父组件的状态,但不建议这样做,虽然有时候是省事了,但是这让父子组件之间紧耦合,很难知道到底是谁来维护数据,而且你开发给别人用的组件,随意修改了父级的状态,这会让使用者不知所措。所以子组件应该只关心自己的数据,要修改父级状态时,通过$dispatch()方法派发事件来通知父级,由父级自己修改。

总结

一口气写完这4篇文章,还真是有点辛苦啊。半年前还只是用Vue的一些简单功能,到现在TalkingData的多条产品线已逐步使用这整套架构,这个推动的过程还是很快的,也是真正意识到Vue是一个很出色的框架,对它充满了信心。TalkingData接下来陆续上线的几个重磅产品(Data Cloud、Marketing Cloud),也是证明了这一点。很快Vue2.0也要来了,对它更是充满了期待。前端就是这么神奇,总有玩不尽的框架,但一定要选对一个适合自己的技术栈。

讨论区 (请尽量讨论和主题相关的话题)
##comment.user.name## (作者) ##comment.user.company + comment.user.job##
  • 删除
  • ##comment.comment.content##
    ##reply.reply.content##
  • 删除
  • 回复##comment.add_reply.user_name## (作者)
    发表回复

    添加标签,便于归类
    取消
    收藏
    写点推荐理由,选填
    取消
    推荐

    TalkingCoder

    寻找灵感 发现精彩

    登录 · 注册 · 忘记密码
    意见反馈