import { update, isFn, useFiber, getBoundary } from './reconcile' import { DependencyList, Reducer, Fiber, Dispatch, SetStateAction, EffectCallback, RefObject, FreNode, HookList, HookEffect, HookReducer, HookMemo, } from './type' const EMPTY_ARR = [] let cursor = 0 export const resetCursor = () => { cursor = 0 } export const useState = (initState: T | (() => T)) => { return useReducer>(null, initState) } export const useReducer = ( reducer?: Reducer, initState?: S | (() => S) ): [S, Dispatch] => { const [hook, current] = getSlot(cursor++) if (hook.length === 0) { hook[0] = isFn(initState) ? initState() : initState } hook[1] = (value: A | Dispatch) => { let v = reducer ? reducer(hook[0], value as any) : isFn(value) ? value(hook[0]) : value if (hook[0] !== v) { hook[0] = v if (typeof window !== 'undefined') { update(current) } } } return hook as Required } export const useEffect = (cb: EffectCallback, deps?: DependencyList) => { return effectImpl(cb, deps!, 'effect') } export const useLayout = (cb: EffectCallback, deps?: DependencyList) => { return effectImpl(cb, deps!, 'layout') } const effectImpl = ( cb: EffectCallback, deps: DependencyList, key: 'effect' | 'layout' ) => { const [hook, current] = getSlot(cursor++) if (isChanged(hook[1], deps)) { hook[0] = cb hook[1] = deps current.hooks[key].push(hook as Required) } } export const useMemo = ( cb: () => S, deps?: DependencyList ): S => { const hook = getSlot(cursor++)[0] if (isChanged(hook[1], deps!)) { hook[1] = deps return (hook[0] = cb()) } return hook[0] } export const useCallback = void>( cb: T, deps?: DependencyList ): T => { return useMemo(() => cb, deps) } export const useRef = (current: T): RefObject => { return useMemo(() => ({ current }), []) } export const getSlot = (cursor: number) => { const current: Fiber = useFiber() const hooks = current.hooks || (current.hooks = { list: [], effect: [], layout: [] }) if (cursor >= hooks.list.length) { hooks.list.push([] as any) } return [hooks.list[cursor], current] as unknown as [Partial, Fiber] } export type ContextType = { ({ value, children }: { value: T; children?: FreNode }): FreNode initialValue: T } type SubscriberCb = () => void export const createContext = (value: T): ContextType => { const C = ({ value, children }: Parameters>[0]) => { const ref = useRef(value) const subs = useMemo(() => new Set<() => void>(), EMPTY_ARR) if (ref.current !== value) { ref.current = value subs.forEach(cb => cb()) } return children } C.initialValue = value return C as ContextType } export const useContext = (ctx: ContextType) => { const update = useReducer(null, null)[1] as SubscriberCb let subs: Set useEffect(() => () => subs?.delete(update), EMPTY_ARR) const fiber = getBoundary(useFiber(), ctx) return fiber ? (subs = (fiber.hooks.list[1][0]).add(update), (fiber.hooks.list[0][0] as { current: T }).current) : ctx.initialValue } export const isChanged = (a: DependencyList | undefined, b: DependencyList) => { return ( !a || a.length !== b.length || b.some((arg, index) => !Object.is(arg, a[index])) ) }