此文項目代碼:https://github.com/bei-yang/I-want-to-be-an-architect
碼字不易,辛苦點個star,感謝!
引言
此篇文章主要涉及以下內(nèi)容:
-
react核心api - 探究
setState - 探究
diff算法
學習資源
react核心API
const React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
lazy,
memo,
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
// Note: some APIs are added with feature flags.
// Make sure that stable builds for open source
// don't modify the React object to avoid deopts.
// Also let's not expose their names in stable builds.
if (enableStableConcurrentModeAPIs) {
React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
React.Profiler = REACT_PROFILER_TYPE;
React.unstable_ConcurrentMode = undefined;
React.unstable_Profiler = undefined;
}
export default React;
核心精簡后:
const React = {
createElement,
Component
}
react-dom主要是render邏輯
最核心的api:
- React.createElement:創(chuàng)建虛擬dom
- React.Component:實現(xiàn)自定義組件
- ReactDOM.render:渲染真實DOM
JSX
JSX是對js的擴展,能帶來更好的執(zhí)行速度。
在線嘗試
JSX預處理前:

JSX預處理后:

使用自定義組件的情況
function Comp(props) {
return <h2>hi {props.name}</h2>;
}
const jsx = (
<div id="demo">
<span>hi</span>
<Comp name="kaikeba" />
</div>
);
console.log(jsx);
ReactDOM.render(jsx, document.querySelector("#root"));
build后
function Comp(props) {
return React.createElement(
"h2",
null,
"hi ",
props.name
)
}
ReactDOM.render(React.createElement(
"div",
{ id: "demo" },
React.createElement("span", null, "hi"),
React.createElement(Comp, { name: "kaikeba" })
), mountNode)
構建vdom用js對象形式來描述dom樹結構一一對應

輸出vdom觀察其結構
實現(xiàn)三大接口:React.createElement,React.Component,ReactDom.render
CreateElement
- 創(chuàng)建./src/kreact.js,它需要包含createElement方法
function createElement() {
console.log(arguments)
}
export default {createElement}
- 修改index.js實際引入kcreate,測試
import React from "./kreact"
- 更新kcreate.js:為createElement添加參數(shù)并返回結果對象
function createElement(type, props,...children){
// 父元素需要子元素返回結果,這里可以通過JSX編譯后的代碼得出結論
props.children = children;
return {type,props}
}
export default {createElement}
render
- kreact-dom需要提供一個render函數(shù),能夠?qū)dom渲染出來,這里先打印vdom
function render(vnode, container){
container.innerHTML = `<pre>${JSON.stringify(vnode,null,2)}</pre>`
}
export default {render}
頁面效果

