方法实现及其相互之间调用顺序的思维导图和代码实现
方法
- React
- createElement
- Component
- createRef
- forwardRef
- Fragment
- createContext
- cloneElement
- PureComponent
- memo
- ReactHooks
- useReducer
- useState
- useContext
- useEffect
- useLayoutEffect
- useRef
- useImperativeHandle
- useMemo
- useCallback
- React-dom
- 合成事件和批量更新
- 生命周期
- Diff算法
思维导图

文件作用+文件代码
- react.js react所有的方法
- react-dom.js reactdom.render react-hooks DIFF算法
- Component.js 类组件更新+批量更新
- constants.js 常量定义
- utils.js 公共方法 toVdom(处理文本节点) shallowEqual(浅比较对象)
- event.js 合成事件
- 文件react.js
import {
REACT_ELEMENT, REACT_FORWARD_REF, REACT_FRAGMENT,
REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO
} from './constants';
import { toVdom, shallowEqual } from './utils';
import { Component } from './Component'
import * as hooks from './react-dom';
function createElement(type, config, children) {
let ref;
let key;
if (config) {
ref = config.ref;
key = config.key;
delete config.__source;
delete config.__self;
delete config.ref;
delete config.key;
}
let props = {
...config
};
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(toVdom);
} else if (arguments.length === 3) {
props.children = toVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
props,
ref,
key
}
}
function createRef() {
return { current: null };
}
function forwardRef(render) {
return {
$$typeof: REACT_FORWARD_REF,
render
}
}
function createContext() {
const context = {
$$typeof: REACT_CONTEXT,
_currentValue: undefined
};
context.Provider = {
$$typeof: REACT_PROVIDER,
_context: context
}
context.Consumer = {
$$typeof: REACT_CONTEXT,
_context: context
}
return context;
}
function cloneElement(element, newProps, ...newChildren) {
let oldChildren = element.props && element.props.children;
let children = [...(Array.isArray(oldChildren) ? oldChildren : [oldChildren]), ...newChildren]
.filter(item => item !== undefined && item !== null).map(toVdom)
if (children.length === 1) children = children[0];
let props = { ...element.props, ...newProps, children };
return {
...element, props
};
}
class PureComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}
}
function memo(functionComponent, compare = null) {
return { $$typeof: REACT_MEMO, compare, functionComponent }
}
const React = {
createElement,
Component,
createRef,
forwardRef,
Fragment: REACT_FRAGMENT,
createContext,
cloneElement,
PureComponent,
memo,
...hooks
}
export default React;
import {
REACT_TEXT, REACT_COMPONENT, REACT_FORWARD_REF,
REACT_FRAGMENT, MOVE, PLACEMENT, REACT_CONTEXT, REACT_PROVIDER, REACT_MEMO
} from "./constants";
import { addEvent } from './event';
import { shallowEqual } from "./utils";
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom, container) {
mount(vdom, container)
scheduleUpdate = () => {
hookIndex = 0
compareTwoVdom(container, vdom, vdom);
}
}
export function useReducer(reducer, initialState) {
hookStates[hookIndex] = hookStates[hookIndex] || initialState;
const currentIndex = hookIndex;
function dispatch(action) {
let oldState = hookStates[currentIndex];
if (reducer) {
let newState = reducer(oldState, action);
hookStates[currentIndex] = newState
} else {
let newState = typeof action === 'function' ? action(oldState) : action
hookStates[currentIndex] = newState
}
scheduleUpdate();
}
return [hookStates[hookIndex++], dispatch];
}
export function useState(initialState) {
return useReducer(null, initialState);
}
export function useContext(context) {
return context._currentValue;
}
export function useEffect(callback, deps) {
const currentIndex = hookIndex;
if (hookStates[hookIndex]) {
let [destroy, lastDeps] = hookStates[hookIndex];
let same = deps && deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++
} else {
destroy && destroy();
setTimeout(() => { const destroy = callback();
hookStates[currentIndex] = [destroy, deps]
});
hookIndex++;
}
} else {
setTimeout(() => {
const destroy = callback();
hookStates[currentIndex] = [destroy, deps]
});
hookIndex++;
}
}
export function useLayoutEffect(callback, deps) {
const currentIndex = hookIndex;
if (hookStates[hookIndex]) {
let [destroy, lastDeps] = hookStates[hookIndex];
let same = deps && deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++
} else {
destroy && destroy();
queueMicrotask(() => {
const destroy = callback();
hookStates[currentIndex] = [destroy, deps]
});
hookIndex++;
}
} else {
queueMicrotask(() => {
const destroy = callback();
hookStates[currentIndex] = [destroy, deps]
});
hookIndex++;
}
}
export function useRef() {
hookStates[hookIndex] = hookStates[hookIndex] || { current: null };
return hookStates[hookIndex++]
}
export function useImperativeHandle(ref, factory) {
ref.current = factory();
}
export function useMemo(factory, deps) {
if (hookStates[hookIndex]) {
let [lastMemoObj, lastDeps] = hookStates[hookIndex];
let same = deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++;
return lastMemoObj;
} else {
let newMemoObj = factory();
hookStates[hookIndex++] = [newMemoObj, deps];
return newMemoObj;
}
} else {
let newMemoObj = factory();
hookStates[hookIndex++] = [newMemoObj, deps];
return newMemoObj;
}
}
export function useCallback(callback, deps) {
if (hookStates[hookIndex]) {
let [lastCallback, lastDeps] = hookStates[hookIndex];
let same = deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++;
return lastCallback;
} else {
hookStates[hookIndex++] = [callback, deps];
return callback;
}
} else {
hookStates[hookIndex++] = [callback, deps];
return callback;
}
}
function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
}
function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom;
if (type && type.$$typeof === REACT_MEMO) {
return mountMemoComponent(vdom);
} else if (type && type.$$typeof === REACT_PROVIDER) {
return mountProviderComponent(vdom);
} else if (type && type.$$typeof === REACT_CONTEXT) {
return mountContextComponent(vdom);
} else if (type && type.$$typeof === REACT_FORWARD_REF) {
return mountForwardComponent(vdom);
} else if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (type === REACT_FRAGMENT) {
dom = document.createDocumentFragment();
} else if (typeof type === 'function') {
if (type.isReactComponent === REACT_COMPONENT) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (typeof props === 'object') {
updateProps(dom, {}, props);
if (props.children && typeof props.children === 'object' && props.children.$$typeof) {
props.children.mountIndex = 0;
mount(props.children, dom);
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
function mountMemoComponent(vdom) {
let { type: { functionComponent }, props } = vdom;
vdom.prevProps = props;
let renderVdom = functionComponent(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountProviderComponent(vdom) {
let { type, props } = vdom;
let context = type._context;
context._currentValue = props.value;
let renderVdom = props.children;
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountContextComponent(vdom) {
let { type, props } = vdom;
let context = type._context;
let renderVdom = props.children(context._currentValue);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
let { type: ClassComponent, props, ref } = vdom;
let classInstance = new ClassComponent(props);
if (ClassComponent.contextType) {
classInstance.context = ClassComponent.contextType._currentValue;
}
vdom.classInstance = classInstance;
if (classInstance.UNSAFE_componentWillMount) {
classInstance.UNSAFE_componentWillMount();
}
if (ref) ref.current = classInstance;
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
function mountFunctionComponent(vdom) {
let { type: FunctionComponent, props } = vdom;
let renderVdom = FunctionComponent(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
childrenVdom[i].mountIndex = i;
mount(childrenVdom[i], parentDOM);
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
console.log(vdom,'vdom');
if (vdom.classInstance) {
vdom = vdom.classInstance.oldRenderVdom;
} else {
vdom = vdom.oldRenderVdom;
}
return findDOM(vdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {
if (!oldVdom && !newVdom) {
return;
} else if (oldVdom && !newVdom) {
unMountVdom(oldVdom);
} else if (!oldVdom && newVdom) {
let newDOM = createDOM(newVdom);
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else if (oldVdom && newVdom && oldVdom.type !== newVdom.type) {
unMountVdom(oldVdom);
let newDOM = createDOM(newVdom);
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else {
updateElement(parentDOM, oldVdom, newVdom);
}
}
function updateElement(parentDOM, oldVdom, newVdom) {
if (oldVdom.type.$$typeof === REACT_MEMO) {
updateMemoComponent(oldVdom, newVdom);
} else if (oldVdom.type.$$typeof === REACT_CONTEXT) {
updateContextComponent(oldVdom, newVdom);
} else if (oldVdom.type.$$typeof === REACT_PROVIDER) {
updateProviderComponent(oldVdom, newVdom);
} else if (oldVdom.type === REACT_TEXT) {
let currentDOM = newVdom.dom = findDOM(oldVdom);
if (oldVdom.props !== newVdom.props) {
currentDOM.textContent = newVdom.props;
}
} else if (typeof oldVdom.type === 'string') {
let currentDOM = newVdom.dom = findDOM(oldVdom);
updateProps(currentDOM, oldVdom.props, newVdom.props);
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
} else if (oldVdom.type === REACT_FRAGMENT) {
let currentDOM = newVdom.dom = findDOM(oldVdom);
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
} else if (typeof oldVdom.type === 'function') {
if (oldVdom.type.isReactComponent) {
updateClassComponent(oldVdom, newVdom);
} else {
updateFunctionComponent(oldVdom, newVdom);
}
}
}
function updateMemoComponent(oldVdom, newVdom) {
let { type: { compare }, prevProps } = oldVdom;
compare = compare || shallowEqual
if (compare(prevProps, newVdom.props)) {
newVdom.prevProps = prevProps;
newVdom.oldRenderVdom = oldVdom.oldRenderVdom;
} else {
let oldDOM = findDOM(oldVdom);
let { type: { functionComponent }, props } = newVdom;
let renderVdom = functionComponent(props);
compareTwoVdom(oldDOM.parentNode, oldVdom.oldRenderVdom, renderVdom)
newVdom.prevProps = props;
newVdom.oldRenderVdom = renderVdom;
}
}
function updateProviderComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
let parentDOM = currentDOM.parentNode;
let { type, props } = newVdom;
let context = type._context;
context._currentValue = props.value;
let renderVdom = props.children;
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
}
function updateContextComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
let parentDOM = currentDOM.parentNode;
let { type, props } = newVdom;
let context = type._context;
let newRenderVdom = props.children(context._currentValue);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
function updateFunctionComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
if (!currentDOM) return;
let { type, props } = newVdom;
let newRenderVdom = type(props);
compareTwoVdom(currentDOM.parentNode, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
function updateClassComponent(oldVdom, newVdom) {
const classInstance = newVdom.classInstance = oldVdom.classInstance;
if (classInstance.UNSAFE_componentWillReceiveProps) {
classInstance.UNSAFE_componentWillReceiveProps();
}
classInstance.updater.emitUpdate(newVdom.props);
}
function updateChildren(parentDOM, oldVChildren, newVChildren) {
oldVChildren = (Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren]).filter(item => typeof item !== 'undefined' && item !== null);
newVChildren = Array.isArray(newVChildren) ? newVChildren : [newVChildren].filter(item => typeof item !== 'undefined' && item !== null);
const keyedOldMap = {};
let lastPlacedIndex = 0;
oldVChildren.forEach((oldVChild, index) => {
let oldKey = oldVChild.key ? oldVChild.key : index;
keyedOldMap[oldKey] = oldVChild;
});
const patch = [];
newVChildren.forEach((newVChild, index) => {
newVChild.mountIndex = index;
const newKey = newVChild.key ? newVChild.key : index;
let oldVChild = keyedOldMap[newKey];
if (oldVChild) {
updateElement(findDOM(oldVChild).parentNode, oldVChild, newVChild);
if (oldVChild.mountIndex < lastPlacedIndex) {
patch.push({
type: MOVE,
oldVChild,
newVChild,
mountIndex: index
});
}
delete keyedOldMap[newKey];
lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);
} else {
patch.push({
type: PLACEMENT,
newVChild,
mountIndex: index
});
}
});
let moveVChildren = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);
Object.values(keyedOldMap).concat(moveVChildren).forEach(oldVChild => {
let currentDOM = findDOM(oldVChild);
currentDOM.remove();
});
patch.forEach(action => {
let { type, oldVChild, newVChild, mountIndex } = action;
let childNodes = parentDOM.childNodes;
if (type === PLACEMENT) {
let newDOM = createDOM(newVChild);
let childNode = childNodes[mountIndex];
if (childNode) {
parentDOM.insertBefore(newDOM, childNode);
} else {
parentDOM.appendChild(newDOM);
}
} else if (type === MOVE) {
let oldDOM = findDOM(oldVChild);
let childNode = childNodes[mountIndex];
if (childNode) {
parentDOM.insertBefore(oldDOM, childNode);
} else {
parentDOM.appendChild(oldDOM);
}
}
});
}
function unMountVdom(vdom) {
let { props, ref, classInstance } = vdom;
let currentDOM = findDOM(vdom);
if (classInstance && classInstance.UNSAFE_componentWillMount) {
classInstance.UNSAFE_componentWillMount();
}
if (ref) {
ref.current = null;
}
if (props.children) {
let children = (Array.isArray(props.children) ? props.children : [props.children]).filter(item => typeof item !== 'undefined' && item !== null);
children.forEach(unMountVdom);
}
if (currentDOM) currentDOM.remove();
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (const key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key]
}
}
for (const key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
delete dom[key];
}
}
}
const ReactDOM = {
render,
createPortal: render
}
export default ReactDOM;
import { REACT_COMPONENT } from './constants';
import { findDOM, compareTwoVdom } from './react-dom'
export const updateQueue = {
isBatchingUpdate: false,
updaters: new Set(),
batchUpdate() {
updateQueue.isBatchingUpdate = false;
for (const updater of updateQueue.updaters) {
updater.updateComponent();
}
updateQueue.updaters.clear();
}
}
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
this.callbacks = [];
}
addState(partialState, callback) {
this.pendingStates.push(partialState);
if (callback) {
this.callbacks.push(callback);
}
this.emitUpdate();
}
emitUpdate(nextProps) {
this.nextProps = nextProps;
if (updateQueue.isBatchingUpdate) {
updateQueue.updaters.add(this);
} else {
this.updateComponent();
}
}
updateComponent() {
const { classInstance, pendingStates, nextProps, callbacks } = this;
if (nextProps || pendingStates.length > 0) {
let newState = this.getState();
shouldUpdate(classInstance, nextProps, newState);
}
}
getState() {
const { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach(nextState => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = {
...state, ...nextState
};
});
pendingStates.length = 0;
return state;
}
}
function shouldUpdate(classInstance, nextProps, newState) {
let willUpdate = true;
if (classInstance.shouldComponentUpdate &&
!classInstance.shouldComponentUpdate(nextProps, newState)) {
willUpdate = false;
}
if (willUpdate && classInstance.UNSAFE_componentWillUpdate) {
classInstance.UNSAFE_componentWillUpdate();
}
if (nextProps) {
classInstance.props = nextProps;
}
classInstance.state = newState;
if (willUpdate)
classInstance.forceUpdate();
}
export class Component {
static isReactComponent = REACT_COMPONENT;
constructor(props) {
this.props = props;
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom;
let oldDOM = findDOM(oldRenderVdom);
if (this.constructor.contextType) {
this.context = this.constructor.contextType._currentValue;
}
if (this.constructor.getDerivedStateFromProps) {
let newState = this.constructor.getDerivedStateFromProps(this.props, this.state);
if (newState) {
this.state = {
...this.state, ...newState
};
}
}
let snapShot = this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
if (this.componentDidUpdate) {
this.componentDidUpdate(this.props, this.state, snapShot);
}
}
}
import { updateQueue } from './Component';
export function addEvent(dom, eventType, handler) {
let store = dom._store_ || (dom._store_ = {});
store[eventType] = handler;
if (!document[eventType]) {
document[eventType] = dispatchEvent;
}
}
function dispatchEvent(event) {
const { type, target } = event;
const eventType = `on${type}`;
updateQueue.isBatchingUpdate = true;
let syntheticEvent = createSyntheticEvent(event);
let currentTarget = target;
while (currentTarget) {
syntheticEvent.currentTarget = currentTarget;
let { _store_ } = currentTarget;
let handler = _store_ && _store_[eventType];
handler && handler(syntheticEvent);
if (syntheticEvent.isPropagationStopped) {
break;
}
currentTarget = currentTarget.parentNode;
}
updateQueue.batchUpdate();
}
function createSyntheticEvent(nativeEvent) {
let syntheticEvent = {};
for (let key in nativeEvent) {
let value = nativeEvent[key];
if (typeof value === 'function')
value = value.bind(nativeEvent);
syntheticEvent[key] = value;
}
syntheticEvent.nativeEvent = nativeEvent;
syntheticEvent.isPropagationStopped = false;
syntheticEvent.isDefaultPrevented = false;
syntheticEvent.preventDefault = preventDefault;
syntheticEvent.stopPropagation = stopPropagation;
return syntheticEvent;
}
function preventDefault() {
this.isDefaultPrevented = true;
let event = this.nativeEvent;
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
function stopPropagation() {
this.isPropagationStopped = true;
let event = this.nativeEvent;
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = false;
}
}
export const REACT_TEXT = Symbol('react.text');
export const REACT_COMPONENT = Symbol('react.component');
export const REACT_ELEMENT = Symbol('react.element');
export const REACT_FORWARD_REF = Symbol('react.forward_ref')
export const REACT_FRAGMENT = Symbol('react.fragment')
export const PLACEMENT = 'PLACEMENT';
export const MOVE = 'MOVE';
export const REACT_PROVIDER = Symbol('react.provider')
export const REACT_CONTEXT = Symbol('react.context')
export const REACT_MEMO = Symbol('react.memo')
import { REACT_TEXT, REACT_ELEMENT } from './constants';
export function toVdom(element) {
return typeof element === 'string' || typeof element === 'number' ? {
$$typeof: REACT_ELEMENT, type: REACT_TEXT, props: element
} : element
}
export function shallowEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}