Skip to content

Commit 18d2504

Browse files
committed
feat: context
1 parent 4e29af0 commit 18d2504

File tree

16 files changed

+161
-9
lines changed

16 files changed

+161
-9
lines changed

demos/context/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>context测试</title>
9+
</head>
10+
11+
<body>
12+
<div id="root"></div>
13+
<script type="module" src="main.tsx"></script>
14+
</body>
15+
16+
</html>

demos/context/main.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useState, createContext, useContext } from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
4+
const ctxA = createContext('deafult A');
5+
const ctxB = createContext('default B');
6+
7+
function App() {
8+
return (
9+
<ctxA.Provider value={'A0'}>
10+
<ctxB.Provider value={'B0'}>
11+
<ctxA.Provider value={'A1'}>
12+
<Cpn />
13+
</ctxA.Provider>
14+
</ctxB.Provider>
15+
<Cpn />
16+
</ctxA.Provider>
17+
);
18+
}
19+
20+
function Cpn() {
21+
const a = useContext(ctxA);
22+
const b = useContext(ctxB);
23+
return (
24+
<div>
25+
A: {a} B: {b}
26+
</div>
27+
);
28+
}
29+
30+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
31+
<App />
32+
);

demos/context/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "index.js",
66
"scripts": {
77
"build:dev": "rm -rf dist && rollup --config scripts/rollup/dev.config.js",
8-
"demo": "vite serve demos/ref --config scripts/vite/vite.config.js --force",
8+
"demo": "vite serve demos/context --config scripts/vite/vite.config.js --force",
99
"lint": "eslint --ext .ts,.jsx,.tsx --fix --quiet ./packages",
1010
"test": "jest --config scripts/jest/jest.config.js"
1111
},

packages/react-reconciler/src/beginWork.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { renderWithHooks } from './fiberHooks';
55
import { Lane } from './fiberLanes';
66
import { processUpdateQueue, UpdateQueue } from './updateQueue';
77
import {
8+
ContextProvider,
89
Fragment,
910
FunctionComponent,
1011
HostComponent,
1112
HostRoot,
1213
HostText
1314
} from './workTags';
1415
import { Ref } from './fiberFlags';
16+
import { pushProvider } from './fiberContext';
1517