創(chuàng)建kvdom.js:將createElement返回的結果對象轉(zhuǎn)換為vdom
- 傳遞給createElement的組件有三種組件類型,使用type屬性標識,并且抽離vdom相關代碼到kvdom.js
- dom組件
- 函數(shù)式組件
- class組件
// kvdom.js
export function createVNode(vtype, type, props) {
let vnode = {
vtype: vtype, // 用于區(qū)分函數(shù)組件、類組件、原生組件
type: type,
props: props
}
return vnode
}
- 添加Component類,kreact.js
export class Component {
// 這個組件來區(qū)分是不是class組件
static isClassComponent = true
constructor(props){
this.props = props
this.state = {}
}
}
淺層封裝,setState現(xiàn)在只是一個占位符
- 添加類組件,index.js
import React, {Component} from "./kreact";
class Comp2 extends Component {
render() {
return (
<div>
<h2>hi {this.props.name}</h2>
</div>
)
}
}
- 判斷組件類型,kreact.js
import { createVNode } from "./kvdom";
function createElement(type, props, ...children) {
//...
//判斷組件類型
let vtype;
if (typeof type === "string") {
// 原生標簽
vtype = 1;
} else if (typeof type === "function") {
if (type.isReactComponent) {
// 類組件
vtype = 2;
} else {
// 函數(shù)組件
vtype = 3;
}
}
return createVNode(vtype, type, props);
}
- 轉(zhuǎn)換vdom為真實dom
export function initVNode(vnode) {
let { vtype } = vnode;
if (!vtype) {
// 沒有vtype,是一個文本節(jié)點
return document.createTextNode(vnode);
}
if (vtype === 1) {
// 1是原生元素
return createElement(vnode);
} else if (vtype === 2) {
// 2是類組件
return createClassComp(vnode);
} else if (vtype === 3) {
// 3是函數(shù)組件
return createFuncComp(vnode);
}
}
// 創(chuàng)建原生元素
function createElement(vnode) {
const { type, props } = vnode;
const node = document.createElement(type);
// 過濾key,children等特殊props
const { key, children, ...rest } = props;
Object.keys(rest).forEach(k => {
// 需要特殊處理的屬性名:class和for
if (k === "className") {
node.setAttribute("class", rest[k]);
} else if (k === "htmlFor") {
node.setAttribute("for", rest[k]);
} else {
node.setAttribute(k, rest[k]);
}
});
// 遞歸初始化子元素
children.forEach(c => {
// 子元素也是一個vnode,所以調(diào)用initVNode
node.appendChild(initVNode(c));
});
return node;
}
// 創(chuàng)建函數(shù)組件
function createFuncComp(vnode) {
const { type, props } = vnode;
// type是函數(shù),它本身即是渲染函數(shù),返回vdom
const newNode = type(props);
return initVNode(newNode);
}
// 創(chuàng)建類組件
function createClassComp(vnode) {
const { type } = vnode;
// 創(chuàng)建類組件實例
const component = new type(vnode.props);
// 調(diào)用其render獲得vdom
const newNode = component.render();
return initVNode(newNode);
}
- 執(zhí)行渲染,kreact-dom.js
import { initVNode } from "./kvdom";
function render(vnode, container) {
const node = initVNode(vnode);
container.appendChild(node);
// container.innerHTML = `<pre>${JSON.stringify(vnode,null,2)}</pre>`
}
- 渲染vdom數(shù)組,index.js
class Comp2 extends Component {
render() {
const users=[{id:1,name:'tom'},{id:2,name:'jerry'}]
return (
<div>
<h2>hi {this.props.name}</h2>
<ul>
{users.map(user=>(<li key={user.id}>{user.name}</li>))}
</ul>
</div>
)
}
}
// 測試報錯,因為kvdom中沒有考慮到該情況
- 處理vdom數(shù)組,kvdom.js
children.forEach(c => {
if (Array.isArray(c)) {// c是vdom數(shù)組的情況
c.forEach(n => {
node.appendChild(initVNode(n));
});
} else {
node.appendChild(initVNode(c));
}
});
總結
- webpack+babel編譯時,替換JSX為React.createElement(type,props,...children)
- 所有React.createElement()執(zhí)行結束后得到一個JS對象,它能夠完整描述dom結構,稱之為vdom
- ReactDOM.render(vdom,container)可以將vdom轉(zhuǎn)換為dom并追加到container中,通過遞歸遍歷vdom樹,根據(jù)vtype不同,執(zhí)行不同邏輯:vtype為1生成原生元素,為2則需要將類組件實例化并執(zhí)行其render將返回vdom初始化,為3則將函數(shù)執(zhí)行并將返回vdom初始化。
PureComponent
繼承Component,主要是設置了shouldComponentUpdate生命周期
import shallowEqual from './shallowEqual'
import Component from './Component'
export default function PureComponent(props, context) {
Component.call(this, props, context)
}
PureComponent.prototype = Object.create(Component.prototype)
PureComponent.prototype.constructor = PureComponent
PureComponent.prototype.isPureReactComponent = true
PureComponent.prototype.shouldComponentUpdate = shallowCompare
function shallowCompare(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState)
}
setState
class組件的特點,就是擁有特殊狀態(tài)并且可以通過setState更新狀態(tài),從而重新渲染視圖,是學習react中最重要的api。
setState并沒有直接操作去渲染,而是執(zhí)行了一個異步的update隊列,我們使用一個類來專門管理,./kkreact/Component.js
export let updateQueue = {
updaters: [],
isPending: false,
add(updater) {
_.addItem(this.updaters, updater);
},
batchUpdate() {
if (this.isPending) {
return;
}
this.isPending = true;
/*
each updater.update may add new updater to updateQueue
clear them with a loop
event bubbles from bottom-level to top-level
reverse the updater order can merge some props and state and reduce the
refresh times
see Updater.update method below to know why
*/
let { updaters } = this;
let updater;
while ((updater = updaters.pop())) {
updater.updateComponent();
}
this.isPending = false;
}
};
function Updater(instance) {
this.instance = instance;
this.pendingStates = [];
this.pendingCallbacks = [];
this.isPending = false;
this.nextProps = this.nextContext = null;
this.clearCallbacks = this.clearCallbacks.bind(this);
}
Updater.prototype = {
emitUpdate(nextProps, nextContext) {
this.nextProps = nextProps;
this.nextContext = nextContext;
// receive nextProps!! should update immediately
nextProps || !updateQueue.isPending
? this.updateComponent()
: updateQueue.add(this);
},
updateComponent() {
let { instance, pendingStates, nextProps, nextContext } = this;
if (nextProps || pendingStates.length > 0) {
nextProps = nextProps || instance.props;
nextContext = nextContext || instance.context;
this.nextProps = this.nextContext = null;
// merge the nextProps and nextState and update by one time
shouldUpdate(
instance,
nextProps,
this.getState(),
nextContext,
this.clearCallbacks
);
}
},
addState(nextState) {
if (nextState) {
_.addItem(this.pendingStates, nextState);
if (!this.isPending) {
this.emitUpdate();
}
}
},
replaceState(nextState) {
let { pendingStates } = this;
pendingStates.pop();
// push special params to point out should replace state
_.addItem(pendingStates, [nextState]);
},
getState() {
let { instance, pendingStates } = this;
let { state, props } = instance;
if (pendingStates.length) {
state = _.extend({}, state);
pendingStates.forEach(nextState => {
let isReplace = _.isArr(nextState);
if (isReplace) {
nextState = nextState[0];
}
if (_.isFn(nextState)) {
nextState = nextState.call(instance, state, props);
}
// replace state
if (isReplace) {
state = _.extend({}, nextState);
} else {
_.extend(state, nextState);
}
});
pendingStates.length = 0;
}
return state;
},
clearCallbacks() {
let { pendingCallbacks, instance } = this;
if (pendingCallbacks.length > 0) {
this.pendingCallbacks = [];
pendingCallbacks.forEach(callback => callback.call(instance));
}
},
addCallback(callback) {
if (_.isFn(callback)) {
_.addItem(this.pendingCallbacks, callback);
}
}
};
虛擬dom&&diff算法
常見問題:react virtual dom 是什么?說一下diff算法?
what?用JavaScript對象表示DOM信息和結構,當狀態(tài)變更的時候,重新渲染這個JavaScript的對象結構,這個JavaScript對象稱為virtual dom;
傳統(tǒng)dom渲染流程

