hook.ts
import { update, isFn, getCurrentFiber } from "./reconcile"
import {
DependencyList,
Reducer,
IFiber,
Dispatch,
SetStateAction,
EffectCallback,
HookTypes,
RefObject,
IEffect,
FreNode,
} from "./type"
const EMPTY_ARR = []
// cursor 是当前 hook 的个数
let cursor = 0
export const resetCursor = () => {
cursor = 0
}
// 传入:initState
// 返回:一个有两个元素的数组 hook
// hook[0]: state
// hook[1]: dispatch
export const useState = <T>(initState: T): [T, Dispatch<SetStateAction<T>>] => {
return useReducer(null, initState)
}
// 传入:
// reducer:一个函数,描述 state 如何变化,以及如何响应 actions 并发送到 store
// initState
// 返回值:一个有两个元素的数组 hook
// hook[0]: initState
// hook[1]: 修改当前 state 的函数,在 useState 中是 setXX,在 useReducer 中是 dispatch
export const useReducer = <S, A>(
reducer?: Reducer<S, A>, // 即 (prevState: STATE, action: ACTION) => STATE 类型,传入之前的 state,和要如何对这个 state 进行修改的 action,返回新 state 的
initState?: S
): [S, Dispatch<A>] => {
// 新增一个 hook
const [hook, current]: [any, IFiber] = getHook<S>(cursor++)
// 初始化这个 hook
if (hook.length === 0) {
hook[0] = initState
// hook[1] 为修改 hook[0](当前 state)的函数
hook[1] = (value: A | Dispatch<A>) => {
// 如果 reducer 不为空,如 useReducer
// 调用 reducer(initState, value),将 hook[0] 修改为新的值
// 如果 reducer 为空,例如 useState
// 看传入的描述修改的 value 是什么类型
// value 是 Function,如 setCount((count)=>count+1),则调用里面的 Function((count)=>count+1)
// 注意:原生 React 不支持这种写法
// value 是一个值,如 setCount(3),则直接给 count 赋值(3)
hook[0] = reducer
? reducer(hook[0], value as any)
: isFn(value)
? value(hook[0])
: value
update(current)
}
}
return hook
}
// 传入:
// cb: dependency 变化时要执行的函数
// deps:dependency list
// 返回:null
export const useEffect = (cb: EffectCallback, deps?: DependencyList): void => {
return effectImpl(cb, deps!, "effect")
}
// 类似于 useEffect,但 useLayout 是同步的,阻塞 UI
// 传入:
// cb: dependency 变化时要执行的函数
// deps:dependency list
// 返回:null
export const useLayout = (cb: EffectCallback, deps?: DependencyList): void => {
return effectImpl(cb, deps!, "layout")
}
// 传入:
// cb: dependency 变化时要执行的函数
// deps:dependency list
// key: hook 的类型,useEffect 为 "effect",useLayout 为 "layout"
const effectImpl = (
cb: EffectCallback,
deps: DependencyList,
key: HookTypes
): void => {
const [hook, current] = getHook(cursor++)
if (isChanged(hook[1], deps)) {
hook[0] = cb
hook[1] = deps
current.hooks[key].push(hook)
}
}
// 如果 deps 变化了,返回执行 cb 后的新的 state,否则返回旧的 state,state 不变
// 传入:
// cb: dependency 变化时要执行的函数,为 ()=>STATE 类型
// deps:dependency list
// 返回值:state
export const useMemo = <S = Function>(
cb: () => S,
deps?: DependencyList
): S => {
const hook = getHook<S>(cursor++)[0]
if (isChanged(hook[1], deps!)) {
hook[1] = deps
return (hook[0] = cb())
}
return hook[0]
}
// 对 useMemo 进行封装,useCallback(cb, deps) 等价于 useMemo(()=>cb, deps)
// 传入:
// cb: dependency 变化时要执行的函数
// deps:dependency list
// 返回值:state
export const useCallback = <T extends (...args: any[]) => void>(
cb: T,
deps?: DependencyList
): T => {
return useMemo(() => cb, deps)
}
// 对 useMemo 进行封装,useRef(x) 等价于 useMemo(()=>{x}, [])
// 首次 t=useRef(null),其实什么都没做
// t 后,t 这个 ref 才被绑定到 Element,此时 t 的值为 { current:t }
export const useRef = <T>(current: T): RefObject<T> => {
return useMemo(() => ({ current }), [])
}
// 传入:cursor
// 返回:一个 hook 数组
// hook[0]:[state, dependency]
// hook[1]:current
export const getHook = <S = Function | undefined, Dependency = any>(
cursor: number
): [[S, Dependency], IFiber] => {
const current: IFiber<any> = getCurrentFiber()
// 如果 current.hooks 为 null,则将其初始化为 { list: [], effect: [], layout: [] }
// useState 和 use
const hooks =
current.hooks || (current.hooks = { list: [], effect: [], layout: [] })
// 如果要新增一个 hook,在 hooks.list 里插入一个新的 hook
if (cursor >= hooks.list.length) {
hooks.list.push([] as IEffect)
}
// list[0] 是 state
// list[1] 是 dependency
return [(hooks.list[cursor] as unknown) as [S, Dependency], current]
}
// {
// whatever: {
// value: T;
// children: FreNode
// };
// initialValue: Text;
// }
export type ContextType<T> = {
({ value, children }: { value: T, children: FreNode }): FreNode;
initialValue: T;
}
type SubscriberCb = () => void;
// 从组件中读取和订阅上下文
//
export const createContext = <T>(initialValue: T): ContextType<T> => {
const contextComponent: ContextType<T> = ({ value, children }) => {
const valueRef = useRef(value)
const subscribers = useMemo(() => new Set<SubscriberCb>(), EMPTY_ARR)
if (valueRef.current !== value) {
valueRef.current = value;
subscribers.forEach((subscriber) => subscriber())
}
return children
}
contextComponent.initialValue = initialValue;
return contextComponent;
}
export const useContext = <T>(contextType: ContextType<T>): T => {
let subscribersSet: Set<Function>
const triggerUpdate = useReducer(null, null)[1] as SubscriberCb
useEffect(() => {
return () => subscribersSet && subscribersSet.delete(triggerUpdate)
}, EMPTY_ARR);
let contextFiber = getCurrentFiber().parent
while (contextFiber && contextFiber.type !== contextType) {
contextFiber = contextFiber.parent
}
if (contextFiber) {
const hooks = contextFiber.hooks.list as unknown as [[RefObject<T>], [Set<SubscriberCb>]]
const [[value], [subscribers]] = hooks;
subscribersSet = subscribers.add(triggerUpdate)
return value.current
} else {
return contextType.initialValue
}
}
export const isChanged = (a: DependencyList, b: DependencyList) => {
return !a || a.length !== b.length || b.some((arg, index) => !Object.is(arg, a[index]))
}
type.ts
// Fiber 存储了 dom 相关信息、fiber 树相关的引用、要更新时的 side effect 等
// 通过 Fiber 可以实现跟踪、调度、暂停和中止工作,形成异步,解决 react 组件树比较庞大时,更新会阻塞 js 主线程的问题
export interface IFiber<P extends Attributes = any> {
key?: string
type: string | FC<P>
parentNode: HTMLElementEx
childNodes: any
node: HTMLElementEx // 真实 dom 节点
kids?: any
parent?: IFiber<P> // 父 fiber
sibling?: IFiber<P> // 下一个兄弟 fiber
child?: IFiber<P> // 第一个子 fiber
done?: () => void
ref: IRef
hooks: IHook
oldProps: P
after: any
props: P
lane: number // 优先级
time: number
next: IFiber
dirty: boolean
isComp: boolean
}