Skip to content

Commit

Permalink
Unify context implementations
Browse files Browse the repository at this point in the history
Implements the legacy, string-based context API on top of the new
context API. This doesn't save much in code size because most of the
legacy stuff deals with merging and masking. Arguably, it's a conceptual
complexity win. I'm ambivalent about whether we land this.
  • Loading branch information
acdlite committed Jul 3, 2018
1 parent f2670bf commit 073d7d0
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 340 deletions.
179 changes: 137 additions & 42 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,13 @@ import {
stopBaseRenderTimerIfRunning,
} from './ReactProfilerTimer';
import {
getMaskedContext,
getUnmaskedContext,
hasContextChanged as hasLegacyContextChanged,
pushContextProvider as pushLegacyContextProvider,
pushTopLevelContextObject,
invalidateContextProvider,
hasLegacyContextChanged,
readUnmaskedLegacyContext,
maskLegacyContext,
emptyContextObject,
pushRootLegacyContext,
calculateLegacyChildContext,
pushLegacyContext,
} from './ReactFiberContext';
import {
enterHydrationState,
Expand Down Expand Up @@ -260,19 +261,30 @@ function updateFunctionalComponent(
}
}

const unmaskedContext = getUnmaskedContext(workInProgress);
const context = getMaskedContext(workInProgress, unmaskedContext);
prepareToReadContext();

let legacyContext;
const contextTypes = fn.contextTypes;
if (typeof contextTypes === 'object' && contextTypes !== null) {
const unmaskedContext = readUnmaskedLegacyContext();
legacyContext = maskLegacyContext(
unmaskedContext,
unmaskedContext,
contextTypes,
);
} else {
legacyContext = emptyContextObject;
}

let nextChildren;

prepareToReadContext();
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactDebugCurrentFiber.setCurrentPhase('render');
nextChildren = fn(nextProps, context);
nextChildren = fn(nextProps, legacyContext);
ReactDebugCurrentFiber.setCurrentPhase(null);
} else {
nextChildren = fn(nextProps, context);
nextChildren = fn(nextProps, legacyContext);
}
workInProgress.firstContextReader = finishReadingContext();

Expand All @@ -288,11 +300,33 @@ function updateClassComponent(
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
// 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.
const hasContext = pushLegacyContextProvider(workInProgress);
// It's possible for a component to both provide and read from context. We
// should read the current context before pushing additional context onto
// the stack.
prepareToReadContext();
let maskedLegacyContext;
const contextTypes = workInProgress.type.contextTypes;
if (typeof contextTypes === 'object' && contextTypes !== null) {
const unmaskedLegacyContext = readUnmaskedLegacyContext();
const instance = workInProgress.stateNode;
if (
instance !== null &&
instance.__reactInternalUnmaskedLegacyContext === unmaskedLegacyContext
) {
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
maskedLegacyContext = instance.__reactInternalMaskedLegacyContext;
} else {
maskedLegacyContext = maskLegacyContext(
workInProgress,
unmaskedLegacyContext,
contextTypes,
);
}
} else {
maskedLegacyContext = emptyContextObject;
}

let shouldUpdate;
if (current === null) {
Expand All @@ -301,30 +335,44 @@ function updateClassComponent(
constructClassInstance(
workInProgress,
workInProgress.pendingProps,
maskedLegacyContext,
renderExpirationTime,
);
mountClassInstance(
workInProgress,
maskedLegacyContext,
renderExpirationTime,
);
mountClassInstance(workInProgress, renderExpirationTime);

shouldUpdate = true;
} else {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
maskedLegacyContext,
renderExpirationTime,
);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
maskedLegacyContext,
renderExpirationTime,
);
}

// We can assume we have an instance at this point
const instance = workInProgress.stateNode;
if (typeof contextTypes === 'object' && contextTypes !== null) {
instance.__reactInternalUnmaskedLegacyContext = readUnmaskedLegacyContext();
instance.__reactInternalMaskedLegacyContext = maskedLegacyContext;
}

return finishClassComponent(
current,
workInProgress,
shouldUpdate,
hasContext,
renderExpirationTime,
);
}
Expand All @@ -333,26 +381,35 @@ function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
const ctor = workInProgress.type;
const childContextTypes = ctor.childContextTypes;

// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);

const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;

