React原理剖析

此文項目代碼:https://github.com/bei-yang/I-want-to-be-an-architect
碼字不易,辛苦點個star,感謝!

引言


此篇文章主要涉及以下內(nèi)容:

  1. react核心api
  2. 探究setState
  3. 探究diff算法

學習資源


  1. react

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
  1. dom組件
  2. 函數(shù)式組件
  3. 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));
 }
});

總結

  1. webpack+babel編譯時,替換JSX為React.createElement(type,props,...children)
  2. 所有React.createElement()執(zhí)行結束后得到一個JS對象,它能夠完整描述dom結構,稱之為vdom
  3. 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策略

  1. 同級比較,Web UI中DOM節(jié)點跨層級的移動操作特別少,可以忽略不計。



  2. 擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構。
    例如:div->p,CompA->CompB
  3. 對于同一層級的一組子節(jié)點,通過唯一的key進行區(qū)分。

基于以上三個前提策略,React分別對tree diff、component diff以及element diff進行算法優(yōu)化,事實也證明這三個前提策略是合理且準確的,它保證了整體界面構建的性能。

  • tree diff
  • component diff
  • element diff

element diff


差異類型:

  1. 替換原來的節(jié)點,例如把div換成了p,Comp1換成Comp2;
  2. 移動、刪除、新增子節(jié)點,例如ul中的多個子節(jié)點li中出現(xiàn)了順序互換;
  3. 修改了節(jié)點的屬性,例如節(jié)點類名發(fā)生了變化;
  4. 對于文本節(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

  1. 為什么需要redux,他是什么
  2. 解決了什么問題
  3. 如何使用
  4. 單向數(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;
  }
}

你的贊是我前進的動力

求贊,求評論,求分享...

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容