何大小成

mpvue · 输入框 · 光标跳位的优化

在用Mpuve的时候,做一个搜索功能:一遍输入一遍请求后台返回搜索建议列表。在Vue工程里这是挺简单的一个功能:就是一边输入,一边监听value,当发生变化就立刻请求相关接口。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<input type="text" v-model="value" />
</template>
<script>
export default {
data: () => ({
value: ''
}),
watch: {
value(val) {
this.loadSuggest(val)
}
},
methods: {
loadSuggest(val) {
console.log(val)
}
}
}
</script>

但是在mpvue你会发现:这样会导致闪屏以及输入框跳位。

因为在Mpvue里, 每次变更vlaue都会从微信小程序的 JSCore 进程,通过 setData 序列化成字符串后发送到 JSRender 进程。

issue其实也有相关问题解决,但是需要修改源码。我也尝试了也发现挺多问题,最终还是放弃使用。

https://github.com/Meituan-Dianping/mpvue/issues/639

后来发现: 函数去抖(debounce)可以解决。

函数去抖: 当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。

具体的函数去抖(debounce)以及函数节流(throttle)这里就不作详细介绍了。具体可以看:
https://github.com/hanzichi/underscore-analysis/issues/20

回到项目

上面说到函数去抖可以解决了,那么如何优化输入框跳位的问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<input type="text" v-model="value" />
</template>
<script>
export default {
data: () => ({
value: ''
}),
watch: {
value(val) {
this.loadSuggest(val)
}
},
methods: {
loadSuggest: debounce(async function(val) {
console.log(val)
}, 1000)
}
}
</script>

函数去抖(debounce)以及函数节流(throttle)的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* @Author: hopkinson
* @Date: 2018-07-23T13:44:34+08:00
* @Last modified by: hopkinson
* @Last modified time: 2018-08-19T16:22:47+08:00
*/

/**
*
* @param fn {Function} 实际要执行的函数
* @param delay {Number} 执行间隔,单位是毫秒(ms)
*
* @return {Function} 返回一个“节流”函数
*/
/* eslint-disable */
export const throttle = function(fn, threshhold) {
// 记录上次执行的时间
var last

// 定时器
var timer

// 默认间隔为 250ms
threshhold || (threshhold = 250)

// 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数
return function() {

// 保存函数调用时的上下文和参数,传递给 fn
var context = this
var args = arguments

var now = + new Date()

// 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃
// 执行 fn,并重新计时
if (last && now < last + threshhold) {
clearTimeout(timer)

// 保证在当前时间区间结束后,再执行一次 fn
timer = setTimeout(function() {
last = now
fn.apply(context, args)
}, threshhold)

// 在时间区间的最开始和到达指定间隔的时候执行一次 fn
} else {
last = now
fn.apply(context, args)
}
}
}
/**
*
* @param fn {Function} 实际要执行的函数
* @param delay {Number} 延迟时间,单位是毫秒(ms)
*
* @return {Function} 返回一个“防反跳”了的函数
*/

export const debounce = function(fn, delay) {

// 定时器,用来 setTimeout
var timer

// 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数
return function() {

// 保存函数调用时的上下文和参数,传递给 fn
var context = this
var args = arguments

// 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn
clearTimeout(timer)

// 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作),
// 再过 delay 毫秒就执行 fn
timer = setTimeout(function() {
fn.apply(context, args)
}, delay)
}
}