一、服务器尽量返回一维数组
比如,一个商品列表,每个商品又属于不同的大类,甚至又属于不同的小类,可能你的一个选择是,让服务器返回一个二维(甚至三维)数组,但是这里面问题很大,我慢慢说:
二维数组问题很大
-
goodsList有可能要放在vuex里,因为它会被多个页面调用或者修改。没错吧?
-
这个goodsList是个二维(或者三维)数组。没错吧?(下文为简单描述起见,只说是二维数组。)
-
列表页UI中,每个商品占一定空间,然后从上到下排列,没错吧?从优化上说,每个商品一定要做一个组件,比如叫goods-item。没错吧?(如果你不同意提炼出子组件,我负责任地说,你根本不懂Vue和虚拟DOM。)
-
这时候涉及到向
<goods-item>
组件传递数据,用于渲染,这时候如果直接传一个对象,比如{goodsId: 323, goodsName: '咸鸭蛋', currnetPrice: 23.5}
,这里注意,这个对象是非响应式的,虽然只用于渲染是足够了,但是,如果要修改数据,比如修改saleVolume(即购买量),那么显然,直接修改是非响应式的。没错吧?所以还是要去vuex里修改。 -
那么,修改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。
两套数据有什么好处?
-
一维的goodsList用于vuex修改数据,修改数据的时候只需要向mutation传递该goods在整个数组的下标即可,根本不用传递goodsId,因为很显然,用下标定位数组元素是最方便也是最快的。对吧?
-
一维的goodsList也可以用于渲染一些定制化的列表,比如搜索结果,搜索结果里通常不需要按照category分类。如果真的需要按照category分类,你还有一个categoryList可以用,对吧?
-
如果你真的需要二维数组去渲染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 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
所以:
-
不要做影分身!state原则上跟getter不要有重复,除非有某种必要。
-
getter要发挥“监听”的特点,比如说,某商品的saleVolume发生了变化,它会影响一系列的数据,比如购物车数量、购物车总价、是否达到起送价(如果是外卖APP的话),等等,那么怎么做?购物车数量我应该设为一个getter,同理,购物车总价我设为getter,是否达到起送价我设为getter,全部由goodsList开始计算,当goodsList发生了变化,这些计算属性全都会重新计算。
如果不设为getter,只设为state,那么我们就只能写面向过程的代码,也就是,修改saleVolume的语句下面要跟上一大串的计算购物车数量、购物车总价、是否达到起送价的代码,是不是很蠢? -
影分身的省事的地方在于只用写mapGetters,不用写mapState。我只能说,请别偷懒,偷懒不是这么偷的。state是有mutation操作的数据,getters是不会被直接操作的数据,区别很大,不要混淆!
三、用户触发的事件回调函数应使用onClick...、onChange...这类方法名
一个Vue实例方法,通常有2种用途:
- 作为用户事件的回调
用户事件的回调,比如点击按钮,播放视频,那么,我在元素上绑定的事件就可以叫onClickPlayButton
。
- 作为业务逻辑的一部分
点击播放按钮,当然是播放视频,那么我就可以定义一个方法叫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>
网友评论