Skip to content

Commit

Permalink
New context API
Browse files Browse the repository at this point in the history
Introduces a declarative context API that propagates updates even when
shouldComponentUpdate returns false.
  • Loading branch information
acdlite committed Dec 9, 2017
1 parent 6e258c1 commit e32309e
Show file tree
Hide file tree
Showing 10 changed files with 866 additions and 20 deletions.
216 changes: 215 additions & 1 deletion packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
*/

import type {ReactElement} from 'shared/ReactElementType';
import type {ReactCall, ReactPortal, ReactReturn} from 'shared/ReactTypes';
import type {
ReactCall,
ReactPortal,
ReactReturn,
ReactProvider,
ReactConsumer,
} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {ExpirationTime} from 'react-reconciler/src/ReactFiberExpirationTime';

Expand All @@ -20,6 +26,8 @@ import {
REACT_CALL_TYPE,
REACT_RETURN_TYPE,
REACT_PORTAL_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONSUMER_TYPE,
} from 'shared/ReactSymbols';
import {
FunctionalComponent,
Expand All @@ -29,6 +37,8 @@ import {
CallComponent,
ReturnComponent,
Fragment,
ProviderComponent,
ConsumerComponent,
} from 'shared/ReactTypeOfWork';
import emptyObject from 'fbjs/lib/emptyObject';
import invariant from 'fbjs/lib/invariant';
Expand All @@ -42,6 +52,8 @@ import {
createFiberFromCall,
createFiberFromReturn,
createFiberFromPortal,
createFiberFromProvider,
createFiberFromConsumer,
} from './ReactFiber';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';

Expand Down Expand Up @@ -465,6 +477,52 @@ function ChildReconciler(shouldTrackSideEffects) {
}
}

function updateProviderComponent(
returnFiber: Fiber,
current: Fiber | null,
provider: ReactProvider<any>,
expirationTime: ExpirationTime,
) {
if (current !== null && current.type === provider.context) {
// Move based on index
const existing = useFiber(current, provider, expirationTime);
existing.return = returnFiber;
return existing;
} else {
// Insert
const created = createFiberFromProvider(
provider,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
}
}

function updateConsumerComponent(
returnFiber: Fiber,
current: Fiber | null,
consumer: ReactConsumer<any>,
expirationTime: ExpirationTime,
) {
if (current !== null && current.type === consumer.context) {
// Move based on index
const existing = useFiber(current, consumer, expirationTime);
existing.return = returnFiber;
return existing;
} else {
// Insert
const created = createFiberFromConsumer(
consumer,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
}
}

function createChild(
returnFiber: Fiber,
newChild: any,
Expand Down Expand Up @@ -537,6 +595,24 @@ function ChildReconciler(shouldTrackSideEffects) {
created.return = returnFiber;
return created;
}
case REACT_PROVIDER_TYPE: {
const created = createFiberFromProvider(
newChild,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
}
case REACT_CONSUMER_TYPE: {
const created = createFiberFromConsumer(
newChild,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
}
}

if (isArray(newChild) || getIteratorFn(newChild)) {
Expand Down Expand Up @@ -647,6 +723,30 @@ function ChildReconciler(shouldTrackSideEffects) {
return null;
}
}
case REACT_PROVIDER_TYPE: {
if (newChild.key === key) {
return updateProviderComponent(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
case REACT_CONSUMER_TYPE: {
if (newChild.key === key) {
return updateConsumerComponent(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
}

if (isArray(newChild) || getIteratorFn(newChild)) {
Expand Down Expand Up @@ -755,6 +855,30 @@ function ChildReconciler(shouldTrackSideEffects) {
expirationTime,
);
}
case REACT_PROVIDER_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
return updateProviderComponent(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}
case REACT_CONSUMER_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
return updateConsumerComponent(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}
}

if (isArray(newChild) || getIteratorFn(newChild)) {
Expand Down Expand Up @@ -1362,6 +1486,78 @@ function ChildReconciler(shouldTrackSideEffects) {
return created;
}

function reconcileSingleProvider(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
provider: ReactProvider<any>,
expirationTime: ExpirationTime,
): Fiber {
const key = provider.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key && child.type === provider.context) {
if (child.tag === ProviderComponent) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, provider, expirationTime);
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}

const created = createFiberFromProvider(
provider,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
}

function reconcileSingleConsumer(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
consumer: ReactConsumer<any>,
expirationTime: ExpirationTime,
): Fiber {
const key = consumer.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key && child.type === consumer.context) {
if (child.tag === ConsumerComponent) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, consumer, expirationTime);
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}

const created = createFiberFromConsumer(
consumer,
returnFiber.internalContextTag,
expirationTime,
);
created.return = returnFiber;
return created;
}

// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
Expand Down Expand Up @@ -1430,6 +1626,24 @@ function ChildReconciler(shouldTrackSideEffects) {
expirationTime,
),
);
case REACT_PROVIDER_TYPE:
return placeSingleChild(
reconcileSingleProvider(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_CONSUMER_TYPE:
return placeSingleChild(
reconcileSingleConsumer(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}

Expand Down
38 changes: 38 additions & 0 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import type {
ReactFragment,
ReactPortal,
ReactReturn,
ReactProvider,
ReactConsumer,
} from 'shared/ReactTypes';
import type {TypeOfWork} from 'shared/ReactTypeOfWork';
import type {TypeOfInternalContext} from './ReactTypeOfInternalContext';
Expand All @@ -31,6 +33,8 @@ import {
CallComponent,
ReturnComponent,
Fragment,
ProviderComponent,
ConsumerComponent,
} from 'shared/ReactTypeOfWork';
import getComponentName from 'shared/getComponentName';

Expand Down Expand Up @@ -442,3 +446,37 @@ export function createFiberFromPortal(
};
return fiber;
}

export function createFiberFromProvider<T>(
provider: ReactProvider<T>,
internalContextTag: TypeOfInternalContext,
expirationTime: ExpirationTime,
): Fiber {
const pendingProps = provider;
const fiber = createFiber(
ProviderComponent,
pendingProps,
provider.key,
internalContextTag,
);
fiber.expirationTime = expirationTime;
fiber.type = provider.context;
return fiber;
}

export function createFiberFromConsumer<T>(
consumer: ReactConsumer<T>,
internalContextTag: TypeOfInternalContext,
expirationTime: ExpirationTime,
): Fiber {
const pendingProps = consumer;
const fiber = createFiber(
ConsumerComponent,
pendingProps,
consumer.key,
internalContextTag,
);
fiber.expirationTime = expirationTime;
fiber.type = consumer.context;
return fiber;
}
Loading

0 comments on commit e32309e

Please sign in to comment.