Skip to content

Instantly share code, notes, and snippets.

@cacheflow
Created August 29, 2018 05:31
Show Gist options
  • Save cacheflow/2563dbab4de9592a6de3fc6636d8ea66 to your computer and use it in GitHub Desktop.
Save cacheflow/2563dbab4de9592a6de3fc6636d8ea66 to your computer and use it in GitHub Desktop.
module.exports = LRUCache
// This will be a proper iterable 'Map' in engines that support it,
// or a fakey-fake PseudoMap in older versions.
var Map = require('pseudomap')
var util = require('util')
// A linked list to keep track of recently-used-ness
var Yallist = require('yallist')
// use symbols if possible, otherwise just _props
var hasSymbol = typeof Symbol === 'function'
var makeSymbol
if (hasSymbol) {
makeSymbol = function (key) {
return Symbol(key)
}
} else {
makeSymbol = function (key) {
return '_' + key
}
}
var MAX = makeSymbol('max')
var LENGTH = makeSymbol('length')
var LENGTH_CALCULATOR = makeSymbol('lengthCalculator')
var ALLOW_STALE = makeSymbol('allowStale')
var MAX_AGE = makeSymbol('maxAge')
var DISPOSE = makeSymbol('dispose')
var NO_DISPOSE_ON_SET = makeSymbol('noDisposeOnSet')
var LRU_LIST = makeSymbol('lruList')
var CACHE = makeSymbol('cache')
function naiveLength () { return 1 }
// lruList is a yallist where the head is the youngest
// item, and the tail is the oldest. the list contains the Hit
// objects as the entries.
// Each Hit object has a reference to its Yallist.Node. This
// never changes.
//
// cache is a Map (or PseudoMap) that matches the keys to
// the Yallist.Node object.
function LRUCache (options) {
if (!(this instanceof LRUCache)) {
return new LRUCache(options)
}
if (typeof options === 'number') {
options = { max: options }
}
if (!options) {
options = {}
}
var max = this[MAX] = options.max
// Kind of weird to have a default max of Infinity, but oh well.
if (!max ||
!(typeof max === 'number') ||
max <= 0) {
this[MAX] = Infinity
}
var lc = options.length || naiveLength
if (typeof lc !== 'function') {
lc = naiveLength
}
this[LENGTH_CALCULATOR] = lc
this[ALLOW_STALE] = options.stale || false
this[MAX_AGE] = options.maxAge || 0
this[DISPOSE] = options.dispose
this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false
this.reset()
}
// resize the cache when the max changes.
Object.defineProperty(LRUCache.prototype, 'max', {
set: function (mL) {
if (!mL || !(typeof mL === 'number') || mL <= 0) {
mL = Infinity
}
this[MAX] = mL
trim(this)
},
get: function () {
return this[MAX]
},
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'allowStale', {
set: function (allowStale) {
this[ALLOW_STALE] = !!allowStale
},
get: function () {
return this[ALLOW_STALE]
},
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'maxAge', {
set: function (mA) {
if (!mA || !(typeof mA === 'number') || mA < 0) {
mA = 0
}
this[MAX_AGE] = mA
trim(this)
},
get: function () {
return this[MAX_AGE]
},
enumerable: true
})
// resize the cache when the lengthCalculator changes.
Object.defineProperty(LRUCache.prototype, 'lengthCalculator', {
set: function (lC) {
if (typeof lC !== 'function') {
lC = naiveLength
}
if (lC !== this[LENGTH_CALCULATOR]) {
this[LENGTH_CALCULATOR] = lC
this[LENGTH] = 0
this[LRU_LIST].forEach(function (hit) {
hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key)
this[LENGTH] += hit.length
}, this)
}
trim(this)
},
get: function () { return this[LENGTH_CALCULATOR] },
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'length', {
get: function () { return this[LENGTH] },
enumerable: true
})
Object.defineProperty(LRUCache.prototype, 'itemCount', {
get: function () { return this[LRU_LIST].length },
enumerable: true
})
LRUCache.prototype.rforEach = function (fn, thisp) {
thisp = thisp || this
for (var walker = this[LRU_LIST].tail; walker !== null;) {
var prev = walker.prev
forEachStep(this, fn, walker, thisp)
walker = prev
}
}
function forEachStep (self, fn, node, thisp) {
var hit = node.value
if (isStale(self, hit)) {
del(self, node)
if (!self[ALLOW_STALE]) {
hit = undefined
}
}
if (hit) {
fn.call(thisp, hit.value, hit.key, self)
}
}
LRUCache.prototype.forEach = function (fn, thisp) {
thisp = thisp || this
for (var walker = this[LRU_LIST].head; walker !== null;) {
var next = walker.next
forEachStep(this, fn, walker, thisp)
walker = next
}
}
LRUCache.prototype.keys = function () {
return this[LRU_LIST].toArray().map(function (k) {
return k.key
}, this)
}
LRUCache.prototype.values = function () {
return this[LRU_LIST].toArray().map(function (k) {
return k.value
}, this)
}
LRUCache.prototype.reset = function () {
if (this[DISPOSE] &&
this[LRU_LIST] &&
this[LRU_LIST].length) {
this[LRU_LIST].forEach(function (hit) {
this[DISPOSE](hit.key, hit.value)
}, this)
}
this[CACHE] = new Map() // hash of items by key
this[LRU_LIST] = new Yallist() // list of items in order of use recency
this[LENGTH] = 0 // length of items in the list
}
LRUCache.prototype.dump = function () {
return this[LRU_LIST].map(function (hit) {
if (!isStale(this, hit)) {
return {
k: hit.key,
v: hit.value,
e: hit.now + (hit.maxAge || 0)
}
}
}, this).toArray().filter(function (h) {
return h
})
}
LRUCache.prototype.dumpLru = function () {
return this[LRU_LIST]
}
LRUCache.prototype.inspect = function (n, opts) {
var str = 'LRUCache {'
var extras = false
var as = this[ALLOW_STALE]
if (as) {
str += '\n allowStale: true'
extras = true
}
var max = this[MAX]
if (max && max !== Infinity) {
if (extras) {
str += ','
}
str += '\n max: ' + util.inspect(max, opts)
extras = true
}
var maxAge = this[MAX_AGE]
if (maxAge) {
if (extras) {
str += ','
}
str += '\n maxAge: ' + util.inspect(maxAge, opts)
extras = true
}
var lc = this[LENGTH_CALCULATOR]
if (lc && lc !== naiveLength) {
if (extras) {
str += ','
}
str += '\n length: ' + util.inspect(this[LENGTH], opts)
extras = true
}
var didFirst = false
this[LRU_LIST].forEach(function (item) {
if (didFirst) {
str += ',\n '
} else {
if (extras) {
str += ',\n'
}
didFirst = true
str += '\n '
}
var key = util.inspect(item.key).split('\n').join('\n ')
var val = { value: item.value }
if (item.maxAge !== maxAge) {
val.maxAge = item.maxAge
}
if (lc !== naiveLength) {
val.length = item.length
}
if (isStale(this, item)) {
val.stale = true
}
val = util.inspect(val, opts).split('\n').join('\n ')
str += key + ' => ' + val
})
if (didFirst || extras) {
str += '\n'
}
str += '}'
return str
}
LRUCache.prototype.set = function (key, value, maxAge) {
maxAge = maxAge || this[MAX_AGE]
var now = maxAge ? Date.now() : 0
var len = this[LENGTH_CALCULATOR](value, key)
if (this[CACHE].has(key)) {
if (len > this[MAX]) {
del(this, this[CACHE].get(key))
return false
}
var node = this[CACHE].get(key)
var item = node.value
// dispose of the old one before overwriting
// split out into 2 ifs for better coverage tracking
if (this[DISPOSE]) {
if (!this[NO_DISPOSE_ON_SET]) {
this[DISPOSE](key, item.value)
}
}
item.now = now
item.maxAge = maxAge
item.value = value
this[LENGTH] += len - item.length
item.length = len
this.get(key)
trim(this)
return true
}
var hit = new Entry(key, value, len, now, maxAge)
// oversized objects fall out of cache automatically.
if (hit.length > this[MAX]) {
if (this[DISPOSE]) {
this[DISPOSE](key, value)
}
return false
}
this[LENGTH] += hit.length
this[LRU_LIST].unshift(hit)
this[CACHE].set(key, this[LRU_LIST].head)
trim(this)
return true
}
LRUCache.prototype.has = function (key) {
if (!this[CACHE].has(key)) return false
var hit = this[CACHE].get(key).value
if (isStale(this, hit)) {
return false
}
return true
}
LRUCache.prototype.get = function (key) {
return get(this, key, true)
}
LRUCache.prototype.peek = function (key) {
return get(this, key, false)
}
LRUCache.prototype.pop = function () {
var node = this[LRU_LIST].tail
if (!node) return null
del(this, node)
return node.value
}
LRUCache.prototype.del = function (key) {
del(this, this[CACHE].get(key))
}
LRUCache.prototype.load = function (arr) {
// reset the cache
this.reset()
var now = Date.now()
// A previous serialized cache has the most recent items first
for (var l = arr.length - 1; l >= 0; l--) {
var hit = arr[l]
var expiresAt = hit.e || 0
if (expiresAt === 0) {
// the item was created without expiration in a non aged cache
this.set(hit.k, hit.v)
} else {
var maxAge = expiresAt - now
// dont add already expired items
if (maxAge > 0) {
this.set(hit.k, hit.v, maxAge)
}
}
}
}
LRUCache.prototype.prune = function () {
var self = this
this[CACHE].forEach(function (value, key) {
get(self, key, false)
})
}
function get (self, key, doUse) {
var node = self[CACHE].get(key)
if (node) {
var hit = node.value
if (isStale(self, hit)) {
del(self, node)
if (!self[ALLOW_STALE]) hit = undefined
} else {
if (doUse) {
self[LRU_LIST].unshiftNode(node)
}
}
if (hit) hit = hit.value
}
return hit
}
function isStale (self, hit) {
if (!hit || (!hit.maxAge && !self[MAX_AGE])) {
return false
}
var stale = false
var diff = Date.now() - hit.now
if (hit.maxAge) {
stale = diff > hit.maxAge
} else {
stale = self[MAX_AGE] && (diff > self[MAX_AGE])
}
return stale
}
function trim (self) {
if (self[LENGTH] > self[MAX]) {
for (var walker = self[LRU_LIST].tail;
self[LENGTH] > self[MAX] && walker !== null;) {
// We know that we're about to delete this one, and also
// what the next least recently used key will be, so just
// go ahead and set it now.
var prev = walker.prev
del(self, walker)
walker = prev
}
}
}
function del (self, node) {
if (node) {
var hit = node.value
if (self[DISPOSE]) {
self[DISPOSE](hit.key, hit.value)
}
self[LENGTH] -= hit.length
self[CACHE].delete(hit.key)
self[LRU_LIST].removeNode(node)
}
}
// classy, since V8 prefers predictable objects.
function Entry (key, value, length, now, maxAge) {
this.key = key
this.value = value
this.length = length
this.now = now
this.maxAge = maxAge || 0
}
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import checkPropTypes from 'prop-types/checkPropTypes';
import {
IndeterminateComponent,
FunctionalComponent,
FunctionalComponentLazy,
ClassComponent,
ClassComponentLazy,
HostRoot,
HostComponent,
HostText,
HostPortal,
ForwardRef,
ForwardRefLazy,
Fragment,
Mode,
ContextProvider,
ContextConsumer,
Profiler,
PlaceholderComponent,
} from 'shared/ReactWorkTags';
import {
NoEffect,
PerformedWork,
Placement,
ContentReset,
DidCapture,
Update,
Ref,
} from 'shared/ReactSideEffectTags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
enableGetDerivedStateFromCatch,
enableSuspense,
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import getComponentName from 'shared/getComponentName';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';
import * as ReactCurrentFiber from './ReactCurrentFiber';
import {cancelWorkTimer} from './ReactDebugFiberPerf';
import {applyDerivedStateFromProps} from './ReactFiberClassComponent';
import {
mountChildFibers,
reconcileChildFibers,
cloneChildFibers,
} from './ReactChildFiber';
import {processUpdateQueue} from './ReactUpdateQueue';
import {NoWork, Never} from './ReactFiberExpirationTime';
import {AsyncMode, StrictMode} from './ReactTypeOfMode';
import {
shouldSetTextContent,
shouldDeprioritizeSubtree,
} from './ReactFiberHostConfig';
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext';
import {
pushProvider,
propagateContextChange,
readContext,
prepareToReadContext,
calculateChangedBits,
} from './ReactFiberNewContext';
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
import {
getMaskedContext,
getUnmaskedContext,
hasContextChanged as hasLegacyContextChanged,
pushContextProvider as pushLegacyContextProvider,
isContextProvider as isLegacyContextProvider,
pushTopLevelContextObject,
invalidateContextProvider,
} from './ReactFiberContext';
import {
enterHydrationState,
resetHydrationState,
tryToClaimNextHydratableInstance,
} from './ReactFiberHydrationContext';
import {
adoptClassInstance,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
updateClassInstance,
} from './ReactFiberClassComponent';
import {readLazyComponentType} from './ReactFiberLazyComponent';
import {getResultFromResolvedThenable} from 'shared/ReactLazyComponent';
import {resolveLazyComponentTag} from './ReactFiber';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
let didWarnAboutBadClass;
let didWarnAboutGetDerivedStateOnFunctionalComponent;
let didWarnAboutStatelessRefs;
if (__DEV__) {
didWarnAboutBadClass = {};
didWarnAboutGetDerivedStateOnFunctionalComponent = {};
didWarnAboutStatelessRefs = {};
}
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
type: any,
nextProps: any,
renderExpirationTime: ExpirationTime,
) {
const render = type.render;
const ref = workInProgress.ref;
if (hasLegacyContextChanged()) {
} else if (workInProgress.memoizedProps === nextProps) {
const currentRef = current !== null ? current.ref : null;
if (ref === currentRef) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
let nextChildren;
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
nextChildren = render(nextProps, ref);
ReactCurrentFiber.setCurrentPhase(null);
} else {
nextChildren = render(nextProps, ref);
}
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
function updateFragment(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
const nextChildren = workInProgress.pendingProps;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextChildren);
return workInProgress.child;
}
function updateMode(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextChildren);
return workInProgress.child;
}
function updateProfiler(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
}
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
(current !== null && current.ref !== ref)
) {
workInProgress.effectTag |= Ref;
}
}
function updateFunctionalComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
nextChildren = Component(nextProps, context);
ReactCurrentFiber.setCurrentPhase(null);
} else {
nextChildren = Component(nextProps, context);
}
workInProgress.effectTag |= PerformedWork;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderExpirationTime);
let shouldUpdate;
if (current === null) {
if (workInProgress.stateNode === null) {
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
shouldUpdate = true;
} else {
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
return finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
}
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (
didCaptureError &&
(!enableGetDerivedStateFromCatch ||
typeof Component.getDerivedStateFromCatch !== 'function')
) {
// If we captured an error, but getDerivedStateFrom catch is not defined,
// unmount all the children. componentDidCatch will schedule an update to
// re-render a fallback. This is temporary until we migrate everyone to
// the new API.
// TODO: Warn in a future release.
nextChildren = null;
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
} else {
if (__DEV__) {
ReactCurrentFiber.setCurrentPhase('render');
nextChildren = instance.render();
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
instance.render();
}
ReactCurrentFiber.setCurrentPhase(null);
} else {
nextChildren = instance.render();
}
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
if (current !== null && didCaptureError) {
// If we're recovering from an error, reconcile twice: first to delete
// all the existing children.
reconcileChildren(current, workInProgress, null, renderExpirationTime);
workInProgress.child = null;
// Now we can continue reconciling like normal. This has the effect of
// remounting all children regardless of whether their their
// identity matches.
}
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
// Memoize props and state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
memoizeState(workInProgress, instance.state);
memoizeProps(workInProgress, instance.props);
// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
return workInProgress.child;
}
function pushHostRootContext(workInProgress) {
const root = (workInProgress.stateNode: FiberRoot);
if (root.pendingContext) {
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
);
} else if (root.context) {
// Should always be set
pushTopLevelContextObject(workInProgress, root.context, false);
}
pushHostContainer(workInProgress, root.containerInfo);
}
function updateHostRoot(current, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress);
const updateQueue = workInProgress.updateQueue;
invariant(
updateQueue !== null,
'If the root does not have an updateQueue, we should have already ' +
'bailed out. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null;
processUpdateQueue(
workInProgress,
updateQueue,
nextProps,
null,
renderExpirationTime,
);
const nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
const root: FiberRoot = workInProgress.stateNode;
if (
(current === null || current.child === null) &&
root.hydrate &&
enterHydrationState(workInProgress)
) {
// If we don't have any current children this might be the first pass.
// We always try to hydrate. If this isn't a hydration pass there won't
// be any children to hydrate which is effectively the same thing as
// not hydrating.
// This is a bit of a hack. We track the host root as a placement to
// know that we're currently in a mounting state. That way isMounted
// works as expected. We must reset this before committing.
// TODO: Delete this when we delete isMounted and findDOMNode.
workInProgress.effectTag |= Placement;
// Ensure that children mount into this root without tracking
// side-effects. This ensures that we don't store Placement effects on
// nodes that will be hydrated.
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
resetHydrationState();
}
return workInProgress.child;
}
function updateHostComponent(current, workInProgress, renderExpirationTime) {
pushHostContext(workInProgress);
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also have access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.effectTag |= ContentReset;
}
markRef(current, workInProgress);
// Check the host config to see if the children are offscreen/hidden.
if (
renderExpirationTime !== Never &&
workInProgress.mode & AsyncMode &&
shouldDeprioritizeSubtree(type, nextProps)
) {
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = Never;
workInProgress.memoizedProps = nextProps;
return null;
}
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextProps);
return workInProgress.child;
}
function updateHostText(current, workInProgress) {
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
const nextProps = workInProgress.pendingProps;
memoizeProps(workInProgress, nextProps);
// Nothing to do here. This is terminal. We'll do the completion step
// immediately after.
return null;
}
function resolveDefaultProps(Component, baseProps) {
if (Component && Component.defaultProps) {
// Resolve default props. Taken from ReactElement
const props = Object.assign({}, baseProps);
const defaultProps = Component.defaultProps;
for (let propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
return props;
}
return baseProps;
}
function mountIndeterminateComponent(
current,
workInProgress,
Component,
renderExpirationTime,
) {
invariant(
current === null,
'An indeterminate component should never have mounted. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
const props = workInProgress.pendingProps;
if (
typeof Component === 'object' &&
Component !== null &&
typeof Component.then === 'function'
) {
Component = readLazyComponentType(Component);
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(
workInProgress,
Component,
));
const resolvedProps = resolveDefaultProps(Component, props);
switch (resolvedTag) {
case FunctionalComponentLazy: {
return updateFunctionalComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case ClassComponentLazy: {
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case ForwardRefLazy: {
return updateForwardRef(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
default: {
// This message intentionally doesn't metion ForwardRef because the
// fact that it's a separate type of work is an implementation detail.
invariant(
false,
'Element type is invalid. Received a promise that resolves to: %s. ' +
'Promise elements must resolve to a class or function.',
Component,
);
}
}
}
const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
const context = getMaskedContext(workInProgress, unmaskedContext);
prepareToReadContext(workInProgress, renderExpirationTime);
let value;
if (__DEV__) {
if (
Component.prototype &&
typeof Component.prototype.render === 'function'
) {
const componentName = getComponentName(Component) || 'Unknown';
if (!didWarnAboutBadClass[componentName]) {
warningWithoutStack(
false,
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
'This is likely to cause errors. Change %s to extend React.Component instead.',
componentName,
componentName,
);
didWarnAboutBadClass[componentName] = true;
}
}
if (workInProgress.mode & StrictMode) {
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
}
ReactCurrentOwner.current = workInProgress;
value = Component(props, context);
} else {
value = Component(props, context);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
if (
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
// 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.
let hasContext = false;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
Component,
getDerivedStateFromProps,
props,
);
}
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, Component, props, renderExpirationTime);
return finishClassComponent(
current,
workInProgress,
Component,
true,
hasContext,
renderExpirationTime,
);
} else {
workInProgress.tag = FunctionalComponent;
if (__DEV__) {
if (Component) {
warningWithoutStack(
!Component.childContextTypes,
'%s(...): childContextTypes cannot be defined on a functional component.',
Component.displayName || Component.name || 'Component',
);
}
if (workInProgress.ref !== null) {
let info = '';
const ownerName = ReactCurrentFiber.getCurrentFiberOwnerNameInDevOrNull();
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
let warningKey = ownerName || workInProgress._debugID || '';
const debugSource = workInProgress._debugSource;
if (debugSource) {
warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
}
if (!didWarnAboutStatelessRefs[warningKey]) {
didWarnAboutStatelessRefs[warningKey] = true;
warning(
false,
'Stateless function components cannot be given refs. ' +
'Attempts to access this ref will fail.%s',
info,
);
}
}
if (typeof Component.getDerivedStateFromProps === 'function') {
const componentName = getComponentName(Component) || 'Unknown';
if (!didWarnAboutGetDerivedStateOnFunctionalComponent[componentName]) {
warningWithoutStack(
false,
'%s: Stateless functional components do not support getDerivedStateFromProps.',
componentName,
);
didWarnAboutGetDerivedStateOnFunctionalComponent[
componentName
] = true;
}
}
}
reconcileChildren(current, workInProgress, value, renderExpirationTime);
memoizeProps(workInProgress, props);
return workInProgress.child;
}
}
function updatePlaceholderComponent(
current,
workInProgress,
renderExpirationTime,
) {
if (enableSuspense) {
const nextProps = workInProgress.pendingProps;
const alreadyCaptured =
(workInProgress.effectTag & DidCapture) === NoEffect;
let nextDidTimeout;
if (current !== null && workInProgress.updateQueue !== null) {
workInProgress.updateQueue = null;
nextDidTimeout = true;
reconcileChildren(current, workInProgress, null, renderExpirationTime);
current.child = null;
} else {
nextDidTimeout = !alreadyCaptured;
}
if ((workInProgress.mode & StrictMode) !== NoEffect) {
if (nextDidTimeout) {
workInProgress.effectTag |= Update;
} else {
workInProgress.stateNode = null;
}
}
const children = nextProps.children;
let nextChildren;
if (typeof children === 'function') {
nextChildren = children(nextDidTimeout);
} else {
nextChildren = nextDidTimeout ? nextProps.fallback : children;
}
workInProgress.memoizedProps = nextProps;
workInProgress.memoizedState = nextDidTimeout;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
} else {
return null;
}
}
function updatePortalComponent(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
const nextChildren = workInProgress.pendingProps;
if (current === null) {
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextChildren);
} else {
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
memoizeProps(workInProgress, nextChildren);
}
return workInProgress.child;
}
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
const providerType: ReactProviderType<any> = workInProgress.type;
const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value;
workInProgress.memoizedProps = newProps;
if (__DEV__) {
const providerPropTypes = workInProgress.type.propTypes;
if (providerPropTypes) {
checkPropTypes(
providerPropTypes,
newProps,
'prop',
'Context.Provider',
ReactCurrentFiber.getCurrentFiberStackInDev,
);
}
}
pushProvider(workInProgress, newValue);
if (oldProps !== null) {
const oldValue = oldProps.value;
const changedBits = calculateChangedBits(context, newValue, oldValue);
if (changedBits === 0) {
if (
oldProps.children === newProps.children &&
!hasLegacyContextChanged()
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
} else {
// The context value changed. Search for matching consumers and schedule
// them to update.
propagateContextChange(
workInProgress,
context,
changedBits,
renderExpirationTime,
);
}
}
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
return workInProgress.child;
}
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
const context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
const render = newProps.children;
if (__DEV__) {
warningWithoutStack(
typeof render === 'function',
'A context consumer was rendered with multiple children, or a child ' +
"that isn't a function. A context consumer expects a single child " +
'that is a function. If you did pass a function, make sure there ' +
'is no trailing or leading whitespace around it.',
);
}
prepareToReadContext(workInProgress, renderExpirationTime);
const newValue = readContext(context, newProps.unstable_observedBits);
let newChildren;
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
newChildren = render(newValue);
ReactCurrentFiber.setCurrentPhase(null);
} else {
newChildren = render(newValue);
}
workInProgress.effectTag |= PerformedWork;
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
workInProgress.memoizedProps = newProps;
return workInProgress.child;
}
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
cancelWorkTimer(workInProgress);
if (current !== null) {
workInProgress.firstContextDependency = current.firstContextDependency;
}
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
const childExpirationTime = workInProgress.childExpirationTime;
if (
childExpirationTime === NoWork ||
childExpirationTime > renderExpirationTime
) {
return null;
} else {
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
}
function memoizeProps(workInProgress: Fiber, nextProps: any) {
workInProgress.memoizedProps = nextProps;
}
function memoizeState(workInProgress: Fiber, nextState: any) {
workInProgress.memoizedState = nextState;
}
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;
if (
!hasLegacyContextChanged() &&
(updateExpirationTime === NoWork ||
updateExpirationTime > renderExpirationTime)
) {
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;
case HostComponent:
pushHostContext(workInProgress);
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case ClassComponentLazy: {
const thenable = workInProgress.type;
const Component = getResultFromResolvedThenable(thenable);
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
pushProvider(workInProgress, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
}
break;
}
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
workInProgress.expirationTime = NoWork;
switch (workInProgress.tag) {
case IndeterminateComponent: {
const Component = workInProgress.type;
return mountIndeterminateComponent(
current,
workInProgress,
Component,
renderExpirationTime,
);
}
case FunctionalComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
return updateFunctionalComponent(
current,
workInProgress,
Component,
unresolvedProps,
renderExpirationTime,
);
}
case FunctionalComponentLazy: {
const thenable = workInProgress.type;
const Component = getResultFromResolvedThenable(thenable);
const unresolvedProps = workInProgress.pendingProps;
const child = updateFunctionalComponent(
current,
workInProgress,
Component,
resolveDefaultProps(Component, unresolvedProps),
renderExpirationTime,
);
workInProgress.memoizedProps = unresolvedProps;
return child;
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
return updateClassComponent(
current,
workInProgress,
Component,
unresolvedProps,
renderExpirationTime,
);
}
case ClassComponentLazy: {
const thenable = workInProgress.type;
const Component = getResultFromResolvedThenable(thenable);
const unresolvedProps = workInProgress.pendingProps;
const child = updateClassComponent(
current,
workInProgress,
Component,
resolveDefaultProps(Component, unresolvedProps),
renderExpirationTime,
);
workInProgress.memoizedProps = unresolvedProps;
return child;
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current, workInProgress);
case PlaceholderComponent:
return updatePlaceholderComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ForwardRef: {
const type = workInProgress.type;
return updateForwardRef(
current,
workInProgress,
type,
workInProgress.pendingProps,
renderExpirationTime,
);
}
case ForwardRefLazy:
const thenable = workInProgress.type;
const Component = getResultFromResolvedThenable(thenable);
const unresolvedProps = workInProgress.pendingProps;
const child = updateForwardRef(
current,
workInProgress,
Component,
resolveDefaultProps(Component, unresolvedProps),
renderExpirationTime,
);
workInProgress.memoizedProps = unresolvedProps;
return child;
case Fragment:
return updateFragment(current, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
}
export {beginWork};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment