diff --git a/README.md b/README.md index b1ce476..e2aef72 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ ### QReact -The dead simple implementation of React for the learning purpose on how React works internally. Currently, it is based on the old React architecture. +The dead simple implementation of React for the learning purpose on how React works internally. + +There are two qreact's version: +- [qReact](https://github.com/MQuy/qreact/releases/tag/qreact) is similar to React-15.6 +- [qReact Fiber](https://github.com/MQuy/qreact/releases/tag/qreact-fiber) is based on the React fiber ### Todo List diff --git a/demo/package.json b/demo/package.json index 43f858d..1092381 100644 --- a/demo/package.json +++ b/demo/package.json @@ -17,9 +17,5 @@ "webpack": "4.28.4", "webpack-cli": "3.2.1", "webpack-dev-server": "3.1.14" - }, - "dependencies": { - "react": "16.8.0-alpha.1", - "react-dom": "16.8.0-alpha.1" } } diff --git a/demo/yarn.lock b/demo/yarn.lock index 928fbf1..bcbd764 100644 --- a/demo/yarn.lock +++ b/demo/yarn.lock @@ -2796,7 +2796,7 @@ js-tokens@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -2927,13 +2927,6 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" -loose-envify@^1.1.0, loose-envify@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" @@ -3326,7 +3319,7 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3635,14 +3628,6 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -prop-types@^15.6.2: - version "15.6.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" - integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== - dependencies: - loose-envify "^1.3.1" - object-assign "^4.1.1" - proxy-addr@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" @@ -3759,26 +3744,6 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@16.8.0-alpha.1: - version "16.8.0-alpha.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.0-alpha.1.tgz#dab73b8354ba2e498e3127d18e29d4546cea889e" - integrity sha512-tZCUM8BpnwUHJmLnUWP9c3vVZxnCqYotj7s4tx7umojG6BKv745KIBtuPTzt0EI0q50GMLEpmT/CPQ8iA61TwQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.0-alpha.1" - -react@16.8.0-alpha.1: - version "16.8.0-alpha.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.0-alpha.1.tgz#c2b32689f3b466d3ce85a634dd9035f789d2cd97" - integrity sha512-vLwwnhM2dXrCsiQmcSxF2UdZVV5xsiXjK5Yetmy8dVqngJhQ3aw3YJhZN/YmyonxwdimH40wVqFQfsl4gSu2RA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.0-alpha.1" - "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -4017,14 +3982,6 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.13.0-alpha.1: - version "0.13.0-alpha.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.0-alpha.1.tgz#753977fb4fb35d8cdd559868a11e46b640955556" - integrity sha512-W0sH0848sVuPKg+I18vTYQyzVtA4X1lrVgSeXK6KnOPUltFdJcY5nkbTkjGUeS/E0x+eBsNYfSdhJtGjT95njw== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - schema-utils@^0.4.4: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" diff --git a/src/Component.js b/src/Component.js index 91025c6..1891d2f 100644 --- a/src/Component.js +++ b/src/Component.js @@ -15,7 +15,7 @@ export class Component { scheduleWork(fiber, expirationTime); } - isReactComponent = {}; + isReactComponent() {} } export const ReactInstanceMap = { diff --git a/src/FiberScheduler.js b/src/FiberScheduler.js index 2c55528..3038438 100644 --- a/src/FiberScheduler.js +++ b/src/FiberScheduler.js @@ -21,7 +21,9 @@ import { getUpdateExpirationTime } from "./UpdateQueue"; import { AsyncMode } from "./TypeOfMode"; const timeHeuristicForUnitOfWork = 1; +let nextFlushedExpirationTime = NoWork; let nextFlushedRoot = null; +let firstScheduledRoot = null; let lastScheduledRoot = null; let nextUnitOfWork = null; let nextEffect = null; @@ -66,18 +68,7 @@ export function scheduleWork(fiber, expirationTime) { } function requestWork(root, expirationTime) { - if (!nextFlushedRoot) { - lastScheduledRoot = nextFlushedRoot = root; - root.remainingExpirationTime = expirationTime; - } else { - const remainingExpirationTime = root.remainingExpirationTime; - if ( - remainingExpirationTime === NoWork || - expirationTime < remainingExpirationTime - ) { - root.remainingExpirationTime = expirationTime; - } - } + addRootToSchedule(root, expirationTime); if (isBatchingUpdates || isRendering) { return; @@ -89,6 +80,33 @@ function requestWork(root, expirationTime) { } } +function addRootToSchedule(root, expirationTime) { + // Add the root to the schedule. + // Check if this root is already part of the schedule. + if (root.nextScheduledRoot == null) { + // This root is not already scheduled. Add it. + root.remainingExpirationTime = expirationTime; + if (lastScheduledRoot == null) { + firstScheduledRoot = lastScheduledRoot = root; + root.nextScheduledRoot = root; + } else { + lastScheduledRoot.nextScheduledRoot = root; + lastScheduledRoot = root; + lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; + } + } else { + // This root is already scheduled, but its priority may have increased. + var remainingExpirationTime = root.remainingExpirationTime; + if ( + remainingExpirationTime === NoWork || + expirationTime < remainingExpirationTime + ) { + // Update the priority. + root.remainingExpirationTime = expirationTime; + } + } +} + function performAsyncWork(deadline) { performWork(NoWork, deadline); } @@ -97,22 +115,101 @@ function performSyncWork() { performWork(Sync, null); } -export function performWork(minExpirationTime, deadline) { +function findHighestPriorityRoot() { + var highestPriorityWork = NoWork; + var highestPriorityRoot = null; if (lastScheduledRoot != null) { - nextFlushedRoot = lastScheduledRoot; + var previousScheduledRoot = lastScheduledRoot; + var root = firstScheduledRoot; + while (root != null) { + var remainingExpirationTime = root.remainingExpirationTime; + if (remainingExpirationTime === NoWork) { + // This root no longer has work. Remove it from the scheduler. + + // TODO: This check is redudant, but Flow is confused by the branch + // below where we set lastScheduledRoot to null, even though we break + // from the loop right after. + !(previousScheduledRoot != null && lastScheduledRoot != null) + ? invariant( + false, + "Should have a previous and last root. This error is likely caused by a bug in React. Please file an issue.", + ) + : void 0; + if (root === root.nextScheduledRoot) { + // This is the only root in the list. + root.nextScheduledRoot = null; + firstScheduledRoot = lastScheduledRoot = null; + break; + } else if (root === firstScheduledRoot) { + // This is the first root in the list. + var next = root.nextScheduledRoot; + firstScheduledRoot = next; + lastScheduledRoot.nextScheduledRoot = next; + root.nextScheduledRoot = null; + } else if (root === lastScheduledRoot) { + // This is the last root in the list. + lastScheduledRoot = previousScheduledRoot; + lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; + root.nextScheduledRoot = null; + break; + } else { + previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot; + root.nextScheduledRoot = null; + } + root = previousScheduledRoot.nextScheduledRoot; + } else { + if ( + highestPriorityWork === NoWork || + remainingExpirationTime < highestPriorityWork + ) { + // Update the priority, if it's higher + highestPriorityWork = remainingExpirationTime; + highestPriorityRoot = root; + } + if (root === lastScheduledRoot) { + break; + } + previousScheduledRoot = root; + root = root.nextScheduledRoot; + } + } } - if ( - minExpirationTime === NoWork || - nextFlushedRoot.remainingExpirationTime <= minExpirationTime - ) { - performWorkOnRoot( - nextFlushedRoot, - nextFlushedRoot.remainingExpirationTime, - deadline, - ); + + // If the next root is the same as the previous root, this is a nested + // update. To prevent an infinite loop, increment the nested update count. + nextFlushedRoot = highestPriorityRoot; + nextFlushedExpirationTime = highestPriorityWork; +} +export function performWork(minExpirationTime, deadline) { + // Keep working on roots until there's no more work, or until the we reach + // the deadline. + findHighestPriorityRoot(); + + if (deadline) { + while ( + nextFlushedRoot != null && + nextFlushedExpirationTime !== NoWork && + (minExpirationTime === NoWork || + minExpirationTime >= nextFlushedExpirationTime) && + (deadline.timeRemaining() > timeHeuristicForUnitOfWork || + recalculateCurrentTime() >= nextFlushedExpirationTime) + ) { + performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, deadline); + findHighestPriorityRoot(); + } + } else { + while ( + nextFlushedRoot != null && + nextFlushedExpirationTime !== NoWork && + (minExpirationTime === NoWork || + minExpirationTime >= nextFlushedExpirationTime) + ) { + performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false); + findHighestPriorityRoot(); + } } - if (nextFlushedRoot.remainingExpirationTime == NoWork) { + if (nextFlushedExpirationTime === NoWork) { nextFlushedRoot = null; } else { requestIdleCallback(performAsyncWork);