Skip to content

Commit

Permalink
Store atom state in context
Browse files Browse the repository at this point in the history
Ref nanostores#171

Here's initial attempt to move all stores state into global context
object. Context is basically a listeners queue and a map of stores
states.

Context management api is still yet to explore. For now it's for
internal usage now.
  • Loading branch information
TrySound committed May 27, 2023
1 parent faaca47 commit f7b43c5
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 38 deletions.
55 changes: 31 additions & 24 deletions atom/index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
import { clean } from '../clean-stores/index.js'

let listenerQueue = []
import { getListenerQueue, getStoreState } from '../context/index.js'

export let atom = (initialValue, level) => {
let listeners = []
let store = {
lc: 0,
l: level || 0,
value: initialValue,
iv: initialValue,
get lc() {
let state = getStoreState(store)
return state.lc
},
get value() {
let state = getStoreState(store)
return state.v
},
set(data) {
if (store.value !== data) {
store.value = data
let state = getStoreState(store)
if (state.v !== data) {
state.v = data
store.notify()
}
},
get() {
if (!store.lc) {
let state = getStoreState(store)
if (!state.lc) {
store.listen(() => {})()
}
return store.value
return state.v
},
notify(changedKey) {
let state = getStoreState(store)
let listenerQueue = getListenerQueue()
let runListenerQueue = !listenerQueue.length
for (let i = 0; i < listeners.length; i += 2) {
listenerQueue.push(
listeners[i],
store.value,
changedKey,
listeners[i + 1]
)
for (let i = 0; i < state.ls.length; i += 2) {
listenerQueue.push(state.ls[i], state.v, changedKey, state.ls[i + 1])
}

if (runListenerQueue) {
Expand Down Expand Up @@ -56,20 +60,22 @@ export let atom = (initialValue, level) => {
}
},
listen(listener, listenerLevel) {
store.lc = listeners.push(listener, listenerLevel || store.l) / 2
let state = getStoreState(store)
state.lc = state.ls.push(listener, listenerLevel || store.l) / 2

return () => {
let index = listeners.indexOf(listener)
let index = state.ls.indexOf(listener)
if (~index) {
listeners.splice(index, 2)
store.lc--
if (!store.lc) store.off()
state.ls.splice(index, 2)
state.lc--
if (!state.lc) store.off()
}
}
},
subscribe(cb, listenerLevel) {
let state = getStoreState(store)
let unbind = store.listen(cb, listenerLevel)
cb(store.value)
cb(state.v)
return unbind
},
off() {} /* It will be called on last listener unsubscribing.
Expand All @@ -78,8 +84,9 @@ export let atom = (initialValue, level) => {

if (process.env.NODE_ENV !== 'production') {
store[clean] = () => {
listeners = []
store.lc = 0
let state = getStoreState(store)
state.ls = []
state.lc = 0
store.off()
}
}
Expand Down
32 changes: 32 additions & 0 deletions context/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
let createContext = () => {
let context = {
// listener queue
q: [],
// store states
s: new Map(),
}
return context;
}

let context = createContext()

export let getListenerQueue = () => {
return context.q
}

// lazily initialize store state in context
export let getStoreState = (store) => {
let state = context.s.get(store)
if (!state) {
state = {
// listeners
ls: [],
// listeners count
lc: 0,
// value
v: store.iv,
}
context.s.set(store, state)
}
return state
}
6 changes: 4 additions & 2 deletions deep-map/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { atom } from '../atom/index.js'
import { getStoreState } from '../context/index.js'
import { getPath, setPath } from './path.js'

export { getPath, setPath } from './path.js'

export function deepMap(initial = {}) {
let store = atom(initial)
store.setKey = (key, value) => {
if (getPath(store.value, key) !== value) {
store.value = setPath(store.value, key, value)
let state = getStoreState(store)
if (getPath(state.v, key) !== value) {
state.v = setPath(state.v, key, value)
store.notify(key)
}
}
Expand Down
13 changes: 9 additions & 4 deletions lifecycle/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { clean } from '../clean-stores/index.js'
import { getStoreState } from '../context/index.js'

const START = 0
const STOP = 1
Expand Down Expand Up @@ -38,7 +39,8 @@ export let onStart = (store, listener) =>
on(store, listener, START, runListeners => {
let originListen = store.listen
store.listen = arg => {
if (!store.lc && !store.starting) {
let state = getStoreState(store)
if (!state.lc && !store.starting) {
store.starting = true
runListeners()
delete store.starting
Expand Down Expand Up @@ -68,6 +70,7 @@ export let onSet = (store, listener) =>
let originSetKey = store.setKey
if (store.setKey) {
store.setKey = (changed, changedValue) => {
let state = getStoreState(store)
let isAborted
let abort = () => {
isAborted = true
Expand All @@ -76,7 +79,7 @@ export let onSet = (store, listener) =>
runListeners({
abort,
changed,
newValue: { ...store.value, [changed]: changedValue }
newValue: { ...state.v, [changed]: changedValue }
})
if (!isAborted) return originSetKey(changed, changedValue)
}
Expand Down Expand Up @@ -123,7 +126,8 @@ export let onMount = (store, initialize) => {
return on(store, listener, MOUNT, runListeners => {
let originListen = store.listen
store.listen = (...args) => {
if (!store.lc && !store.active) {
let state = getStoreState(store)
if (!state.lc && !store.active) {
store.active = true
runListeners()
}
Expand All @@ -133,9 +137,10 @@ export let onMount = (store, initialize) => {
let originOff = store.off
store.events[UNMOUNT] = []
store.off = () => {
let state = getStoreState(store)
originOff()
setTimeout(() => {
if (store.active && !store.lc) {
if (store.active && !state.lc) {
store.active = false
for (let destroy of store.events[UNMOUNT]) destroy()
store.events[UNMOUNT] = []
Expand Down
14 changes: 8 additions & 6 deletions map/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { atom } from '../atom/index.js'
import { getStoreState } from '../context/index.js'

export let map = (value = {}) => {
let store = atom(value)

store.setKey = function (key, newValue) {
let state = getStoreState(store)
if (typeof newValue === 'undefined') {
if (key in store.value) {
store.value = { ...store.value }
delete store.value[key]
if (key in state.v) {
state.v = { ...state.v }
delete state.v[key]
store.notify(key)
}
} else if (store.value[key] !== newValue) {
store.value = {
...store.value,
} else if (state.v[key] !== newValue) {
state.v = {
...state.v,
[key]: newValue
}
store.notify(key)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@
"import": {
"./index.js": "{ atom }"
},
"limit": "313 B"
"limit": "411 B"
},
{
"name": "All",
"import": {
"./index.js": "{ map, computed, action }"
},
"limit": "1019 B"
"limit": "1129 B"
}
],
"clean-publish": {
Expand Down

0 comments on commit f7b43c5

Please sign in to comment.