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

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

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

二维数组问题很大

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

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

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

  4. 这时候涉及到向组件传递数据,用于渲染,这时候如果直接传一个对象,比如{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。比如:


九、

你可能感兴趣的:(Vue实战中的经验总结(不定期更新))