Repository: tranbathanhtung/react-fiber-implement Branch: master Commit: 02970bea916e Files: 41 Total size: 84.3 KB Directory structure: gitextract_sdllfs4y/ ├── .babelrc ├── .flowconfig ├── .gitignore ├── README.md ├── index.html ├── index.js ├── package.json ├── src/ │ ├── core/ │ │ ├── h.js │ │ ├── life-cycle.js │ │ └── with-state.js │ ├── dom/ │ │ ├── config.js │ │ ├── constants.js │ │ ├── index.js │ │ └── utils/ │ │ ├── append.js │ │ ├── createElement.js │ │ ├── getDocumentByElement.js │ │ ├── insert.js │ │ ├── remove.js │ │ ├── textElement.js │ │ └── validate.js │ ├── fiber/ │ │ ├── begin-work.js │ │ ├── children.js │ │ ├── commit-work.js │ │ ├── complete-work.js │ │ ├── f-life-cycle.js │ │ ├── f-node.js │ │ ├── f-with.js │ │ ├── host-context.js │ │ ├── reconciler.js │ │ ├── root-render.js │ │ ├── scheduler.js │ │ └── stack.js │ ├── shared/ │ │ ├── effect-tag.js │ │ ├── shallowEqual.js │ │ ├── status-work.js │ │ ├── tag.js │ │ ├── types.js │ │ ├── validate.js │ │ └── with-effect.js │ └── structures/ │ └── linked-list.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "plugins": [ "transform-react-jsx", "@babel/plugin-proposal-class-properties", ], "presets": [ "@babel/flow", "@babel/preset-env", "@babel/preset-react" ] } ================================================ FILE: .flowconfig ================================================ [ignore] [include] [libs] [lints] [options] [strict] ================================================ FILE: .gitignore ================================================ # dependencies /node_modules yarn.lock ================================================ FILE: README.md ================================================ ## React fiber 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. ## Something you should read and learn before start read source code #### Keyword, Algorithms and Data Structure Used - Single linked list, Circular linked list - Simple stack and queue - Recursive - Structural sharing - [Reconciliation](https://reactjs.org/docs/reconciliation.html) - Scheduler - Bitwise Operators - JSX - DOM ###### And more - [React Components, Elements, and Instances](https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html) - [Design Principles](https://reactjs.org/docs/design-principles.html) - [React Fiber resources](https://github.com/koba04/react-fiber-resources) - [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) - [In-depth explanation of state and props update in React ](https://medium.com/react-in-depth/in-depth-explanation-of-state-and-props-update-in-react-51ab94563311) ###### Recommend - [Lin Clark - A Cartoon Intro to Fiber - React Conf 2017 ](https://www.youtube.com/watch?v=ZCuYPiUIONs) - [A look inside React Fiber ](https://makersden.io/blog/look-inside-fiber/) - [Build your own React Fiber](https://engineering.hexacta.com/didact-fiber-incremental-reconciliation-b2fe028dcaec) - [React Fiber Architecture @acdlite](https://github.com/acdlite/react-fiber-architecture) and [React Fiber Architecture @SaeedMalikx](https://github.com/SaeedMalikx/React-Fiber-Architecture) ## Overview ### Fiber tree ![](https://cdn-images-1.medium.com/max/1600/1*cLqBZRht7RgR9enHet_0fQ.png) [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) ### Keyword ``` work (unitOfWork): A component, node element => fiber current: Current fiber what is displayed on browser WIP (workInProgress): New fiber tree we will build fiber: { type: string | Function ('div', 'span', function Button) instanceNode: HTMLElement (div, span) return: fiber (parent of fiber) child: fiber (child of fiber) sibling: fiber (sibling of fiber) alternate: link current - WIP and WIP - current effectTag: number (give we know what will happen this fiber) } requestIdleCallback main function: createWorkInProgress() beginWork() reconcileChildren() completeWork() commitWork() ``` ### Process of first render ``` Render -> Reconciler -> Scheduler -> Begin Work (build fiber tree) -> ChildReconciler(create child and effectTag) -> if work has child we will continue to run beginWork -> no child -> 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 -> Commit Work : It will base on list effect tag to commit each fiber (Placement, Update, Delete, Lifecycle) // In first render current fiber is null. // current is workInProgress when commit ``` ### Process when update ``` Do something -> Get current Fiber what corresponding to the component -> Recursive to find Root -> Clone fiber from root to component has update -> Begin Work from this fiber (it's maybe clone fiber when children of component use memo, pure component or use shouldComponentUpdate) -> Complete Work -> Commit Work ``` ### About With(Hook v16.7) ``` Hooks are stored as a linked list on the fiber's prevState field of fiber. current tree - current hook <=> WIP - WIP hook ``` ================================================ FILE: index.html ================================================
================================================ FILE: index.js ================================================ /** @jsx h */ import { h } from './src/core/h'; import { withState } from './src/core/with-state'; import { lifeCycle } from './src/core/life-cycle'; import { render } from './src/dom'; let list = [] for (let i = 0; i < 5; i++) { list = [ ...list, { name: 'tung', age: 10, id: i, } ] } const User = ({ user, update, remove }) => { lifeCycle({ mounted() { console.log('mounted User') return () => console.log('unmounted User') } }) return (

Name: {user.name}

Age: {user.age}

) } const Test = ({ title }) => { const [count, dispatch] = withState(1); const [users, setUsers] = withState(list); function add() { const newUsers = [...users, { name: 'teng', age: 12, id: users.length }]; setUsers(newUsers); } function update(id) { const newUsers = users.map(u => u.id === id ? {...u, name: 'aaaa', age: 15} : u); setUsers(newUsers); } function remove(id) { const newUsers = users.filter(u => u.id !== id); setUsers(newUsers); } return (

{count}

{users.map(user => )}
) } render(, document.getElementById('root')); ================================================ FILE: package.json ================================================ { "name": "react-fiber-implement", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack-dev-server --content-base dist", "build": "webpack" }, "author": "tungtbt", "license": "MIT", "devDependencies": { "@babel/cli": "^7.1.5", "@babel/core": "^7.1.6", "@babel/plugin-proposal-class-properties": "^7.1.0", "@babel/preset-env": "^7.1.6", "@babel/preset-flow": "^7.0.0", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.4", "babel-plugin-transform-react-jsx": "^6.24.1", "extract-text-webpack-plugin": "^3.0.2", "flow-bin": "^0.86.0", "html-webpack-plugin": "^3.2.0", "replace-bundle-webpack-plugin": "^1.0.0", "webpack": "^4.26.0", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.10" } } ================================================ FILE: src/core/h.js ================================================ // @flow import { isNil, isFunction } from '../shared/validate'; const hasOwnProperty = Object.prototype.hasOwnProperty; const hasSymbol = typeof Symbol === 'function' && Symbol.for; export const REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7; export const REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb; const RESERVED_PROPS = { key: true, ref: true, }; function hasValidKey(options) { return options.key !== undefined; } function VNode(type, props, key) { const vnode = { $$typeof: REACT_ELEMENT_TYPE, type: type, props: props, key: key, } return vnode; } export function h(type, options, children) { let propName; const props = {}; let key = null; if (!isNil(options)) { if (hasValidKey(options)) { key = '' + options.key; } for (propName in options) { // Why use hasOwnProperty.call instead of someObj.hasOwnProperty? // 1.hasOwnProperty is defined on the object as something else // 2.The object in question is being used as a map and doesn't inherit from Object.prototype, so it doesn't have hasOwnProperty: if (hasOwnProperty.call(options, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = options[propName]; } } } // Children can be more than one argument, and those are transferred onto // the newly allocated props object. // if createElement has 5 params number of children will be 3 const childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { // create Array empty has childrenLength element const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { // create array child childArray[i] = arguments[i + 2]; } props.children = childArray; } // Resolve default props if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } return VNode(type, props, key); } ================================================ FILE: src/core/life-cycle.js ================================================ import { withLifeCycle } from '../fiber/f-with'; import { Update as UpdateEffect, Passive } from '../shared/effect-tag'; import { NoEffect as NoHookEffect, UnmountSnapshot, UnmountMutation, MountMutation, MountLayout, UnmountPassive, MountPassive, } from '../shared/with-effect'; export const lifeCycle = ({mounted, destroyed, updated}) => { return withLifeCycle(UpdateEffect | Passive, UnmountPassive | MountPassive, {mounted, destroyed, updated}); } ================================================ FILE: src/core/with-state.js ================================================ import { withReducer } from '../fiber/f-with'; export function withState(initialState) { return withReducer(initialState); } ================================================ FILE: src/dom/config.js ================================================ import createElement from './utils/createElement'; import { createTextNode, setTextContent, resetTextContent } from './utils/textElement'; import { appendChildToContainer, appendInitialChild, appendChild } from './utils/append'; import { removeChildFromContainer, removeChild } from './utils/remove'; import { insertInContainerBefore, insertBefore } from './utils/insert'; import { isDocumentNode } from './utils/validate'; const CHILDREN = 'children'; // Assumes there is no parent namespace. const randomKey = Math.floor((Math.random() * 100) + 1); const internalInstanceKey = '__reactInternalInstance$' + randomKey; const internalEventHandlersKey = '__reactEventHandlers$' + randomKey; export function precacheFiberNode(hostInst, node) { node[internalInstanceKey] = hostInst; } export function getFiberCurrentPropsFromNode(node) { return node[internalEventHandlersKey] || null; } export function updateFiberProps(node, props) { node[internalEventHandlersKey] = props; } export function createDomNodeInstance( type, props, rootContainerInstance, hostContext, internalInstanceHandle) { let parentNamespace; parentNamespace = hostContext; const domElement = createElement( type, props, rootContainerInstance, parentNamespace, ); precacheFiberNode(internalInstanceHandle, domElement); updateFiberProps(domElement, props); return domElement; } function ensureListeningTo(rootContainerElement, eventName, callback) { const isDocumentOrFragment = isDocumentNode(rootContainerElement); const dom = isDocumentOrFragment ? rootContainerElement.ownerDocument : rootContainerElement; dom.addEventListener('click', callback, false); } function setInitialDOMProperties( tag, domElement, rootContainerElement, nextProps, isCustomComponentTag ) { for (const propKey in nextProps) { if (!nextProps.hasOwnProperty(propKey)) { continue; } const nextProp = nextProps[propKey]; if (propKey === CHILDREN) { if (typeof nextProp === 'string') { // Avoid setting initial textContent when the text is empty. In IE11 setting // textContent on a