[React] Custom useState and useEffect hook
import { flushSync } from 'react-dom'
import { createRoot } from 'react-dom/client'
let hookIndex = 0
const states: Array<[any, (newState: any) => void]> = []
const INITIALIZATION = Symbol('INITIALIZATION')
const UPDATE = Symbol('UPDATE')
type Phase = typeof INITIALIZATION | typeof UPDATE
let phase = INITIALIZATION
/**
* 1. Multiple useState problem
* a. We need to use `INITIALIZATION` and `UPDATE symbol to keep track of the phase
* b. We need a Hook id to keep track of the current hook
* - ID will be from 0 and onwards
* - ID will be reset to 0 for each render
*/
function useState<T>(initialState: T) {
const id = hookIndex++
if (phase === INITIALIZATION) {
states[id] = [
initialState,
(newState: T) => {
states[id][0] = newState
render(UPDATE)
},
]
}
return states[id] as [T, (newState: T) => void]
}
type EffectCallback = () => void
const effects: Array<{
callback: EffectCallback
deps?: any[]
prevDeps?: any[]
}> = []
function useEffect(callback: EffectCallback, deps?: any[]) {
const id = hookIndex++
const prevDeps = effects[id]?.deps
effects[id] = { callback, deps, prevDeps }
}
function Counter() {
const [count, setCount] = useState(0)
const [enabled, setEnabled] = useState(true)
const increment = () => setCount(count + 1)
const toggle = () => setEnabled(!enabled)
useEffect(() => {
console.log('efftivec')
}, [enabled])
return (
<div className="counter">
<button onClick={increment}>{count}</button>
<button onClick={toggle}>{enabled ? 'Disable' : 'Enable'}</button>
</div>
)
}
const rootEl = document.createElement('div')
document.body.append(rootEl)
const appRoot = createRoot(rootEl)
function render(newPhase: Phase) {
hookIndex = 0
phase = newPhase
flushSync(() => {
appRoot.render(<Counter />)
})
for (const effect of effects) {
if (!effect) continue
const hasDepsChanged = effect.deps
? !effect.deps.every((dep, i) => Object.is(dep, effect.prevDeps?.[i]))
: true
if (hasDepsChanged) {
effect.callback()
}
}
}
render(INITIALIZATION)