Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Initial hooks implementation
Includes:
- useState
- useContext
- useEffect
- useRef
- useReducer
- useCallback
- useMemo
- useAPI
  • Loading branch information
acdlite committed Oct 29, 2018
commit 7bee9fbdd49aa5b9365a94b0ddf6db04bc1bf51c
31 changes: 16 additions & 15 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import {
prepareToReadContext,
calculateChangedBits,
} from './ReactFiberNewContext';
import {prepareToUseHooks, finishHooks, resetHooks} from './ReactFiberHooks';
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
import {
getMaskedContext,
Expand Down Expand Up @@ -193,27 +194,17 @@ function forceUnmountCurrentAndReconcile(
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
type: any,
Component: any,
nextProps: any,
renderExpirationTime: ExpirationTime,
) {
const render = type.render;
const render = Component.render;
const ref = workInProgress.ref;
if (hasLegacyContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
} else if (workInProgress.memoizedProps === nextProps) {
const currentRef = current !== null ? current.ref : null;
if (ref === currentRef) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}

// The rest is a fork of updateFunctionComponent
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(current, workInProgress, renderExpirationTime);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
Expand All @@ -222,7 +213,10 @@ function updateForwardRef(
} else {
nextChildren = render(nextProps, ref);
}
nextChildren = finishHooks(render, nextProps, nextChildren, ref);

// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
reconcileChildren(
current,
workInProgress,
Expand Down Expand Up @@ -406,6 +400,7 @@ function updateFunctionComponent(

let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(current, workInProgress, renderExpirationTime);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
Expand All @@ -414,6 +409,7 @@ function updateFunctionComponent(
} else {
nextChildren = Component(nextProps, context);
}
nextChildren = finishHooks(Component, nextProps, nextChildren, context);

// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
Expand Down Expand Up @@ -921,6 +917,7 @@ function mountIndeterminateComponent(
const context = getMaskedContext(workInProgress, unmaskedContext);

prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(null, workInProgress, renderExpirationTime);

let value;

Expand Down Expand Up @@ -964,6 +961,9 @@ function mountIndeterminateComponent(
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;

// Throw out any hooks that were used.
resetHooks();

// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
Expand Down Expand Up @@ -1001,6 +1001,7 @@ function mountIndeterminateComponent(
} else {
// Proceed under the assumption that this is a function component
workInProgress.tag = FunctionComponent;
value = finishHooks(Component, props, value, context);
if (__DEV__) {
if (Component) {
warningWithoutStack(
Expand Down
170 changes: 170 additions & 0 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
import type {SuspenseState} from './ReactFiberSuspenseComponent';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';

import {
enableSchedulerTracing,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
ForwardRef,
ClassComponent,
HostRoot,
HostComponent,
Expand Down Expand Up @@ -180,6 +183,22 @@ function safelyDetachRef(current: Fiber) {
}
}

function safelyCallDestroy(current, destroy) {
if (__DEV__) {
invokeGuardedCallback(null, destroy, null);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(current, error);
}
} else {
try {
destroy();
} catch (error) {
captureCommitPhaseError(current, error);
}
}
}

function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
Expand Down Expand Up @@ -235,13 +254,145 @@ function commitBeforeMutationLifeCycles(
}
}

function destroyRemainingEffects(firstToDestroy, stopAt) {
let effect = firstToDestroy;
do {
const destroy = effect.value;
if (destroy !== null) {
destroy();
}
effect = effect.next;
} while (effect !== stopAt);
}

function destroyMountedEffects(current) {
const oldUpdateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (oldUpdateQueue !== null) {
const oldLastEffect = oldUpdateQueue.lastEffect;
if (oldLastEffect !== null) {
const oldFirstEffect = oldLastEffect.next;
destroyRemainingEffects(oldFirstEffect, oldFirstEffect);
}
}
}

function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedExpirationTime: ExpirationTime,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef: {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
// Mount new effects and destroy the old ones by comparing to the
// current list of effects. This could be a bit simpler if we avoided
// the need to compare to the previous effect list by transferring the
// old `destroy` method to the new effect during the render phase.
// That's how I originally implemented it, but it requires an additional
// field on the effect object.
//
// This supports removing effects from the end of the list. If we adopt
// the constraint that hooks are append only, that would also save a bit
// on code size.
const newLastEffect = updateQueue.lastEffect;
if (newLastEffect !== null) {
const newFirstEffect = newLastEffect.next;
let oldLastEffect = null;
if (current !== null) {
const oldUpdateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (oldUpdateQueue !== null) {
oldLastEffect = oldUpdateQueue.lastEffect;
}
}
if (oldLastEffect !== null) {
const oldFirstEffect = oldLastEffect.next;
let newEffect = newFirstEffect;
let oldEffect = oldFirstEffect;

// Before mounting the new effects, unmount all the old ones.
do {
if (oldEffect !== null) {
if (newEffect.inputs !== oldEffect.inputs) {
const destroy = oldEffect.value;
if (destroy !== null) {
destroy();
}
}
oldEffect = oldEffect.next;
if (oldEffect === oldFirstEffect) {
oldEffect = null;
}
}
newEffect = newEffect.next;
} while (newEffect !== newFirstEffect);

// Unmount any remaining effects in the old list that do not
// appear in the new one.
if (oldEffect !== null) {
destroyRemainingEffects(oldEffect, oldFirstEffect);
}

// Now loop through the list again to mount the new effects
oldEffect = oldFirstEffect;
do {
const create = newEffect.value;
if (oldEffect !== null) {
if (newEffect.inputs !== oldEffect.inputs) {
const newDestroy = create();
newEffect.value =
typeof newDestroy === 'function' ? newDestroy : null;
} else {
newEffect.value = oldEffect.value;
}
oldEffect = oldEffect.next;
if (oldEffect === oldFirstEffect) {
oldEffect = null;
}
} else {
const newDestroy = create();
newEffect.value =
typeof newDestroy === 'function' ? newDestroy : null;
}
newEffect = newEffect.next;
} while (newEffect !== newFirstEffect);
} else {
let newEffect = newFirstEffect;
do {
const create = newEffect.value;
const newDestroy = create();
newEffect.value =
typeof newDestroy === 'function' ? newDestroy : null;
newEffect = newEffect.next;
} while (newEffect !== newFirstEffect);
}
} else if (current !== null) {
// There are no effects, which means all current effects must
// be destroyed
destroyMountedEffects(current);
}

const callbackList = updateQueue.callbackList;
if (callbackList !== null) {
updateQueue.callbackList = null;
for (let i = 0; i < callbackList.length; i++) {
const update = callbackList[i];
// Assume this is non-null, since otherwise it would not be part
// of the callback list.
const callback: () => mixed = (update.callback: any);
update.callback = null;
callback();
}
}
} else if (current !== null) {
// There are no effects, which means all current effects must
// be destroyed
destroyMountedEffects(current);
}
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
Expand Down Expand Up @@ -496,6 +647,25 @@ function commitUnmount(current: Fiber): void {
onCommitUnmount(current);

switch (current.tag) {
case FunctionComponent:
case ForwardRef: {
const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
if (updateQueue !== null) {
const lastEffect = updateQueue.lastEffect;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
const destroy = effect.value;
if (destroy !== null) {
safelyCallDestroy(current, destroy);
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
break;
}
case ClassComponent: {
safelyDetachRef(current);
const instance = current.stateNode;
Expand Down
16 changes: 16 additions & 0 deletions packages/react-reconciler/src/ReactFiberDispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,23 @@
*/

import {readContext} from './ReactFiberNewContext';
import {
useState,
useReducer,
useEffect,
useCallback,
useMemo,
useRef,
useAPI,
} from './ReactFiberHooks';

export const Dispatcher = {
readContext,
useState,
useReducer,
useEffect,
useCallback,
useMemo,
useRef,
useAPI,
};
Loading