Skip to content

Commit

Permalink
feat: useTransition
Browse files Browse the repository at this point in the history
  • Loading branch information
BetaSu committed Mar 16, 2023
1 parent ac2759e commit e075472
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 32 deletions.
3 changes: 3 additions & 0 deletions demos/transition/AboutTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AboutTab() {
return <p>我是卡颂,这是我的个人页</p>;
}
11 changes: 11 additions & 0 deletions demos/transition/ContactTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function ContactTab() {
return (
<>
<p>你可以通过如下方式联系我:</p>
<ul>
<li>B站:魔术师卡颂</li>
<li>微信:kasong555</li>
</ul>
</>
);
}
16 changes: 16 additions & 0 deletions demos/transition/PostsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const PostsTab = function PostsTab() {
const items = [];
for (let i = 0; i < 500; i++) {
items.push(<SlowPost key={i} index={i} />);
}
return <ul className="items">{items}</ul>;
};

function SlowPost({ index }) {
const startTime = performance.now();
while (performance.now() - startTime < 4) {}

return <li className="item">博文 #{index + 1}</li>;
}

export default PostsTab;
16 changes: 16 additions & 0 deletions demos/transition/TabButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useTransition } from 'react';

export default function TabButton({ children, isActive, onClick }) {
if (isActive) {
return <b>{children}</b>;
}
return (
<button
onClick={() => {
onClick();
}}
>
{children}
</button>
);
}
17 changes: 17 additions & 0 deletions demos/transition/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!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>Vite + React + TS</title>
</head>

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

</html>
44 changes: 44 additions & 0 deletions demos/transition/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ReactDOM from 'react-dom';

import { useState, useTransition } from 'react';
import TabButton from './TabButton';
import AboutTab from './AboutTab';
import PostsTab from './PostsTab';
import ContactTab from './ContactTab';
import './style.css';

function App() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
console.log('hello');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}

return (
<>
<TabButton isActive={tab === 'about'} onClick={() => selectTab('about')}>
首页
</TabButton>
<TabButton isActive={tab === 'posts'} onClick={() => selectTab('posts')}>
博客 (render慢)
</TabButton>
<TabButton
isActive={tab === 'contact'}
onClick={() => selectTab('contact')}
>
联系我
</TabButton>
<hr />
{tab === 'about' && <AboutTab />}
{tab === 'posts' && <PostsTab />}
{tab === 'contact' && <ContactTab />}
</>
);
}

const root = ReactDOM.createRoot(document.querySelector('#root'));

