目前可以想到的方案有两种:

  1. 通过变量进行控制;
  2. 节流函数。

解决方案

方案一:通过变量进行控制

book.vue

<template>
    <view @tap="handleClickBook(bookId)">点我试试看</view>
</template>

<script>
import Taro from '@taro/tarojs';
import {throttle} from '../utils/utils';

export default {
    data: {
        // 初始为可以跳转的状态
        canJump: true,
    },
    onHide() {
        this.canJump = true;
    },
    methods: {
        handleClickBook(bookId: number) {
            if (this.canJump) {
                // 当触发跳转函数的时候,需要将跳转的标志位设为false,表示在跳转完成之前,不能重复进行操作
                this.canJump = false;
                Taro.navigateTo({
                    url: '/pages/detail/index' + bookId
                });
            }
        },
    }
};
</script>

这种方式是满足我们的需求的,但却不是最好的方案,更好的方案是使用 ** 节流函数** 。为什么说它更好呢?我们接着往下看👇

方案二:节流函数(推荐)

首先来看一看如何来编写我们的节流函数,这里给出一个示例,这个示例中的节流方法有两种:

  1. 时间戳;
  2. setTimeout。

utils/utils.js

/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
export function throttle(func, wait, type = 1) {
    let previous = 0;
    let timeout: null | NodeJS.Timeout = null;
    if (type === 1) {
        previous = 0;
    }
    else if (type === 2) {
        timeout = null;
    }
    return function () {
        const context = this;
        const args = arguments;
        if (type === 1) {
            const now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }
        else if (type === 2) {
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args);
                }, wait);
            }
        }
    };
}

好了,已经变写好了我们的节流函数,接下来看看如何去应用它:

book.vue

<template>
    <view @tap="handleClickBook(bookId)">点我试试看</view>
</template>

<script>
import Taro from '@taro/tarojs';
import {throttle} from '../utils/utils';

export default {
    data: {
        bookId: 1,
    },
    methods: {
        handleClickBook: throttle(function nav(bookId: number) {
            Taro.navigateTo({
                url: '/pages/detail/index' + bookId
            })
        }, 1500),
    }
};
</script>

🙆‍♂️OK,这样我们就解决了我们遇到的重复跳转的问题啦!

❓那为什么我更推荐使用节流函数的方案呢?是因为节流函数的方案更便于将跳转api封装成工具方法,避免在所有涉及到跳转的地方都需要做一次防止重复点击的处理。

👁那我们又该如何将跳转api封装成工具方法?如何去使用被封装的工具方法呢?

util/index.js:封装跳转api

import Taro from '@taro/tarojs';
import {throttle} from '../utils/utils';

export const navigateTo = throttle(function nav(bookId: number): void {
    Taro.navigateTo({
        url: '/pages/detail/index' + bookId
    })
}, 1500);

book.vue

<template>
    <view @tap="handleClickBook(bookId)">点我试试看</view>
</template>

<script>
import Taro from '@taro/tarojs';
import {throttle} from '../utils/utils';
import {navigateTo} from '../utils/index';

export default {
    data: {
        bookId: 1,
    },
    methods: {
        // 只需要调用一下封装好的api就好了
        handleClickBook() {
            navigateTo(bookId);
        },
    }
};
</script>

👀你看,这样是不是更简单了,仅仅需要在一个地方做防止重复跳转的逻辑就好了!

以下为我的踩坑记录,不用看。

踩坑

在开发过程中,我在以上两种方案上都踩过坑。

方案一踩的坑

我曾在 Taro.navigatesuccess 回调中去复原 canump 的值,但是发现根本没有解决问题。原因在于 Taro.navigatesuccess 回调并不是在跳转完成之后执行的,而是在程序确认改路径可以跳转后就已经执行,但是在那个时候,页面还处于存在的状态。一旦canump 的值被复原,那么用户又可以重复点击重复跳转了。

方案二踩的坑

这个坑踩在封装跳转函数的时候,先看踩坑的代码:

import Taro from '@taro/tarojs';
import {throttle} from '../utils/utils';

export const navigateTo = (bookId): void => throttle(function nav() {
    Taro.navigateTo({
        url: '/pages/detail/index' + bookId
    })
}, 1500);

拿这里的代码跟上面可用的工具方法对比一下就知道问题出在哪里了:我将 throttle 外面又包装了一个函数,这样的话 throttle 函数就没有起到它应有的作用。因为每次 navigateTo 执行的都是一个新函数。