使用JavaScripts的Array操作实现RatingBar评分效果

最近项目上要做一个点击星星评价的效果,大致设计图如下:

其实看到这个设计图,自己就想GitHub上肯定有挺多基于ReactNative的RatingBar相关的库,Google了一下确实很多,不过想着这么简单的一个东西,完全也没必要用第三方库,自己实现也应该很容易的。虽然我也没看第三方库是怎么实现的,但是自己对于在ReactNative上实现这个RatingBar还是有思路的

思路

最开始是想着新建一个包含有5个初始状态的list,然后通过map函数来绘制出5个没有评级的默认状态,然后用户点击某个星星的时候,通过list下标index来将list里面的index之前的星星替换成评级过的黄色星星,但是这里需要注意的一点就是,因为评级在提交之前是可以随意更改的,所以不能说点击了某个星星之后就把对应index之前的变为黄色的已评价的,毕竟如果用户一开始选择了4颗星,后面又点击选择2颗星,这个时候一方面是把2之前的星星换成黄色,另一方面是把2-4之前的黄色变为默认的白色星星

splice&&slice实现

在说第一种实现方式之前,先了解一下splice和slice的基本用法

splice基本用法

splice主要功能是通过删除现有元素和/或添加新元素来更改一个数组的内容

1
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

start是指定修改的开始位置(从0计数)。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从-1计数);若只使用start参数而不使用deleteCount、item,如:array.splice(start) ,表示删除[start,end]的元素。

deleteCount是可选参数,表示要移除的数组元素的个数,需要说明的是如果deleteCount大于start之后的所有元素个数,那么 start后面的元素都将被删除(含第start位)这个和省略deleteCount效果一样。

slice基本用法

slice方法返回一个数组从开始到结束(前闭后开)的一部分浅拷贝到一个新数组对象,需要注意的是原始数组不会被修改。

1
2
3
4
5
6
7
8
9
arr.slice([begin[, end]])
arr.slice();
// [0, end]

arr.slice(begin);
// [begin, end]

arr.slice(begin, end);
// [begin, end)

参数begin和end都可以为负数,如果begin为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2)表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。如果end负数,则它表示在原数组中的倒数第几个元素结束抽取。 slice(-2,-1)表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。

slice 不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:

  • 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

  • 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
    如果向两个数组任一中添加了新元素,则另一个不会受到影响。

slice和splice的基本用法搞明白之后,下面就可以使用这两个来实现这个评分效果了,思路:
既然想着在用户点击星星开始评分的时候来改变背景的星星图片,从而展示评分效果,首先想到的就是涉及到array数组的操作,比如用户点击三个星的时候(index=2),这个时候我就需要把前三个星星替换成黄色星星的图片,我们通过简单的示例先演示一下,我们用A代表未评分的白色星星,B代表评分的黄色星星,那么简化操作如下:

1
2
3
4
5
6
7
//假设用户评分3颗星,也就是点击的index为2的星星
let index=2
let defaultStars = ['A','A','A','A','A']
defaultStars.splice(0,index+1,'B','B','B','B','B')
//console.log(defaultStars);
let newRatingStars = defaultStars.slice(-5)
//console.log(defaultStars.slice(-5))

其实,再使用splice的时候,我们可以根据index计算出0~index之间有几个白色星星需要替换(插入),这里我没有计算,直接用了5个评分过的星星,这样一来就会将index+1之前的星星A都替换成B,还会把多余的几个B星星也插入到数组前面,这样一来最后的数组长度可能是大于5的,然后使用slice截取倒数5个就ok了,后面通过setState更新星星背景图片数组,那么评分效果就出来了

fill实现方式

写代码不能只满足于实现了某一功能,要反复去思考是不是还有”更好”的办法,上面的那种方法又是通过splice替换,又是通过截取,其实挺麻烦的,肯定有更简单的方法,那就是用fill来实现,先看看fill的基本用法:

fill基本用法

fill方法用一个固定值填充一个数组中从起始索引到终止索引内(前闭后开)的全部元素

1
arr.fill(value[, start[, end]])

value是用来填充的元素,start和end就不用介绍了,填充的起点和终点(不包括终点),最后返回修改后的数组

使用fill方式思路很简单,fill是将指定范围的元素用新元素填充,那我每次点击RatingBar评分的时候,是把点击index之前的换成评分过的,index之后的换成未评分的,反过来想一下,我每次点击的时候以一个充满评分背景图片的数组未基准来处理,用户每次点击的时候,就直接把index到末尾的数组元素替换成未评分的,那么效果不就出来了么,而且同样适用于已经评分再次点击不同的index来更改分数,思路有了代码就很简单了,同样使用A表示为评分的白色,B表示评分的黄色,代码如下:

1
2
3
let index=2
let defaultStars = ['B','B','B','B','B']
defaultStars.fill('A', index + 1)

然后通过setState在用户每次点击的时候,处理并更新背景数组就over了,是不是比第一种简洁高效很多

数组复制

上面处理数组的时候,会用到数组复制,一些常用的数组复制方法如下,这其中有的是深复制有的是签复制,大家根据自己的需要来选择合适的数组复制方法:

1
2
3
4
5
6
7
8
9
10
const names = [ 'B', 'B', 'B', 'B', 'A']

const copy1 = names.slice()
const copy2 = [].concat(names)
const copy3 = Object.values(names)
const copy4 = [...names]
const copy5 = Array.of(...names)
const copy6 = JSON.parse(JSON.stringify(names))
const copy7 = names.map(i => i)
const copy8 = Object.assign([], names)