root.render(<App />);
3 changes: 3 additions & 0 deletions demos/transition/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
button {
margin: 0 5px;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"lint": "eslint --ext .js,.ts,.jsx,.tsx --fix --quiet ./packages",
"build:dev": "rimraf dist && rollup --bundleConfigAsCjs --config scripts/rollup/dev.config.js ",
"demo": "vite serve demos/test-fc --config scripts/vite/vite.config.js --force",
"demo": "vite serve demos/transition --config scripts/vite/vite.config.js --force",
"test": "jest --config scripts/jest/jest.config.js"
},
"keywords": [],
Expand Down
55 changes: 42 additions & 13 deletions packages/react-reconciler/src/fiberHooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react';
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 { FiberNode } from './fiber';
Expand Down Expand Up @@ -80,12 +80,14 @@ export function renderWithHooks(wip: FiberNode, lane: Lane) {

const HooksDispatcherOnMount: Dispatcher = {
useState: mountState,
useEffect: mountEffect
useEffect: mountEffect,
useTransition: mountTransition
};

const HooksDispatcherOnUpdate: Dispatcher = {
useState: updateState,
useEffect: updateEffect
useEffect: updateEffect,
useTransition: updateTransition
};

function mountEffect(create: EffectCallback | void, deps: EffectDeps | void) {
Expand Down Expand Up @@ -215,17 +217,17 @@ function updateState<State>(): [State, Dispatch<State>] {
// 保存在current中
current.baseQueue = pending;
queue.shared.pending = null;
}

if (baseQueue !== null) {
const {
memoizedState,
baseQueue: newBaseQueue,
baseState: newBaseState
} = processUpdateQueue(baseState, baseQueue, renderLane);
hook.memoizedState = memoizedState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueue;
}
if (baseQueue !== null) {
const {
memoizedState,
baseQueue: newBaseQueue,
baseState: newBaseState
} = processUpdateQueue(baseState, baseQueue, renderLane);
hook.memoizedState = memoizedState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueue;
}

return [hook.memoizedState, queue.dispatch as Dispatch<State>];
Expand Down Expand Up @@ -295,13 +297,40 @@ function mountState<State>(
const queue = createUpdateQueue<State>();
hook.updateQueue = queue;
hook.memoizedState = memoizedState;
hook.baseState = memoizedState;

// @ts-ignore
const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
queue.dispatch = dispatch;
return [memoizedState, dispatch];
}

function mountTransition(): [boolean, (callback: () => void) => void] {
const [isPending, setPending] = mountState(false);
const hook = mountWorkInProgresHook();
const start = startTransition.bind(null, setPending);
hook.memoizedState = start;
return [isPending, start];
}

function updateTransition(): [boolean, (callback: () => void) => void] {
const [isPending] = updateState();
const hook = updateWorkInProgresHook();
const start = hook.memoizedState;
return [isPending as boolean, start];
}

function startTransition(setPending: Dispatch<boolean>, callback: () => void) {
setPending(true);
const prevTransition = currentBatchConfig.transition;
currentBatchConfig.transition = 1;

callback();
setPending(false);

currentBatchConfig.transition = prevTransition;
}

function dispatchSetState<State>(
fiber: FiberNode,
updateQueue: UpdateQueue<State>,
Expand Down
19 changes: 13 additions & 6 deletions packages/react-reconciler/src/fiberLanes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ReactCurrentBatchConfig from 'react/src/currentBatchConfig';
import {
unstable_getCurrentPriorityLevel,
unstable_IdlePriority,
Expand All @@ -10,18 +11,24 @@ import { FiberRootNode } from './fiber';
export type Lane = number;
export type Lanes = number;

export const SyncLane = 0b0001;
export const NoLane = 0b0000;
export const NoLanes = 0b0000;
export const InputContinuousLane = 0b0010;
export const DefaultLane = 0b0100;
export const IdleLane = 0b1000;
export const SyncLane = 0b00001;
export const NoLane = 0b00000;
export const NoLanes = 0b00000;
export const InputContinuousLane = 0b00010;
export const DefaultLane = 0b00100;
export const TransitionLane = 0b01000;
export const IdleLane = 0b10000;

export function mergeLanes(laneA: Lane, laneB: Lane): Lanes {
return laneA | laneB;
}

export function requestUpdateLane() {
const isTransition = ReactCurrentBatchConfig.transition !== null;
if (isTransition) {
return TransitionLane;
}

// 从上下文环境中获取Scheduler优先级
const currentSchedulerPriority = unstable_getCurrentPriorityLevel();
const lane = schedulerPriorityToLane(currentSchedulerPriority);
Expand Down
22 changes: 14 additions & 8 deletions packages/react-reconciler/src/fiberReconciler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Container } from 'hostConfig';
import {
unstable_ImmediatePriority,
unstable_runWithPriority
} from 'scheduler';
import { ReactElementType } from 'shared/ReactTypes';
import { FiberNode, FiberRootNode } from './fiber';
import { requestUpdateLane } from './fiberLanes';
Expand All @@ -22,13 +26,15 @@ export function updateContainer(
element: ReactElementType | null,
root: FiberRootNode
) {
const hostRootFiber = root.current;
const lane = requestUpdateLane();
const update = createUpdate<ReactElementType | null>(element, lane);
enqueueUpdate(
hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
update
);
scheduleUpdateOnFiber(hostRootFiber, lane);
unstable_runWithPriority(unstable_ImmediatePriority, () => {
const hostRootFiber = root.current;
const lane = requestUpdateLane();
const update = createUpdate<ReactElementType | null>(element, lane);
enqueueUpdate(
hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
update
);
scheduleUpdateOnFiber(hostRootFiber, lane);
});
return element;
}
10 changes: 7 additions & 3 deletions packages/react-reconciler/src/workLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,15 @@ function ensureRootIsScheduled(root: FiberRootNode) {
}
let newCallbackNode = null;

if (__DEV__) {
console.log(
`在${updateLane === SyncLane ? '微' : '宏'}任务中调度,优先级:`,
updateLane
);
}

if (updateLane === SyncLane) {
// 同步优先级 用微任务调度
if (__DEV__) {
console.log('在微任务中调度,优先级:', updateLane);
}
// [performSyncWorkOnRoot, performSyncWorkOnRoot, performSyncWorkOnRoot]
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root, updateLane));
scheduleMicroTask(flushSyncCallbacks);
Expand Down
9 changes: 8 additions & 1 deletion packages/react/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Dispatcher, resolveDispatcher } from './src/currentDispatcher';
import currentDispatcher from './src/currentDispatcher';
import currentBatchConfig from './src/currentBatchConfig';
import {
createElement as createElementFn,
isValidElement as isValidElementFn
Expand All @@ -17,9 +18,15 @@ export const useEffect: Dispatcher['useEffect'] = (create, deps) => {
return dispatcher.useEffect(create, deps);
};

export const useTransition: Dispatcher['useTransition'] = () => {
const dispatcher = resolveDispatcher();
return dispatcher.useTransition();
};

// 内部数据共享层
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
currentDispatcher
currentDispatcher,
currentBatchConfig
};

export const version = '0.0.0';
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/currentBatchConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
interface BatchConfig {
transition: number | null;
}

const ReactCurrentBatchConfig: BatchConfig = {
transition: null
};

export default ReactCurrentBatchConfig;
1 change: 1 addition & 0 deletions packages/react/src/currentDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Action } 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];
}

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

0 comments on commit e075472

Please sign in to comment.