├── .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 | ![](https://cdn-images-1.medium.com/max/1600/1*cLqBZRht7RgR9enHet_0fQ.png) 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