美文网首页
Vue实战中的经验总结(不定期更新)

Vue实战中的经验总结(不定期更新)

作者: microkof | 来源:发表于2020-04-10 18:31 被阅读0次

    一、服务器尽量返回一维数组

    比如,一个商品列表,每个商品又属于不同的大类,甚至又属于不同的小类,可能你的一个选择是,让服务器返回一个二维(甚至三维)数组,但是这里面问题很大,我慢慢说:

    二维数组问题很大

    1. goodsList有可能要放在vuex里,因为它会被多个页面调用或者修改。没错吧?

    2. 这个goodsList是个二维(或者三维)数组。没错吧?(下文为简单描述起见,只说是二维数组。)

    3. 列表页UI中,每个商品占一定空间,然后从上到下排列,没错吧?从优化上说,每个商品一定要做一个组件,比如叫goods-item。没错吧?(如果你不同意提炼出子组件,我负责任地说,你根本不懂Vue和虚拟DOM。)

    4. 这时候涉及到向<goods-item>组件传递数据,用于渲染,这时候如果直接传一个对象,比如{goodsId: 323, goodsName: '咸鸭蛋', currnetPrice: 23.5},这里注意,这个对象是非响应式的,虽然只用于渲染是足够了,但是,如果要修改数据,比如修改saleVolume(即购买量),那么显然,直接修改是非响应式的。没错吧?所以还是要去vuex里修改。

    5. 那么,修改vuex的state,必然只能使用mutation(或者action),所以这就需要调用mutation(或Action),还要给它传递到底修改哪个商品,以及修改成多少,所以我们就要先在goodsList里定位这个商品,然后再修改这个商品。怎么定位呢?你需要根据categoryId和goodsId,先外循环category列表,再内循环goods列表,结果只为修改一个saleVolume,这很蠢,对吧?

    那么应该怎么做?

    goodsList

    服务器返回一维数组,叫goodsList,存入state里。这个goodsList必须至少有categoryId字段。

    categoryList
    选择一:服务器直接发送

    categoryList可以由服务器直接发送,形式是:

        [
          {
            categoryId: 1,
            categoryName: '糕点坊',
            goodsList: [], // 原始数据里没有这个字段,需要拿到数据之后本地JS给加上空数组
          },
          {
            categoryId: 2,
            categoryName: '炒货铺',
            goodsList: [], // 原始数据里没有这个字段,需要拿到数据之后本地JS给加上空数组
          },
        ]
    

    计算categoryList里每一个category的goodsList,把每一个goods分别push进去,就OK了!

    选择二:本地计算

    我们也可以本地计算,本地计算的话,goodsList里就必须带足够多的category的信息,比如至少有categoryName字段。

    在vuex中新建一个getter叫categoryList,由state.goodsList计算categoryList,这是一个由一维数组变成二维数组的过程。计算细节就不多说了。

    最终我们有一个一维的goodsList,和一个二维的categoryList,它每项有一个键是goodsList,用于存放自己的所有goods。

    两套数据有什么好处?

    1. 一维的goodsList用于vuex修改数据,修改数据的时候只需要向mutation传递该goods在整个数组的下标即可,根本不用传递goodsId,因为很显然,用下标定位数组元素是最方便也是最快的。对吧?

    2. 一维的goodsList也可以用于渲染一些定制化的列表,比如搜索结果,搜索结果里通常不需要按照category分类。如果真的需要按照category分类,你还有一个categoryList可以用,对吧?

    3. 如果你真的需要二维数组去渲染UI,而且还要数组内容能修改,那么,在刚请求到一维goodsList的时候,就给每个goods加上个index属性。将来修改数据的时候,依然只需要给vuex传入下标就行了。

    总之,想修改数据,就传下标,想渲染数据,我有一维和二维两套数据,你随便用,这样还有问题吗?并且,后端的操作也简化,毕竟后端不用先计算二维数组,你也不用跟后端约定数据结构,对不对?

    二、不要把vuex的getter当做state的影分身

    包括一些牛人的开源代码中,依然可以见到这样的代码:

    export default {
      state: {
        a: 1,
      },
      getter: {
        a: (state) => state.a,
      }
    }
    

    然后getter可能会写出几十个,全是state的影分身,全是x: (state) => state.x这种写法。调用都是mapGetters。胡闹么这不是?不用mapState留着下崽?

    官方文档对getter说的很清楚:

    有时候我们需要从 store 中的 state 中派生出一些状态。

    Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

    所以:

    1. 不要做影分身!state原则上跟getter不要有重复,除非有某种必要。

    2. getter要发挥“监听”的特点,比如说,某商品的saleVolume发生了变化,它会影响一系列的数据,比如购物车数量、购物车总价、是否达到起送价(如果是外卖APP的话),等等,那么怎么做?购物车数量我应该设为一个getter,同理,购物车总价我设为getter,是否达到起送价我设为getter,全部由goodsList开始计算,当goodsList发生了变化,这些计算属性全都会重新计算。
      如果不设为getter,只设为state,那么我们就只能写面向过程的代码,也就是,修改saleVolume的语句下面要跟上一大串的计算购物车数量、购物车总价、是否达到起送价的代码,是不是很蠢?

    3. 影分身的省事的地方在于只用写mapGetters,不用写mapState。我只能说,请别偷懒,偷懒不是这么偷的。state是有mutation操作的数据,getters是不会被直接操作的数据,区别很大,不要混淆!

    三、用户触发的事件回调函数应使用onClick...、onChange...这类方法名

    一个Vue实例方法,通常有2种用途:

    1. 作为用户事件的回调

    用户事件的回调,比如点击按钮,播放视频,那么,我在元素上绑定的事件就可以叫onClickPlayButton

    1. 作为业务逻辑的一部分

    点击播放按钮,当然是播放视频,那么我就可以定义一个方法叫playVideo

    OK,这里有个问题是,为何用途1函数名不叫playVideo

    这就是我的用意:

    • 事件驱动,用onClick打头,类似onClickPlayButton这种

    • 业务驱动,用play这种写法,类似playVideo这种

    原因:

    这符合“一个函数只做一件事”的原则,我点击按钮,可能除了播放本视频外还要干别的,比如记录日志,比如暂停其他的视频,等等。因此,playVideo可能是事件回调的全部逻辑,又可能是onClickPlayButton的一部分逻辑。

    题外话:

    你可能会说“千篇一律用onClick很烦”。但是浏览器事件可不只是click事件,原生事件就不知道有多少,再加上自定义事件就多了去了,代码写多了你就发现这种写法的优势。

    四、props接收的数据如果需要修改,且没必要告诉父组件,则可以深克隆一份

    父组件传给子组件的复杂数据类型,也就是数组和对象,往往需要在子组件中进行修改,并且,这种修改不需要告诉父组件。比如一个可视化图表组件,你传入的数据不符合图表需要的数据格式,那么图表组件就需要把数据加工一下,这种加工并不需要反馈给父组件,此时就可以把数据深克隆一份,赋值给data,然后想怎么改就怎么改就好了。

    首先,图表组件默认是不初始化的,因为需要等待传入数据,而数据一般来说需要ajax,所以一般会使用监听,监听props。

    然后,一旦监听到,就this.xxx = JSON.parse(JSON.stringify(newVal));,然后对xxx一通修改,最后执行图表初始化命令就行了。

    五、如何科学的合并data中的Object数据

    合并Object我们使用Object.assign(),现在假设this.form{},ajax了一套数据,准备合并到this.form

    错误用法

    Object.assign(this.form, response.data);
    

    这会将this.form由响应式变成非响应式。同样的,这样也不行:

    this.form = Object.assign(this.form, response.data);
    

    正确用法

    this.form = Object.assign({}, this.form, response.data);
    

    这里面注意数据覆盖问题,覆盖原则一句话说就是:重名属性,以最后的包含该属性的对象为准。

    另外,还有更骚的方法是:

    this.form = {...this.form, ...response.data};
    

    六、v-for的:key到底用id还是index

    当我们在使用v-for进行渲染时,尽可能使用渲染元素自身属性的id给渲染的元素绑定一个key值,这样在当前渲染元素的DOM结构发生变化时,能够单独响应该元素而不触发所有元素的渲染。

    实在没有id的时候,应考察item是否有其他的唯一属性,比如item.name,如果有,也可以用。

    如果item本身是数字或者字符串,如果你确定item值是唯一的,也可以拿item本身当做:key。

    最下策是用index。

    七、在某生命周期监听另外生命周期

    比如想在mounted监听beforeMounted周期的发生,怎么做?

    mounted() {
        this.$once('hook:beforeDestroy', () => {
          // ...
        })
      },
    

    八、组件外监听组件内部生命周期

    举个例子,一个组件是第三方的,没提供数据变化的钩子,你也不知道组件内什么时候发生了data变化,但是业务需要得知什么时候组件有数据变化,怎么办?

    可以通过@hook:updated监听组件的updated生命钩子函数。事实上,组件的所有生命周期钩子都可以通过@hook:钩子函数名来监听触发,只不过用的最多的还是@hook:updated。比如:

    <template>
      <custom-select @hook:updated="xx自定义事件名" />
    </template>
    

    九、

    相关文章

      网友评论

          本文标题:Vue实战中的经验总结(不定期更新)

          本文链接:https://www.haomeiwen.com/subject/ajhayhtx.html