响应式的 switchboard:让又大又慢的Vue/AIpine 页面爆快

科技资讯 投稿 5800 0 评论

响应式的 switchboard:让又大又慢的Vue/AIpine 页面爆快


官方地址: [AIpine.js]https://alpinejs.dev


小提示: 在这篇文章中我将使用Vue/AIpine 术语,但是我认为此模式可以应用于更多不同的语言框架

问题就是:激活某一个单行居然差不多要耗时整整一秒钟

所以,我用一个加载了1万行的页面去测试寻找以找到尽可能的提高性能方法。 不久,我想出了一个简洁的模式让页面可以立即更新状态。我称它为: Switchboard 模式

在此例子中,我不打算用AIpine 。取而代之的是AIpine 底层用到的 vue 提供的一些通用响应式方法。 如果你对 “watchEffect” 和 “ref” 不太熟悉,通过后面的代码片断你应该能凭直觉就知道它们的用法,如果还是不知道,那么 api 文档就在这里查看

let activeRow = 12

document.querySelectorAll('tr'.forEach((row => {
    if (row.id === activeRow {
        row.classList.add('active'
    } else {
        row.classList.remove('active'
    }
}

现在,当不同行被点击时,我们可以给行添加点击事件来设置新的高亮行

let activeRow = 12

document.querySelectorAll('tr'.forEach((row => {
    row.addEventListener('click', ( => {
        activeRow = row.id
    }

    if (row.id === activeRow {
        row.classList.add('active'
    } else {
        row.classList.remove('active'
    }
}

以上代码的问题是,当一个行被点击,当前激活行会更新,但在视觉上我们看不到任何变化。

import { ref, watchEffect } from 'vue'

let activeRow = ref(12

document.querySelectorAll('tr'.forEach((row => {
    row.addEventListener('click', ( => {
        activeRow.value = row.id
    }

    watchEffect(( => {
        if (row.id === activeRow.value {
            row.classList.add('active'
        } else {
            row.classList.remove('active'
        }
    }
}

上面的代码片断做了这么几件事

    用ref 包裹 activeRow 变量,从而使得它可以被响应依赖追踪
  • 循环1万行,添加点击事件,用于改变响应式的 actievRow 变量
  • 注册一个响应式副作用 watchEffect 它会在任意响应依赖变更时重新运行(此处是 activeRow )

现在,当某个用户点击某行,那么被点击行将变成 active 其它行自动变成 deactivated

为什么 ?因为每当activeRow变量发生变化时,1万个watchEffect 回调会被执行。

解决方案: 一个响应式的switchboard

响应式 switchboard 这术语是我现在为这个概念创造的。 非常有可能这个模式也许已经有了其它的名称,但是,管它呢…

假如换成一个单独状态,和1万个不同预存的值(和上面一样),我们拥有了1万个不同的状态,每个状态是一个布尔值,代表了每个预设值。举个栗子:

// Before
let activeRow = ref(4

// After
let rowStates = {
    1: ref(false,
    2: ref(false,
    3: ref(false,
    4: ref(true,
    5: ref(false,
    ...
}

让我们稍变动一下上面例子的代码来使用此模式:

import { ref, watchEffect } from 'vue'

let rowStates = {}

document.querySelectorAll('tr'.forEach((row => {
    rowStates[row.id] = ref(false

    row.addEventListener('click', ( => {
        rowStates[row.id].value = true
    }

    watchEffect(( => {
        if (rowStates[row.id].value {
            row.classList.add('active'
        } else {
            row.classList.remove('active'
        }
    }
}

好了,现在你能看到,不同于activeRow存储单一的row ID,我们使用 rowStates 来存储1万条数据,每条key就是row ID, 每条数据值就是一个响应式的布尔值,代表了当前行是否处于激活状态

不过还有一个问题 之前 因为 activeRow 只包含引用一个值,相同时间当前只有一个行被允许激活。 前一个行会自动变更为非激活态,因为每行都会自动重新计算。

让我们添加一丢丢代码来实现它:

import { ref, watchEffect } from 'vue'

let rowStates = {}

document.querySelectorAll('tr'.forEach((row => {
    rowStates[row.id] = ref(false

    row.addEventListener('click', ( => {
        // Deactivate the old row...
        for (id in rowStates {
            if (rowStates[id].value === true {
                rowStates[id].value = false
                return
            }
        }

        rowStates[row.id].value = true
    }

    watchEffect(( => {
        if (rowStates[row.id].value {
            row.classList.add('active'
        } else {
            row.classList.remove('active'
        }
    }
}

正如你所看到的,我们添加了一丢丢代码在点击事件内,循环全部的行并设置为非激活态

结果是我们回头在之前优化中使用过的,通过添加一点数据来存储当前激活的行ID。 它类似于基础的暂存,使得我们无需再使用循环了:

import { ref, watchEffect } from 'vue'

let rowStates = {}
let activeRow

document.querySelectorAll('tr'.forEach((row => {
    rowStates[row.id] = ref(false

    row.addEventListener('click', ( => {
        if (activeRow rowStates[activeRow].value = false

        activeRow = row.id

        rowStates[row.id].value = true
    }

    watchEffect(( => {
        if (rowStates[row.id].value {
            row.classList.add('active'
        } else {
            row.classList.remove('active'
        }
    }
}

得了,现在我们添加 了activeRow 变量,我们搞定了完美高效更新

小的函数 switchboard 它包含了一个值,并返回一些通用函数用于访问和变更这个值

import { watchEffect } from 'vue'
import { switchboard } from 'reactive-switchboard' // Heads up: this isn't on NPM

let { set: activate, is: isActive } = switchboard(12

document.querySelectorAll('tr'.forEach((row => {
    row.addEventListener('click', ( => {
        activate(row.id
    }

    watchEffect(( => {
        if (isActive(row.id {
            row.classList.add('active'
        } else {
            row.classList.remove('active'
        }
    }
}

现在通过小小的switchboard 函数,我们拥有了和之前一样洁净的代码,并且拥有超高效的性能

import { switchboard } from 'reactive-switchboard' // Heads up: this isn't on NPM

let { get, set, is } = switchboard(12

// get( returns "12" in this case (non-reactively
// set(10 sets the internal value to 10
// is(10 runs a reactive comparison to the underlying value

这对于追踪类似于active激活态超级有用,因为只有一个激活状态值了

对于这些需求,我新建了一个通用方法 switchboardSet 它拥有类似 Set 对象的API (可能有更好的名字,但管它呢…

import { switchboardSet } from 'reactive-switchboard' // Heads up: this isn't on NPM

let { get, add, remove, has, clear } = switchboardSet([12]

// get( returns [12] (non-reactively
// add(10 sets the internal array to [12, 10]
// remove(10 sets the array back to [12]
// has(12 returns a reactive boolean
// clear( reactively clears the internal array: []

老弟你行了,发现问题,找到解决方法,并抽象它。

自取!

https://calebporzio.com/reactive-switchboard


Email: willian12345@126.com
https://github.com/willian12345


编程笔记 » 响应式的 switchboard:让又大又慢的Vue/AIpine 页面爆快

赞同 (29) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