Created
August 29, 2018 05:31
-
-
Save cacheflow/2563dbab4de9592a6de3fc6636d8ea66 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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