├── .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 |
2 |
3 |
名字:{{state.name}} 年龄:{{state.age}} 岁
4 |
5 | 换一个年龄
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/components/Animal/index.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
名字{{state.name}} ; {{state.age}} 岁
4 |
5 | 换一个年龄
6 |
7 |
8 |
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 |
5 |
6 | 页面
7 |
8 |
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 |
5 |
6 |
7 |
8 |
{{state.text}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/pages/page1/index.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{state.text}}
7 |
8 | {{item}}
9 |
10 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{item.text}}
15 |
16 |
17 |
18 |
19 |
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 |
3 |
4 |
5 |
{{state.text}}
6 |
7 |
8 | showModal
9 |
10 |
11 | 显示上下文菜单
12 |
13 |
14 | 通知消息
15 |
16 |
17 | 震动
18 |
19 |
20 | 分享
21 |
22 |
23 | 文件上传2222
24 |
25 |
26 | 文件下载
27 |
28 |
29 |
30 |
31 |
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{item.bizTitle}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{item.title}}
23 |
24 |
25 |
26 |
27 |
特价专区
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
活动专区
36 |
37 |
38 |
39 |
40 |
41 | 何时飞
42 | 机票趋势早知道
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 人格测试
52 | 简直惊悚
53 |
54 |
55 |
56 |
57 |
58 | 飞行宝贝
59 | 榜单有利
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
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 | };
--------------------------------------------------------------------------------