├── .gitignore
├── src
├── shared
│ ├── status-work.js
│ ├── types.js
│ ├── tag.js
│ ├── effect-tag.js
│ ├── with-effect.js
│ ├── validate.js
│ └── shallowEqual.js
├── core
│ ├── with-state.js
│ ├── life-cycle.js
│ └── h.js
├── dom
│ ├── utils
│ │ ├── getDocumentByElement.js
│ │ ├── remove.js
│ │ ├── validate.js
│ │ ├── insert.js
│ │ ├── append.js
│ │ ├── createElement.js
│ │ └── textElement.js
│ ├── constants.js
│ ├── index.js
│ └── config.js
├── fiber
│ ├── root-render.js
│ ├── reconciler.js
│ ├── stack.js
│ ├── host-context.js
│ ├── f-life-cycle.js
│ ├── f-node.js
│ ├── begin-work.js
│ ├── complete-work.js
│ ├── f-with.js
│ ├── scheduler.js
│ ├── commit-work.js
│ └── children.js
└── structures
│ └── linked-list.js
├── .flowconfig
├── index.html
├── .babelrc
├── webpack.config.js
├── package.json
├── index.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | yarn.lock
4 |
--------------------------------------------------------------------------------
/src/shared/status-work.js:
--------------------------------------------------------------------------------
1 | const NoWork = 0;
2 | const Working = 1;
3 |
4 | export {
5 | NoWork,
6 | Working,
7 | }
8 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 | [options]
10 |
11 | [strict]
12 |
--------------------------------------------------------------------------------
/src/core/with-state.js:
--------------------------------------------------------------------------------
1 | import { withReducer } from '../fiber/f-with';
2 |
3 | export function withState(initialState) {
4 | return withReducer(initialState);
5 | }
6 |
--------------------------------------------------------------------------------
/src/shared/types.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | export type VNodeElement = {
3 | $$typeof: any,
4 | type: any,
5 | key: any,
6 | props: any,
7 | }
8 |
9 | export type Container = Element | Document;
10 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/shared/tag.js:
--------------------------------------------------------------------------------
1 | const Root = 0;
2 | const DNode = 1;
3 | const FComponent = 2;
4 | const Text = 3;
5 | const Fragment = 7;
6 |
7 |
8 | export {
9 | Root,
10 | DNode,
11 | FComponent,
12 | Text,
13 | Fragment,
14 | }
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-react-jsx",
4 | "@babel/plugin-proposal-class-properties",
5 | ],
6 | "presets": [
7 | "@babel/flow",
8 | "@babel/preset-env",
9 | "@babel/preset-react"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/dom/utils/getDocumentByElement.js:
--------------------------------------------------------------------------------
1 | import { isDocumentNode } from './validate';
2 |
3 | /**
4 | * @param {HTMLElement} element
5 | * @return {Document}
6 | */
7 | function getDocumentByElement(element) {
8 | return isDocumentNode(element) ? element : element.ownerDocument;
9 | }
10 |
11 | export default getDocumentByElement;
12 |
--------------------------------------------------------------------------------
/src/dom/constants.js:
--------------------------------------------------------------------------------
1 | const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
2 | const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
3 |
4 | const Namespaces = {
5 | html: HTML_NAMESPACE,
6 | svg: SVG_NAMESPACE,
7 | };
8 | export const TEXT_NODE = 3;
9 | const COMMENT_NODE = 8;
10 | export const DOCUMENT_NODE = 9;
11 |
12 | const CHILDREN = 'children';
13 |
--------------------------------------------------------------------------------
/src/fiber/root-render.js:
--------------------------------------------------------------------------------
1 | export function createRootRender(el) {
2 | const rootRender = {
3 | element: el,
4 | }
5 | return rootRender;
6 | }
7 |
8 |
9 | export function updateRootRender(WIP, rootRender) {
10 | let resultState;
11 | if (rootRender && rootRender.element) {
12 | resultState = rootRender;
13 | }
14 | WIP.prevState = resultState;
15 | }
16 |
--------------------------------------------------------------------------------
/src/dom/utils/remove.js:
--------------------------------------------------------------------------------
1 | import { isCommentNode } from './validate';
2 |
3 | export function removeChildFromContainer(container, child) {
4 |
5 | if (isCommentNode(container)) {
6 | container.parentNode.removeChild(child);
7 | } else {
8 | container.removeChild(child);
9 | }
10 | }
11 |
12 | export function removeChild(parentInstance, child) {
13 | parentInstance.removeChild(child);
14 | }
15 |
--------------------------------------------------------------------------------
/src/dom/utils/validate.js:
--------------------------------------------------------------------------------
1 | import { DOCUMENT_NODE, TEXT_NODE, COMMENT_NODE } from '../constants';
2 |
3 | function isDocumentNode(el) {
4 | return el.nodeType === DOCUMENT_NODE;
5 | }
6 |
7 | function isTextNode(el) {
8 | return el.nodeType === TEXT_NODE;
9 | }
10 |
11 | function isCommentNode(el) {
12 | return el.nodeType === COMMENT_NODE;
13 | }
14 |
15 | export {
16 | isDocumentNode,
17 | isTextNode,
18 | isCommentNode,
19 | };
20 |
--------------------------------------------------------------------------------
/src/shared/effect-tag.js:
--------------------------------------------------------------------------------
1 | const NoEffect = 0;
2 | const PerformedWork = 1;
3 | const Placement = 2;
4 | const Update = 4;
5 | const PlacementAndUpdate = 6;
6 | const Deletion = 8;
7 |
8 | const Incomplete = 1024;
9 | const ContentReset = 11;
10 | const Passive = 512;
11 |
12 | export {
13 | NoEffect,
14 | PerformedWork,
15 | Placement,
16 | Update,
17 | PlacementAndUpdate,
18 | Deletion,
19 |
20 | Incomplete,
21 | ContentReset ,
22 | Passive
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/dom/utils/insert.js:
--------------------------------------------------------------------------------
1 | import { isCommentNode } from './validate';
2 |
3 | function insertBefore(parent, child, beforeChild) {
4 | parent.insertBefore(child, beforeChild);
5 | }
6 |
7 | function insertInContainerBefore(container, child, beforeChild) {
8 | if (isCommentNode(container)) {
9 | container.parentNode.insertBefore(child, beforeChild);
10 | } else {
11 | container.insertBefore(child, beforeChild);
12 | }
13 | }
14 |
15 | export {
16 | insertInContainerBefore,
17 | insertBefore
18 | }
19 |
--------------------------------------------------------------------------------
/src/dom/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | createContainer,
3 | updateContainer
4 | } from '../fiber/reconciler';
5 |
6 | class Root {
7 | constructor(container) {
8 | const root = createContainer(container);
9 | this._root = root;
10 | }
11 |
12 | render(el) {
13 | updateContainer(el, this._root);
14 | }
15 | }
16 |
17 | export function render(el, container) {
18 | let root = container._rootContainer;
19 | if (!root) {
20 | root = container._rootContainer = new Root(container);
21 | root.render(el);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/core/life-cycle.js:
--------------------------------------------------------------------------------
1 | import { withLifeCycle } from '../fiber/f-with';
2 | import {
3 | Update as UpdateEffect,
4 | Passive
5 | } from '../shared/effect-tag';
6 | import {
7 | NoEffect as NoHookEffect,
8 | UnmountSnapshot,
9 | UnmountMutation,
10 | MountMutation,
11 | MountLayout,
12 | UnmountPassive,
13 | MountPassive,
14 | } from '../shared/with-effect';
15 |
16 | export const lifeCycle = ({mounted, destroyed, updated}) => {
17 | return withLifeCycle(UpdateEffect | Passive, UnmountPassive | MountPassive, {mounted, destroyed, updated});
18 | }
19 |
--------------------------------------------------------------------------------
/src/dom/utils/append.js:
--------------------------------------------------------------------------------
1 | import { isCommentNode } from './validate';
2 |
3 | function appendInitialChild(parent, child) {
4 | parent.appendChild(child);
5 | }
6 |
7 | function appendChild(parent, child) {
8 | parent.appendChild(child);
9 | }
10 |
11 | function appendChildToContainer(
12 | container,
13 | child
14 | ) {
15 | let parentNode;
16 | if (isCommentNode(container)) {
17 | parentNode = container.parentNode;
18 | parentNode.insertBefore(child, container)
19 | } else {
20 | parentNode = container;
21 | parentNode.appendChild(child);
22 | }
23 | }
24 |
25 | export {
26 | appendChildToContainer,
27 | appendInitialChild,
28 | appendChild,
29 | }
30 |
--------------------------------------------------------------------------------
/src/structures/linked-list.js:
--------------------------------------------------------------------------------
1 | export function LinkedList() {
2 | this.first = null;
3 | this.last = null;
4 | return this;
5 | }
6 |
7 | LinkedList.prototype.add = function (node) {
8 | if (this.last === null) {
9 | this.last = node;
10 | this.first = node;
11 | return;
12 | }
13 | this.last.next = node;
14 | this.last = node;
15 | };
16 |
17 | // custom single linked-list add node
18 | LinkedList.prototype.addEffectToParent = function (node) {
19 | if (this.first === null) {
20 | this.first = node.linkedList.first;
21 | }
22 |
23 | if (node.linkedList.last !== null) {
24 | if (this.last !== null) {
25 | this.last.next = node.linkedList.first;
26 | }
27 | this.last = node.linkedList.last;
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/shared/with-effect.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 | export type HookEffectTag = number;
11 |
12 | export const NoEffect = /* */ 0b00000000;
13 | export const UnmountSnapshot = /* */ 0b00000010;
14 | export const UnmountMutation = /* */ 0b00000100;
15 | export const MountMutation = /* */ 0b00001000;
16 | export const UnmountLayout = /* */ 0b00010000;
17 | export const MountLayout = /* */ 0b00100000;
18 | export const MountPassive = /* */ 0b01000000;
19 | export const UnmountPassive = /* */ 0b10000000;
20 |
--------------------------------------------------------------------------------
/src/fiber/reconciler.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { VNodeElement, Container } from '../shared/types';
3 | import type { FNode, FRoot } from './f-node';
4 |
5 | import { createFRoot } from './f-node';
6 | import { scheduleWork } from './scheduler';
7 | import { createRootRender } from './root-render';
8 |
9 | export function createContainer(container: Container): FRoot {
10 | return createFRoot(container);
11 | }
12 |
13 | export function updateContainer(el: VNodeElement, FRoot: FRoot): void {
14 | const current = FRoot.current;
15 | return scheduleRootUpdate(current, el);
16 | }
17 |
18 | function scheduleRootUpdate(current: FNode, el: VNodeElement): void {
19 | const rootRender = createRootRender(el);
20 | current.rootRender = rootRender;
21 |
22 | scheduleWork(current);
23 | }
24 |
--------------------------------------------------------------------------------
/src/shared/validate.js:
--------------------------------------------------------------------------------
1 | /** Check Array**/
2 | const isArray = Array.isArray;
3 | /** Check null**/
4 | const isNil = value => typeof value === 'object' && value === null;
5 | /** Check object**/
6 | const isObject = value => typeof value === 'object' && value !== null && !isArray(value);
7 | /** Check undefined**/
8 | const isUndef = value => typeof value === 'undefined';
9 | /** Check function**/
10 | const isFunction = value => !isUndef(value) && typeof value === 'function';
11 | /** Check number**/
12 | const isNumber = value => Number.isInteger(value) && typeof value === "number";
13 | /** Check string**/
14 | const isString = value => typeof value === 'string';
15 |
16 | export {
17 | isNil,
18 | isObject,
19 | isUndef,
20 | isFunction,
21 | isArray,
22 | isString,
23 | isNumber,
24 | };
25 |
--------------------------------------------------------------------------------
/src/dom/utils/createElement.js:
--------------------------------------------------------------------------------
1 | import getDocumentByElement from './getDocumentByElement';
2 |
3 | /**
4 | * @param {string} type
5 | * @param {object} props
6 | * @param {HTMLElement} rootContainerElement
7 | * @param {string} parentNamespace
8 | * @return {HTMLElement}
9 | */
10 | function createElement(type, props, rootContainerElement, parentNamespace) {
11 | const ownerDocument = getDocumentByElement(rootContainerElement);
12 | let element;
13 | if (typeof props.is === 'string') {
14 | element = ownerDocument.createElement(type, {is: props.is});
15 | } else {
16 | element = ownerDocument.createElement(type);
17 | if (type === 'select' && props.multiple) {
18 | const node = element;
19 | node.multiple = true;
20 | }
21 | }
22 | return element;
23 |
24 | }
25 |
26 | export default createElement;
27 |
--------------------------------------------------------------------------------
/src/dom/utils/textElement.js:
--------------------------------------------------------------------------------
1 | import getDocumentByElement from './getDocumentByElement';
2 | import { isTextNode } from './validate';
3 |
4 | function resetTextContent(element) {
5 | setTextContent(element, '');
6 | }
7 |
8 | function setTextContent(node, text) {
9 | if (text) {
10 | let firstChild = node.firstChild;
11 |
12 | if (
13 | firstChild &&
14 | firstChild === node.lastChild &&
15 | isTextNode(firstChild)
16 | ) {
17 | firstChild.nodeValue = text;
18 | return;
19 | }
20 | }
21 | node.textContent = text;
22 | }
23 |
24 | function createTextNode(text, element) {
25 | const value = typeof text === 'object' ? JSON.stringify(text) : text;
26 | return getDocumentByElement(element).createTextNode(value);
27 | }
28 |
29 | export {
30 | createTextNode,
31 | setTextContent,
32 | resetTextContent,
33 | }
34 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | // var ExtractTextPlugin = require('extract-text-webpack-plugin');
3 | var ReplacePlugin = require('replace-bundle-webpack-plugin');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 |
6 |
7 | module.exports = {
8 | entry: {
9 | bundle: './index.js'
10 | },
11 | output: {
12 | path: path.resolve(__dirname, 'dist'),
13 | filename: 'bundle.js'
14 | },
15 | module: {
16 | rules: [
17 | {
18 | test: /\.jsx?$/,
19 | exclude: /node_modules/,
20 | loader: 'babel-loader'
21 | },
22 | {
23 | test: /\.js$/,
24 | exclude: /node_modules/,
25 | use: 'babel-loader'
26 | },
27 | // {
28 | // test: /\.css$/,
29 | // loader: ExtractTextPlugin.extract('style')
30 | // }
31 | ]
32 | },
33 | plugins: [
34 | new HtmlWebpackPlugin({
35 | template: 'index.html'
36 | })
37 | ],
38 | devtool: 'source-map',
39 | devServer: {
40 | port: process.env.PORT || 8080
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-fiber-implement",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "webpack-dev-server --content-base dist",
9 | "build": "webpack"
10 | },
11 | "author": "tungtbt",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "@babel/cli": "^7.1.5",
15 | "@babel/core": "^7.1.6",
16 | "@babel/plugin-proposal-class-properties": "^7.1.0",
17 | "@babel/preset-env": "^7.1.6",
18 | "@babel/preset-flow": "^7.0.0",
19 | "@babel/preset-react": "^7.0.0",
20 | "babel-loader": "^8.0.4",
21 | "babel-plugin-transform-react-jsx": "^6.24.1",
22 | "extract-text-webpack-plugin": "^3.0.2",
23 | "flow-bin": "^0.86.0",
24 | "html-webpack-plugin": "^3.2.0",
25 | "replace-bundle-webpack-plugin": "^1.0.0",
26 | "webpack": "^4.26.0",
27 | "webpack-cli": "^3.1.2",
28 | "webpack-dev-server": "^3.1.10"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/fiber/stack.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 | export type StackCursor = {
10 | current: T,
11 | };
12 |
13 | const valueStack: Array = [];
14 |
15 | let fiberStack: Array;
16 |
17 | let index = -1;
18 |
19 | function createCursor(defaultValue: T): StackCursor {
20 | return {
21 | current: defaultValue,
22 | };
23 | }
24 |
25 | function isEmpty(): boolean {
26 | return index === -1;
27 | }
28 |
29 | function pop(cursor: StackCursor, fiber): void {
30 | if (index < 0) {
31 | return;
32 | }
33 |
34 | cursor.current = valueStack[index];
35 | valueStack[index] = null;
36 | // fiberStack[index] = null;
37 | index--;
38 | }
39 |
40 | function push(cursor: StackCursor, value: T, fiber): void {
41 | index++;
42 |
43 | valueStack[index] = cursor.current;
44 | // fiberStack[index] = fiber;
45 |
46 | cursor.current = value;
47 | }
48 |
49 | export {
50 | createCursor,
51 | isEmpty,
52 | pop,
53 | push,
54 | };
55 |
--------------------------------------------------------------------------------
/src/fiber/host-context.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 | import type {StackCursor} from './stack';
10 | import {createCursor, push, pop} from './stack';
11 |
12 | declare class NoContextT {}
13 | const NO_CONTEXT: NoContextT = ({}: any);
14 |
15 | let rootInstanceStackCursor: StackCursor = createCursor(
16 | NO_CONTEXT,
17 | );
18 |
19 | function requiredContext(c: Value | NoContextT): Value {
20 | return (c: any);
21 | }
22 |
23 | function getRootHostContainer() {
24 | const rootInstance = requiredContext(rootInstanceStackCursor.current);
25 | return rootInstance;
26 | }
27 |
28 | function pushHostContainer(fiber, nextRootInstance) {
29 | // Push current root instance onto the stack;
30 | // This allows us to reset root when portals are popped.
31 | push(rootInstanceStackCursor, nextRootInstance, fiber);
32 | }
33 |
34 | function popHostContainer(fiber) {
35 | pop(rootInstanceStackCursor, fiber);
36 | }
37 |
38 | export {
39 | getRootHostContainer,
40 | popHostContainer,
41 | pushHostContainer,
42 | };
43 |
--------------------------------------------------------------------------------
/src/fiber/f-life-cycle.js:
--------------------------------------------------------------------------------
1 | let firstCallbackNode = null;
2 |
3 | function flushFirstCallback() {
4 | let flushedNode = firstCallbackNode;
5 |
6 | let next = firstCallbackNode.next;
7 | if (firstCallbackNode === next) {
8 | // This is the last callback in the list.
9 | firstCallbackNode = null;
10 | next = null;
11 | } else {
12 | let lastCallbackNode = firstCallbackNode.previous;
13 | firstCallbackNode = lastCallbackNode.next = next;
14 | next.previous = lastCallbackNode;
15 | }
16 | flushedNode.next = flushedNode.previous = null;
17 |
18 | const callback = flushedNode.callback;
19 | let continuationCallback;
20 |
21 | continuationCallback = callback();
22 |
23 | }
24 |
25 |
26 | export function callLifeCycle(callback) {
27 | const newNode = {
28 | callback: callback,
29 | next: null,
30 | previous: null,
31 | }
32 | if (firstCallbackNode === null) {
33 | firstCallbackNode = newNode.next = newNode.previous = newNode;
34 | flushFirstCallback();
35 | } else {
36 | let next = null;
37 | let node = firstCallbackNode;
38 |
39 | do {
40 | next = node;
41 | } while (node !== firstCallbackNode);
42 |
43 | if (next === null) {
44 | next = firstCallbackNode;
45 | } else if (next === firstCallbackNode) {
46 | firstCallbackNode = newNode;
47 | flushFirstCallback();
48 | }
49 |
50 | let previous = next.previous;
51 | previous.next = next.previous = newNode;
52 | newNode.next = next;
53 | newNode.previous = previous;
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | import { h } from './src/core/h';
3 | import { withState } from './src/core/with-state';
4 | import { lifeCycle } from './src/core/life-cycle';
5 | import { render } from './src/dom';
6 |
7 | let list = []
8 |
9 | for (let i = 0; i < 5; i++) {
10 | list = [
11 | ...list,
12 | {
13 | name: 'tung',
14 | age: 10,
15 | id: i,
16 | }
17 | ]
18 | }
19 |
20 | const User = ({ user, update, remove }) => {
21 | lifeCycle({
22 | mounted() {
23 | console.log('mounted User')
24 | return () => console.log('unmounted User')
25 | }
26 | })
27 | return (
28 |
29 |
Name: {user.name}
30 |
Age: {user.age}
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | const Test = ({ title }) => {
38 | const [count, dispatch] = withState(1);
39 | const [users, setUsers] = withState(list);
40 |
41 | function add() {
42 | const newUsers = [...users, { name: 'teng', age: 12, id: users.length }];
43 | setUsers(newUsers);
44 | }
45 | function update(id) {
46 | const newUsers = users.map(u => u.id === id ? {...u, name: 'aaaa', age: 15} : u);
47 | setUsers(newUsers);
48 | }
49 | function remove(id) {
50 | const newUsers = users.filter(u => u.id !== id);
51 | setUsers(newUsers);
52 | }
53 | return (
54 |
55 |
56 |
{count}
57 | {users.map(user =>
58 |
63 | )}
64 |
65 | )
66 |
67 | }
68 |
69 | render(, document.getElementById('root'));
70 |
--------------------------------------------------------------------------------
/src/shared/shallowEqual.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 | /*eslint-disable no-self-compare */
11 |
12 | const hasOwnProperty = Object.prototype.hasOwnProperty;
13 |
14 | /**
15 | * inlined Object.is polyfill to avoid requiring consumers ship their own
16 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
17 | */
18 | function is(x, y) {
19 | // SameValue algorithm
20 | if (x === y) {
21 | // Steps 1-5, 7-10
22 | // Steps 6.b-6.e: +0 != -0
23 | // Added the nonzero y check to make Flow happy, but it is redundant
24 | return x !== 0 || y !== 0 || 1 / x === 1 / y;
25 | } else {
26 | // Step 6.a: NaN == NaN
27 | return x !== x && y !== y;
28 | }
29 | }
30 |
31 | /**
32 | * Performs equality by iterating through keys on an object and returning false
33 | * when any key has values which are not strictly equal between the arguments.
34 | * Returns true when the values of all keys are strictly equal.
35 | */
36 | function shallowEqual(objA: mixed, objB: mixed): boolean {
37 | if (is(objA, objB)) {
38 | return true;
39 | }
40 |
41 | if (
42 | typeof objA !== 'object' ||
43 | objA === null ||
44 | typeof objB !== 'object' ||
45 | objB === null
46 | ) {
47 | return false;
48 | }
49 |
50 | const keysA = Object.keys(objA);
51 | const keysB = Object.keys(objB);
52 |
53 | if (keysA.length !== keysB.length) {
54 | return false;
55 | }
56 |
57 | // Test for A's keys different from B.
58 | for (let i = 0; i < keysA.length; i++) {
59 | if (
60 | !hasOwnProperty.call(objB, keysA[i]) ||
61 | !is(objA[keysA[i]], objB[keysA[i]])
62 | ) {
63 | return false;
64 | }
65 | }
66 |
67 | return true;
68 | }
69 |
70 | export default shallowEqual;
71 |
--------------------------------------------------------------------------------
/src/core/h.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { isNil, isFunction } from '../shared/validate';
3 | const hasOwnProperty = Object.prototype.hasOwnProperty;
4 |
5 |
6 | const hasSymbol = typeof Symbol === 'function' && Symbol.for;
7 |
8 | export const REACT_ELEMENT_TYPE = hasSymbol
9 | ? Symbol.for('react.element')
10 | : 0xeac7;
11 | export const REACT_FRAGMENT_TYPE = hasSymbol
12 | ? Symbol.for('react.fragment')
13 | : 0xeacb;
14 |
15 | const RESERVED_PROPS = {
16 | key: true,
17 | ref: true,
18 | };
19 |
20 | function hasValidKey(options) {
21 | return options.key !== undefined;
22 | }
23 |
24 | function VNode(type, props, key) {
25 | const vnode = {
26 | $$typeof: REACT_ELEMENT_TYPE,
27 |
28 | type: type,
29 | props: props,
30 | key: key,
31 | }
32 |
33 | return vnode;
34 | }
35 |
36 | export function h(type, options, children) {
37 | let propName;
38 | const props = {};
39 | let key = null;
40 | if (!isNil(options)) {
41 | if (hasValidKey(options)) {
42 | key = '' + options.key;
43 | }
44 | for (propName in options) {
45 | // Why use hasOwnProperty.call instead of someObj.hasOwnProperty?
46 | // 1.hasOwnProperty is defined on the object as something else
47 | // 2.The object in question is being used as a map and doesn't inherit from Object.prototype, so it doesn't have hasOwnProperty:
48 | if (hasOwnProperty.call(options, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
49 | props[propName] = options[propName];
50 | }
51 | }
52 | }
53 | // Children can be more than one argument, and those are transferred onto
54 | // the newly allocated props object.
55 | // if createElement has 5 params number of children will be 3
56 | const childrenLength = arguments.length - 2;
57 | if (childrenLength === 1) {
58 | props.children = children;
59 | } else if (childrenLength > 1) {
60 | // create Array empty has childrenLength element
61 | const childArray = Array(childrenLength);
62 | for (let i = 0; i < childrenLength; i++) {
63 | // create array child
64 | childArray[i] = arguments[i + 2];
65 | }
66 | props.children = childArray;
67 | }
68 |
69 | // Resolve default props
70 | if (type && type.defaultProps) {
71 | const defaultProps = type.defaultProps;
72 | for (propName in defaultProps) {
73 | if (props[propName] === undefined) {
74 | props[propName] = defaultProps[propName];
75 | }
76 | }
77 | }
78 |
79 | return VNode(type, props, key);
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## React fiber
2 | react-fiber is my self-study project help me understand how react work. In fact, all codebase re-implement each step , so it looks similar to the source code of react. Though, I think it's still smaller and easier to understand than when you actually read the react source code. I hope it helpful for people who want to start learn how react fiber work.
3 |
4 | ## Something you should read and learn before start read source code
5 |
6 | #### Keyword, Algorithms and Data Structure Used
7 | - Single linked list, Circular linked list
8 | - Simple stack and queue
9 | - Recursive
10 | - Structural sharing
11 | - [Reconciliation](https://reactjs.org/docs/reconciliation.html)
12 | - Scheduler
13 | - Bitwise Operators
14 | - JSX
15 | - DOM
16 | ###### And more
17 | - [React Components, Elements, and Instances](https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html)
18 | - [Design Principles](https://reactjs.org/docs/design-principles.html)
19 | - [React Fiber resources](https://github.com/koba04/react-fiber-resources)
20 | - [The how and why on React’s usage of linked list in Fiber to walk the component’s tree](https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7)
21 | - [In-depth explanation of state and props update in React
22 | ](https://medium.com/react-in-depth/in-depth-explanation-of-state-and-props-update-in-react-51ab94563311)
23 | ###### Recommend
24 | - [Lin Clark - A Cartoon Intro to Fiber - React Conf 2017
25 | ](https://www.youtube.com/watch?v=ZCuYPiUIONs)
26 | - [A look inside React Fiber
27 | ](https://makersden.io/blog/look-inside-fiber/)
28 | - [Build your own React Fiber](https://engineering.hexacta.com/didact-fiber-incremental-reconciliation-b2fe028dcaec)
29 | - [React Fiber Architecture @acdlite](https://github.com/acdlite/react-fiber-architecture) and [React Fiber Architecture @SaeedMalikx](https://github.com/SaeedMalikx/React-Fiber-Architecture)
30 |
31 |
32 | ## Overview
33 |
34 | ### Fiber tree
35 | 
36 |
37 | [Inside Fiber: in-depth overview of the new reconciliation algorithm in React](https://medium.com/react-in-depth/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react-e1c04700ef6e)
38 | ### Keyword
39 | ```
40 | work (unitOfWork): A component, node element => fiber
41 |
42 | current: Current fiber what is displayed on browser
43 |
44 | WIP (workInProgress): New fiber tree we will build
45 |
46 | fiber: {
47 | type: string | Function ('div', 'span', function Button)
48 | instanceNode: HTMLElement (div, span)
49 | return: fiber (parent of fiber)
50 | child: fiber (child of fiber)
51 | sibling: fiber (sibling of fiber)
52 |
53 | alternate: link current - WIP and WIP - current
54 | effectTag: number (give we know what will happen this fiber)
55 |
56 | }
57 |
58 | requestIdleCallback
59 |
60 | main function:
61 | createWorkInProgress()
62 | beginWork()
63 | reconcileChildren()
64 | completeWork()
65 | commitWork()
66 | ```
67 |
68 | ### Process of first render
69 | ```
70 | Render -> Reconciler -> Scheduler ->
71 | Begin Work (build fiber tree) -> ChildReconciler(create child and effectTag) -> if work has child we will continue to run beginWork -> no child ->
72 | Complete Work (build list effect, mark tag and create instanceNode) -> sibling has child -> turn back Begin Work -> no child -> Complete Work -> no sibling -> has a new tree with effect tag ->
73 | Commit Work : It will base on list effect tag to commit each fiber (Placement, Update, Delete, Lifecycle)
74 |
75 | // In first render current fiber is null.
76 | // current is workInProgress when commit
77 | ```
78 | ### Process when update
79 | ```
80 | Do something ->
81 | Get current Fiber what corresponding to the component ->
82 | Recursive to find Root ->
83 | Clone fiber from root to component has update ->
84 | Begin Work from this fiber (it's maybe clone fiber when children of component use memo, pure component or use shouldComponentUpdate) ->
85 | Complete Work ->
86 | Commit Work
87 | ```
88 |
89 | ### About With(Hook v16.7)
90 | ```
91 | Hooks are stored as a linked list on the fiber's prevState field of fiber.
92 | current tree - current hook <=> WIP - WIP hook
93 |
94 | ```
95 |
--------------------------------------------------------------------------------
/src/fiber/f-node.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type { VNodeElement, Container } from '../shared/types';
3 | import * as Tag from '../shared/tag';
4 | import * as Status from '../shared/status-work';
5 | import { isString, isFunction } from '../shared/validate';
6 | import { LinkedList } from '../structures/linked-list';
7 |
8 | export type FNode = {
9 | // tag is what we know what is this fiber like root, function component or text ...
10 | tag: number,
11 | key: string | null,
12 | // type of element like button, div
13 | elementType: string | null,
14 | // it like element type
15 | type: string | null,
16 | // instanceNode is dom element
17 | instanceNode: any,
18 | // parent of node
19 | return: FNode | null,
20 | // child of node
21 | child: FNode | null,
22 | // sibling of node
23 | sibling: FNode | null,
24 | // index is index of array children element
25 | // Eg: [f1, f2, f3] index of f2 is 1
26 | index: number,
27 | // props is pending props wait to work
28 | props: any,
29 | prevProps: any,
30 | prevState: any,
31 | // effect
32 | effectTag: number,
33 | nextEffect: FNode | null,
34 | lastEffect: FNode | null,
35 | firstEffect: FNode | null,
36 | // this to test linked list
37 | linkedList: any,
38 | // rootRender
39 | rootRender: any,
40 | // alternate
41 | alternate: FNode | null,
42 | // status to know this fiber need work or not
43 | status: number,
44 | // life cycle of this fiber
45 | lifeCycle: any,
46 |
47 | }
48 |
49 | export type FRoot = {
50 | current: FNode,
51 | containerInfo: any,
52 | }
53 |
54 | function FNode(
55 | tag: number,
56 | props: any,
57 | key: string | null
58 | ) {
59 | this.tag = tag;
60 | this.key = key;
61 | this.elementType = null;
62 | this.type = null;
63 |
64 | this.instanceNode = null;
65 | this.return = null;
66 | this.child = null;
67 | this.sibling = null;
68 | this.index = 0;
69 |
70 | this.props = props;
71 | this.prevProps = null;
72 | this.prevState = null;
73 |
74 | this.effectTag = 0;
75 | this.nextEffect = null;
76 | this.firstEffect = null;
77 | this.lastEffect = null;
78 | this.linkedList = new LinkedList();
79 | this.next = null;
80 |
81 | this.rootRender = null;
82 |
83 | this.alternate = null;
84 |
85 | this.status = Status.Working;
86 |
87 | this.lifeCycle = null;
88 | }
89 |
90 | export function createFNode(tag: number, props: any, key: string | null): FNode {
91 | return new FNode(tag, props, key);
92 | }
93 |
94 | export function createFRoot(container: Container): FRoot {
95 | const current = new FNode(Tag.Root, null, null);
96 | const root = {
97 | current: current,
98 | containerInfo: container,
99 | }
100 | current.instanceNode = root;
101 | return root;
102 | }
103 |
104 | /**
105 | * @param {FNode} current is current fnode is displayed on screen
106 | * @param {any} props is nextProps of fiber
107 | * @return {FNode} new Fnode is next fiber to work is called work-in-progress
108 | */
109 |
110 | export function createWIP(current: FNode, props: any): FNode {
111 | if (current === null) return;
112 | let WIP = current.alternate;
113 | if (WIP === null) {
114 | // if workInProgress === null we will start create a work-in-progress tree
115 | WIP = createFNode(current.tag, props, current.key);
116 | WIP.elementType = current.elementType;
117 | WIP.type = current.type;
118 | WIP.instanceNode = current.instanceNode;
119 |
120 | WIP.alternate = current;
121 | current.alternate = WIP;
122 | } else {
123 | // set props and reset effect tag
124 | WIP.props = props;
125 | WIP.effectTag = 0;
126 |
127 | // The effect list is no longer valid.
128 | WIP.nextEffect = null;
129 | WIP.firstEffect = null;
130 | WIP.lastEffect = null;
131 | WIP.linkedList = new LinkedList();;
132 | WIP.next = null;
133 |
134 |
135 | }
136 | WIP.child = current.child;
137 |
138 | WIP.prevProps = current.prevProps;
139 | WIP.prevState = current.prevState;
140 | WIP.rootRender = current.rootRender;
141 |
142 |
143 | WIP.sibling = current.sibling;
144 | WIP.index = current.index;
145 |
146 | WIP.status = current.status;
147 |
148 | WIP.lifeCycle = current.lifeCycle;
149 |
150 | return WIP;
151 |
152 | };
153 |
154 | /**
155 | * @param {Element} el is v-node
156 | * @return {FNode} new Fnode is created based on v-node element
157 | */
158 |
159 | export function createFNodeFromElement(el: VNodeElement): FNode {
160 | if (el === null) return null;
161 | const { type = '', key = null, props = {} } = el;
162 | let fnode;
163 | if (isString(type)) {
164 | fnode = createFNode(Tag.DNode, props, key);
165 | } else if (isFunction(type)) {
166 | fnode = createFNode(Tag.FComponent, props, key);
167 | }
168 | if (fnode !== null) {
169 | fnode.elementType = type;
170 | fnode.type = type;
171 | }
172 | return fnode;
173 | }
174 |
175 | export function createFNodeFromFragment(elements, key) {
176 | const fnode = createFNode(Tag.Fragment, elements, key);
177 | return fnode;
178 | }
179 |
--------------------------------------------------------------------------------
/src/fiber/begin-work.js:
--------------------------------------------------------------------------------
1 | import type {FNode} from 'f-node';
2 |
3 | import {Root, DNode, FComponent, Text, Fragment} from '../shared/tag';
4 | import { isObject } from '../shared/validate';
5 | import {PerformedWork} from '../shared/effect-tag';
6 | import {reconcileChildren, cloneChildFNodes} from './children';
7 | import {pushHostContainer} from './host-context';
8 | import {prepareWithState, finishedWith} from './f-with';
9 | import {updateRootRender} from './root-render';
10 | import * as Status from '../shared/status-work';
11 | import shallowEqual from '../shared/shallowEqual';
12 |
13 | // test
14 |
15 | export function saveProps(WIP: FNode, props: any): void {
16 | WIP.prevProps = props;
17 | }
18 |
19 | export function saveState(WIP: FNode, state: any): void {
20 | WIP.prevState = state;
21 | }
22 |
23 | function shouldSetTextContent(type, props) {
24 | return type === 'textarea' ||
25 | typeof props.children === 'string' ||
26 | typeof props.children === 'number' ||
27 | typeof props.dangerouslySetInnerHTML === 'object'
28 | && props.dangerouslySetInnerHTML !== null
29 | && typeof props.dangerouslySetInnerHTML.__html === 'string';
30 | }
31 |
32 | function pushHostRootContext(WIP: FNode): void {
33 | const root = WIP.instanceNode;
34 | pushHostContainer(WIP, root.containerInfo);
35 | }
36 |
37 | function updateRoot(current: FNode | null, WIP: FNode): FNode | null {
38 | pushHostRootContext(WIP);
39 |
40 | const rootRender = WIP.rootRender;
41 | const nextProps = WIP.props;
42 | const prevState = WIP.prevState;
43 | const prevChild = prevState !== null
44 | ? prevState.element
45 | : null;
46 | // processUpdateQueue(WIP, updateQueue, nextProps, null);
47 | updateRootRender(WIP, rootRender, nextProps, null)
48 | const nextState = WIP.prevState;
49 | const nextChildren = nextState.element;
50 |
51 | reconcileChildren(current, WIP, nextChildren);
52 | return WIP.child;
53 | }
54 |
55 | function updateDomNode(current: FNode | null, WIP: FNode): FNode | null {
56 |
57 | const type = WIP.type;
58 | const nextProps = WIP.props;
59 | const prevProps = current !== null
60 | ? current.prevProps
61 | : null;
62 | let nextChildren = nextProps.children;
63 | reconcileChildren(current, WIP, nextChildren);
64 | saveProps(WIP, nextProps);
65 | return WIP.child;
66 | }
67 |
68 | function updateFunctionComponent(current: FNode | null, WIP: FNode, status): FNode | null {
69 | const Component = WIP.type;
70 | const unresolvedProps = WIP.props;
71 | const nextProps = resolveDefaultProps(Component, unresolvedProps);
72 | if (current !== null && status === Status.NoWork) {
73 | const prevProps = current.prevProps;
74 | if (shallowEqual(prevProps, nextProps) && current.ref === WIP.ref) {
75 | cloneChildFNodes(current, WIP);
76 | return WIP.child;
77 | }
78 | }
79 |
80 | let nextChildren;
81 | prepareWithState(current, WIP);
82 |
83 | nextChildren = Component(nextProps);
84 |
85 | nextChildren = finishedWith(Component, nextProps, nextChildren);
86 | WIP.effectTag |= PerformedWork;
87 | reconcileChildren(current, WIP, nextChildren);
88 | return WIP.child;
89 | }
90 |
91 | function updateTextNode(current, WIP) {
92 | const nextProps = WIP.props;
93 | saveProps(WIP, nextProps);
94 | return null;
95 | }
96 |
97 | function updateFragment(current, WIP) {
98 | const nextChildren = WIP.props;
99 | reconcileChildren(current, WIP, nextChildren);
100 | return WIP.child;
101 | }
102 |
103 | function resolveDefaultProps(Component: Function, baseProps: any) {
104 | if (Component && Component.defaultProps) {
105 | // Resolve default props. Taken from ReactElement
106 | const props = Object.assign({}, baseProps);
107 | const defaultProps = Component.defaultProps;
108 | for (let propName in defaultProps) {
109 | if (props[propName] === undefined) {
110 | props[propName] = defaultProps[propName];
111 | }
112 | }
113 | return props;
114 | }
115 | return baseProps;
116 | }
117 |
118 | /**
119 | * @param {FNode} current
120 | * @param {FNode} WIP
121 | * @return {FNode | null}
122 | */
123 |
124 | export function beginWork(current: FNode | null, WIP: FNode): FNode | null {
125 | const status = WIP.status;
126 | if (current !== null) {
127 | const oldProps = current.prevProps;
128 | const newProps = WIP.props;
129 | if (oldProps === newProps && WIP.status === Status.NoWork) {
130 | // we just push root to stack
131 | if (WIP.tag === Root) {
132 | pushHostRootContext(WIP);
133 | }
134 | // clone this fiber and return child
135 | cloneChildFNodes(current, WIP);
136 | return WIP.child;
137 | }
138 | }
139 | // reset WIP
140 | WIP.status = Status.NoWork;
141 |
142 | if (WIP.tag === Root) {
143 | return updateRoot(current, WIP);
144 | } else if (WIP.tag === DNode) {
145 | return updateDomNode(current, WIP);
146 | } else if (WIP.tag === FComponent) {
147 | return updateFunctionComponent(current, WIP, status)
148 | } else if (WIP.tag === Text) {
149 | return updateTextNode(current, WIP);
150 | } else if (WIP.tag === Fragment) {
151 | return updateFragment(current, WIP);
152 | } else
153 | return null;
154 | }
155 |
--------------------------------------------------------------------------------
/src/fiber/complete-work.js:
--------------------------------------------------------------------------------
1 | // Then it build a list of effects.
2 | // This list will contain all the fibers from the work-in-progress sub-tree
3 | // that have any effectTag
4 | // (it also contains the fibers from the old sub-tree with the DELETION effectTag).
5 | import {
6 | Root,
7 | DNode,
8 | Text,
9 | FComponent,
10 | Fragment
11 | } from '../shared/tag';
12 | import { Placement, Update } from '../shared/effect-tag';
13 |
14 | import {
15 | getRootHostContainer,
16 | popHostContainer
17 | } from './host-context'
18 |
19 | import {
20 | createTextInstance,
21 | createDomNodeInstance,
22 | appendInitialChild,
23 | finalizeInitialChildren,
24 | prepareUpdate,
25 | } from '../dom/config';
26 |
27 |
28 | function markUpdate(WIP) {
29 | // Tag the fiber with an update effect. This turns a Placement into
30 | // a PlacementAndUpdate.
31 | WIP.effectTag |= Update;
32 | }
33 |
34 | export function updateHostContainer(WIP) {
35 | }
36 |
37 | export function updateHostComponent(
38 | current,
39 | WIP,
40 | type,
41 | newProps,
42 | rootContainerInstance
43 | ) {
44 | // If we have an alternate, that means this is an update and we need to
45 | // schedule a side-effect to do the updates.
46 | const oldProps = current.prevProps;
47 | if (oldProps === newProps) {
48 | // In mutation mode, this is sufficient for a bailout because
49 | // we won't touch this node even if children changed.
50 | return;
51 | }
52 |
53 | // If we get updated because one of our children updated, we don't
54 | // have newProps so we'll have to reuse them.
55 | // TODO: Split the update API as separate for the props vs. children.
56 | // Even better would be if children weren't special cased at all tho.
57 | const instance = WIP.instanceNode;
58 | // TODO: Experiencing an error where oldProps is null. Suggests a host
59 | // component is hitting the resume path. Figure out why. Possibly
60 | // related to `hidden`.
61 | const updatePayload = prepareUpdate(
62 | instance,
63 | type,
64 | oldProps,
65 | newProps,
66 | rootContainerInstance,
67 | );
68 |
69 | // // TODO: Type this specific to this type of component.
70 | WIP.updateQueue = WIP;
71 | // If the update payload indicates that there is a change or if there
72 | // is a new ref we mark this as an update. All the work is done in commitWork.
73 | if (updatePayload) {
74 | markUpdate(WIP);
75 | }
76 |
77 | }
78 |
79 | export function updateHostText(
80 | current,
81 | WIP,
82 | oldText,
83 | newText
84 | ) {
85 | if (oldText !== newText) {
86 | markUpdate(WIP);
87 | }
88 | }
89 |
90 | function appendAllChildren(
91 | parent,
92 | WIP
93 | ) {
94 | let node = WIP.child;
95 | while (node !== null) {
96 | if (node.tag === DNode || node.tag === Text) {
97 | appendInitialChild(parent, node.instanceNode);
98 | } else if (node.child !== null) {
99 | node.child.return = node;
100 | node = node.child;
101 | continue;
102 | }
103 | if (node === WIP) {
104 | return;
105 | }
106 | while (node.sibling === null) {
107 | if (node.return === null || node.return === WIP) {
108 | return;
109 | }
110 | node = node.return;
111 | }
112 | node.sibling.return = node.return;
113 | node = node.sibling;
114 | }
115 | }
116 |
117 | export function completeWork(
118 | current,
119 | WIP,
120 | ) {
121 | // after beginWork work we props is new props
122 | const newProps = WIP.props;
123 | switch (WIP.tag) {
124 | case Root: {
125 | popHostContainer(WIP);
126 | // const fiberRoot = WIP.instanceNode;
127 | if (current === null || current.child === null) {
128 | WIP.effectTag &= ~Placement;
129 | }
130 | // updateHostContainer(WIP);
131 | return null;
132 | }
133 | case FComponent: {
134 | return null;
135 | }
136 | case DNode: {
137 | const rootContainerInstance = getRootHostContainer();
138 | const type = WIP.type;
139 | if (current !== null && WIP.instanceNode !== null) {
140 | updateHostComponent(
141 | current,
142 | WIP,
143 | type,
144 | newProps,
145 | rootContainerInstance,
146 | );
147 | } else {
148 | if (!newProps) {
149 | break;
150 | }
151 |
152 | // const currentHostContext = getHostContext();
153 | const currentHostContext = {
154 | namespace: "http://www.w3.org/1999/xhtml"
155 | }
156 | // create instance of element or fiber.. instance will be like document.createElement('div')
157 | let instance = createDomNodeInstance(
158 | type,
159 | newProps,
160 | rootContainerInstance,
161 | currentHostContext,
162 | WIP,
163 | );
164 | appendAllChildren(instance, WIP);
165 | // this function to set property to element
166 | finalizeInitialChildren(instance, type, newProps, rootContainerInstance, currentHostContext);
167 | // and set state node
168 | WIP.instanceNode = instance;
169 |
170 | }
171 | return null;
172 | }
173 | case Text: {
174 | const newText = newProps;
175 | // that means it rendered
176 | if (current !== null && WIP.instanceNode !== null) {
177 | let oldText = current.prevProps;
178 | updateHostText(current, WIP, oldText, newText);
179 | } else {
180 | if (typeof newText !== 'string') {
181 | return null;
182 | }
183 | const rootContainerInstance = getRootHostContainer();
184 | WIP.instanceNode = createTextInstance(newText, rootContainerInstance, WIP);
185 | }
186 | return null;
187 | }
188 | case Fragment: {
189 | return null;
190 | }
191 | default:
192 | return null;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/dom/config.js:
--------------------------------------------------------------------------------
1 | import createElement from './utils/createElement';
2 | import { createTextNode, setTextContent, resetTextContent } from './utils/textElement';
3 | import { appendChildToContainer, appendInitialChild, appendChild } from './utils/append';
4 | import { removeChildFromContainer, removeChild } from './utils/remove';
5 | import { insertInContainerBefore, insertBefore } from './utils/insert';
6 | import { isDocumentNode } from './utils/validate';
7 |
8 | const CHILDREN = 'children';
9 |
10 | // Assumes there is no parent namespace.
11 |
12 | const randomKey = Math.floor((Math.random() * 100) + 1);
13 |
14 | const internalInstanceKey = '__reactInternalInstance$' + randomKey;
15 | const internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
16 |
17 | export function precacheFiberNode(hostInst, node) {
18 | node[internalInstanceKey] = hostInst;
19 | }
20 | export function getFiberCurrentPropsFromNode(node) {
21 | return node[internalEventHandlersKey] || null;
22 | }
23 |
24 | export function updateFiberProps(node, props) {
25 | node[internalEventHandlersKey] = props;
26 | }
27 |
28 | export function createDomNodeInstance(
29 | type,
30 | props,
31 | rootContainerInstance,
32 | hostContext,
33 | internalInstanceHandle) {
34 | let parentNamespace;
35 | parentNamespace = hostContext;
36 | const domElement = createElement(
37 | type,
38 | props,
39 | rootContainerInstance,
40 | parentNamespace,
41 | );
42 | precacheFiberNode(internalInstanceHandle, domElement);
43 | updateFiberProps(domElement, props);
44 | return domElement;
45 | }
46 |
47 | function ensureListeningTo(rootContainerElement, eventName, callback) {
48 | const isDocumentOrFragment = isDocumentNode(rootContainerElement);
49 | const dom = isDocumentOrFragment
50 | ? rootContainerElement.ownerDocument
51 | : rootContainerElement;
52 | dom.addEventListener('click', callback, false);
53 | }
54 |
55 | function setInitialDOMProperties(
56 | tag,
57 | domElement,
58 | rootContainerElement,
59 | nextProps,
60 | isCustomComponentTag
61 | ) {
62 | for (const propKey in nextProps) {
63 | if (!nextProps.hasOwnProperty(propKey)) {
64 | continue;
65 | }
66 | const nextProp = nextProps[propKey];
67 | if (propKey === CHILDREN) {
68 | if (typeof nextProp === 'string') {
69 | // Avoid setting initial textContent when the text is empty. In IE11 setting
70 | // textContent on a