Skip to content

Instantly share code, notes, and snippets.

@calebporzio
Last active June 7, 2024 20:41
Show Gist options
  • Save calebporzio/3153ddfc31ae5a00463b71b2fc938ca0 to your computer and use it in GitHub Desktop.
Save calebporzio/3153ddfc31ae5a00463b71b2fc938ca0 to your computer and use it in GitHub Desktop.
import { reactive } from '@vue/reactivity'
function switchboard(value) {
let lookup = {}
let current
let get = () => current
let set = (newValue) => {
if (newValue === current) return
if (current !== undefined) lookup[current].state = false
current = newValue
if (lookup[newValue] === undefined) {
lookup[newValue] = reactive({ state: true })
} else {
lookup[newValue].state = true
}
}
let is = (comparisonValue) => {
if (lookup[comparisonValue] === undefined) {
lookup[comparisonValue] = reactive({ state: false })
return lookup[comparisonValue].state
}
return !! lookup[comparisonValue].state
}
value === undefined || set(value)
return { get, set, is }
}
function switchboardSet(value) {
let lookup = {}
let current = []
let all = () => current
let add = (newValue) => {
if (current.includes(newValue)) return
current.push(newValue)
if (lookup[newValue] === undefined) {
lookup[newValue] = reactive({ state: true })
} else {
lookup[newValue].state = true
}
}
let remove = (newValue) => {
if (! current.includes(newValue)) return
current = current.filter(i => i !== newValue)
if (lookup[newValue] === undefined) {
lookup[newValue] = reactive({ state: false })
} else {
lookup[newValue].state = false
}
}
let has = (comparisonValue) => {
if (lookup[comparisonValue] === undefined) {
lookup[comparisonValue] = reactive({ state: false })
return lookup[comparisonValue].state
}
return !! lookup[comparisonValue].state
}
let clear = () => {
for (let i = 0; i < current.length; i++) {
let key = current[i]
delete current[i]
lookup[key].state = false
}
current = current.filter(i => i)
}
value === undefined || value.forEach(i => add(i))
return { all, add, remove, has, clear }
}
@AlexVipond
Copy link

This is super cool, I'm building a useGrid Vue composable to power interactive grids, and will definitely keep an eye on this pattern for optimization in the future.

If used in a Vue setup function, the "gotcha" with reactive-switchboard is that you can't/shouldn't create reactive state or effects outside of a component's setup function, for example, when dynamically adding new rows to the table and registering them in the switchboard lookup + setting up new watchers. Doing this in a Vue component would give you memory leaks and/or strange bugs and unpredictability.

It's a solved problem though—you can use effectScope to fix it. With that, you'd scope all the dynamic runtime reactive stuff, and you'd also return a thin wrapper around watchEffect to make sure effects run in the same scope. You can also return a stop function so people can clean up the reactive-switchboard scope on demand.

This fork shows what I mean (I haven't tested any of this): https://gist.github.com/AlexVipond/4fd4591deebc643b7e3bd9a5a9194599

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment