diff --git a/demos/context/index.html b/demos/context/index.html new file mode 100644 index 0000000..41b1595 --- /dev/null +++ b/demos/context/index.html @@ -0,0 +1,16 @@ + + + + + + + + context测试 + + + +
+ + + + \ No newline at end of file diff --git a/demos/context/main.tsx b/demos/context/main.tsx new file mode 100644 index 0000000..7e4c9d3 --- /dev/null +++ b/demos/context/main.tsx @@ -0,0 +1,32 @@ +import { useState, createContext, useContext } from 'react'; +import ReactDOM from 'react-dom/client'; + +const ctxA = createContext('deafult A'); +const ctxB = createContext('default B'); + +function App() { + return ( + + + + + + + + + ); +} + +function Cpn() { + const a = useContext(ctxA); + const b = useContext(ctxB); + return ( +
+ A: {a} B: {b} +
+ ); +} + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + +); diff --git a/demos/context/vite-env.d.ts b/demos/context/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/demos/context/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/package.json b/package.json index 05a51dd..2eef03b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js", - "demo": "vite serve demos/ref --config scripts/vite/vite.config.js --force", + "demo": "vite serve demos/context --config scripts/vite/vite.config.js --force", "lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages", "test": "jest --config scripts/jest/jest.config.js" }, diff --git a/packages/react-reconciler/src/beginWork.ts b/packages/react-reconciler/src/beginWork.ts index 77d814c..d83b612 100644 --- a/packages/react-reconciler/src/beginWork.ts +++ b/packages/react-reconciler/src/beginWork.ts @@ -5,6 +5,7 @@ import { renderWithHooks } from './fiberHooks'; import { Lane } from './fiberLanes'; import { processUpdateQueue, UpdateQueue } from './updateQueue'; import { + ContextProvider, Fragment, FunctionComponent, HostComponent, @@ -12,6 +13,7 @@ import { HostText } from './workTags'; import { Ref } from './fiberFlags'; +import { pushProvider } from './fiberContext'; // 递归中的递阶段 export const beginWork = (wip: FiberNode, renderLane: Lane) => { @@ -27,6 +29,8 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => { return updateFunctionComponent(wip, renderLane); case Fragment: return updateFragment(wip); + case ContextProvider: + return updateContextProvider(wip); default: if (__DEV__) { console.warn('beginWork未实现的类型'); @@ -36,6 +40,18 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => { return null; }; +function updateContextProvider(wip: FiberNode) { + const providerType = wip.type; + const context = providerType._context; + const newProps = wip.pendingProps; + + pushProvider(context, newProps.value); + + const nextChildren = newProps.children; + reconcileChildren(wip, nextChildren); + return wip.child; +} + function updateFragment(wip: FiberNode) { const nextChildren = wip.pendingProps; reconcileChildren(wip, nextChildren); diff --git a/packages/react-reconciler/src/childFibers.ts b/packages/react-reconciler/src/childFibers.ts index 409efb8..224c234 100644 --- a/packages/react-reconciler/src/childFibers.ts +++ b/packages/react-reconciler/src/childFibers.ts @@ -188,7 +188,9 @@ function ChildReconciler(shouldTrackEffects: boolean) { if ( Array.isArray(element) || typeof element === 'string' || - typeof element === 'number' + typeof element === 'number' || + element === undefined || + element === null ) { return index; } diff --git a/packages/react-reconciler/src/completeWork.ts b/packages/react-reconciler/src/completeWork.ts index c80a7cf..e343545 100644 --- a/packages/react-reconciler/src/completeWork.ts +++ b/packages/react-reconciler/src/completeWork.ts @@ -12,8 +12,10 @@ import { HostText, HostComponent, FunctionComponent, - Fragment + Fragment, + ContextProvider } from './workTags'; +import { popProvider } from './fiberContext'; function markUpdate(fiber: FiberNode) { fiber.flags |= Update; @@ -76,6 +78,11 @@ export const completeWork = (wip: FiberNode) => { case Fragment: bubbleProperties(wip); return null; + case ContextProvider: + const context = wip.type._context; + popProvider(context); + bubbleProperties(wip); + return null; default: if (__DEV__) { console.warn('未处理的completeWork情况', wip); diff --git a/packages/react-reconciler/src/fiber.ts b/packages/react-reconciler/src/fiber.ts index e034327..404f572 100644 --- a/packages/react-reconciler/src/fiber.ts +++ b/packages/react-reconciler/src/fiber.ts @@ -1,5 +1,6 @@ import { Props, Key, Ref, ReactElementType } from 'shared/ReactTypes'; import { + ContextProvider, Fragment, FunctionComponent, HostComponent, @@ -10,6 +11,7 @@ import { Container } from 'hostConfig'; import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes'; import { Effect } from './fiberHooks'; import { CallbackNode } from 'scheduler'; +import { REACT_PROVIDER_TYPE } from 'shared/ReactSymbols'; export class FiberNode { type: any; @@ -134,6 +136,11 @@ export function createFiberFromElement(element: ReactElementType): FiberNode { if (typeof type === 'string') { //
type: 'div' fiberTag = HostComponent; + } else if ( + typeof type === 'object' && + type.$$typeof === REACT_PROVIDER_TYPE + ) { + fiberTag = ContextProvider; } else if (typeof type !== 'function' && __DEV__) { console.warn('为定义的type类型', element); } diff --git a/packages/react-reconciler/src/fiberContext.ts b/packages/react-reconciler/src/fiberContext.ts new file mode 100644 index 0000000..a0a97d7 --- /dev/null +++ b/packages/react-reconciler/src/fiberContext.ts @@ -0,0 +1,17 @@ +import { ReactContext } from 'shared/ReactTypes'; + +let prevContextValue: any = null; +const prevContextValueStack: any[] = []; + +export function pushProvider(context: ReactContext, newValue: T) { + prevContextValueStack.push(prevContextValue); + + prevContextValue = context._currentValue; + context._currentValue = newValue; +} + +export function popProvider(context: ReactContext) { + context._currentValue = prevContextValue; + + prevContextValue = prevContextValueStack.pop(); +} diff --git a/packages/react-reconciler/src/fiberHooks.ts b/packages/react-reconciler/src/fiberHooks.ts index b04298a..e892525 100644 --- a/packages/react-reconciler/src/fiberHooks.ts +++ b/packages/react-reconciler/src/fiberHooks.ts @@ -2,7 +2,7 @@ import { Dispatch } from 'react/src/currentDispatcher'; import { Dispatcher } from 'react/src/currentDispatcher'; import currentBatchConfig from 'react/src/currentBatchConfig'; import internals from 'shared/internals'; -import { Action } from 'shared/ReactTypes'; +import { Action, ReactContext } from 'shared/ReactTypes'; import { FiberNode } from './fiber'; import { Flags, PassiveEffect } from './fiberFlags'; import { Lane, NoLane, requestUpdateLane } from './fiberLanes'; @@ -82,14 +82,16 @@ const HooksDispatcherOnMount: Dispatcher = { useState: mountState, useEffect: mountEffect, useTransition: mountTransition, - useRef: mountRef + useRef: mountRef, + useContext: readContext }; const HooksDispatcherOnUpdate: Dispatcher = { useState: updateState, useEffect: updateEffect, useTransition: updateTransition, - useRef: updateRef + useRef: updateRef, + useContext: readContext }; function mountEffect(create: EffectCallback | void, deps: EffectDeps | void) { @@ -379,3 +381,12 @@ function mountWorkInProgressHook(): Hook { } return workInProgressHook; } + +function readContext(context: ReactContext): T { + const consumer = currentlyRenderingFiber; + if (consumer === null) { + throw new Error('只能在函数组件中调用useContext'); + } + const value = context._currentValue; + return value; +} diff --git a/packages/react-reconciler/src/workTags.ts b/packages/react-reconciler/src/workTags.ts index f7af904..bd1c563 100644 --- a/packages/react-reconciler/src/workTags.ts +++ b/packages/react-reconciler/src/workTags.ts @@ -3,7 +3,8 @@ export type WorkTag = | typeof HostRoot | typeof HostComponent | typeof HostText - | typeof Fragment; + | typeof Fragment + | typeof ContextProvider; export const FunctionComponent = 0; export const HostRoot = 3; @@ -12,3 +13,4 @@ export const HostComponent = 5; //
123
export const HostText = 6; export const Fragment = 7; +export const ContextProvider = 8; diff --git a/packages/react/index.ts b/packages/react/index.ts index 6a44f29..c1e8470 100644 --- a/packages/react/index.ts +++ b/packages/react/index.ts @@ -6,6 +6,7 @@ import { isValidElement as isValidElementFn } from './src/jsx'; export { REACT_FRAGMENT_TYPE as Fragment } from 'shared/ReactSymbols'; +export { createContext } from './src/context'; // React export const useState: Dispatcher['useState'] = (initialState) => { @@ -28,6 +29,11 @@ export const useRef: Dispatcher['useRef'] = (initialValue) => { return dispatcher.useRef(initialValue); }; +export const useContext: Dispatcher['useContext'] = (context) => { + const dispatcher = resolveDispatcher() as Dispatcher; + return dispatcher.useContext(context); +}; + // 内部数据共享层 export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { currentDispatcher, diff --git a/packages/react/src/context.ts b/packages/react/src/context.ts new file mode 100644 index 0000000..9596323 --- /dev/null +++ b/packages/react/src/context.ts @@ -0,0 +1,15 @@ +import { REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE } from 'shared/ReactSymbols'; +import { ReactContext } from 'shared/ReactTypes'; + +export function createContext(defaultValue: T): ReactContext { + const context: ReactContext = { + $$typeof: REACT_CONTEXT_TYPE, + Provider: null, + _currentValue: defaultValue + }; + context.Provider = { + $$typeof: REACT_PROVIDER_TYPE, + _context: context + }; + return context; +} diff --git a/packages/react/src/currentDispatcher.ts b/packages/react/src/currentDispatcher.ts index c33277f..381b77e 100644 --- a/packages/react/src/currentDispatcher.ts +++ b/packages/react/src/currentDispatcher.ts @@ -1,10 +1,11 @@ -import { Action } from 'shared/ReactTypes'; +import { Action, ReactContext } from 'shared/ReactTypes'; export interface Dispatcher { useState: (initialState: (() => T) | T) => [T, Dispatch]; useEffect: (callback: () => void | void, deps: any[] | void) => void; useTransition: () => [boolean, (callback: () => void) => void]; useRef: (initialValue: T) => { current: T }; + useContext: (context: ReactContext) => T; } export type Dispatch = (action: Action) => void; diff --git a/packages/shared/ReactSymbols.ts b/packages/shared/ReactSymbols.ts index 7608cf6..4dc382c 100644 --- a/packages/shared/ReactSymbols.ts +++ b/packages/shared/ReactSymbols.ts @@ -6,4 +6,12 @@ export const REACT_ELEMENT_TYPE = supportSymbol export const REACT_FRAGMENT_TYPE = supportSymbol ? Symbol.for('react.fragment') - : 0xeacb; + : 0xeaca; + +export const REACT_CONTEXT_TYPE = supportSymbol + ? Symbol.for('react.context') + : 0xeacc; + +export const REACT_PROVIDER_TYPE = supportSymbol + ? Symbol.for('react.provider') + : 0xeac2; diff --git a/packages/shared/ReactTypes.ts b/packages/shared/ReactTypes.ts index 8bbbed1..3df1e4d 100644 --- a/packages/shared/ReactTypes.ts +++ b/packages/shared/ReactTypes.ts @@ -14,3 +14,14 @@ export interface ReactElementType { } export type Action = State | ((prevState: State) => State); + +export type ReactContext = { + $$typeof: symbol | number; + Provider: ReactProviderType | null; + _currentValue: T; +}; + +export type ReactProviderType = { + $$typeof: symbol | number; + _context: ReactContext | null; +};