├── .babelrc ├── src ├── assets │ ├── logo.png │ └── image │ │ ├── arrow.png │ │ ├── eye.png │ │ ├── open.png │ │ ├── sort.png │ │ ├── message.png │ │ ├── search.png │ │ ├── switch.png │ │ ├── agree_with.png │ │ ├── order_none.png │ │ ├── search_btn.png │ │ ├── train_icon.png │ │ ├── uc_normal.png │ │ ├── uc_select.png │ │ ├── open_select.png │ │ ├── homepage_normal.png │ │ ├── homepage_select.png │ │ ├── question_normal.png │ │ ├── question_select.png │ │ └── train_highSpeed.png ├── components │ ├── Dog │ │ ├── index.ux │ │ └── index.js │ ├── Animal │ │ ├── index.ux │ │ └── index.js │ ├── Naviagtor │ │ └── index.js │ └── PageWrapper │ │ └── index.ux ├── pages │ ├── demo │ │ └── index │ │ │ └── index.ux │ ├── page2 │ │ └── index.ux │ ├── page1 │ │ └── index.ux │ ├── page3 │ │ └── index.ux │ └── index │ │ └── index.ux ├── manifest.json ├── app.ux └── utils.js ├── react ├── ReactFiberErrorDialog.js ├── ReactFiberStack.js ├── ReactFiberErrorLogger.js ├── ReactFiberExpirationTime.js ├── ReactFiberRoot.js ├── ReactFiberReconciler.js ├── ReactFiberHydrationContext.js ├── ReactFiberPendingPriority.js ├── ReactFiberUnwindWork.js ├── ReactFiberCompleteWork.js └── ReactFiberCommitWork.js ├── dist └── com.application.demo.debug.rpk ├── .gitignore ├── readme.md ├── package.json ├── sign └── debug │ ├── certificate.pem │ └── private.pem └── .eslintrc.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ] 5 | } -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /react/ReactFiberErrorDialog.js: -------------------------------------------------------------------------------- 1 | export function showErrorDialog(capturedError) { 2 | return true; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/image/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/arrow.png -------------------------------------------------------------------------------- /src/assets/image/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/eye.png -------------------------------------------------------------------------------- /src/assets/image/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/open.png -------------------------------------------------------------------------------- /src/assets/image/sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/sort.png -------------------------------------------------------------------------------- /src/assets/image/message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/message.png -------------------------------------------------------------------------------- /src/assets/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/search.png -------------------------------------------------------------------------------- /src/assets/image/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/switch.png -------------------------------------------------------------------------------- /src/assets/image/agree_with.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/agree_with.png -------------------------------------------------------------------------------- /src/assets/image/order_none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/order_none.png -------------------------------------------------------------------------------- /src/assets/image/search_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/search_btn.png -------------------------------------------------------------------------------- /src/assets/image/train_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/train_icon.png -------------------------------------------------------------------------------- /src/assets/image/uc_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/uc_normal.png -------------------------------------------------------------------------------- /src/assets/image/uc_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/uc_select.png -------------------------------------------------------------------------------- /src/assets/image/open_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/open_select.png -------------------------------------------------------------------------------- /dist/com.application.demo.debug.rpk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/dist/com.application.demo.debug.rpk -------------------------------------------------------------------------------- /src/assets/image/homepage_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/homepage_normal.png -------------------------------------------------------------------------------- /src/assets/image/homepage_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/homepage_select.png -------------------------------------------------------------------------------- /src/assets/image/question_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/question_normal.png -------------------------------------------------------------------------------- /src/assets/image/question_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/question_select.png -------------------------------------------------------------------------------- /src/assets/image/train_highSpeed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/quickdemo/HEAD/src/assets/image/train_highSpeed.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules/* 3 | /.vscode/* 4 | /.history/* 5 | .DS_Store 6 | build/ 7 | dist/ 8 | 9 | 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 快应用的demo 2 | 3 | 本项目是为了熟悉快应用语法而建 4 | 尽量靠近娜娜奇的去哪儿模板 5 | 6 | 下载回来 `npm i` 7 | 8 | 到下面地址下载安装调试器 9 | 10 | https://doc.quickapp.cn/tutorial/framework/framework-instructions.html -------------------------------------------------------------------------------- /src/components/Dog/index.ux: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/components/Animal/index.ux: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /react/ReactFiberStack.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const valueStack = [] 4 | 5 | 6 | let index = -1; 7 | 8 | function createCursor(defaultValue) { 9 | return { 10 | current: defaultValue, 11 | }; 12 | } 13 | 14 | function isEmpty() { 15 | return index === -1; 16 | } 17 | 18 | function pop(cursor, fiber) { 19 | if (index < 0) { 20 | return; 21 | } 22 | 23 | cursor.current = valueStack[index]; 24 | 25 | valueStack[index] = null; 26 | 27 | index--; 28 | } 29 | 30 | function push(cursor, value, fiber) { 31 | index++; 32 | 33 | valueStack[index] = cursor.current; 34 | 35 | cursor.current = value; 36 | } 37 | 38 | export { createCursor, isEmpty, pop, push }; 39 | -------------------------------------------------------------------------------- /src/pages/demo/index/index.ux: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 26 | -------------------------------------------------------------------------------- /react/ReactFiberErrorLogger.js: -------------------------------------------------------------------------------- 1 | 2 | import { showErrorDialog } from './ReactFiberErrorDialog'; 3 | 4 | export function logCapturedError(capturedError) { 5 | const logError = showErrorDialog(capturedError); 6 | 7 | // Allow injected showErrorDialog() to prevent default console.error logging. 8 | // This enables renderers like ReactNative to better manage redbox behavior. 9 | if (logError === false) { 10 | return; 11 | } 12 | 13 | const error = capturedError.error; 14 | 15 | // In production, we print the error directly. 16 | // This will include the message, the JS stack, and anything the browser wants to show. 17 | // We pass the error object instead of custom message so that the browser displays the error natively. 18 | console.error(error); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Dog/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import React from '../../ReactQuick.js'; 3 | import Animal from '../Animal/index'; 4 | function Dog() { 5 | } 6 | Dog = React.toClass(Dog, Animal, { 7 | componentWillMount: function() { 8 | // eslint-disable-next-line 9 | console.log('Dog componentWillMount'); 10 | }, 11 | render: function() { 12 | var h = React.createElement; 13 | 14 | return h('div', { 15 | style: React.toStyle({ 16 | color: '#ffff00' 17 | }, this.props, 'style608') 18 | }, '名字:', this.state.name, ' 年龄:', this.state.age, ' 岁', h('div', { 19 | catchClick: this.changeAge.bind(this), 20 | 'data-tap-uid': 'e848', 21 | 'data-class-uid': 'c590' 22 | }, '换一个年龄')); 23 | }, 24 | classUid: 'c590' 25 | }, {}); 26 | export { React } 27 | 28 | export default Dog; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aaa", 3 | "version": "1.0.0", 4 | "subversion": { 5 | "toolkit": "0.0.36" 6 | }, 7 | "description": "", 8 | "scripts": { 9 | "server": "hap server", 10 | "postinstall": "hap postinstall", 11 | "debug": "hap debug", 12 | "build": "hap build", 13 | "release": "hap release", 14 | "watch": "hap watch" 15 | }, 16 | "devDependencies": { 17 | "hap-toolkit": "0.0.36", 18 | "babel-cli": "^6.10.1", 19 | "babel-core": "^6.26.0", 20 | "babel-eslint": "^8.2.1", 21 | "babel-loader": "^7.1.4", 22 | "babel-plugin-syntax-jsx": "^6.18.0", 23 | "cross-env": "^5.1.4", 24 | "css-what": "^2.1.0", 25 | "koa": "^2.3.0", 26 | "koa-send": "^4.1.1", 27 | "koa-static": "^4.0.1", 28 | "koa-body": "^2.5.0", 29 | "koa-router": "^7.2.1", 30 | "socket.io": "^2.1.0", 31 | "webpack": "^3.11.0" 32 | }, 33 | "dependencies": { 34 | "less": "^3.8.1", 35 | "less-loader": "^4.1.0", 36 | "node-sass": "^4.9.3", 37 | "sass-loader": "^7.1.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": "com.application.demo", 3 | "name": "aaa", 4 | "versionName": "1.0.0", 5 | "versionCode": "1", 6 | "minPlatformVersion": "101", 7 | "icon": "/assets/logo.png", 8 | "features": [ 9 | { "name": "system.prompt" }, 10 | { "name": "system.router" }, 11 | { "name": "system.shortcut" }, 12 | { "name": "system.share" }, 13 | { "name": "system.notification" }, 14 | {"name": "system.vibrator"}, 15 | {"name": "system.request"}, 16 | {"name": "system.media"} 17 | ], 18 | "permissions": [{ "origin": "*" }], 19 | "config": { 20 | "logLevel": "debug" 21 | }, 22 | "router": { 23 | "entry": "pages/index", 24 | "pages": { 25 | "pages/index": { 26 | "component": "index" 27 | }, 28 | "pages/page1": { 29 | "component": "index" 30 | }, 31 | "pages/page2": { 32 | "component": "index" 33 | }, 34 | "pages/page3": { 35 | "component": "index" 36 | } 37 | } 38 | }, 39 | "display": { 40 | "titleBarBackgroundColor": "#f2f2f2", 41 | "titleBarTextColor": "#414141", 42 | "menu": true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app.ux: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sign/debug/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDMTCCAhmgAwIBAgIJAMKpjyszxkDpMA0GCSqGSIb3DQEBCwUAMC4xCzAJBgNV 3 | BAYTAkNOMQwwCgYDVQQKDANSUEsxETAPBgNVBAMMCFJQS0RlYnVnMCAXDTE3MDQx 4 | OTAyMzE0OVoYDzIxMTYwMzI2MDIzMTQ5WjAuMQswCQYDVQQGEwJDTjEMMAoGA1UE 5 | CgwDUlBLMREwDwYDVQQDDAhSUEtEZWJ1ZzCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBAK3kPd9jzvTctTIA3XNZVv9cHHDbAc6nTBfdZp9mtPOTkXFpvyCb 7 | kL0QjOog0+1pv8D7dFeP4ptWXU5CT3ImvaPR+16dAtMRcsxEr5q4zieJzx3O6huL 8 | UBa1k+xrzjXpRzkcOysmc8fTxt0tAwbDgJ2AA5TlXLTcVyb7GmJ+hl5CjnhoG5NN 9 | LrkqI7S29c1U3uokj8Q7hzaj0TURu/uB5ZIMCLZY9KFDugqaEcvmUyJiD0fuV6sA 10 | O/4kpiZUOnhV8/xWpRbMI4WFQsfgLOCV+X9uzUa29D677y//46t/EDSuQTHyBZbl 11 | AcNMENkpMWZsH7J/+F19+U0/Hd5bJgneVRkCAwEAAaNQME4wHQYDVR0OBBYEFKDN 12 | SZtt47ttOBDQzIchFYyxsg3mMB8GA1UdIwQYMBaAFKDNSZtt47ttOBDQzIchFYyx 13 | sg3mMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABaZctNrn4gLmNf/ 14 | eNJ3x5CJIPjPwm6j9nwKhtadJ6BF+TIzSkJuHSgxULjW436F37otv94NPzT5PCBF 15 | WxgXoqgLqnWwvsaqC4LUEjsZviWW4CB824YDUquEUVGFLE/U5KTZ7Kh1ceyUk4N8 16 | +mtkXkanWoBBk0OF24lNrAsNLB63yTLr9HxEe75+kmvxf1qVJUGtaOEWIhiFMiAB 17 | 5D4w/j2EFWktumjuy5TTwU0zhl52bc8V9SNixM1IaqzNrVPrdjv8viUX548pU3WT 18 | xZ5ylDsxhMC1q4BXQVeIY8C0cMEX+WHOmOCvWrkxCkP91pKsSPkuVrWlzrkn8Ojo 19 | swP6sBw= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "parser": "babel-eslint", 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true, 11 | "jsx": true 12 | } 13 | }, 14 | "globals": { 15 | "loadData": false, 16 | "saveData": false, 17 | "history": false, 18 | "console": false, 19 | "setTimeout": false, 20 | "clearTimeout": false, 21 | "setInterval": false, 22 | "clearInterval": false 23 | }, 24 | "plugins": [ 25 | "hybrid" 26 | ], 27 | "rules": { 28 | "indent": [ 29 | "warn", 30 | 2 31 | ], 32 | "no-console": [ 33 | "warn", 34 | { 35 | "allow": [ 36 | "info", 37 | "warn", 38 | "error" 39 | ] 40 | } 41 | ], 42 | "no-unused-vars": [ 43 | "warn", 44 | { 45 | "varsIgnorePattern": "prompt" 46 | } 47 | ], 48 | "quotes": [ 49 | "warn", 50 | "single", 51 | { 52 | "avoidEscape": true, 53 | "allowTemplateLiterals": true 54 | } 55 | ], 56 | "linebreak-style": [ 57 | "warn", 58 | "unix" 59 | ], 60 | "semi": [ 61 | "warn", 62 | "never" 63 | ] 64 | } 65 | } -------------------------------------------------------------------------------- /src/components/Animal/index.js: -------------------------------------------------------------------------------- 1 | import React from '../../ReactQuick.js'; 2 | 3 | function Animal(props) { 4 | this.state = { 5 | name: props.name, 6 | age: props.age || 1 7 | }; 8 | } 9 | // 10 | Animal = React.toClass(Animal, React.Component, { 11 | changeAge: function () { 12 | this.setState({ 13 | age: ~~(Math.random() * 10) 14 | }); 15 | }, 16 | componentDidMount: function () { 17 | // eslint-disable-next-line 18 | console.log('Animal componentDidMount'); 19 | }, 20 | componentWillReceiveProps: function (props) { 21 | this.setState({ 22 | name: props.name 23 | }); 24 | }, 25 | render: function () { 26 | var h = React.createElement; 27 | 28 | return h('div', null, 29 | h("text", { 30 | style: React.toStyle({ 31 | color: '#d7131c' 32 | }, this.props, 'style1362') 33 | }, '名字:', this.state.name, ' 年龄:', this.state.age, ' 岁'), 34 | h('div', { 35 | catchClick: this.changeAge.bind(this), 36 | 'data-tap-uid': 'e1602', 37 | 'data-class-uid': 'c901' 38 | }, '换一个年龄') 39 | 40 | ); 41 | }, 42 | classUid: 'c901' 43 | }, { 44 | defaultProps: { 45 | age: 1, 46 | name: 'animal' 47 | } 48 | }); 49 | // Animal 是一个函数 50 | export { React } 51 | export default Animal ;//这是给你们用的组件定义 Animal ={props, onReady, onInit, } -------------------------------------------------------------------------------- /sign/debug/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCt5D3fY8703LUy 3 | AN1zWVb/XBxw2wHOp0wX3WafZrTzk5Fxab8gm5C9EIzqINPtab/A+3RXj+KbVl1O 4 | Qk9yJr2j0ftenQLTEXLMRK+auM4nic8dzuobi1AWtZPsa8416Uc5HDsrJnPH08bd 5 | LQMGw4CdgAOU5Vy03Fcm+xpifoZeQo54aBuTTS65KiO0tvXNVN7qJI/EO4c2o9E1 6 | Ebv7geWSDAi2WPShQ7oKmhHL5lMiYg9H7lerADv+JKYmVDp4VfP8VqUWzCOFhULH 7 | 4Czglfl/bs1GtvQ+u+8v/+OrfxA0rkEx8gWW5QHDTBDZKTFmbB+yf/hdfflNPx3e 8 | WyYJ3lUZAgMBAAECggEBAJTnCBBdUB+fSs1prjeS/gsmnfgJoY+K9H7PCIxgj3yw 9 | FXAvZAmRDKzJGlF2EOOQlTG0YNiGDj6EAtv7rjoKcINyULSg8IU6wLmn61MrAuUa 10 | fa+Bujgh4E/B5swhOHAztNhzkzsM70Hi17wXSislh+HWd7qteOgqcbqgdOR4gaj+ 11 | HUqtcxG3H3hCL3dWugnjLZMtestLKGHSSZvbQNjYM3kKy2LvO8NpxmDE4a+TXygK 12 | qhaZjmS/dc/nJBJzOfkzby58RvGbzlJflfW/Uu3/gizj13GFQKWonq1xh630RAhv 13 | xX5ySok2aAx/+/SiJSpNXvM09grQuoORSr7D1tm+5rECgYEA3vf0hRfua0XAOu6f 14 | pyzNvLRRJ/pEew7XpNPCyS2TuMTd1yvXjGVxQfP46N6x1IM3SRU0zE+LSk80EF7l 15 | u1Or7GyCEhabYNe/7P2F8ENP73Do0HwvcI1jGrgr6r9oK0J27Xei+f6Q0bgJOPI2 16 | qaLj+V37cOjkNSM1mhTjtDwK8k0CgYEAx6cMrkjHl1+lDIIOc3qAEL3jb3xQveYk 17 | WrMF/B+j048k6boU4VvFJAIyQxOxMNxLjw3/9+zXCFJT4WaZK3TMXlg614ASGx3H 18 | tKjJM9O07ywwMq1gbutFS4nHCg3L3Os6esL0SPwMdATR3Yh22n5OGI9o+/aURulL 19 | GPEXef1Z2/0CgYEAgmwp5LxV4vu+8Pnp+4DSq4ISQr861XyeGTUhKEp3sUm+tgFY 20 | KTChakHKpHS3Mqa6bQ5xft08je/8dWL9IHFWDIqAHxKIOsKY6oh1k0/cbyPtmx45 21 | Ja4efV+jmMHzrfJH3KnxdCg7D+GFy4CrBtlYXuJhlO81pft9fC6h7yh8ArUCgYBq 22 | gvkl5Zftbs4rnRq+iqTVyagTKvwcQzIz3PwdZHfO/rfPpUFMdNv4eN99n3zRN0Vs 23 | HSjoiEazntA3GLgwUdBRqLpDi4SdSMbo337vkksdqbJQ5uPiaMuAIBG6kF+pDSkW 24 | ovkWErlGD+gySoI10FozihaVDRhPuFgjB0PiBcIxtQKBgGNSzX+Bx5+ux1Qny0Sn 25 | SUcBtepLnO8M8wafoGNyehbMnLzfuMbaDiJOdozGlBNHZTtPB3r4AYb8WnltdKW0 26 | 7i3fk26YZGiMVeUJvewA6/LOBEaqMdwoNwnoptvbR6ehHeE/PPtRtge2cD3bPIM7 27 | U9VlWgfgj9Dxfwhslqb9hmyp 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/components/Naviagtor/index.js: -------------------------------------------------------------------------------- 1 | const React from "@react"; 2 | const noop = function(){} 3 | class Navigtor extends React.Component{ 4 | static defaultProps = { 5 | target: "self", 6 | url: "", 7 | "open-type": "navigate", 8 | "hover-class": "navigator-hover" 9 | } 10 | goPage(){ 11 | var method = this.props['open-type']; 12 | var hook = methodMap[method] || "navigateTo"; 13 | React.api[hook]({ 14 | uri: this.props.url, 15 | success: this.props.bindsuccess || noop, 16 | fail:this.props.bindfail || noop, 17 | complete: this.props.bindcomplete || noop 18 | }) 19 | } 20 | render(){ 21 | return
{this.props.children}
22 | } 23 | 24 | } 25 | const methodMap = { 26 | navigate: "navigateTo", 27 | redirect: "redirectTo", 28 | switchTab: "switchTab", 29 | reLaunch: "reLaunch", 30 | "navigateBack": "navigateBack" 31 | } 32 | /* 33 | target String self 在哪个目标上发生跳转,默认当前小程序,可选值self/miniProgram 2.0.7 34 | url String 当前小程序内的跳转链接 35 | open-type String navigate 跳转方式 36 | delta Number 当 open-type 为 'navigateBack' 时有效,表示回退的层数 37 | app-id String 当target="miniProgram"时有效,要打开的小程序 appId 2.0.7 38 | path String 当target="miniProgram"时有效,打开的页面路径,如果为空则打开首页 2.0.7 39 | extra-data Object 当target="miniProgram"时有效,需要传递给目标小程序的数据,目标小程序可在 App.onLaunch(),App.onShow() 中获取到这份数据。详情 2.0.7 40 | version version release 当target="miniProgram"时有效,要打开的小程序版本,有效值 develop(开发版),trial(体验版),release(正式版),仅在当前小程序为开发版或体验版时此参数有效;如果当前小程序是正式版,则打开的小程序必定是正式版。 2.0.7 41 | hover-class String navigator-hover 指定点击时的样式类,当hover-class="none"时,没有点击态效果 42 | hover-stop-propagation Boolean false 指定是否阻止本节点的祖先节点出现点击态 1.5.0 43 | hover-start-time Number 50 按住后多久出现点击态,单位毫秒 44 | hover-stay-time Number 600 手指松开后点击态保留时间,单位毫秒 45 | bindsuccess String 当target="miniProgram"时有效,跳转小程序成功 2.0.7 46 | bindfail String 当target="miniProgram"时有效,跳转小程序失败 2.0.7 47 | bindcomplete String 当target="miniProgram"时有效,跳转小程序完成 48 | */ -------------------------------------------------------------------------------- /react/ReactFiberExpirationTime.js: -------------------------------------------------------------------------------- 1 | import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt'; 2 | 3 | export const NoWork = 0; 4 | export const Sync = 1; 5 | export const Never = MAX_SIGNED_31_BIT_INT; 6 | 7 | const UNIT_SIZE = 10; 8 | const MAGIC_NUMBER_OFFSET = 2; 9 | 10 | // 1 unit of expiration time represents 10ms. 11 | export function msToExpirationTime(ms) { 12 | // Always add an offset so that we don't clash with the magic number for NoWork. 13 | return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET; 14 | } 15 | 16 | export function expirationTimeToMs(expirationTime) { 17 | return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE; 18 | } 19 | 20 | function ceiling(num, precision) { 21 | return (((num / precision) | 0) + 1) * precision; 22 | } 23 | 24 | function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs) { 25 | return ( 26 | MAGIC_NUMBER_OFFSET + 27 | ceiling(currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE) 28 | ); 29 | } 30 | 31 | export const LOW_PRIORITY_EXPIRATION = 5000; 32 | export const LOW_PRIORITY_BATCH_SIZE = 250; 33 | 34 | export function computeAsyncExpiration(currentTime) { 35 | return computeExpirationBucket(currentTime, LOW_PRIORITY_EXPIRATION, LOW_PRIORITY_BATCH_SIZE); 36 | } 37 | 38 | // We intentionally set a higher expiration time for interactive updates in 39 | // dev than in production. 40 | // 41 | // If the main thread is being blocked so long that you hit the expiration, 42 | // it's a problem that could be solved with better scheduling. 43 | // 44 | // People will be more likely to notice this and fix it with the long 45 | // expiration time in development. 46 | // 47 | // In production we opt for better UX at the risk of masking scheduling 48 | // problems, by expiring fast. 49 | export const HIGH_PRIORITY_EXPIRATION = 150; 50 | export const HIGH_PRIORITY_BATCH_SIZE = 100; 51 | 52 | export function computeInteractiveExpiration(currentTime) { 53 | return computeExpirationBucket(currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE); 54 | } 55 | -------------------------------------------------------------------------------- /src/pages/page2/index.ux: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /src/pages/page1/index.ux: -------------------------------------------------------------------------------- 1 | 2 | 14 | 22 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | var showMenuList = [ 2 | { 3 | title: "保存桌面", 4 | callback() { 5 | createShortcut(); 6 | } 7 | }, 8 | { 9 | title: "关于", 10 | callback() { 11 | const router = require("@system.router"); 12 | const appInfo = require("@system.app").getInfo(); 13 | router.push({ 14 | uri: "/pages/About", 15 | params: { 16 | name: appInfo.name, 17 | icon: appInfo.icon 18 | } 19 | }); 20 | } 21 | }, 22 | { 23 | title: "取消", 24 | callback() {} 25 | } 26 | ]; 27 | /** 28 | * 显示菜单 29 | */ 30 | export function showMenu(shareObject) { 31 | const prompt = require("@system.prompt"); 32 | let list = showMenuList; 33 | if (shareObject) { 34 | list = [ 35 | { 36 | title: "分享", 37 | callback() { 38 | const share = require("@system.share"); 39 | share.share({ 40 | shareType: 0, 41 | title: "标题", 42 | summary: "摘要", 43 | imagePath: "xxx/xxx/xxx/share.jpg", 44 | targetUrl: "http://www.example.com", 45 | platforms: ["WEIBO"], 46 | success: function(data) { 47 | console.log("handling success"); 48 | }, 49 | fail: function(data, code) { 50 | console.log(`handling fail, code = ${code}`); 51 | } 52 | }); 53 | } 54 | } 55 | ].concat(showMenuList); 56 | } 57 | //分享转发 https://doc.quickapp.cn/features/service/share.html 58 | prompt.showContextMenu({ 59 | itemList: list.map(function(el) { 60 | return el.title; 61 | }), 62 | success: function(ret) { 63 | var el = list[ret.index]; 64 | if (el) { 65 | el.callback(); 66 | } 67 | } 68 | }); 69 | } 70 | export var shareObject = {}; 71 | export function getApp() { 72 | return shareObject.app; 73 | } 74 | /** 75 | * 创建桌面图标 76 | * 注意:使用加载器测试`创建桌面快捷方式`功能时,请先在`系统设置`中打开`应用加载器`的`桌面快捷方式`权限 77 | */ 78 | export function createShortcut() { 79 | const prompt = require("@system.prompt"); 80 | const shortcut = require("@system.shortcut"); 81 | shortcut.hasInstalled({ 82 | success: function(ret) { 83 | if (ret) { 84 | prompt.showToast({ 85 | message: "已创建桌面图标" 86 | }); 87 | } else { 88 | shortcut.install({ 89 | success: function() { 90 | prompt.showToast({ 91 | message: "成功创建桌面图标" 92 | }); 93 | }, 94 | fail: function(errmsg, errcode) { 95 | prompt.showToast({ 96 | message: `${errcode}: ${errmsg}` 97 | }); 98 | } 99 | }); 100 | } 101 | } 102 | }); 103 | } 104 | 105 | export function toPage(pageClass, pagePath) { 106 | var instance = new pageClass({}, {}); 107 | var config = { 108 | private: { 109 | props: Object, 110 | context: Object, 111 | state: Object 112 | }, 113 | onInit() { 114 | this.props = instance.props; 115 | this.state = instance.state; 116 | this.context = instance.context; 117 | instance.wx = this; 118 | instance.onInit && instance.onInit() 119 | transmitData(pageClass, pagePath, instance, this); 120 | }, 121 | onShow() { 122 | instance.onShow && instance.onShow() 123 | transmitData(pageClass, pagePath, instance, this); 124 | }, 125 | onReady() { 126 | instance.onReady && instance.onReady() 127 | }, 128 | onMenuPress(a) { 129 | instance.onMenuPress && instance.onMenuPress(a); 130 | } 131 | }; 132 | return config; 133 | } 134 | // shareObject的数据不是长久的,在页面跳转时,就会丢失 135 | function transmitData(pageClass, pagePath, reactInstance, quickInstance) { 136 | var cc = reactInstance.config || pageClass.config; 137 | shareObject.pageConfig = cc; 138 | shareObject.pagePath = pagePath; 139 | shareObject.page = reactInstance; //React实例 140 | shareObject.app = quickInstance.$app.$def; //app 141 | } 142 | -------------------------------------------------------------------------------- /react/ReactFiberRoot.js: -------------------------------------------------------------------------------- 1 | import { noTimeout } from './ReactFiberHostConfig'; 2 | import { createHostRootFiber } from './ReactFiber'; 3 | import { NoWork } from './ReactFiberExpirationTime'; 4 | // TODO: This should be lifted into the renderer. 5 | /* 6 | export type Batch = { 7 | _defer: boolean, 8 | _expirationTime: ExpirationTime, 9 | _onComplete: () => mixed, 10 | _next: Batch | null, 11 | }; 12 | 13 | 14 | type BaseFiberRootProperties = {| 15 | // Any additional information from the host associated with this root. 16 | containerInfo: any, 17 | // Used only by persistent updates. 18 | pendingChildren: any, 19 | // The currently active root fiber. This is the mutable root of the tree. 20 | current: Fiber, 21 | 22 | // The following priority levels are used to distinguish between 1) 23 | // uncommitted work, 2) uncommitted work that is suspended, and 3) uncommitted 24 | // work that may be unsuspended. We choose not to track each individual 25 | // pending level, trading granularity for performance. 26 | // 27 | // The earliest and latest priority levels that are suspended from committing. 28 | earliestSuspendedTime: ExpirationTime, 29 | latestSuspendedTime: ExpirationTime, 30 | // The earliest and latest priority levels that are not known to be suspended. 31 | earliestPendingTime: ExpirationTime, 32 | latestPendingTime: ExpirationTime, 33 | // The latest priority level that was pinged by a resolved promise and can 34 | // be retried. 35 | latestPingedTime: ExpirationTime, 36 | 37 | // If an error is thrown, and there are no more updates in the queue, we try 38 | // rendering from the root one more time, synchronously, before handling 39 | // the error. 40 | didError: boolean, 41 | 42 | pendingCommitExpirationTime: ExpirationTime, 43 | // A finished work-in-progress HostRoot that's ready to be committed. 44 | finishedWork: Fiber | null, 45 | // Timeout handle returned by setTimeout. Used to cancel a pending timeout, if 46 | // it's superseded by a new one. 47 | timeoutHandle: TimeoutHandle | NoTimeout, 48 | // Top context object, used by renderSubtreeIntoContainer 49 | context: Object | null, 50 | pendingContext: Object | null, 51 | // Determines if we should attempt to hydrate on the initial mount 52 | +hydrate: boolean, 53 | // Remaining expiration time on this root. 54 | // TODO: Lift this into the renderer 55 | nextExpirationTimeToWorkOn: ExpirationTime, 56 | expirationTime: ExpirationTime, 57 | // List of top-level batches. This list indicates whether a commit should be 58 | // deferred. Also contains completion callbacks. 59 | // TODO: Lift this into the renderer 60 | firstBatch: Batch | null, 61 | // Linked-list of roots 62 | nextScheduledRoot: FiberRoot | null, 63 | |}; 64 | 65 | // The following attributes are only used by interaction tracing builds. 66 | // They enable interactions to be associated with their async work, 67 | // And expose interaction metadata to the React DevTools Profiler plugin. 68 | // Note that these attributes are only defined when the enableSchedulerTracing flag is enabled. 69 | type ProfilingOnlyFiberRootProperties = {| 70 | interactionThreadID: number, 71 | memoizedInteractions: Set, 72 | pendingInteractionMap: PendingInteractionMap, 73 | |}; 74 | 75 | // Exported FiberRoot type includes all properties, 76 | // To avoid requiring potentially error-prone :any casts throughout the project. 77 | // Profiling properties are only safe to access in profiling builds (when enableSchedulerTracing is true). 78 | // The types are defined separately within this file to ensure they stay in sync. 79 | // (We don't have to use an inline :any cast when enableSchedulerTracing is disabled.) 80 | export type FiberRoot = { 81 | ...BaseFiberRootProperties, 82 | ...ProfilingOnlyFiberRootProperties, 83 | }; 84 | */ 85 | export function createFiberRoot(containerInfo, isConcurrent, hydrate) { 86 | // Cyclic construction. This cheats the type system right now because 87 | // stateNode is any. 88 | const uninitializedFiber = createHostRootFiber(isConcurrent); 89 | 90 | let root = { 91 | current: uninitializedFiber, 92 | containerInfo: containerInfo, 93 | pendingChildren: null, 94 | 95 | earliestPendingTime: NoWork, 96 | latestPendingTime: NoWork, 97 | earliestSuspendedTime: NoWork, 98 | latestSuspendedTime: NoWork, 99 | latestPingedTime: NoWork, 100 | 101 | didError: false, 102 | 103 | pendingCommitExpirationTime: NoWork, 104 | finishedWork: null, 105 | timeoutHandle: noTimeout, 106 | context: null, 107 | pendingContext: null, 108 | hydrate, 109 | nextExpirationTimeToWorkOn: NoWork, 110 | expirationTime: NoWork, 111 | firstBatch: null, 112 | nextScheduledRoot: null, 113 | }; 114 | 115 | uninitializedFiber.stateNode = root; 116 | 117 | // The reason for the way the Flow types are structured in this file, 118 | // Is to avoid needing :any casts everywhere interaction tracing fields are used. 119 | // Unfortunately that requires an :any cast for non-interaction tracing capable builds. 120 | // $FlowFixMe Remove this :any cast and replace it with something better. 121 | return root 122 | } 123 | -------------------------------------------------------------------------------- /src/components/PageWrapper/index.ux: -------------------------------------------------------------------------------- 1 | 20 | 52 | -------------------------------------------------------------------------------- /react/ReactFiberReconciler.js: -------------------------------------------------------------------------------- 1 | import { findCurrentHostFiber, findCurrentHostFiberWithNoPortals } from 'react-reconciler/reflection'; 2 | import * as ReactInstanceMap from 'shared/ReactInstanceMap'; 3 | import { HostComponent, ClassComponent, ClassComponentLazy } from 'shared/ReactWorkTags'; 4 | 5 | import { getResultFromResolvedThenable } from 'shared/ReactLazyComponent'; 6 | 7 | import { getPublicInstance } from './ReactFiberHostConfig'; 8 | import { 9 | findCurrentUnmaskedContext, 10 | processChildContext, 11 | emptyContextObject, 12 | isContextProvider as isLegacyContextProvider, 13 | } from './ReactFiberContext'; 14 | import { createFiberRoot } from './ReactFiberRoot'; 15 | import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook'; 16 | import { 17 | computeUniqueAsyncExpiration, 18 | requestCurrentTime, 19 | computeExpirationForFiber, 20 | scheduleWork, 21 | requestWork, 22 | flushRoot, 23 | batchedUpdates, 24 | unbatchedUpdates, 25 | flushSync, 26 | flushControlled, 27 | deferredUpdates, 28 | syncUpdates, 29 | interactiveUpdates, 30 | flushInteractiveUpdates, 31 | } from './ReactFiberScheduler'; 32 | import { createUpdate, enqueueUpdate } from './ReactUpdateQueue'; 33 | 34 | 35 | 36 | function getContextForSubtree(parentComponent) { 37 | if (!parentComponent) { 38 | return emptyContextObject; 39 | } 40 | 41 | const fiber = ReactInstanceMap.get(parentComponent); 42 | const parentContext = findCurrentUnmaskedContext(fiber); 43 | 44 | if (fiber.tag === ClassComponent) { 45 | const Component = fiber.type; 46 | if (isLegacyContextProvider(Component)) { 47 | return processChildContext(fiber, Component, parentContext); 48 | } 49 | } else if (fiber.tag === ClassComponentLazy) { 50 | const Component = getResultFromResolvedThenable(fiber.type); 51 | if (isLegacyContextProvider(Component)) { 52 | return processChildContext(fiber, Component, parentContext); 53 | } 54 | } 55 | 56 | return parentContext; 57 | } 58 | 59 | function scheduleRootUpdate(current, element, expirationTime, callback) { 60 | const update = createUpdate(expirationTime); 61 | // Caution: React DevTools currently depends on this property 62 | // being called "element". 63 | // (导弹、火箭等的)有效载荷,有效负荷;收费载重,酬载;(工厂、企业等)工资负担 64 | update.payload = { element }; 65 | 66 | callback = callback === undefined ? null : callback; 67 | if (callback !== null) { 68 | update.callback = callback; 69 | } 70 | enqueueUpdate(current, update); 71 | 72 | scheduleWork(current, expirationTime); 73 | return expirationTime; 74 | } 75 | // updateContainerAtExpirationTime使用scheduleRootUpdate 76 | export function updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback) { 77 | // TODO: If this is a nested container, this won't be the root. 78 | const current = container.current; 79 | 80 | const context = getContextForSubtree(parentComponent); 81 | if (container.context === null) { 82 | container.context = context; 83 | } else { 84 | container.pendingContext = context; 85 | } 86 | 87 | return scheduleRootUpdate(current, element, expirationTime, callback); 88 | } 89 | //找元素节点 90 | function findHostInstance(component) { 91 | const fiber = ReactInstanceMap.get(component); 92 | 93 | const hostFiber = findCurrentHostFiber(fiber); 94 | if (hostFiber === null) { 95 | return null; 96 | } 97 | return hostFiber.stateNode; 98 | } 99 | //有什么意义 100 | export function createContainer(containerInfo, isConcurrent, hydrate) { 101 | return createFiberRoot(containerInfo, isConcurrent, hydrate); 102 | } 103 | 104 | export function updateContainer(element, container, parentComponent, callback) { 105 | const current = container.current; 106 | const currentTime = requestCurrentTime(); 107 | const expirationTime = computeExpirationForFiber(currentTime, current); 108 | return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback); 109 | } 110 | 111 | export { 112 | flushRoot, 113 | requestWork, 114 | computeUniqueAsyncExpiration, 115 | batchedUpdates, 116 | unbatchedUpdates, 117 | deferredUpdates, 118 | syncUpdates, 119 | interactiveUpdates, 120 | flushInteractiveUpdates, 121 | flushControlled, 122 | flushSync 123 | }; 124 | 125 | export { findHostInstance }; 126 | //得到元素节点或组件实例 127 | export function getPublicRootInstance(container) { 128 | const containerFiber = container.current; 129 | if (!containerFiber.child) { 130 | return null; 131 | } 132 | switch (containerFiber.child.tag) { 133 | case HostComponent: 134 | return getPublicInstance(containerFiber.child.stateNode); 135 | default: 136 | return containerFiber.child.stateNode; 137 | } 138 | } 139 | 140 | 141 | export function findHostInstanceWithNoPortals(fiber) { 142 | const hostFiber = findCurrentHostFiberWithNoPortals(fiber); 143 | if (hostFiber === null) { 144 | return null; 145 | } 146 | return hostFiber.stateNode; 147 | } 148 | 149 | export function injectIntoDevTools(devToolsConfig) { 150 | const { findFiberByHostInstance } = devToolsConfig; 151 | return ReactFiberDevToolsHook.injectInternals({ 152 | ...devToolsConfig, 153 | findHostInstanceByFiber(fiber) { 154 | const hostFiber = findCurrentHostFiber(fiber); 155 | if (hostFiber === null) { 156 | return null; 157 | } 158 | return hostFiber.stateNode; 159 | }, 160 | findFiberByHostInstance(instance) { 161 | if (!findFiberByHostInstance) { 162 | // Might not be implemented by the renderer. 163 | return null; 164 | } 165 | return findFiberByHostInstance(instance); 166 | }, 167 | }); 168 | } 169 | -------------------------------------------------------------------------------- /react/ReactFiberHydrationContext.js: -------------------------------------------------------------------------------- 1 | import { HostComponent, HostText, HostRoot } from 'shared/ReactWorkTags'; 2 | import { Deletion, Placement } from 'shared/ReactSideEffectTags'; 3 | import { createFiberFromHostInstanceForDeletion } from './ReactFiber'; 4 | import { 5 | shouldSetTextContent, 6 | supportsHydration, 7 | canHydrateInstance, 8 | canHydrateTextInstance, 9 | getNextHydratableSibling, 10 | getFirstHydratableChild, 11 | hydrateInstance, 12 | hydrateTextInstance 13 | } from './ReactFiberHostConfig'; 14 | 15 | // The deepest Fiber on the stack involved in a hydration context. 16 | // This may have been an insertion or a hydration. 17 | let hydrationParentFiber = null; 18 | let nextHydratableInstance = null; 19 | let isHydrating = false; 20 | 21 | function enterHydrationState(fiber) { 22 | if (!supportsHydration) { 23 | return false; 24 | } 25 | 26 | const parentInstance = fiber.stateNode.containerInfo; 27 | nextHydratableInstance = getFirstHydratableChild(parentInstance); 28 | hydrationParentFiber = fiber; 29 | isHydrating = true; 30 | return true; 31 | } 32 | 33 | function deleteHydratableInstance(returnFiber, instance) { 34 | const childToDelete = createFiberFromHostInstanceForDeletion(); 35 | childToDelete.stateNode = instance; 36 | childToDelete.return = returnFiber; 37 | childToDelete.effectTag = Deletion; 38 | 39 | // This might seem like it belongs on progressedFirstDeletion. However, 40 | // these children are not part of the reconciliation list of children. 41 | // Even if we abort and rereconcile the children, that will try to hydrate 42 | // again and the nodes are still in the host tree so these will be 43 | // recreated. 44 | if (returnFiber.lastEffect !== null) { 45 | returnFiber.lastEffect.nextEffect = childToDelete; 46 | returnFiber.lastEffect = childToDelete; 47 | } else { 48 | returnFiber.firstEffect = returnFiber.lastEffect = childToDelete; 49 | } 50 | } 51 | 52 | function insertNonHydratedInstance(returnFiber, fiber) { 53 | fiber.effectTag |= Placement; 54 | } 55 | 56 | function tryHydrate(fiber, nextInstance) { 57 | switch (fiber.tag) { 58 | case HostComponent: { 59 | const type = fiber.type; 60 | const props = fiber.pendingProps; 61 | const instance = canHydrateInstance(nextInstance, type, props); 62 | if (instance !== null) { 63 | fiber.stateNode = instance; 64 | return true; 65 | } 66 | return false; 67 | } 68 | case HostText: { 69 | const text = fiber.pendingProps; 70 | const textInstance = canHydrateTextInstance(nextInstance, text); 71 | if (textInstance !== null) { 72 | fiber.stateNode = textInstance; 73 | return true; 74 | } 75 | return false; 76 | } 77 | default: 78 | return false; 79 | } 80 | } 81 | 82 | function tryToClaimNextHydratableInstance(fiber) { 83 | if (!isHydrating) { 84 | return; 85 | } 86 | let nextInstance = nextHydratableInstance; 87 | if (!nextInstance) { 88 | // Nothing to hydrate. Make it an insertion. 89 | insertNonHydratedInstance((hydrationParentFiber: any), fiber); 90 | isHydrating = false; 91 | hydrationParentFiber = fiber; 92 | return; 93 | } 94 | const firstAttemptedInstance = nextInstance; 95 | if (!tryHydrate(fiber, nextInstance)) { 96 | // If we can't hydrate this instance let's try the next one. 97 | // We use this as a heuristic. It's based on intuition and not data so it 98 | // might be flawed or unnecessary. 99 | nextInstance = getNextHydratableSibling(firstAttemptedInstance); 100 | if (!nextInstance || !tryHydrate(fiber, nextInstance)) { 101 | // Nothing to hydrate. Make it an insertion. 102 | insertNonHydratedInstance((hydrationParentFiber), fiber); 103 | isHydrating = false; 104 | hydrationParentFiber = fiber; 105 | return; 106 | } 107 | // We matched the next one, we'll now assume that the first one was 108 | // superfluous and we'll delete it. Since we can't eagerly delete it 109 | // we'll have to schedule a deletion. To do that, this node needs a dummy 110 | // fiber associated with it. 111 | deleteHydratableInstance((hydrationParentFiber), firstAttemptedInstance); 112 | } 113 | hydrationParentFiber = fiber; 114 | nextHydratableInstance = getFirstHydratableChild((nextInstance)); 115 | } 116 | 117 | function prepareToHydrateHostInstance(fiber, rootContainerInstance, hostContext) { 118 | const instance = fiber.stateNode; 119 | const updatePayload = hydrateInstance( 120 | instance, 121 | fiber.type, 122 | fiber.memoizedProps, 123 | rootContainerInstance, 124 | hostContext, 125 | fiber 126 | ); 127 | // TODO: Type this specific to this type of component. 128 | fiber.updateQueue = (updatePayload); 129 | // If the update payload indicates that there is a change or if there 130 | // is a new ref we mark this as an update. 131 | if (updatePayload !== null) { 132 | return true; 133 | } 134 | return false; 135 | } 136 | 137 | function prepareToHydrateHostTextInstance(fiber) { 138 | 139 | const textInstance = fiber.stateNode; 140 | const textContent = fiber.memoizedProps; 141 | const shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber); 142 | 143 | return shouldUpdate; 144 | } 145 | 146 | function popToNextHostParent(fiber) { 147 | let parent = fiber.return; 148 | while (parent !== null && parent.tag !== HostComponent && parent.tag !== HostRoot) { 149 | parent = parent.return; 150 | } 151 | hydrationParentFiber = parent; 152 | } 153 | 154 | function popHydrationState(fiber) { 155 | if (!supportsHydration) { 156 | return false; 157 | } 158 | if (fiber !== hydrationParentFiber) { 159 | // We're deeper than the current hydration context, inside an inserted 160 | // tree. 161 | return false; 162 | } 163 | if (!isHydrating) { 164 | // If we're not currently hydrating but we're in a hydration context, then 165 | // we were an insertion and now need to pop up reenter hydration of our 166 | // siblings. 167 | popToNextHostParent(fiber); 168 | isHydrating = true; 169 | return false; 170 | } 171 | 172 | const type = fiber.type; 173 | 174 | // If we have any remaining hydratable nodes, we need to delete them now. 175 | // We only do this deeper than head and body since they tend to have random 176 | // other nodes in them. We also ignore components with pure text content in 177 | // side of them. 178 | // TODO: Better heuristic. 179 | if ( 180 | fiber.tag !== HostComponent || 181 | (type !== 'head' && type !== 'body' && !shouldSetTextContent(type, fiber.memoizedProps)) 182 | ) { 183 | let nextInstance = nextHydratableInstance; 184 | while (nextInstance) { 185 | deleteHydratableInstance(fiber, nextInstance); 186 | nextInstance = getNextHydratableSibling(nextInstance); 187 | } 188 | } 189 | 190 | popToNextHostParent(fiber); 191 | nextHydratableInstance = hydrationParentFiber ? getNextHydratableSibling(fiber.stateNode) : null; 192 | return true; 193 | } 194 | 195 | function resetHydrationState() { 196 | if (!supportsHydration) { 197 | return; 198 | } 199 | 200 | hydrationParentFiber = null; 201 | nextHydratableInstance = null; 202 | isHydrating = false; 203 | } 204 | 205 | export { 206 | enterHydrationState, 207 | resetHydrationState, 208 | tryToClaimNextHydratableInstance, 209 | prepareToHydrateHostInstance, 210 | prepareToHydrateHostTextInstance, 211 | popHydrationState, 212 | }; 213 | -------------------------------------------------------------------------------- /src/pages/page3/index.ux: -------------------------------------------------------------------------------- 1 | 2 | 32 | 52 | 53 | -------------------------------------------------------------------------------- /react/ReactFiberPendingPriority.js: -------------------------------------------------------------------------------- 1 | 2 | import type {FiberRoot} from './ReactFiberRoot'; 3 | import type {ExpirationTime} from './ReactFiberExpirationTime'; 4 | 5 | import {NoWork} from './ReactFiberExpirationTime'; 6 | 7 | // TODO: Offscreen updates should never suspend. However, a promise that 8 | // suspended inside an offscreen subtree should be able to ping at the priority 9 | // of the outer render. 10 | 11 | export function markPendingPriorityLevel( 12 | root: FiberRoot, 13 | expirationTime: ExpirationTime, 14 | ): void { 15 | // If there's a gap between completing a failed root and retrying it, 16 | // additional updates may be scheduled. Clear `didError`, in case the update 17 | // is sufficient to fix the error. 18 | root.didError = false; 19 | 20 | // Update the latest and earliest pending times 21 | const earliestPendingTime = root.earliestPendingTime; 22 | if (earliestPendingTime === NoWork) { 23 | // No other pending updates. 24 | root.earliestPendingTime = root.latestPendingTime = expirationTime; 25 | } else { 26 | if (earliestPendingTime < expirationTime) { 27 | // This is the earliest pending update. 28 | root.earliestPendingTime = expirationTime; 29 | } else { 30 | const latestPendingTime = root.latestPendingTime; 31 | if (latestPendingTime > expirationTime) { 32 | // This is the latest pending update 33 | root.latestPendingTime = expirationTime; 34 | } 35 | } 36 | } 37 | findNextExpirationTimeToWorkOn(expirationTime, root); 38 | } 39 | 40 | export function markCommittedPriorityLevels( 41 | root: FiberRoot, 42 | earliestRemainingTime: ExpirationTime, 43 | ): void { 44 | root.didError = false; 45 | 46 | if (earliestRemainingTime === NoWork) { 47 | // Fast path. There's no remaining work. Clear everything. 48 | root.earliestPendingTime = NoWork; 49 | root.latestPendingTime = NoWork; 50 | root.earliestSuspendedTime = NoWork; 51 | root.latestSuspendedTime = NoWork; 52 | root.latestPingedTime = NoWork; 53 | findNextExpirationTimeToWorkOn(NoWork, root); 54 | return; 55 | } 56 | 57 | // Let's see if the previous latest known pending level was just flushed. 58 | const latestPendingTime = root.latestPendingTime; 59 | if (latestPendingTime !== NoWork) { 60 | if (latestPendingTime > earliestRemainingTime) { 61 | // We've flushed all the known pending levels. 62 | root.earliestPendingTime = root.latestPendingTime = NoWork; 63 | } else { 64 | const earliestPendingTime = root.earliestPendingTime; 65 | if (earliestPendingTime > earliestRemainingTime) { 66 | // We've flushed the earliest known pending level. Set this to the 67 | // latest pending time. 68 | root.earliestPendingTime = root.latestPendingTime; 69 | } 70 | } 71 | } 72 | 73 | // Now let's handle the earliest remaining level in the whole tree. We need to 74 | // decide whether to treat it as a pending level or as suspended. Check 75 | // it falls within the range of known suspended levels. 76 | 77 | const earliestSuspendedTime = root.earliestSuspendedTime; 78 | if (earliestSuspendedTime === NoWork) { 79 | // There's no suspended work. Treat the earliest remaining level as a 80 | // pending level. 81 | markPendingPriorityLevel(root, earliestRemainingTime); 82 | findNextExpirationTimeToWorkOn(NoWork, root); 83 | return; 84 | } 85 | 86 | const latestSuspendedTime = root.latestSuspendedTime; 87 | if (earliestRemainingTime < latestSuspendedTime) { 88 | // The earliest remaining level is later than all the suspended work. That 89 | // means we've flushed all the suspended work. 90 | root.earliestSuspendedTime = NoWork; 91 | root.latestSuspendedTime = NoWork; 92 | root.latestPingedTime = NoWork; 93 | 94 | // There's no suspended work. Treat the earliest remaining level as a 95 | // pending level. 96 | markPendingPriorityLevel(root, earliestRemainingTime); 97 | findNextExpirationTimeToWorkOn(NoWork, root); 98 | return; 99 | } 100 | 101 | if (earliestRemainingTime > earliestSuspendedTime) { 102 | // The earliest remaining time is earlier than all the suspended work. 103 | // Treat it as a pending update. 104 | markPendingPriorityLevel(root, earliestRemainingTime); 105 | findNextExpirationTimeToWorkOn(NoWork, root); 106 | return; 107 | } 108 | 109 | // The earliest remaining time falls within the range of known suspended 110 | // levels. We should treat this as suspended work. 111 | findNextExpirationTimeToWorkOn(NoWork, root); 112 | } 113 | 114 | export function hasLowerPriorityWork( 115 | root: FiberRoot, 116 | erroredExpirationTime: ExpirationTime, 117 | ): boolean { 118 | const latestPendingTime = root.latestPendingTime; 119 | const latestSuspendedTime = root.latestSuspendedTime; 120 | const latestPingedTime = root.latestPingedTime; 121 | return ( 122 | (latestPendingTime !== NoWork && 123 | latestPendingTime < erroredExpirationTime) || 124 | (latestSuspendedTime !== NoWork && 125 | latestSuspendedTime < erroredExpirationTime) || 126 | (latestPingedTime !== NoWork && latestPingedTime < erroredExpirationTime) 127 | ); 128 | } 129 | 130 | export function isPriorityLevelSuspended( 131 | root: FiberRoot, 132 | expirationTime: ExpirationTime, 133 | ): boolean { 134 | const earliestSuspendedTime = root.earliestSuspendedTime; 135 | const latestSuspendedTime = root.latestSuspendedTime; 136 | return ( 137 | earliestSuspendedTime !== NoWork && 138 | expirationTime <= earliestSuspendedTime && 139 | expirationTime >= latestSuspendedTime 140 | ); 141 | } 142 | 143 | export function markSuspendedPriorityLevel( 144 | root: FiberRoot, 145 | suspendedTime: ExpirationTime, 146 | ): void { 147 | root.didError = false; 148 | clearPing(root, suspendedTime); 149 | 150 | // First, check the known pending levels and update them if needed. 151 | const earliestPendingTime = root.earliestPendingTime; 152 | const latestPendingTime = root.latestPendingTime; 153 | if (earliestPendingTime === suspendedTime) { 154 | if (latestPendingTime === suspendedTime) { 155 | // Both known pending levels were suspended. Clear them. 156 | root.earliestPendingTime = root.latestPendingTime = NoWork; 157 | } else { 158 | // The earliest pending level was suspended. Clear by setting it to the 159 | // latest pending level. 160 | root.earliestPendingTime = latestPendingTime; 161 | } 162 | } else if (latestPendingTime === suspendedTime) { 163 | // The latest pending level was suspended. Clear by setting it to the 164 | // latest pending level. 165 | root.latestPendingTime = earliestPendingTime; 166 | } 167 | 168 | // Finally, update the known suspended levels. 169 | const earliestSuspendedTime = root.earliestSuspendedTime; 170 | const latestSuspendedTime = root.latestSuspendedTime; 171 | if (earliestSuspendedTime === NoWork) { 172 | // No other suspended levels. 173 | root.earliestSuspendedTime = root.latestSuspendedTime = suspendedTime; 174 | } else { 175 | if (earliestSuspendedTime < suspendedTime) { 176 | // This is the earliest suspended level. 177 | root.earliestSuspendedTime = suspendedTime; 178 | } else if (latestSuspendedTime > suspendedTime) { 179 | // This is the latest suspended level 180 | root.latestSuspendedTime = suspendedTime; 181 | } 182 | } 183 | 184 | findNextExpirationTimeToWorkOn(suspendedTime, root); 185 | } 186 | 187 | export function markPingedPriorityLevel( 188 | root: FiberRoot, 189 | pingedTime: ExpirationTime, 190 | ): void { 191 | root.didError = false; 192 | 193 | // TODO: When we add back resuming, we need to ensure the progressed work 194 | // is thrown out and not reused during the restarted render. One way to 195 | // invalidate the progressed work is to restart at expirationTime + 1. 196 | const latestPingedTime = root.latestPingedTime; 197 | if (latestPingedTime === NoWork || latestPingedTime > pingedTime) { 198 | root.latestPingedTime = pingedTime; 199 | } 200 | findNextExpirationTimeToWorkOn(pingedTime, root); 201 | } 202 | 203 | function clearPing(root, completedTime) { 204 | // TODO: Track whether the root was pinged during the render phase. If so, 205 | // we need to make sure we don't lose track of it. 206 | const latestPingedTime = root.latestPingedTime; 207 | if (latestPingedTime !== NoWork && latestPingedTime >= completedTime) { 208 | root.latestPingedTime = NoWork; 209 | } 210 | } 211 | 212 | export function findEarliestOutstandingPriorityLevel( 213 | root: FiberRoot, 214 | renderExpirationTime: ExpirationTime, 215 | ): ExpirationTime { 216 | let earliestExpirationTime = renderExpirationTime; 217 | 218 | const earliestPendingTime = root.earliestPendingTime; 219 | const earliestSuspendedTime = root.earliestSuspendedTime; 220 | if (earliestPendingTime > earliestExpirationTime) { 221 | earliestExpirationTime = earliestPendingTime; 222 | } 223 | if (earliestSuspendedTime > earliestExpirationTime) { 224 | earliestExpirationTime = earliestSuspendedTime; 225 | } 226 | return earliestExpirationTime; 227 | } 228 | 229 | export function didExpireAtExpirationTime( 230 | root: FiberRoot, 231 | currentTime: ExpirationTime, 232 | ): void { 233 | const expirationTime = root.expirationTime; 234 | if (expirationTime !== NoWork && currentTime <= expirationTime) { 235 | // The root has expired. Flush all work up to the current time. 236 | root.nextExpirationTimeToWorkOn = currentTime; 237 | } 238 | } 239 | 240 | function findNextExpirationTimeToWorkOn(completedExpirationTime, root) { 241 | const earliestSuspendedTime = root.earliestSuspendedTime; 242 | const latestSuspendedTime = root.latestSuspendedTime; 243 | const earliestPendingTime = root.earliestPendingTime; 244 | const latestPingedTime = root.latestPingedTime; 245 | 246 | // Work on the earliest pending time. Failing that, work on the latest 247 | // pinged time. 248 | let nextExpirationTimeToWorkOn = 249 | earliestPendingTime !== NoWork ? earliestPendingTime : latestPingedTime; 250 | 251 | // If there is no pending or pinged work, check if there's suspended work 252 | // that's lower priority than what we just completed. 253 | if ( 254 | nextExpirationTimeToWorkOn === NoWork && 255 | (completedExpirationTime === NoWork || 256 | latestSuspendedTime < completedExpirationTime) 257 | ) { 258 | // The lowest priority suspended work is the work most likely to be 259 | // committed next. Let's start rendering it again, so that if it times out, 260 | // it's ready to commit. 261 | nextExpirationTimeToWorkOn = latestSuspendedTime; 262 | } 263 | 264 | let expirationTime = nextExpirationTimeToWorkOn; 265 | if (expirationTime !== NoWork && earliestSuspendedTime > expirationTime) { 266 | // Expire using the earliest known expiration time. 267 | expirationTime = earliestSuspendedTime; 268 | } 269 | 270 | root.nextExpirationTimeToWorkOn = nextExpirationTimeToWorkOn; 271 | root.expirationTime = expirationTime; 272 | } -------------------------------------------------------------------------------- /src/pages/index/index.ux: -------------------------------------------------------------------------------- 1 | 2 | 3 | 68 | 258 | -------------------------------------------------------------------------------- /react/ReactFiberUnwindWork.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import { unstable_wrap as Schedule_tracing_wrap } from 'scheduler/tracing'; 5 | 6 | import { 7 | ClassComponent, 8 | HostRoot, 9 | HostComponent, 10 | HostPortal, 11 | ContextProvider, 12 | SuspenseComponent, 13 | IncompleteClassComponent, 14 | } from 'shared/ReactWorkTags'; 15 | import { 16 | DidCapture, 17 | Incomplete, 18 | NoEffect, 19 | ShouldCapture, 20 | Callback as CallbackEffect, 21 | LifecycleEffectMask, 22 | } from 'shared/ReactSideEffectTags'; 23 | import { enableSchedulerTracing } from 'shared/ReactFeatureFlags'; 24 | import { ConcurrentMode } from './ReactTypeOfMode'; 25 | import { shouldCaptureSuspense } from './ReactFiberSuspenseComponent'; 26 | 27 | import { createCapturedValue } from './ReactCapturedValue'; 28 | import { 29 | enqueueCapturedUpdate, 30 | createUpdate, 31 | CaptureUpdate, 32 | } from './ReactUpdateQueue'; 33 | import { logError } from './ReactFiberCommitWork'; 34 | import { popHostContainer, popHostContext } from './ReactFiberHostContext'; 35 | import { 36 | isContextProvider as isLegacyContextProvider, 37 | popContext as popLegacyContext, 38 | popTopLevelContextObject as popTopLevelLegacyContextObject, 39 | } from './ReactFiberContext'; 40 | import { popProvider } from './ReactFiberNewContext'; 41 | import { 42 | renderDidSuspend, 43 | renderDidError, 44 | onUncaughtError, 45 | markLegacyErrorBoundaryAsFailed, 46 | isAlreadyFailedLegacyErrorBoundary, 47 | retrySuspendedRoot, 48 | } from './ReactFiberScheduler'; 49 | import { NoWork, Sync } from './ReactFiberExpirationTime'; 50 | 51 | import invariant from 'shared/invariant'; 52 | import maxSigned31BitInt from './maxSigned31BitInt'; 53 | import { 54 | expirationTimeToMs, 55 | LOW_PRIORITY_EXPIRATION, 56 | } from './ReactFiberExpirationTime'; 57 | import { findEarliestOutstandingPriorityLevel } from './ReactFiberPendingPriority'; 58 | import { reconcileChildren } from './ReactFiberBeginWork'; 59 | 60 | function createRootErrorUpdate( 61 | fiber, 62 | errorInfo, 63 | expirationTime, 64 | ) { 65 | const update = createUpdate(expirationTime); 66 | // Unmount the root by rendering null. 67 | update.tag = CaptureUpdate; 68 | // Caution: React DevTools currently depends on this property 69 | // being called "element". 70 | update.payload = { element: null }; 71 | const error = errorInfo.value; 72 | update.callback = () => { 73 | onUncaughtError(error); 74 | logError(fiber, errorInfo); 75 | }; 76 | return update; 77 | } 78 | 79 | function createClassErrorUpdate( 80 | fiber, 81 | errorInfo, 82 | expirationTime, 83 | ) { 84 | const update = createUpdate(expirationTime); 85 | update.tag = CaptureUpdate; 86 | const getDerivedStateFromError = fiber.type.getDerivedStateFromError; 87 | if (typeof getDerivedStateFromError === 'function') { 88 | const error = errorInfo.value; 89 | update.payload = () => { 90 | return getDerivedStateFromError(error); 91 | }; 92 | } 93 | 94 | const inst = fiber.stateNode; 95 | if (inst !== null && typeof inst.componentDidCatch === 'function') { 96 | update.callback = function callback() { 97 | if (typeof getDerivedStateFromError !== 'function') { 98 | // To preserve the preexisting retry behavior of error boundaries, 99 | // we keep track of which ones already failed during this batch. 100 | // This gets reset before we yield back to the browser. 101 | // TODO: Warn in strict mode if getDerivedStateFromError is 102 | // not defined. 103 | markLegacyErrorBoundaryAsFailed(this); 104 | } 105 | const error = errorInfo.value; 106 | const stack = errorInfo.stack; 107 | logError(fiber, errorInfo); 108 | this.componentDidCatch(error, { 109 | componentStack: stack !== null ? stack : '', 110 | }); 111 | 112 | }; 113 | } 114 | return update; 115 | } 116 | 117 | function throwException( 118 | root, 119 | returnFiber, 120 | sourceFiber, 121 | value, 122 | renderExpirationTime, 123 | ) { 124 | // The source fiber did not complete. 125 | sourceFiber.effectTag |= Incomplete; 126 | // Its effect list is no longer valid. 127 | sourceFiber.firstEffect = sourceFiber.lastEffect = null; 128 | 129 | if ( 130 | value !== null && 131 | typeof value === 'object' && 132 | typeof value.then === 'function' 133 | ) { 134 | // This is a thenable. 135 | const thenable = value; 136 | 137 | // Find the earliest timeout threshold of all the placeholders in the 138 | // ancestor path. We could avoid this traversal by storing the thresholds on 139 | // the stack, but we choose not to because we only hit this path if we're 140 | // IO-bound (i.e. if something suspends). Whereas the stack is used even in 141 | // the non-IO- bound case. 142 | let workInProgress = returnFiber; 143 | let earliestTimeoutMs = -1; 144 | let startTimeMs = -1; 145 | do { 146 | if (workInProgress.tag === SuspenseComponent) { 147 | const current = workInProgress.alternate; 148 | if (current !== null) { 149 | const currentState = current.memoizedState; 150 | if (currentState !== null && currentState.didTimeout) { 151 | // Reached a boundary that already timed out. Do not search 152 | // any further. 153 | const timedOutAt = currentState.timedOutAt; 154 | startTimeMs = expirationTimeToMs(timedOutAt); 155 | // Do not search any further. 156 | break; 157 | } 158 | } 159 | let timeoutPropMs = workInProgress.pendingProps.maxDuration; 160 | if (typeof timeoutPropMs === 'number') { 161 | if (timeoutPropMs <= 0) { 162 | earliestTimeoutMs = 0; 163 | } else if ( 164 | earliestTimeoutMs === -1 || 165 | timeoutPropMs < earliestTimeoutMs 166 | ) { 167 | earliestTimeoutMs = timeoutPropMs; 168 | } 169 | } 170 | } 171 | workInProgress = workInProgress.return; 172 | } while (workInProgress !== null); 173 | 174 | // Schedule the nearest Suspense to re-render the timed out view. 175 | workInProgress = returnFiber; 176 | do { 177 | if ( 178 | workInProgress.tag === SuspenseComponent && 179 | shouldCaptureSuspense(workInProgress.alternate, workInProgress) 180 | ) { 181 | // Found the nearest boundary. 182 | 183 | // If the boundary is not in concurrent mode, we should not suspend, and 184 | // likewise, when the promise resolves, we should ping synchronously. 185 | const pingTime = 186 | (workInProgress.mode & ConcurrentMode) === NoEffect 187 | ? Sync 188 | : renderExpirationTime; 189 | 190 | // Attach a listener to the promise to "ping" the root and retry. 191 | let onResolveOrReject = retrySuspendedRoot.bind( 192 | null, 193 | root, 194 | workInProgress, 195 | sourceFiber, 196 | pingTime, 197 | ); 198 | 199 | thenable.then(onResolveOrReject, onResolveOrReject); 200 | 201 | // If the boundary is outside of concurrent mode, we should *not* 202 | // suspend the commit. Pretend as if the suspended component rendered 203 | // null and keep rendering. In the commit phase, we'll schedule a 204 | // subsequent synchronous update to re-render the Suspense. 205 | // 206 | // Note: It doesn't matter whether the component that suspended was 207 | // inside a concurrent mode tree. If the Suspense is outside of it, we 208 | // should *not* suspend the commit. 209 | if ((workInProgress.mode & ConcurrentMode) === NoEffect) { 210 | workInProgress.effectTag |= CallbackEffect; 211 | 212 | // Unmount the source fiber's children 213 | const nextChildren = null; 214 | reconcileChildren( 215 | sourceFiber.alternate, 216 | sourceFiber, 217 | nextChildren, 218 | renderExpirationTime, 219 | ); 220 | sourceFiber.effectTag &= ~Incomplete; 221 | 222 | // We're going to commit this fiber even though it didn't complete. 223 | // But we shouldn't call any lifecycle methods or callbacks. Remove 224 | // all lifecycle effect tags. 225 | sourceFiber.effectTag &= ~LifecycleEffectMask; 226 | 227 | if (sourceFiber.tag === ClassComponent) { 228 | const current = sourceFiber.alternate; 229 | if (current === null) { 230 | // This is a new mount. Change the tag so it's not mistaken for a 231 | // completed class component. For example, we should not call 232 | // componentWillUnmount if it is deleted. 233 | sourceFiber.tag = IncompleteClassComponent; 234 | } 235 | } 236 | 237 | // Exit without suspending. 238 | return; 239 | } 240 | 241 | // Confirmed that the boundary is in a concurrent mode tree. Continue 242 | // with the normal suspend path. 243 | 244 | let absoluteTimeoutMs; 245 | if (earliestTimeoutMs === -1) { 246 | // If no explicit threshold is given, default to an abitrarily large 247 | // value. The actual size doesn't matter because the threshold for the 248 | // whole tree will be clamped to the expiration time. 249 | absoluteTimeoutMs = maxSigned31BitInt; 250 | } else { 251 | if (startTimeMs === -1) { 252 | // This suspend happened outside of any already timed-out 253 | // placeholders. We don't know exactly when the update was 254 | // scheduled, but we can infer an approximate start time from the 255 | // expiration time. First, find the earliest uncommitted expiration 256 | // time in the tree, including work that is suspended. Then subtract 257 | // the offset used to compute an async update's expiration time. 258 | // This will cause high priority (interactive) work to expire 259 | // earlier than necessary, but we can account for this by adjusting 260 | // for the Just Noticeable Difference. 261 | const earliestExpirationTime = findEarliestOutstandingPriorityLevel( 262 | root, 263 | renderExpirationTime, 264 | ); 265 | const earliestExpirationTimeMs = expirationTimeToMs( 266 | earliestExpirationTime, 267 | ); 268 | startTimeMs = earliestExpirationTimeMs - LOW_PRIORITY_EXPIRATION; 269 | } 270 | absoluteTimeoutMs = startTimeMs + earliestTimeoutMs; 271 | } 272 | 273 | // Mark the earliest timeout in the suspended fiber's ancestor path. 274 | // After completing the root, we'll take the largest of all the 275 | // suspended fiber's timeouts and use it to compute a timeout for the 276 | // whole tree. 277 | renderDidSuspend(root, absoluteTimeoutMs, renderExpirationTime); 278 | 279 | workInProgress.effectTag |= ShouldCapture; 280 | workInProgress.expirationTime = renderExpirationTime; 281 | return; 282 | } 283 | // This boundary already captured during this render. Continue to the next 284 | // boundary. 285 | workInProgress = workInProgress.return; 286 | } while (workInProgress !== null); 287 | // No boundary was found. Fallthrough to error mode. 288 | value = new Error( 289 | 'An update was suspended, but no placeholder UI was provided.', 290 | ); 291 | } 292 | 293 | // We didn't find a boundary that could handle this type of exception. Start 294 | // over and traverse parent path again, this time treating the exception 295 | // as an error. 296 | renderDidError(); 297 | value = createCapturedValue(value, sourceFiber); 298 | let workInProgress = returnFiber; 299 | do { 300 | switch (workInProgress.tag) { 301 | case HostRoot: { 302 | const errorInfo = value; 303 | workInProgress.effectTag |= ShouldCapture; 304 | workInProgress.expirationTime = renderExpirationTime; 305 | const update = createRootErrorUpdate( 306 | workInProgress, 307 | errorInfo, 308 | renderExpirationTime, 309 | ); 310 | enqueueCapturedUpdate(workInProgress, update); 311 | return; 312 | } 313 | case ClassComponent: 314 | // Capture and retry 315 | const errorInfo = value; 316 | const ctor = workInProgress.type; 317 | const instance = workInProgress.stateNode; 318 | if ( 319 | (workInProgress.effectTag & DidCapture) === NoEffect && 320 | (typeof ctor.getDerivedStateFromError === 'function' || 321 | (instance !== null && 322 | typeof instance.componentDidCatch === 'function' && 323 | !isAlreadyFailedLegacyErrorBoundary(instance))) 324 | ) { 325 | workInProgress.effectTag |= ShouldCapture; 326 | workInProgress.expirationTime = renderExpirationTime; 327 | // Schedule the error boundary to re-render using updated state 328 | const update = createClassErrorUpdate( 329 | workInProgress, 330 | errorInfo, 331 | renderExpirationTime, 332 | ); 333 | enqueueCapturedUpdate(workInProgress, update); 334 | return; 335 | } 336 | break; 337 | default: 338 | break; 339 | } 340 | workInProgress = workInProgress.return; 341 | } while (workInProgress !== null); 342 | } 343 | 344 | function unwindWork( 345 | workInProgress, 346 | renderExpirationTime, 347 | ) { 348 | switch (workInProgress.tag) { 349 | case ClassComponent: { 350 | const Component = workInProgress.type; 351 | if (isLegacyContextProvider(Component)) { 352 | popLegacyContext(workInProgress); 353 | } 354 | const effectTag = workInProgress.effectTag; 355 | if (effectTag & ShouldCapture) { 356 | workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture; 357 | return workInProgress; 358 | } 359 | return null; 360 | } 361 | case HostRoot: { 362 | popHostContainer(workInProgress); 363 | popTopLevelLegacyContextObject(workInProgress); 364 | const effectTag = workInProgress.effectTag; 365 | invariant( 366 | (effectTag & DidCapture) === NoEffect, 367 | 'The root failed to unmount after an error. This is likely a bug in ' + 368 | 'React. Please file an issue.', 369 | ); 370 | workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture; 371 | return workInProgress; 372 | } 373 | case HostComponent: { 374 | popHostContext(workInProgress); 375 | return null; 376 | } 377 | case SuspenseComponent: { 378 | const effectTag = workInProgress.effectTag; 379 | if (effectTag & ShouldCapture) { 380 | workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture; 381 | // Captured a suspense effect. Set the boundary's `alreadyCaptured` 382 | // state to true so we know to render the fallback. 383 | const current = workInProgress.alternate; 384 | const currentState = 385 | current !== null ? current.memoizedState : null; 386 | let nextState = workInProgress.memoizedState; 387 | if (nextState === null) { 388 | // No existing state. Create a new object. 389 | nextState = { 390 | alreadyCaptured: true, 391 | didTimeout: false, 392 | timedOutAt: NoWork, 393 | }; 394 | } else if (currentState === nextState) { 395 | // There is an existing state but it's the same as the current tree's. 396 | // Clone the object. 397 | nextState = { 398 | alreadyCaptured: true, 399 | didTimeout: nextState.didTimeout, 400 | timedOutAt: nextState.timedOutAt, 401 | }; 402 | } else { 403 | // Already have a clone, so it's safe to mutate. 404 | nextState.alreadyCaptured = true; 405 | } 406 | workInProgress.memoizedState = nextState; 407 | // Re-render the boundary. 408 | return workInProgress; 409 | } 410 | return null; 411 | } 412 | case HostPortal: 413 | popHostContainer(workInProgress); 414 | return null; 415 | case ContextProvider: 416 | popProvider(workInProgress); 417 | return null; 418 | default: 419 | return null; 420 | } 421 | } 422 | 423 | function unwindInterruptedWork(interruptedWork) { 424 | switch (interruptedWork.tag) { 425 | case ClassComponent: { 426 | const childContextTypes = interruptedWork.type.childContextTypes; 427 | if (childContextTypes !== null && childContextTypes !== undefined) { 428 | popLegacyContext(interruptedWork); 429 | } 430 | break; 431 | } 432 | case HostRoot: { 433 | popHostContainer(interruptedWork); 434 | popTopLevelLegacyContextObject(interruptedWork); 435 | break; 436 | } 437 | case HostComponent: { 438 | popHostContext(interruptedWork); 439 | break; 440 | } 441 | case HostPortal: 442 | popHostContainer(interruptedWork); 443 | break; 444 | case ContextProvider: 445 | popProvider(interruptedWork); 446 | break; 447 | default: 448 | break; 449 | } 450 | } 451 | 452 | export { 453 | throwException, 454 | unwindWork, 455 | unwindInterruptedWork, 456 | createRootErrorUpdate, 457 | createClassErrorUpdate, 458 | }; -------------------------------------------------------------------------------- /react/ReactFiberCompleteWork.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @flow 8 | */ 9 | 10 | 11 | 12 | import { 13 | IndeterminateComponent, 14 | FunctionComponent, 15 | ClassComponent, 16 | HostRoot, 17 | HostComponent, 18 | HostText, 19 | HostPortal, 20 | ContextProvider, 21 | ContextConsumer, 22 | ForwardRef, 23 | Fragment, 24 | Mode, 25 | Profiler, 26 | SuspenseComponent, 27 | MemoComponent, 28 | SimpleMemoComponent, 29 | LazyComponent, 30 | IncompleteClassComponent, 31 | } from 'shared/ReactWorkTags'; 32 | import { Placement, Ref, Update } from 'shared/ReactSideEffectTags'; 33 | import invariant from 'shared/invariant'; 34 | 35 | import { 36 | createInstance, 37 | createTextInstance, 38 | createHiddenTextInstance, 39 | appendInitialChild, 40 | finalizeInitialChildren, 41 | prepareUpdate, 42 | supportsMutation, 43 | supportsPersistence, 44 | cloneInstance, 45 | cloneHiddenInstance, 46 | cloneUnhiddenInstance, 47 | createContainerChildSet, 48 | appendChildToContainerChildSet, 49 | finalizeContainerChildren, 50 | } from './ReactFiberHostConfig'; 51 | import { 52 | getRootHostContainer, 53 | popHostContext, 54 | getHostContext, 55 | popHostContainer, 56 | } from './ReactFiberHostContext'; 57 | import { 58 | isContextProvider as isLegacyContextProvider, 59 | popContext as popLegacyContext, 60 | popTopLevelContextObject as popTopLevelLegacyContextObject, 61 | } from './ReactFiberContext'; 62 | import { popProvider } from './ReactFiberNewContext'; 63 | import { 64 | prepareToHydrateHostInstance, 65 | prepareToHydrateHostTextInstance, 66 | popHydrationState, 67 | } from './ReactFiberHydrationContext'; 68 | 69 | function markUpdate(workInProgress) { 70 | // Tag the fiber with an update effect. This turns a Placement into 71 | // a PlacementAndUpdate. 72 | workInProgress.effectTag |= Update; 73 | } 74 | 75 | function markRef(workInProgress) { 76 | workInProgress.effectTag |= Ref; 77 | } 78 | 79 | let appendAllChildren; 80 | let updateHostContainer; 81 | let updateHostComponent; 82 | let updateHostText; 83 | if (supportsMutation) { 84 | // Mutation mode 85 | 86 | appendAllChildren = function ( 87 | parent, 88 | workInProgress, 89 | needsVisibilityToggle, 90 | isHidden, 91 | ) { 92 | // We only have the top Fiber that was created but we need recurse down its 93 | // children to find all the terminal nodes. 94 | let node = workInProgress.child; 95 | while (node !== null) { 96 | if (node.tag === HostComponent || node.tag === HostText) { 97 | appendInitialChild(parent, node.stateNode); 98 | } else if (node.tag === HostPortal) { 99 | // If we have a portal child, then we don't want to traverse 100 | // down its children. Instead, we'll get insertions from each child in 101 | // the portal directly. 102 | } else if (node.child !== null) { 103 | node.child.return = node; 104 | node = node.child; 105 | continue; 106 | } 107 | if (node === workInProgress) { 108 | return; 109 | } 110 | while (node.sibling === null) { 111 | if (node.return === null || node.return === workInProgress) { 112 | return; 113 | } 114 | node = node.return; 115 | } 116 | node.sibling.return = node.return; 117 | node = node.sibling; 118 | } 119 | }; 120 | 121 | updateHostContainer = function (workInProgress) { 122 | // Noop 123 | }; 124 | updateHostComponent = function ( 125 | current, 126 | workInProgress, 127 | type, 128 | newProps, 129 | rootContainerInstance, 130 | ) { 131 | // If we have an alternate, that means this is an update and we need to 132 | // schedule a side-effect to do the updates. 133 | const oldProps = current.memoizedProps; 134 | if (oldProps === newProps) { 135 | // In mutation mode, this is sufficient for a bailout because 136 | // we won't touch this node even if children changed. 137 | return; 138 | } 139 | 140 | // If we get updated because one of our children updated, we don't 141 | // have newProps so we'll have to reuse them. 142 | // TODO: Split the update API as separate for the props vs. children. 143 | // Even better would be if children weren't special cased at all tho. 144 | const instance = workInProgress.stateNode; 145 | const currentHostContext = getHostContext(); 146 | // TODO: Experiencing an error where oldProps is null. Suggests a host 147 | // component is hitting the resume path. Figure out why. Possibly 148 | // related to `hidden`. 149 | const updatePayload = prepareUpdate( 150 | instance, 151 | type, 152 | oldProps, 153 | newProps, 154 | rootContainerInstance, 155 | currentHostContext, 156 | ); 157 | // TODO this specific to this type of component. 158 | workInProgress.updateQueue = (updatePayload); 159 | // If the update payload indicates that there is a change or if there 160 | // is a new ref we mark this as an update. All the work is done in commitWork. 161 | if (updatePayload) { 162 | markUpdate(workInProgress); 163 | } 164 | }; 165 | updateHostText = function ( 166 | current, 167 | workInProgress, 168 | oldText, 169 | newText, 170 | ) { 171 | // If the text differs, mark it as an update. All the work in done in commitWork. 172 | if (oldText !== newText) { 173 | markUpdate(workInProgress); 174 | } 175 | }; 176 | } else if (supportsPersistence) { 177 | // Persistent host tree mode 178 | 179 | appendAllChildren = function ( 180 | parent, 181 | workInProgress, 182 | needsVisibilityToggle, 183 | isHidden, 184 | ) { 185 | // We only have the top Fiber that was created but we need recurse down its 186 | // children to find all the terminal nodes. 187 | let node = workInProgress.child; 188 | while (node !== null) { 189 | // eslint-disable-next-line no-labels 190 | branches: if (node.tag === HostComponent) { 191 | let instance = node.stateNode; 192 | if (needsVisibilityToggle) { 193 | const props = node.memoizedProps; 194 | const type = node.type; 195 | if (isHidden) { 196 | // This child is inside a timed out tree. Hide it. 197 | instance = cloneHiddenInstance(instance, type, props, node); 198 | } else { 199 | // This child was previously inside a timed out tree. If it was not 200 | // updated during this render, it may need to be unhidden. Clone 201 | // again to be sure. 202 | instance = cloneUnhiddenInstance(instance, type, props, node); 203 | } 204 | node.stateNode = instance; 205 | } 206 | appendInitialChild(parent, instance); 207 | } else if (node.tag === HostText) { 208 | let instance = node.stateNode; 209 | if (needsVisibilityToggle) { 210 | const text = node.memoizedProps; 211 | const rootContainerInstance = getRootHostContainer(); 212 | const currentHostContext = getHostContext(); 213 | if (isHidden) { 214 | instance = createHiddenTextInstance( 215 | text, 216 | rootContainerInstance, 217 | currentHostContext, 218 | workInProgress, 219 | ); 220 | } else { 221 | instance = createTextInstance( 222 | text, 223 | rootContainerInstance, 224 | currentHostContext, 225 | workInProgress, 226 | ); 227 | } 228 | node.stateNode = instance; 229 | } 230 | appendInitialChild(parent, instance); 231 | } else if (node.tag === HostPortal) { 232 | // If we have a portal child, then we don't want to traverse 233 | // down its children. Instead, we'll get insertions from each child in 234 | // the portal directly. 235 | } else if (node.tag === SuspenseComponent) { 236 | const current = node.alternate; 237 | if (current !== null) { 238 | const oldState = current.memoizedState; 239 | const newState = node.memoizedState; 240 | const oldIsHidden = oldState !== null && oldState.didTimeout; 241 | const newIsHidden = newState !== null && newState.didTimeout; 242 | if (oldIsHidden !== newIsHidden) { 243 | // The placeholder either just timed out or switched back to the normal 244 | // children after having previously timed out. Toggle the visibility of 245 | // the direct host children. 246 | const primaryChildParent = newIsHidden ? node.child : node; 247 | if (primaryChildParent !== null) { 248 | appendAllChildren(parent, primaryChildParent, true, newIsHidden); 249 | } 250 | // eslint-disable-next-line no-labels 251 | break branches; 252 | } 253 | } 254 | if (node.child !== null) { 255 | // Continue traversing like normal 256 | node.child.return = node; 257 | node = node.child; 258 | continue; 259 | } 260 | } else if (node.child !== null) { 261 | node.child.return = node; 262 | node = node.child; 263 | continue; 264 | } 265 | // $FlowFixMe This is correct but Flow is confused by the labeled break. 266 | node = (node); 267 | if (node === workInProgress) { 268 | return; 269 | } 270 | while (node.sibling === null) { 271 | if (node.return === null || node.return === workInProgress) { 272 | return; 273 | } 274 | node = node.return; 275 | } 276 | node.sibling.return = node.return; 277 | node = node.sibling; 278 | } 279 | }; 280 | 281 | // An unfortunate fork of appendAllChildren because we have two different parent types. 282 | const appendAllChildrenToContainer = function ( 283 | containerChildSet, 284 | workInProgress, 285 | needsVisibilityToggle, 286 | isHidden, 287 | ) { 288 | // We only have the top Fiber that was created but we need recurse down its 289 | // children to find all the terminal nodes. 290 | let node = workInProgress.child; 291 | while (node !== null) { 292 | // eslint-disable-next-line no-labels 293 | branches: if (node.tag === HostComponent) { 294 | let instance = node.stateNode; 295 | if (needsVisibilityToggle) { 296 | const props = node.memoizedProps; 297 | const type = node.type; 298 | if (isHidden) { 299 | // This child is inside a timed out tree. Hide it. 300 | instance = cloneHiddenInstance(instance, type, props, node); 301 | } else { 302 | // This child was previously inside a timed out tree. If it was not 303 | // updated during this render, it may need to be unhidden. Clone 304 | // again to be sure. 305 | instance = cloneUnhiddenInstance(instance, type, props, node); 306 | } 307 | node.stateNode = instance; 308 | } 309 | appendChildToContainerChildSet(containerChildSet, instance); 310 | } else if (node.tag === HostText) { 311 | let instance = node.stateNode; 312 | if (needsVisibilityToggle) { 313 | const text = node.memoizedProps; 314 | const rootContainerInstance = getRootHostContainer(); 315 | const currentHostContext = getHostContext(); 316 | if (isHidden) { 317 | instance = createHiddenTextInstance( 318 | text, 319 | rootContainerInstance, 320 | currentHostContext, 321 | workInProgress, 322 | ); 323 | } else { 324 | instance = createTextInstance( 325 | text, 326 | rootContainerInstance, 327 | currentHostContext, 328 | workInProgress, 329 | ); 330 | } 331 | node.stateNode = instance; 332 | } 333 | appendChildToContainerChildSet(containerChildSet, instance); 334 | } else if (node.tag === HostPortal) { 335 | // If we have a portal child, then we don't want to traverse 336 | // down its children. Instead, we'll get insertions from each child in 337 | // the portal directly. 338 | } else if (node.tag === SuspenseComponent) { 339 | const current = node.alternate; 340 | if (current !== null) { 341 | const oldState = current.memoizedState; 342 | const newState = node.memoizedState; 343 | const oldIsHidden = oldState !== null && oldState.didTimeout; 344 | const newIsHidden = newState !== null && newState.didTimeout; 345 | if (oldIsHidden !== newIsHidden) { 346 | // The placeholder either just timed out or switched back to the normal 347 | // children after having previously timed out. Toggle the visibility of 348 | // the direct host children. 349 | const primaryChildParent = newIsHidden ? node.child : node; 350 | if (primaryChildParent !== null) { 351 | appendAllChildrenToContainer( 352 | containerChildSet, 353 | primaryChildParent, 354 | true, 355 | newIsHidden, 356 | ); 357 | } 358 | // eslint-disable-next-line no-labels 359 | break branches; 360 | } 361 | } 362 | if (node.child !== null) { 363 | // Continue traversing like normal 364 | node.child.return = node; 365 | node = node.child; 366 | continue; 367 | } 368 | } else if (node.child !== null) { 369 | node.child.return = node; 370 | node = node.child; 371 | continue; 372 | } 373 | // $FlowFixMe This is correct but Flow is confused by the labeled break. 374 | node = (node); 375 | if (node === workInProgress) { 376 | return; 377 | } 378 | while (node.sibling === null) { 379 | if (node.return === null || node.return === workInProgress) { 380 | return; 381 | } 382 | node = node.return; 383 | } 384 | node.sibling.return = node.return; 385 | node = node.sibling; 386 | } 387 | }; 388 | updateHostContainer = function (workInProgress) { 389 | const portalOrRoot = workInProgress.stateNode; 390 | const childrenUnchanged = workInProgress.firstEffect === null; 391 | if (childrenUnchanged) { 392 | // No changes, just reuse the existing instance. 393 | } else { 394 | const container = portalOrRoot.containerInfo; 395 | let newChildSet = createContainerChildSet(container); 396 | // If children might have changed, we have to add them all to the set. 397 | appendAllChildrenToContainer(newChildSet, workInProgress, false, false); 398 | portalOrRoot.pendingChildren = newChildSet; 399 | // Schedule an update on the container to swap out the container. 400 | markUpdate(workInProgress); 401 | finalizeContainerChildren(container, newChildSet); 402 | } 403 | }; 404 | updateHostComponent = function ( 405 | current, 406 | workInProgress, 407 | type, 408 | newProps, 409 | rootContainerInstance, 410 | ) { 411 | const currentInstance = current.stateNode; 412 | const oldProps = current.memoizedProps; 413 | // If there are no effects associated with this node, then none of our children had any updates. 414 | // This guarantees that we can reuse all of them. 415 | const childrenUnchanged = workInProgress.firstEffect === null; 416 | if (childrenUnchanged && oldProps === newProps) { 417 | // No changes, just reuse the existing instance. 418 | // Note that this might release a previous clone. 419 | workInProgress.stateNode = currentInstance; 420 | return; 421 | } 422 | const recyclableInstance = workInProgress.stateNode; 423 | const currentHostContext = getHostContext(); 424 | let updatePayload = null; 425 | if (oldProps !== newProps) { 426 | updatePayload = prepareUpdate( 427 | recyclableInstance, 428 | type, 429 | oldProps, 430 | newProps, 431 | rootContainerInstance, 432 | currentHostContext, 433 | ); 434 | } 435 | if (childrenUnchanged && updatePayload === null) { 436 | // No changes, just reuse the existing instance. 437 | // Note that this might release a previous clone. 438 | workInProgress.stateNode = currentInstance; 439 | return; 440 | } 441 | let newInstance = cloneInstance( 442 | currentInstance, 443 | updatePayload, 444 | type, 445 | oldProps, 446 | newProps, 447 | workInProgress, 448 | childrenUnchanged, 449 | recyclableInstance, 450 | ); 451 | if ( 452 | finalizeInitialChildren( 453 | newInstance, 454 | type, 455 | newProps, 456 | rootContainerInstance, 457 | currentHostContext, 458 | ) 459 | ) { 460 | markUpdate(workInProgress); 461 | } 462 | workInProgress.stateNode = newInstance; 463 | if (childrenUnchanged) { 464 | // If there are no other effects in this tree, we need to flag this node as having one. 465 | // Even though we're not going to use it for anything. 466 | // Otherwise parents won't know that there are new children to propagate upwards. 467 | markUpdate(workInProgress); 468 | } else { 469 | // If children might have changed, we have to add them all to the set. 470 | appendAllChildren(newInstance, workInProgress, false, false); 471 | } 472 | }; 473 | updateHostText = function ( 474 | current, 475 | workInProgress, 476 | oldText, 477 | newText, 478 | ) { 479 | if (oldText !== newText) { 480 | // If the text content differs, we'll create a new text instance for it. 481 | const rootContainerInstance = getRootHostContainer(); 482 | const currentHostContext = getHostContext(); 483 | workInProgress.stateNode = createTextInstance( 484 | newText, 485 | rootContainerInstance, 486 | currentHostContext, 487 | workInProgress, 488 | ); 489 | // We'll have to mark it as having an effect, even though we won't use the effect for anything. 490 | // This lets the parents know that at least one of their children has changed. 491 | markUpdate(workInProgress); 492 | } 493 | }; 494 | } else { 495 | // No host operations 496 | updateHostContainer = function (workInProgress) { 497 | // Noop 498 | }; 499 | updateHostComponent = function ( 500 | current, 501 | workInProgress, 502 | type, 503 | newProps, 504 | rootContainerInstance, 505 | ) { 506 | // Noop 507 | }; 508 | updateHostText = function ( 509 | current, 510 | workInProgress, 511 | oldText, 512 | newText, 513 | ) { 514 | // Noop 515 | }; 516 | } 517 | 518 | function completeWork( 519 | current, 520 | workInProgress, 521 | renderExpirationTime, 522 | ) { 523 | const newProps = workInProgress.pendingProps; 524 | 525 | switch (workInProgress.tag) { 526 | case IndeterminateComponent: 527 | break; 528 | case LazyComponent: 529 | break; 530 | case SimpleMemoComponent: 531 | case FunctionComponent: 532 | break; 533 | case ClassComponent: { 534 | const Component = workInProgress.type; 535 | if (isLegacyContextProvider(Component)) { 536 | popLegacyContext(workInProgress); 537 | } 538 | break; 539 | } 540 | case HostRoot: { 541 | popHostContainer(workInProgress); 542 | popTopLevelLegacyContextObject(workInProgress); 543 | const fiberRoot = (workInProgress.stateNode); 544 | if (fiberRoot.pendingContext) { 545 | fiberRoot.context = fiberRoot.pendingContext; 546 | fiberRoot.pendingContext = null; 547 | } 548 | if (current === null || current.child === null) { 549 | // If we hydrated, pop so that we can delete any remaining children 550 | // that weren't hydrated. 551 | popHydrationState(workInProgress); 552 | // This resets the hacky state to fix isMounted before committing. 553 | // TODO: Delete this when we delete isMounted and findDOMNode. 554 | workInProgress.effectTag &= ~Placement; 555 | } 556 | updateHostContainer(workInProgress); 557 | break; 558 | } 559 | case HostComponent: { 560 | popHostContext(workInProgress); 561 | const rootContainerInstance = getRootHostContainer(); 562 | const type = workInProgress.type; 563 | if (current !== null && workInProgress.stateNode != null) { 564 | updateHostComponent( 565 | current, 566 | workInProgress, 567 | type, 568 | newProps, 569 | rootContainerInstance, 570 | ); 571 | 572 | if (current.ref !== workInProgress.ref) { 573 | markRef(workInProgress); 574 | } 575 | } else { 576 | if (!newProps) { 577 | invariant( 578 | workInProgress.stateNode !== null, 579 | 'We must have new props for new mounts. This error is likely ' + 580 | 'caused by a bug in React. Please file an issue.', 581 | ); 582 | // This can happen when we abort work. 583 | break; 584 | } 585 | 586 | const currentHostContext = getHostContext(); 587 | // TODO: Move createInstance to beginWork and keep it on a context 588 | // "stack" as the parent. Then append children as we go in beginWork 589 | // or completeWork depending on we want to add then top->down or 590 | // bottom->up. Top->down is faster in IE11. 591 | let wasHydrated = popHydrationState(workInProgress); 592 | if (wasHydrated) { 593 | // TODO: Move this and createInstance step into the beginPhase 594 | // to consolidate. 595 | if ( 596 | prepareToHydrateHostInstance( 597 | workInProgress, 598 | rootContainerInstance, 599 | currentHostContext, 600 | ) 601 | ) { 602 | // If changes to the hydrated node needs to be applied at the 603 | // commit-phase we mark this as such. 604 | markUpdate(workInProgress); 605 | } 606 | } else { 607 | let instance = createInstance( 608 | type, 609 | newProps, 610 | rootContainerInstance, 611 | currentHostContext, 612 | workInProgress, 613 | ); 614 | 615 | appendAllChildren(instance, workInProgress, false, false); 616 | 617 | // Certain renderers require commit-time effects for initial mount. 618 | // (eg DOM renderer supports auto-focus for certain elements). 619 | // Make sure such renderers get scheduled for later work. 620 | if ( 621 | finalizeInitialChildren( 622 | instance, 623 | type, 624 | newProps, 625 | rootContainerInstance, 626 | currentHostContext, 627 | ) 628 | ) { 629 | markUpdate(workInProgress); 630 | } 631 | workInProgress.stateNode = instance; 632 | } 633 | 634 | if (workInProgress.ref !== null) { 635 | // If there is a ref on a host node we need to schedule a callback 636 | markRef(workInProgress); 637 | } 638 | } 639 | break; 640 | } 641 | case HostText: { 642 | let newText = newProps; 643 | if (current && workInProgress.stateNode != null) { 644 | const oldText = current.memoizedProps; 645 | // If we have an alternate, that means this is an update and we need 646 | // to schedule a side-effect to do the updates. 647 | updateHostText(current, workInProgress, oldText, newText); 648 | } else { 649 | if (typeof newText !== 'string') { 650 | invariant( 651 | workInProgress.stateNode !== null, 652 | 'We must have new props for new mounts. This error is likely ' + 653 | 'caused by a bug in React. Please file an issue.', 654 | ); 655 | // This can happen when we abort work. 656 | } 657 | const rootContainerInstance = getRootHostContainer(); 658 | const currentHostContext = getHostContext(); 659 | let wasHydrated = popHydrationState(workInProgress); 660 | if (wasHydrated) { 661 | if (prepareToHydrateHostTextInstance(workInProgress)) { 662 | markUpdate(workInProgress); 663 | } 664 | } else { 665 | workInProgress.stateNode = createTextInstance( 666 | newText, 667 | rootContainerInstance, 668 | currentHostContext, 669 | workInProgress, 670 | ); 671 | } 672 | } 673 | break; 674 | } 675 | case ForwardRef: 676 | break; 677 | case SuspenseComponent: { 678 | const nextState = workInProgress.memoizedState; 679 | const prevState = current !== null ? current.memoizedState : null; 680 | const nextDidTimeout = nextState !== null && nextState.didTimeout; 681 | const prevDidTimeout = prevState !== null && prevState.didTimeout; 682 | if (nextDidTimeout !== prevDidTimeout) { 683 | // If this render commits, and it switches between the normal state 684 | // and the timed-out state, schedule an effect. 685 | workInProgress.effectTag |= Update; 686 | } 687 | break; 688 | } 689 | case Fragment: 690 | break; 691 | case Mode: 692 | break; 693 | case Profiler: 694 | break; 695 | case HostPortal: 696 | popHostContainer(workInProgress); 697 | updateHostContainer(workInProgress); 698 | break; 699 | case ContextProvider: 700 | // Pop provider fiber 701 | popProvider(workInProgress); 702 | break; 703 | case ContextConsumer: 704 | break; 705 | case MemoComponent: 706 | break; 707 | case IncompleteClassComponent: { 708 | // Same as class component case. I put it down here so that the tags are 709 | // sequential to ensure this switch is compiled to a jump table. 710 | const Component = workInProgress.type; 711 | if (isLegacyContextProvider(Component)) { 712 | popLegacyContext(workInProgress); 713 | } 714 | break; 715 | } 716 | default: 717 | invariant( 718 | false, 719 | 'Unknown unit of work tag. This error is likely caused by a bug in ' + 720 | 'React. Please file an issue.', 721 | ); 722 | } 723 | 724 | return null; 725 | } 726 | 727 | export { completeWork }; -------------------------------------------------------------------------------- /react/ReactFiberCommitWork.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | enableSchedulerTracing, 4 | enableProfilerTimer, 5 | } from 'shared/ReactFeatureFlags'; 6 | import { 7 | FunctionComponent, 8 | ForwardRef, 9 | ClassComponent, 10 | HostRoot, 11 | HostComponent, 12 | HostText, 13 | HostPortal, 14 | Profiler, 15 | SuspenseComponent, 16 | IncompleteClassComponent, 17 | MemoComponent, 18 | SimpleMemoComponent, 19 | } from 'shared/ReactWorkTags'; 20 | import { 21 | invokeGuardedCallback, 22 | hasCaughtError, 23 | clearCaughtError, 24 | } from 'shared/ReactErrorUtils'; 25 | import { 26 | ContentReset, 27 | Placement, 28 | Snapshot, 29 | Update, 30 | Callback, 31 | } from 'shared/ReactSideEffectTags'; 32 | import getComponentName from 'shared/getComponentName'; 33 | import invariant from 'shared/invariant'; 34 | import warningWithoutStack from 'shared/warningWithoutStack'; 35 | 36 | import { NoWork, Sync } from './ReactFiberExpirationTime'; 37 | import { onCommitUnmount } from './ReactFiberDevToolsHook'; 38 | import { startPhaseTimer, stopPhaseTimer } from './ReactDebugFiberPerf'; 39 | import { getStackByFiberInDevAndProd } from './ReactCurrentFiber'; 40 | import { logCapturedError } from './ReactFiberErrorLogger'; 41 | import { getCommitTime } from './ReactProfilerTimer'; 42 | import { commitUpdateQueue } from './ReactUpdateQueue'; 43 | import { 44 | getPublicInstance, 45 | supportsMutation, 46 | supportsPersistence, 47 | commitMount, 48 | commitUpdate, 49 | resetTextContent, 50 | commitTextUpdate, 51 | appendChild, 52 | appendChildToContainer, 53 | insertBefore, 54 | insertInContainerBefore, 55 | removeChild, 56 | removeChildFromContainer, 57 | replaceContainerChildren, 58 | createContainerChildSet, 59 | hideInstance, 60 | hideTextInstance, 61 | unhideInstance, 62 | unhideTextInstance, 63 | } from './ReactFiberHostConfig'; 64 | import { 65 | captureCommitPhaseError, 66 | flushPassiveEffects, 67 | requestCurrentTime, 68 | scheduleWork, 69 | } from './ReactFiberScheduler'; 70 | import { 71 | NoEffect as NoHookEffect, 72 | UnmountSnapshot, 73 | UnmountMutation, 74 | MountMutation, 75 | UnmountLayout, 76 | MountLayout, 77 | UnmountPassive, 78 | MountPassive, 79 | } from './ReactHookEffectTags'; 80 | 81 | 82 | export function logError(boundary, errorInfo) { 83 | const source = errorInfo.source; 84 | let stack = errorInfo.stack; 85 | if (stack === null && source !== null) { 86 | stack = getStackByFiberInDevAndProd(source); 87 | } 88 | 89 | const capturedError = { 90 | componentName: source !== null ? getComponentName(source.type) : null, 91 | componentStack: stack !== null ? stack : '', 92 | error: errorInfo.value, 93 | errorBoundary: null, 94 | errorBoundaryName: null, 95 | errorBoundaryFound: false, 96 | willRetry: false, 97 | }; 98 | 99 | if (boundary !== null && boundary.tag === ClassComponent) { 100 | capturedError.errorBoundary = boundary.stateNode; 101 | capturedError.errorBoundaryName = getComponentName(boundary.type); 102 | capturedError.errorBoundaryFound = true; 103 | capturedError.willRetry = true; 104 | } 105 | 106 | try { 107 | logCapturedError(capturedError); 108 | } catch (e) { 109 | // This method must not throw, or React internal state will get messed up. 110 | // If console.error is overridden, or logCapturedError() shows a dialog that throws, 111 | // we want to report this error outside of the normal stack as a last resort. 112 | // https://github.com/facebook/react/issues/13188 113 | setTimeout(() => { 114 | throw e; 115 | }); 116 | } 117 | } 118 | 119 | const callComponentWillUnmountWithTimer = function (current, instance) { 120 | startPhaseTimer(current, 'componentWillUnmount'); 121 | instance.props = current.memoizedProps; 122 | instance.state = current.memoizedState; 123 | instance.componentWillUnmount(); 124 | stopPhaseTimer(); 125 | }; 126 | 127 | // Capture errors so they don't interrupt unmounting. 128 | function safelyCallComponentWillUnmount(current, instance) { 129 | 130 | try { 131 | callComponentWillUnmountWithTimer(current, instance); 132 | } catch (unmountError) { 133 | captureCommitPhaseError(current, unmountError); 134 | } 135 | 136 | } 137 | 138 | function safelyDetachRef(current) { 139 | const ref = current.ref; 140 | if (ref !== null) { 141 | if (typeof ref === 'function') { 142 | 143 | try { 144 | ref(null); 145 | } catch (refError) { 146 | captureCommitPhaseError(current, refError); 147 | } 148 | 149 | } else { 150 | ref.current = null; 151 | } 152 | } 153 | } 154 | 155 | function safelyCallDestroy(current, destroy) { 156 | 157 | try { 158 | destroy(); 159 | } catch (error) { 160 | captureCommitPhaseError(current, error); 161 | } 162 | 163 | } 164 | 165 | function commitBeforeMutationLifeCycles( 166 | current, 167 | finishedWork, 168 | ) { 169 | switch (finishedWork.tag) { 170 | case FunctionComponent: 171 | case ForwardRef: 172 | case SimpleMemoComponent: { 173 | commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork); 174 | return; 175 | } 176 | case ClassComponent: { 177 | if (finishedWork.effectTag & Snapshot) { 178 | if (current !== null) { 179 | const prevProps = current.memoizedProps; 180 | const prevState = current.memoizedState; 181 | startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate'); 182 | const instance = finishedWork.stateNode; 183 | instance.props = finishedWork.memoizedProps; 184 | instance.state = finishedWork.memoizedState; 185 | const snapshot = instance.getSnapshotBeforeUpdate( 186 | prevProps, 187 | prevState, 188 | ); 189 | 190 | instance.__reactInternalSnapshotBeforeUpdate = snapshot; 191 | stopPhaseTimer(); 192 | } 193 | } 194 | return; 195 | } 196 | case HostRoot: 197 | case HostComponent: 198 | case HostText: 199 | case HostPortal: 200 | case IncompleteClassComponent: 201 | // Nothing to do for these component types 202 | return; 203 | default: { 204 | invariant( 205 | false, 206 | 'This unit of work tag should not have side-effects. This error is ' + 207 | 'likely caused by a bug in React. Please file an issue.', 208 | ); 209 | } 210 | } 211 | } 212 | 213 | function commitHookEffectList( 214 | unmountTag, 215 | mountTag, 216 | finishedWork, 217 | ) { 218 | const updateQueue = (finishedWork.updateQueue); 219 | let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; 220 | if (lastEffect !== null) { 221 | const firstEffect = lastEffect.next; 222 | let effect = firstEffect; 223 | do { 224 | if ((effect.tag & unmountTag) !== NoHookEffect) { 225 | // Unmount 226 | const destroy = effect.destroy; 227 | effect.destroy = null; 228 | if (destroy !== null) { 229 | destroy(); 230 | } 231 | } 232 | if ((effect.tag & mountTag) !== NoHookEffect) { 233 | // Mount 234 | const create = effect.create; 235 | const destroy = create(); 236 | effect.destroy = typeof destroy === 'function' ? destroy : null; 237 | } 238 | effect = effect.next; 239 | } while (effect !== firstEffect); 240 | } 241 | } 242 | 243 | export function commitPassiveHookEffects(finishedWork) { 244 | commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork); 245 | commitHookEffectList(NoHookEffect, MountPassive, finishedWork); 246 | } 247 | 248 | function commitLifeCycles( 249 | finishedRoot, 250 | current, 251 | finishedWork, 252 | committedExpirationTime, 253 | ) { 254 | switch (finishedWork.tag) { 255 | case FunctionComponent: 256 | case ForwardRef: 257 | case SimpleMemoComponent: { 258 | commitHookEffectList(UnmountLayout, MountLayout, finishedWork); 259 | const newUpdateQueue = (finishedWork.updateQueue); 260 | if (newUpdateQueue !== null) { 261 | const callbackList = newUpdateQueue.callbackList; 262 | if (callbackList !== null) { 263 | newUpdateQueue.callbackList = null; 264 | for (let i = 0; i < callbackList.length; i++) { 265 | const update = callbackList[i]; 266 | // Assume this is non-null, since otherwise it would not be part 267 | // of the callback list. 268 | 269 | update.callback = null; 270 | callback(); 271 | } 272 | } 273 | } 274 | break; 275 | } 276 | case ClassComponent: { 277 | const instance = finishedWork.stateNode; 278 | if (finishedWork.effectTag & Update) { 279 | if (current === null) { 280 | startPhaseTimer(finishedWork, 'componentDidMount'); 281 | instance.props = finishedWork.memoizedProps; 282 | instance.state = finishedWork.memoizedState; 283 | instance.componentDidMount(); 284 | stopPhaseTimer(); 285 | } else { 286 | const prevProps = current.memoizedProps; 287 | const prevState = current.memoizedState; 288 | startPhaseTimer(finishedWork, 'componentDidUpdate'); 289 | instance.props = finishedWork.memoizedProps; 290 | instance.state = finishedWork.memoizedState; 291 | instance.componentDidUpdate( 292 | prevProps, 293 | prevState, 294 | instance.__reactInternalSnapshotBeforeUpdate, 295 | ); 296 | stopPhaseTimer(); 297 | } 298 | } 299 | const updateQueue = finishedWork.updateQueue; 300 | if (updateQueue !== null) { 301 | instance.props = finishedWork.memoizedProps; 302 | instance.state = finishedWork.memoizedState; 303 | commitUpdateQueue( 304 | finishedWork, 305 | updateQueue, 306 | instance, 307 | committedExpirationTime, 308 | ); 309 | } 310 | return; 311 | } 312 | case HostRoot: { 313 | const updateQueue = finishedWork.updateQueue; 314 | if (updateQueue !== null) { 315 | let instance = null; 316 | if (finishedWork.child !== null) { 317 | switch (finishedWork.child.tag) { 318 | case HostComponent: 319 | instance = getPublicInstance(finishedWork.child.stateNode); 320 | break; 321 | case ClassComponent: 322 | instance = finishedWork.child.stateNode; 323 | break; 324 | } 325 | } 326 | commitUpdateQueue( 327 | finishedWork, 328 | updateQueue, 329 | instance, 330 | committedExpirationTime, 331 | ); 332 | } 333 | return; 334 | } 335 | case HostComponent: { 336 | const instance = finishedWork.stateNode; 337 | 338 | // Renderers may schedule work to be done after host components are mounted 339 | // (eg DOM renderer may schedule auto-focus for inputs and form controls). 340 | // These effects should only be committed when components are first mounted, 341 | // aka when there is no current/alternate. 342 | if (current === null && finishedWork.effectTag & Update) { 343 | const type = finishedWork.type; 344 | const props = finishedWork.memoizedProps; 345 | commitMount(instance, type, props, finishedWork); 346 | } 347 | 348 | return; 349 | } 350 | case HostText: { 351 | // We have no life-cycles associated with text. 352 | return; 353 | } 354 | case HostPortal: { 355 | // We have no life-cycles associated with portals. 356 | return; 357 | } 358 | case Profiler: { 359 | if (enableProfilerTimer) { 360 | const onRender = finishedWork.memoizedProps.onRender; 361 | 362 | if (enableSchedulerTracing) { 363 | onRender( 364 | finishedWork.memoizedProps.id, 365 | current === null ? 'mount' : 'update', 366 | finishedWork.actualDuration, 367 | finishedWork.treeBaseDuration, 368 | finishedWork.actualStartTime, 369 | getCommitTime(), 370 | finishedRoot.memoizedInteractions, 371 | ); 372 | } else { 373 | onRender( 374 | finishedWork.memoizedProps.id, 375 | current === null ? 'mount' : 'update', 376 | finishedWork.actualDuration, 377 | finishedWork.treeBaseDuration, 378 | finishedWork.actualStartTime, 379 | getCommitTime(), 380 | ); 381 | } 382 | } 383 | return; 384 | } 385 | case SuspenseComponent: { 386 | if (finishedWork.effectTag & Callback) { 387 | // In non-strict mode, a suspense boundary times out by commiting 388 | // twice: first, by committing the children in an inconsistent state, 389 | // then hiding them and showing the fallback children in a subsequent 390 | // commit. 391 | const newState = { 392 | alreadyCaptured: true, 393 | didTimeout: false, 394 | timedOutAt: NoWork, 395 | }; 396 | finishedWork.memoizedState = newState; 397 | flushPassiveEffects(); 398 | scheduleWork(finishedWork, Sync); 399 | return; 400 | } 401 | let oldState = 402 | current !== null ? current.memoizedState : null; 403 | let newState = finishedWork.memoizedState; 404 | let oldDidTimeout = oldState !== null ? oldState.didTimeout : false; 405 | 406 | let newDidTimeout; 407 | let primaryChildParent = finishedWork; 408 | if (newState === null) { 409 | newDidTimeout = false; 410 | } else { 411 | newDidTimeout = newState.didTimeout; 412 | if (newDidTimeout) { 413 | primaryChildParent = finishedWork.child; 414 | newState.alreadyCaptured = false; 415 | if (newState.timedOutAt === NoWork) { 416 | // If the children had not already timed out, record the time. 417 | // This is used to compute the elapsed time during subsequent 418 | // attempts to render the children. 419 | newState.timedOutAt = requestCurrentTime(); 420 | } 421 | } 422 | } 423 | 424 | if (newDidTimeout !== oldDidTimeout && primaryChildParent !== null) { 425 | hideOrUnhideAllChildren(primaryChildParent, newDidTimeout); 426 | } 427 | return; 428 | } 429 | case IncompleteClassComponent: 430 | break; 431 | default: { 432 | invariant( 433 | false, 434 | 'This unit of work tag should not have side-effects. This error is ' + 435 | 'likely caused by a bug in React. Please file an issue.', 436 | ); 437 | } 438 | } 439 | } 440 | 441 | function hideOrUnhideAllChildren(finishedWork, isHidden) { 442 | if (supportsMutation) { 443 | // We only have the top Fiber that was inserted but we need recurse down its 444 | // children to find all the terminal nodes. 445 | let node = finishedWork; 446 | while (true) { 447 | if (node.tag === HostComponent) { 448 | const instance = node.stateNode; 449 | if (isHidden) { 450 | hideInstance(instance); 451 | } else { 452 | unhideInstance(node.stateNode, node.memoizedProps); 453 | } 454 | } else if (node.tag === HostText) { 455 | const instance = node.stateNode; 456 | if (isHidden) { 457 | hideTextInstance(instance); 458 | } else { 459 | unhideTextInstance(instance, node.memoizedProps); 460 | } 461 | } else if (node.child !== null) { 462 | node.child.return = node; 463 | node = node.child; 464 | continue; 465 | } 466 | if (node === finishedWork) { 467 | return; 468 | } 469 | while (node.sibling === null) { 470 | if (node.return === null || node.return === finishedWork) { 471 | return; 472 | } 473 | node = node.return; 474 | } 475 | node.sibling.return = node.return; 476 | node = node.sibling; 477 | } 478 | } 479 | } 480 | 481 | function commitAttachRef(finishedWork) { 482 | const ref = finishedWork.ref; 483 | if (ref !== null) { 484 | const instance = finishedWork.stateNode; 485 | let instanceToUse; 486 | switch (finishedWork.tag) { 487 | case HostComponent: 488 | instanceToUse = getPublicInstance(instance); 489 | break; 490 | default: 491 | instanceToUse = instance; 492 | } 493 | if (typeof ref === 'function') { 494 | ref(instanceToUse); 495 | } else { 496 | 497 | 498 | ref.current = instanceToUse; 499 | } 500 | } 501 | } 502 | 503 | function commitDetachRef(current) { 504 | const currentRef = current.ref; 505 | if (currentRef !== null) { 506 | if (typeof currentRef === 'function') { 507 | currentRef(null); 508 | } else { 509 | currentRef.current = null; 510 | } 511 | } 512 | } 513 | 514 | // User-originating errors (lifecycles and refs) should not interrupt 515 | // deletion, so don't let them throw. Host-originating errors should 516 | // interrupt deletion, so it's okay 517 | function commitUnmount(current) { 518 | onCommitUnmount(current); 519 | 520 | switch (current.tag) { 521 | case FunctionComponent: 522 | case ForwardRef: 523 | case MemoComponent: 524 | case SimpleMemoComponent: { 525 | const updateQueue = (current.updateQueue); 526 | if (updateQueue !== null) { 527 | const lastEffect = updateQueue.lastEffect; 528 | if (lastEffect !== null) { 529 | const firstEffect = lastEffect.next; 530 | let effect = firstEffect; 531 | do { 532 | const destroy = effect.destroy; 533 | if (destroy !== null) { 534 | safelyCallDestroy(current, destroy); 535 | } 536 | effect = effect.next; 537 | } while (effect !== firstEffect); 538 | } 539 | } 540 | break; 541 | } 542 | case ClassComponent: { 543 | safelyDetachRef(current); 544 | const instance = current.stateNode; 545 | if (typeof instance.componentWillUnmount === 'function') { 546 | safelyCallComponentWillUnmount(current, instance); 547 | } 548 | return; 549 | } 550 | case HostComponent: { 551 | safelyDetachRef(current); 552 | return; 553 | } 554 | case HostPortal: { 555 | // TODO: this is recursive. 556 | // We are also not using this parent because 557 | // the portal will get pushed immediately. 558 | if (supportsMutation) { 559 | unmountHostComponents(current); 560 | } else if (supportsPersistence) { 561 | emptyPortalContainer(current); 562 | } 563 | return; 564 | } 565 | } 566 | } 567 | 568 | function commitNestedUnmounts(root) { 569 | // While we're inside a removed host node we don't want to call 570 | // removeChild on the inner nodes because they're removed by the top 571 | // call anyway. We also want to call componentWillUnmount on all 572 | // composites before this host node is removed from the tree. Therefore 573 | // we do an inner loop while we're still inside the host node. 574 | let node = root; 575 | while (true) { 576 | commitUnmount(node); 577 | // Visit children because they may contain more composite or host nodes. 578 | // Skip portals because commitUnmount() currently visits them recursively. 579 | if ( 580 | node.child !== null && 581 | // If we use mutation we drill down into portals using commitUnmount above. 582 | // If we don't use mutation we drill down into portals here instead. 583 | (!supportsMutation || node.tag !== HostPortal) 584 | ) { 585 | node.child.return = node; 586 | node = node.child; 587 | continue; 588 | } 589 | if (node === root) { 590 | return; 591 | } 592 | while (node.sibling === null) { 593 | if (node.return === null || node.return === root) { 594 | return; 595 | } 596 | node = node.return; 597 | } 598 | node.sibling.return = node.return; 599 | node = node.sibling; 600 | } 601 | } 602 | 603 | function detachFiber(current) { 604 | // Cut off the return pointers to disconnect it from the tree. Ideally, we 605 | // should clear the child pointer of the parent alternate to let this 606 | // get GC:ed but we don't know which for sure which parent is the current 607 | // one so we'll settle for GC:ing the subtree of this child. This child 608 | // itself will be GC:ed when the parent updates the next time. 609 | current.return = null; 610 | current.child = null; 611 | if (current.alternate) { 612 | current.alternate.child = null; 613 | current.alternate.return = null; 614 | } 615 | } 616 | 617 | function emptyPortalContainer(current) { 618 | if (!supportsPersistence) { 619 | return; 620 | } 621 | 622 | const portal = 623 | current.stateNode; 624 | const { containerInfo } = portal; 625 | const emptyChildSet = createContainerChildSet(containerInfo); 626 | replaceContainerChildren(containerInfo, emptyChildSet); 627 | } 628 | 629 | function commitContainer(finishedWork) { 630 | if (!supportsPersistence) { 631 | return; 632 | } 633 | 634 | switch (finishedWork.tag) { 635 | case ClassComponent: { 636 | return; 637 | } 638 | case HostComponent: { 639 | return; 640 | } 641 | case HostText: { 642 | return; 643 | } 644 | case HostRoot: 645 | case HostPortal: { 646 | const portalOrRoot = 647 | finishedWork.stateNode; 648 | const { containerInfo, pendingChildren } = portalOrRoot; 649 | replaceContainerChildren(containerInfo, pendingChildren); 650 | return; 651 | } 652 | default: { 653 | invariant( 654 | false, 655 | 'This unit of work tag should not have side-effects. This error is ' + 656 | 'likely caused by a bug in React. Please file an issue.', 657 | ); 658 | } 659 | } 660 | } 661 | 662 | function getHostParentFiber(fiber) { 663 | let parent = fiber.return; 664 | while (parent !== null) { 665 | if (isHostParent(parent)) { 666 | return parent; 667 | } 668 | parent = parent.return; 669 | } 670 | invariant( 671 | false, 672 | 'Expected to find a host parent. This error is likely caused by a bug ' + 673 | 'in React. Please file an issue.', 674 | ); 675 | } 676 | 677 | function isHostParent(fiber) { 678 | return ( 679 | fiber.tag === HostComponent || 680 | fiber.tag === HostRoot || 681 | fiber.tag === HostPortal 682 | ); 683 | } 684 | 685 | function getHostSibling(fiber) { 686 | // We're going to search forward into the tree until we find a sibling host 687 | // node. Unfortunately, if multiple insertions are done in a row we have to 688 | // search past them. This leads to exponential search for the next sibling. 689 | // TODO: Find a more efficient way to do this. 690 | let node = fiber; 691 | siblings: while (true) { 692 | // If we didn't find anything, let's try the next sibling. 693 | while (node.sibling === null) { 694 | if (node.return === null || isHostParent(node.return)) { 695 | // If we pop out of the root or hit the parent the fiber we are the 696 | // last sibling. 697 | return null; 698 | } 699 | node = node.return; 700 | } 701 | node.sibling.return = node.return; 702 | node = node.sibling; 703 | while (node.tag !== HostComponent && node.tag !== HostText) { 704 | // If it is not host node and, we might have a host node inside it. 705 | // Try to search down until we find one. 706 | if (node.effectTag & Placement) { 707 | // If we don't have a child, try the siblings instead. 708 | continue siblings; 709 | } 710 | // If we don't have a child, try the siblings instead. 711 | // We also skip portals because they are not part of this host tree. 712 | if (node.child === null || node.tag === HostPortal) { 713 | continue siblings; 714 | } else { 715 | node.child.return = node; 716 | node = node.child; 717 | } 718 | } 719 | // Check if this host node is stable or about to be placed. 720 | if (!(node.effectTag & Placement)) { 721 | // Found it! 722 | return node.stateNode; 723 | } 724 | } 725 | } 726 | 727 | function commitPlacement(finishedWork) { 728 | if (!supportsMutation) { 729 | return; 730 | } 731 | 732 | // Recursively insert all host nodes into the parent. 733 | const parentFiber = getHostParentFiber(finishedWork); 734 | 735 | // Note: these two variables *must* always be updated together. 736 | let parent; 737 | let isContainer; 738 | 739 | switch (parentFiber.tag) { 740 | case HostComponent: 741 | parent = parentFiber.stateNode; 742 | isContainer = false; 743 | break; 744 | case HostRoot: 745 | parent = parentFiber.stateNode.containerInfo; 746 | isContainer = true; 747 | break; 748 | case HostPortal: 749 | parent = parentFiber.stateNode.containerInfo; 750 | isContainer = true; 751 | break; 752 | default: 753 | invariant( 754 | false, 755 | 'Invalid host parent fiber. This error is likely caused by a bug ' + 756 | 'in React. Please file an issue.', 757 | ); 758 | } 759 | if (parentFiber.effectTag & ContentReset) { 760 | // Reset the text content of the parent before doing any insertions 761 | resetTextContent(parent); 762 | // Clear ContentReset from the effect tag 763 | parentFiber.effectTag &= ~ContentReset; 764 | } 765 | 766 | const before = getHostSibling(finishedWork); 767 | // We only have the top Fiber that was inserted but we need recurse down its 768 | // children to find all the terminal nodes. 769 | let node = finishedWork; 770 | while (true) { 771 | if (node.tag === HostComponent || node.tag === HostText) { 772 | if (before) { 773 | if (isContainer) { 774 | insertInContainerBefore(parent, node.stateNode, before); 775 | } else { 776 | insertBefore(parent, node.stateNode, before); 777 | } 778 | } else { 779 | if (isContainer) { 780 | appendChildToContainer(parent, node.stateNode); 781 | } else { 782 | appendChild(parent, node.stateNode); 783 | } 784 | } 785 | } else if (node.tag === HostPortal) { 786 | // If the insertion itself is a portal, then we don't want to traverse 787 | // down its children. Instead, we'll get insertions from each child in 788 | // the portal directly. 789 | } else if (node.child !== null) { 790 | node.child.return = node; 791 | node = node.child; 792 | continue; 793 | } 794 | if (node === finishedWork) { 795 | return; 796 | } 797 | while (node.sibling === null) { 798 | if (node.return === null || node.return === finishedWork) { 799 | return; 800 | } 801 | node = node.return; 802 | } 803 | node.sibling.return = node.return; 804 | node = node.sibling; 805 | } 806 | } 807 | 808 | function unmountHostComponents(current) { 809 | // We only have the top Fiber that was deleted but we need recurse down its 810 | // children to find all the terminal nodes. 811 | let node = current; 812 | 813 | // Each iteration, currentParent is populated with node's host parent if not 814 | // currentParentIsValid. 815 | let currentParentIsValid = false; 816 | 817 | // Note: these two variables *must* always be updated together. 818 | let currentParent; 819 | let currentParentIsContainer; 820 | 821 | while (true) { 822 | if (!currentParentIsValid) { 823 | let parent = node.return; 824 | findParent: while (true) { 825 | invariant( 826 | parent !== null, 827 | 'Expected to find a host parent. This error is likely caused by ' + 828 | 'a bug in React. Please file an issue.', 829 | ); 830 | switch (parent.tag) { 831 | case HostComponent: 832 | currentParent = parent.stateNode; 833 | currentParentIsContainer = false; 834 | break findParent; 835 | case HostRoot: 836 | currentParent = parent.stateNode.containerInfo; 837 | currentParentIsContainer = true; 838 | break findParent; 839 | case HostPortal: 840 | currentParent = parent.stateNode.containerInfo; 841 | currentParentIsContainer = true; 842 | break findParent; 843 | } 844 | parent = parent.return; 845 | } 846 | currentParentIsValid = true; 847 | } 848 | 849 | if (node.tag === HostComponent || node.tag === HostText) { 850 | commitNestedUnmounts(node); 851 | // After all the children have unmounted, it is now safe to remove the 852 | // node from the tree. 853 | if (currentParentIsContainer) { 854 | removeChildFromContainer((currentParent), node.stateNode); 855 | } else { 856 | removeChild((currentParent), node.stateNode); 857 | } 858 | // Don't visit children because we already visited them. 859 | } else if (node.tag === HostPortal) { 860 | // When we go into a portal, it becomes the parent to remove from. 861 | // We will reassign it back when we pop the portal on the way up. 862 | currentParent = node.stateNode.containerInfo; 863 | currentParentIsContainer = true; 864 | // Visit children because portals might contain host components. 865 | if (node.child !== null) { 866 | node.child.return = node; 867 | node = node.child; 868 | continue; 869 | } 870 | } else { 871 | commitUnmount(node); 872 | // Visit children because we may find more host components below. 873 | if (node.child !== null) { 874 | node.child.return = node; 875 | node = node.child; 876 | continue; 877 | } 878 | } 879 | if (node === current) { 880 | return; 881 | } 882 | while (node.sibling === null) { 883 | if (node.return === null || node.return === current) { 884 | return; 885 | } 886 | node = node.return; 887 | if (node.tag === HostPortal) { 888 | // When we go out of the portal, we need to restore the parent. 889 | // Since we don't keep a stack of them, we will search for it. 890 | currentParentIsValid = false; 891 | } 892 | } 893 | node.sibling.return = node.return; 894 | node = node.sibling; 895 | } 896 | } 897 | 898 | function commitDeletion(current) { 899 | if (supportsMutation) { 900 | // Recursively delete all host nodes from the parent. 901 | // Detach refs and call componentWillUnmount() on the whole subtree. 902 | unmountHostComponents(current); 903 | } else { 904 | // Detach refs and call componentWillUnmount() on the whole subtree. 905 | commitNestedUnmounts(current); 906 | } 907 | detachFiber(current); 908 | } 909 | 910 | function commitWork(current, finishedWork) { 911 | if (!supportsMutation) { 912 | switch (finishedWork.tag) { 913 | case FunctionComponent: 914 | case ForwardRef: 915 | case MemoComponent: 916 | case SimpleMemoComponent: { 917 | commitHookEffectList(UnmountMutation, MountMutation, finishedWork); 918 | return; 919 | } 920 | } 921 | 922 | commitContainer(finishedWork); 923 | return; 924 | } 925 | 926 | switch (finishedWork.tag) { 927 | case FunctionComponent: 928 | case ForwardRef: 929 | case MemoComponent: 930 | case SimpleMemoComponent: { 931 | commitHookEffectList(UnmountMutation, MountMutation, finishedWork); 932 | return; 933 | } 934 | case ClassComponent: { 935 | return; 936 | } 937 | case HostComponent: { 938 | const instance = finishedWork.stateNode; 939 | if (instance != null) { 940 | // Commit the work prepared earlier. 941 | const newProps = finishedWork.memoizedProps; 942 | // For hydration we reuse the update path but we treat the oldProps 943 | // as the newProps. The updatePayload will contain the real change in 944 | // this case. 945 | const oldProps = current !== null ? current.memoizedProps : newProps; 946 | const type = finishedWork.type; 947 | // TODO: Type the updateQueue to be specific to host components. 948 | const updatePayload = (finishedWork.updateQueue); 949 | finishedWork.updateQueue = null; 950 | if (updatePayload !== null) { 951 | commitUpdate( 952 | instance, 953 | updatePayload, 954 | type, 955 | oldProps, 956 | newProps, 957 | finishedWork, 958 | ); 959 | } 960 | } 961 | return; 962 | } 963 | case HostText: { 964 | invariant( 965 | finishedWork.stateNode !== null, 966 | 'This should have a text node initialized. This error is likely ' + 967 | 'caused by a bug in React. Please file an issue.', 968 | ); 969 | const textInstance = finishedWork.stateNode; 970 | const newText = finishedWork.memoizedProps; 971 | // For hydration we reuse the update path but we treat the oldProps 972 | // as the newProps. The updatePayload will contain the real change in 973 | // this case. 974 | const oldText = 975 | current !== null ? current.memoizedProps : newText; 976 | commitTextUpdate(textInstance, oldText, newText); 977 | return; 978 | } 979 | case HostRoot: { 980 | return; 981 | } 982 | case Profiler: { 983 | return; 984 | } 985 | case SuspenseComponent: { 986 | return; 987 | } 988 | case IncompleteClassComponent: { 989 | return; 990 | } 991 | default: { 992 | invariant( 993 | false, 994 | 'This unit of work tag should not have side-effects. This error is ' + 995 | 'likely caused by a bug in React. Please file an issue.', 996 | ); 997 | } 998 | } 999 | } 1000 | 1001 | function commitResetTextContent(current) { 1002 | if (!supportsMutation) { 1003 | return; 1004 | } 1005 | resetTextContent(current.stateNode); 1006 | } 1007 | 1008 | export { 1009 | commitBeforeMutationLifeCycles, 1010 | commitResetTextContent, 1011 | commitPlacement, 1012 | commitDeletion, 1013 | commitWork, 1014 | commitLifeCycles, 1015 | commitAttachRef, 1016 | commitDetachRef, 1017 | }; --------------------------------------------------------------------------------