Skip to content

Commit

Permalink
feat: context
Browse files Browse the repository at this point in the history
  • Loading branch information
BetaSu committed Jul 2, 2023
1 parent 4e29af0 commit 18d2504
Show file tree
Hide file tree
Showing 16 changed files with 161 additions and 9 deletions.
16 changes: 16 additions & 0 deletions demos/context/index.html
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>context测试</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="main.tsx"></script>
</body>

</html>
32 changes: 32 additions & 0 deletions 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 (
<ctxA.Provider value={'A0'}>
<ctxB.Provider value={'B0'}>
<ctxA.Provider value={'A1'}>
<Cpn />
</ctxA.Provider>
</ctxB.Provider>
<Cpn />
</ctxA.Provider>
);
}

function Cpn() {
const a = useContext(ctxA);
const b = useContext(ctxB);
return (
<div>
A: {a} B: {b}
</div>
);
}

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<App />
);
1 change: 1 addition & 0 deletions demos/context/vite-env.d.ts
@@ -0,0 +1 @@
/// <reference types="vite/client" />
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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"
},
Expand Down
16 changes: 16 additions & 0 deletions packages/react-reconciler/src/beginWork.ts
Expand Up @@ -5,13 +5,15 @@ import { renderWithHooks } from './fiberHooks';
import { Lane } from './fiberLanes';
import { processUpdateQueue, UpdateQueue } from './updateQueue';
import {
ContextProvider,
Fragment,
FunctionComponent,
HostComponent,
HostRoot,
HostText
} from './workTags';
import { Ref } from './fiberFlags';
import { pushProvider } from './fiberContext';

// 递归中的递阶段
export const beginWork = (wip: FiberNode, renderLane: Lane) => {
Expand All @@ -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未实现的类型');
Expand All @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/childFibers.ts
Expand Up @@ -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;
}
Expand Down
9 changes: 8 additions & 1 deletion packages/react-reconciler/src/completeWork.ts
Expand Up @@ -12,8 +12,10 @@ import {
HostText,
HostComponent,
FunctionComponent,
Fragment
Fragment,
ContextProvider
} from './workTags';
import { popProvider } from './fiberContext';

function markUpdate(fiber: FiberNode) {
fiber.flags |= Update;
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions packages/react-reconciler/src/fiber.ts
@@ -1,5 +1,6 @@
import { Props, Key, Ref, ReactElementType } from 'shared/ReactTypes';
import {
ContextProvider,
Fragment,
FunctionComponent,
HostComponent,
Expand All @@ -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;
Expand Down Expand Up @@ -134,6 +136,11 @@ export function createFiberFromElement(element: ReactElementType): FiberNode {
if (typeof type === 'string') {
// <div/> 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);
}
Expand Down
17 changes: 17 additions & 0 deletions 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<T>(context: ReactContext<T>, newValue: T) {
prevContextValueStack.push(prevContextValue);

prevContextValue = context._currentValue;
context._currentValue = newValue;
}

export function popProvider<T>(context: ReactContext<T>) {
context._currentValue = prevContextValue;

prevContextValue = prevContextValueStack.pop();
}
17 changes: 14 additions & 3 deletions packages/react-reconciler/src/fiberHooks.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -379,3 +381,12 @@ function mountWorkInProgressHook(): Hook {
}
return workInProgressHook;
}

function readContext<T>(context: ReactContext<T>): T {
const consumer = currentlyRenderingFiber;
if (consumer === null) {
throw new Error('只能在函数组件中调用useContext');
}
const value = context._currentValue;
return value;
}
4 changes: 3 additions & 1 deletion packages/react-reconciler/src/workTags.ts
Expand Up @@ -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;
Expand All @@ -12,3 +13,4 @@ export const HostComponent = 5;
// <div>123</div>
export const HostText = 6;
export const Fragment = 7;
export const ContextProvider = 8;
6 changes: 6 additions & 0 deletions packages/react/index.ts
Expand Up @@ -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) => {
Expand All @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions 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<T>(defaultValue: T): ReactContext<T> {
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
Provider: null,
_currentValue: defaultValue
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context
};
return context;
}
3 changes: 2 additions & 1 deletion packages/react/src/currentDispatcher.ts
@@ -1,10 +1,11 @@
import { Action } from 'shared/ReactTypes';
import { Action, ReactContext } from 'shared/ReactTypes';

export interface Dispatcher {
useState: <T>(initialState: (() => T) | T) => [T, Dispatch<T>];
useEffect: (callback: () => void | void, deps: any[] | void) => void;
useTransition: () => [boolean, (callback: () => void) => void];
useRef: <T>(initialValue: T) => { current: T };
useContext: <T>(context: ReactContext<T>) => T;
}

export type Dispatch<State> = (action: Action<State>) => void;
Expand Down
10 changes: 9 additions & 1 deletion packages/shared/ReactSymbols.ts
Expand Up @@ -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;
11 changes: 11 additions & 0 deletions packages/shared/ReactTypes.ts
Expand Up @@ -14,3 +14,14 @@ export interface ReactElementType {
}

export type Action<State> = State | ((prevState: State) => State);

export type ReactContext<T> = {
$$typeof: symbol | number;
Provider: ReactProviderType<T> | null;
_currentValue: T;
};

export type ReactProviderType<T> = {
$$typeof: symbol | number;
_context: ReactContext<T> | null;
};

0 comments on commit 18d2504

Please sign in to comment.