1618
// 递归中的递阶段
1719
export const beginWork = (wip: FiberNode, renderLane: Lane) => {
@@ -27,6 +29,8 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
2729
return updateFunctionComponent(wip, renderLane);
2830
case Fragment:
2931
return updateFragment(wip);
32+
case ContextProvider:
33+
return updateContextProvider(wip);
3034
default:
3135
if (__DEV__) {
3236
console.warn('beginWork未实现的类型');
@@ -36,6 +40,18 @@ export const beginWork = (wip: FiberNode, renderLane: Lane) => {
3640
return null;
3741
};
3842

43+
function updateContextProvider(wip: FiberNode) {
44+
const providerType = wip.type;
45+
const context = providerType._context;
46+
const newProps = wip.pendingProps;
47+
48+
pushProvider(context, newProps.value);
49+
50+
const nextChildren = newProps.children;
51+
reconcileChildren(wip, nextChildren);
52+
return wip.child;
53+
}
54+
3955
function updateFragment(wip: FiberNode) {
4056
const nextChildren = wip.pendingProps;
4157
reconcileChildren(wip, nextChildren);

packages/react-reconciler/src/childFibers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ function ChildReconciler(shouldTrackEffects: boolean) {
188188
if (
189189
Array.isArray(element) ||
190190
typeof element === 'string' ||
191-
typeof element === 'number'
191+
typeof element === 'number' ||
192+
element === undefined ||
193+
element === null
192194
) {
193195
return index;
194196
}

packages/react-reconciler/src/completeWork.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import {
1212
HostText,
1313
HostComponent,
1414
FunctionComponent,
15-
Fragment
15+
Fragment,
16+
ContextProvider
1617
} from './workTags';
18+
import { popProvider } from './fiberContext';
1719

1820
function markUpdate(fiber: FiberNode) {
1921
fiber.flags |= Update;
@@ -76,6 +78,11 @@ export const completeWork = (wip: FiberNode) => {
7678
case Fragment:
7779
bubbleProperties(wip);
7880
return null;
81+
case ContextProvider:
82+
const context = wip.type._context;
83+
popProvider(context);
84+
bubbleProperties(wip);
85+
return null;
7986
default:
8087
if (__DEV__) {
8188
console.warn('未处理的completeWork情况', wip);

packages/react-reconciler/src/fiber.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Props, Key, Ref, ReactElementType } from 'shared/ReactTypes';
22
import {
3+
ContextProvider,
34
Fragment,
45
FunctionComponent,
56
HostComponent,
@@ -10,6 +11,7 @@ import { Container } from 'hostConfig';
1011
import { Lane, Lanes, NoLane, NoLanes } from './fiberLanes';
1112
import { Effect } from './fiberHooks';
1213
import { CallbackNode } from 'scheduler';
14+
import { REACT_PROVIDER_TYPE } from 'shared/ReactSymbols';
1315

1416
export class FiberNode {
1517
type: any;
@@ -134,6 +136,11 @@ export function createFiberFromElement(element: ReactElementType): FiberNode {
134136
if (typeof type === 'string') {
135137
// <div/> type: 'div'
136138
fiberTag = HostComponent;
139+
} else if (
140+
typeof type === 'object' &&
141+
type.$$typeof === REACT_PROVIDER_TYPE
142+
) {
143+
fiberTag = ContextProvider;
137144
} else if (typeof type !== 'function' && __DEV__) {
138145
console.warn('为定义的type类型', element);
139146
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ReactContext } from 'shared/ReactTypes';
2+
3+
let prevContextValue: any = null;
4+
const prevContextValueStack: any[] = [];
5+
6+
export function pushProvider<T>(context: ReactContext<T>, newValue: T) {
7+
prevContextValueStack.push(prevContextValue);
8+
9+
prevContextValue = context._currentValue;
10+
context._currentValue = newValue;
11+
}
12+
13+
export function popProvider<T>(context: ReactContext<T>) {
14+
context._currentValue = prevContextValue;
15+
16+
prevContextValue = prevContextValueStack.pop();
17+
}

packages/react-reconciler/src/fiberHooks.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Dispatch } from 'react/src/currentDispatcher';
22
import { Dispatcher } from 'react/src/currentDispatcher';
33
import currentBatchConfig from 'react/src/currentBatchConfig';
44
import internals from 'shared/internals';
5-
import { Action } from 'shared/ReactTypes';
5+
import { Action, ReactContext } from 'shared/ReactTypes';
66
import { FiberNode } from './fiber';
77
import { Flags, PassiveEffect } from './fiberFlags';
88
import { Lane, NoLane, requestUpdateLane } from './fiberLanes';
@@ -82,14 +82,16 @@ const HooksDispatcherOnMount: Dispatcher = {
8282
useState: mountState,
8383
useEffect: mountEffect,
8484
useTransition: mountTransition,
85-
useRef: mountRef
85+
useRef: mountRef,
86+
useContext: readContext
8687
};
8788

8889
const HooksDispatcherOnUpdate: Dispatcher = {
8990
useState: updateState,
9091
useEffect: updateEffect,
9192
useTransition: updateTransition,
92-
useRef: updateRef
93+
useRef: updateRef,
94+
useContext: readContext
9395
};
9496

9597
function mountEffect(create: EffectCallback | void, deps: EffectDeps | void) {
@@ -379,3 +381,12 @@ function mountWorkInProgressHook(): Hook {
379381
}
380382
return workInProgressHook;
381383
}
384+
385+
function readContext<T>(context: ReactContext<T>): T {
386+
const consumer = currentlyRenderingFiber;
387+
if (consumer === null) {
388+
throw new Error('只能在函数组件中调用useContext');
389+
}
390+
const value = context._currentValue;
391+
return value;
392+
}

packages/react-reconciler/src/workTags.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ export type WorkTag =
33
| typeof HostRoot
44
| typeof HostComponent
55
| typeof HostText
6-
| typeof Fragment;
6+
| typeof Fragment
7+
| typeof ContextProvider;
78

89
export const FunctionComponent = 0;
910
export const HostRoot = 3;
@@ -12,3 +13,4 @@ export const HostComponent = 5;
1213
// <div>123</div>
1314
export const HostText = 6;
1415
export const Fragment = 7;
16+
export const ContextProvider = 8;

packages/react/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
isValidElement as isValidElementFn
77
} from './src/jsx';
88
export { REACT_FRAGMENT_TYPE as Fragment } from 'shared/ReactSymbols';
9+
export { createContext } from './src/context';
910
// React
1011

1112
export const useState: Dispatcher['useState'] = (initialState) => {
@@ -28,6 +29,11 @@ export const useRef: Dispatcher['useRef'] = (initialValue) => {
2829
return dispatcher.useRef(initialValue);
2930
};
3031

32+
export const useContext: Dispatcher['useContext'] = (context) => {
33+
const dispatcher = resolveDispatcher() as Dispatcher;
34+
return dispatcher.useContext(context);
35+
};
36+
3137
// 内部数据共享层
3238
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
3339
currentDispatcher,

packages/react/src/context.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE } from 'shared/ReactSymbols';
2+
import { ReactContext } from 'shared/ReactTypes';
3+
4+
export function createContext<T>(defaultValue: T): ReactContext<T> {
5+
const context: ReactContext<T> = {
6+
$$typeof: REACT_CONTEXT_TYPE,
7+
Provider: null,
8+
_currentValue: defaultValue
9+
};
10+
context.Provider = {
11+
$$typeof: REACT_PROVIDER_TYPE,
12+
_context: context
13+
};
14+
return context;
15+
}

packages/react/src/currentDispatcher.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Action } from 'shared/ReactTypes';
1+
import { Action, ReactContext } from 'shared/ReactTypes';
22

33
export interface Dispatcher {
44
useState: <T>(initialState: (() => T) | T) => [T, Dispatch<T>];
55
useEffect: (callback: () => void | void, deps: any[] | void) => void;
66
useTransition: () => [boolean, (callback: () => void) => void];
77
useRef: <T>(initialValue: T) => { current: T };
8+
useContext: <T>(context: ReactContext<T>) => T;
89
}
910

1011
export type Dispatch<State> = (action: Action<State>) => void;

packages/shared/ReactSymbols.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,12 @@ export const REACT_ELEMENT_TYPE = supportSymbol
66

77
export const REACT_FRAGMENT_TYPE = supportSymbol
88
? Symbol.for('react.fragment')
9-
: 0xeacb;
9+
: 0xeaca;
10+
11+
export const REACT_CONTEXT_TYPE = supportSymbol
12+
? Symbol.for('react.context')
13+
: 0xeacc;
14+
15+
export const REACT_PROVIDER_TYPE = supportSymbol
16+
? Symbol.for('react.provider')
17+
: 0xeac2;

packages/shared/ReactTypes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,14 @@ export interface ReactElementType {
1414
}
1515

1616
export type Action<State> = State | ((prevState: State) => State);
17+
18+
export type ReactContext<T> = {
19+
$$typeof: symbol | number;
20+
Provider: ReactProviderType<T> | null;
21+
_currentValue: T;
22+
};
23+
24+
export type ReactProviderType<T> = {
25+
$$typeof: symbol | number;
26+
_context: ReactContext<T> | null;
27+
};

0 commit comments

Comments
 (0)