diff算法

diff策略
-
同級比較,Web UI中DOM節(jié)點跨層級的移動操作特別少,可以忽略不計。
- 擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構。
例如:div->p,CompA->CompB - 對于同一層級的一組子節(jié)點,通過唯一的key進行區(qū)分。
基于以上三個前提策略,React分別對tree diff、component diff以及element diff進行算法優(yōu)化,事實也證明這三個前提策略是合理且準確的,它保證了整體界面構建的性能。
- tree diff
- component diff
- element diff
element diff
差異類型:
- 替換原來的節(jié)點,例如把div換成了p,Comp1換成Comp2;
- 移動、刪除、新增子節(jié)點,例如ul中的多個子節(jié)點li中出現(xiàn)了順序互換;
- 修改了節(jié)點的屬性,例如節(jié)點類名發(fā)生了變化;
- 對于文本節(jié)點,文本內(nèi)容可能會改變。
重排(reorder)操作:
INSERT_MARKUP(插入)、MOVE_EXISTING(移動)和REMOVE_NODE(刪除)。
INSERT_MARKUP,新的component類型不在老集合里,即是全新的節(jié)點,需要對新節(jié)點執(zhí)行插入操作。
MOVE_EXISTING,在老集合有新component類型,且element是可更新的類型。
-
REMOVE_NODE,老component類型,在新集合里也有,但對應的element不同則不能直接復用和更新,需要執(zhí)行刪除操作,或者老component不在新集合里的,也需要執(zhí)行刪除操作。
ReactDom.render
function renderTreeIntoContainer(vnode, container, callback, parentContext) {
if (!vnode.vtype) {
throw new Error(`cannot render ${vnode} to container`);
}
if (!isValidContainer(container)) {
throw new Error(`container ${container} is not a DOM element`);
}
let id = container[COMPONENT_ID] || (container[COMPONENT_ID] = _.getUid());
let argsCache = pendingRendering[id];
// component lify cycle method maybe call root rendering
// should bundle them and render by only one time
if (argsCache) {
if (argsCache === true) {
pendingRendering[id] = argsCache = { vnode, callback, parentContext };
} else {
argsCache.vnode = vnode;
argsCache.parentContext = parentContext;
argsCache.callback = argsCache.callback
? _.pipe(
argsCache.callback,
callback
)
: callback;
}
return;
}
pendingRendering[id] = true;
let oldVnode = null;
let rootNode = null;
if ((oldVnode = vnodeStore[id])) {
rootNode = compareTwoVnodes(
oldVnode,
vnode,
container.firstChild,
parentContext
);
} else {
rootNode = initVnode(vnode, parentContext, container.namespaceURI);
var childNode = null;
while ((childNode = container.lastChild)) {
container.removeChild(childNode);
}
container.appendChild(rootNode);
}
vnodeStore[id] = vnode;
let isPending = updateQueue.isPending;
updateQueue.isPending = true;
clearPending();
argsCache = pendingRendering[id];
delete pendingRendering[id];
let result = null;
if (typeof argsCache === "object") {
result = renderTreeIntoContainer(
argsCache.vnode,
container,
argsCache.callback,
argsCache.parentContext
);
} else if (vnode.vtype === VELEMENT) {
result = rootNode;
} else if (vnode.vtype === VCOMPONENT) {
result = rootNode.cache[vnode.uid];
}
if (!isPending) {
updateQueue.isPending = false;
updateQueue.batchUpdate();
}
if (callback) {
callback.call(result);
}
return result;
}
redux
- 為什么需要redux,他是什么
- 解決了什么問題
- 如何使用
- 單向數(shù)據(jù)流
export function createStore(reducer, enhancer) {
if (enhancer) {
return enhancer(createStore)(reducer);
}
let currentState = {};
let currentListeners = [];
function getState() {
return currentState;
}
function subscribe(listener) {
currentListeners.push(listener);
}
function dispatch(action) {
currentState = reducer(currentState, action);
currentListeners.forEach(v => v());
return action;
}
dispatch({ type: "@kaikeba/sheng" });
return { getState, subscribe, dispatch };
}
export function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = store.dispatch;
const midApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
};
const middlewareChain = middlewares.map(middleware => middleware(midApi));
dispatch = compose(...middlewareChain)(store.dispatch);
return {
...store,
dispatch
};
};
}
export function compose(...funcs) {
if (funcs.length == 0) {
return arg => arg;
}
if (funcs.length == 1) {
return funcs[0];
}
return funcs.reduce((ret, item) => (...args) => ret(item(...args)));
}
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args));
}
export function bindActionCreators(creators, dispatch) {
return Object.keys(creators).reduce((ret, item) => {
ret[item] = bindActionCreator(creators[item], dispatch);
return ret;
}, {});
}
react-redux
import React from "react";
import PropTypes from "prop-types";
import { bindActionCreators } from "./woniu-redux";
export const connect = (
mapStateToProps = state => state,
mapDispatchToProps = {}
) => WrapComponent => {
return class ConnectComponent extends React.Component {
static contextTypes = {
store: PropTypes.object
};
constructor(props, context) {
super(props, context);
this.state = {
props: {}
};
}
componentDidMount() {
const { store } = this.context;
store.subscribe(() => this.update());
this.update();
}
update() {
const { store } = this.context;
const stateProps = mapStateToProps(store.getState());
const dispatchProps = bindActionCreators(
mapDispatchToProps,
store.dispatch
);
this.setState({
props: {
...this.state.props,
...stateProps,
...dispatchProps
}
});
}
render() {
return <WrapComponent {...this.state.props}></WrapComponent>;
}
};
};
export class Provider extends React.Component {
static childContextTypes = {
store: PropTypes.object
};
getChildContext() {
return { store: this.store };
}
constructor(props, context) {
super(props, context);
this.store = props.store;
}
render() {
return this.props.children;
}
}
你的贊是我前進的動力
求贊,求評論,求分享...




