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;
+};