const instance = workInProgress.stateNode;
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, false);
// Call finishReadingContext to clear the current context list, but don't
// use the result. Because we're about to bail out without rendering, we
// should re-use the previous list.
finishReadingContext();
if (typeof childContextTypes === 'object' && childContextTypes !== null) {
const legacyChildContext =
instance.__reactInternalUnmaskedLegacyChildContext;
pushLegacyContext(
workInProgress,
childContextTypes,
legacyChildContext,
false,
);
}

return bailoutOnAlreadyFinishedWork(current, workInProgress);
}

const ctor = workInProgress.type;
const instance = workInProgress.stateNode;

// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
Expand Down Expand Up @@ -388,8 +445,6 @@ function finishClassComponent(
}
}

workInProgress.firstContextReader = finishReadingContext();

// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
if (didCaptureError) {
Expand Down Expand Up @@ -417,25 +472,37 @@ function finishClassComponent(
memoizeState(workInProgress, instance.state);
memoizeProps(workInProgress, instance.props);

// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, true);
workInProgress.firstContextReader = finishReadingContext();
if (typeof childContextTypes === 'object' && childContextTypes !== null) {
const unmaskedLegacyContext = readUnmaskedLegacyContext();
const legacyChildContext = calculateLegacyChildContext(
workInProgress,
childContextTypes,
unmaskedLegacyContext,
);
instance.__reactInternalUnmaskedLegacyChildContext = legacyChildContext;
pushLegacyContext(
workInProgress,
childContextTypes,
legacyChildContext,
true,
);
}

return workInProgress.child;
}

function pushHostRootContext(workInProgress) {
const root = (workInProgress.stateNode: FiberRoot);
if (root.pendingContext) {
pushTopLevelContextObject(
if (root.pendingContext !== null) {
pushRootLegacyContext(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
);
} else if (root.context) {
} else if (root.context !== null) {
// Should always be set
pushTopLevelContextObject(workInProgress, root.context, false);
pushRootLegacyContext(workInProgress, root.context, false);
}
pushHostContainer(workInProgress, root.containerInfo);
}
Expand Down Expand Up @@ -592,11 +659,26 @@ function mountIndeterminateComponent(
);
const fn = workInProgress.type;
const props = workInProgress.pendingProps;
const unmaskedContext = getUnmaskedContext(workInProgress);
const context = getMaskedContext(workInProgress, unmaskedContext);

prepareToReadContext();

// It's possible for a component to both provide and read from context. We
// should read the current context before pushing additional context onto
// the stack.
prepareToReadContext();
let maskedLegacyContext;
const contextTypes = workInProgress.type.contextTypes;
if (typeof contextTypes === 'object' && contextTypes !== null) {
const unmaskedLegacyContext = readUnmaskedLegacyContext();
maskedLegacyContext = maskLegacyContext(
unmaskedLegacyContext,
unmaskedLegacyContext,
contextTypes,
);
} else {
maskedLegacyContext = emptyContextObject;
}

let value;

if (__DEV__) {
Expand All @@ -620,9 +702,9 @@ function mountIndeterminateComponent(
}

ReactCurrentOwner.current = workInProgress;
value = fn(props, context);
value = fn(props, maskedLegacyContext);
} else {
value = fn(props, context);
value = fn(props, maskedLegacyContext);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
Expand Down Expand Up @@ -655,14 +737,16 @@ function mountIndeterminateComponent(
// 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.
const hasContext = pushLegacyContextProvider(workInProgress);
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, renderExpirationTime);
mountClassInstance(
workInProgress,
maskedLegacyContext,
renderExpirationTime,
);
return finishClassComponent(
current,
workInProgress,
true,
hasContext,
renderExpirationTime,
);
} else {
Expand Down Expand Up @@ -1001,7 +1085,18 @@ function bailoutOnLowPriority(current, workInProgress) {
pushHostRootContext(workInProgress);
break;
case ClassComponent:
pushLegacyContextProvider(workInProgress);
const childContextTypes = workInProgress.type.childContextTypes;
if (typeof childContextTypes === 'object' && childContextTypes !== null) {
const instance = workInProgress.stateNode;
const legacyChildContext =
instance.__reactInternalUnmaskedLegacyChildContext;
pushLegacyContext(
workInProgress,
childContextTypes,
legacyChildContext,
false,
);
}
break;
case HostPortal:
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
Expand Down
Loading

0 comments on commit 073d7d0

Please sign in to comment.