React16是否能异步渲染,在于内部一个变量。在开始以前,咱们须要准备一个例子。javascript
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>by 司徒正美</title> <meta name="viewport" content="width=device-width"> <!-- <script type='text/javascript' src="./src/React2.js"></script>--> <script type='text/javascript' src="./test/react.js"></script> <script type='text/javascript' src="./test/react-dom.js"></script> <script src="test/babel.js"></script> </head> <body> <div id="test"></div> <div id="content"></div> </body> <script type="text/babel"> var container = document.getElementById("test"); class Root extends React.Component{ constructor(props){ super(props) this.props = props } render(){ console.log("Root render..", Date.now()) return <div><A /></div> } } class A extends React.Component{ constructor(props){ super(props) this.props = props } render(){ console.log("A render..", Date.now()) return <div>{1111}</div> } } ReactDOM.render(<Root />, container, function(){ console.log("callback",Date.now()) }) console.log("end", Date.now()) </script> </html>
当中的react.js与react-dom.js是React16.4beta,你们能够在bootcdn上下载。html
前文已经提过,ReactDOM.render/hydrate/unstable_renderSubtreeIntoContainer/unmountComponentAtNode
都是legacyRenderSubtreeIntoContainer方法的加壳方法。java
legacyRenderSubtreeIntoContainer里面调用legacyCreateRootFromDOMContainer建立一个ReactRoot对象,而后再调用其render或legacy_renderSubtreeIntoContainer方法node
//by 司徒正美 function legacyCreateRootFromDOMContainer(container, forceHydrate) { var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content. if (!shouldHydrate) { var warned = false; var rootSibling = void 0; while (rootSibling = container.lastChild) { { if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) { warned = true; warning_1(false, 'render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.'); } } container.removeChild(rootSibling); } } { if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) { warnedAboutHydrateAPI = true; lowPriorityWarning$1(false, 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v17. Replace the ReactDOM.render() call ' + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.'); } } // Legacy roots are not async by default. var isAsync = false; console.log("new ReactRoot",container, isAsync, shouldHydrate) return new ReactRoot(container, isAsync, shouldHydrate); }
留意里面的isAsync,是写死的,强制使用同步,咱们能够改一改,就能使用异步react
var isAsync = true;
本节的内容就准备读如何 异步渲染。
ReactRoot以前已经说过,再贴一下源码。babel
架构
从DOMRenderer.updateContainer到达updateContainerAtExpirationTime到达scheduleWork到达scheduleWork到达scheduleWorkImpl到达requestWork,咱们一路加点注释app
下面是scheduleWorkImpl的代码:dom
requestWork里面才进行同步异步逻辑分家异步
scheduleCallbackWithExpiration是干了什么呢?它会断定是否工做与不干做,工做就是从新计算过时时间,而后执行scheduleDeferredCallback方法。scheduleDeferredCallback有两个参数,第一个是回调函数,第二个是对象,里面的timeout决定它在执行scheduleDeferredCallback最迟多少ms才执行。
scheduleDeferredCallback是何方神圣呢?它是大名鼎鼎的requestIdleCallback
requestIdleCallback的语法以下:
performAsyncWork与performSyncWork也是一对兄弟。
//by 司徒正美 function performAsyncWork(dl) { performWork(NoWork, true, dl); } function performSyncWork() { performWork(Sync, false, null); }
performWork的源码
function performWork(minExpirationTime, isAsync, dl) { deadline = dl; // Keep working on roots until there's no more work, or until the we reach // the deadline. findHighestPriorityRoot(); if (enableUserTimingAPI && deadline !== null) { var didExpire = nextFlushedExpirationTime < recalculateCurrentTime(); stopRequestCallbackTimer(didExpire); } if (isAsync) { while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime) && (!deadlineDidExpire || recalculateCurrentTime() >= nextFlushedExpirationTime)) { performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, !deadlineDidExpire); findHighestPriorityRoot(); } } else { while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime)) { performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false); findHighestPriorityRoot(); } } // We're done flushing work. Either we ran out of time in this callback, // or there's no more work left with sufficient priority. // If we're inside a callback, set this to false since we just completed it. if (deadline !== null) { callbackExpirationTime = NoWork; callbackID = -1; } // If there's work left over, schedule a new callback. if (nextFlushedExpirationTime !== NoWork) { scheduleCallbackWithExpiration(nextFlushedExpirationTime); } // Clean-up. deadline = null; deadlineDidExpire = false; finishRendering(); }
下面是同步与异步的执行状况
但异步模式为何会调用两次render呢?估计这还在测试阶段,许多BUG。咱们追踪到finishClassComponent方法,看到它的render方法:
咱们再改一下Root组件的代码,添加一个componentDidMount.
class Root extends React.Component{ constructor(props){ super(props) this.props = props this.state = { x: 1 } } render(){ console.log("Root render..", Date.now()) return <h1><A x={this.state.x} /></h1> } componentDidMount(){ console.log("Root componentDidMount") this.setState({ x: 2 }) } } class A extends React.Component{ constructor(props){ super(props) this.props = props this.state = { text: props.x } } componentWillReceiveProps(p){ this.setState({ text: p.x }) } render(){ console.log("A render..", Date.now()) return <h2>{this.state.text}</h2> } }
咱们再看一下fiber树。fiber有许多种类型,但主要是四种, ClassFiber, FunctionFiber, HostComponentFiber, HostTextFiber,分别对应原来的类组件,无状态组件,元素虚拟节点,文本虚拟节点。Fiber表面上比React15的虚拟DOM多了一些属性,如parent, child, sibling。换言之,fiber能够像真实DOM同样上下右遍历(没有左)。
React16的源码里面有两个方法beginWork与finishWork重要方法。beginWork,就是从一个Fiber开始,初始化它的state(若是fiber.type为函数,则new 实例或一个相似相似的东西,若是type为标签名,则建立元素节点或文本节点),并遍历它的第一重孩子,让孩子们加上parent,sibling(注意这时孩子没有stateNode)。最后返回第一个孩子,做为刚才fiber的child。 而后对这个child再执行beginWork操做。
beginWork的过程当中,确到组件,须要用到context,context是来自contextStack。这是一个全局对象。在顶层,默认会push一个空对象。而后到达某个组件时,peek一下(不使用pop方法)。 若是这个组件有getChildContext方法呢,这时就会产生一个新context, push进去。
有些fiber是没有孩子的,好比说文本节点,或一些元素节点,这样它开始 finishWork操做,找它的sibling,对sibling进行beginWork操做,没有sibling就往上找,这时就会再次访问到某个组件,若是这个组件有getChildContext,因而就pop一下。
finishWork还有一个重要任务,就是收集DOM操做指令,一开始全部fiber的effects都PLACEMENT,叫作置换,其实至关于append。每次往上找时,父fiber就把它全部孩子的effect收集一下,最后到顶层Root组件时, contextStack为空,而effects则装得满满的,而后交给commintAllWork执行这些指令。
fiber架构是很好地解决context的往下传送问题。