实现一个简易版本的REACT

0、简单介绍

react里包含有丰富的api 有兴趣可以看一下 React.js源码

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,
  createContext,
  forwardRef,
  lazy,
  memo,

  ...
  useContext,
  useEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
  ...
};

这里主要实现3个最常用的API:

React.createElement  // 返回虚拟dom
React.Component // 实现组件的自定义
ReactDOM.render // 创建真实dom

1、React.createElement

我们在打包编译react项目的时候,实际上会有一个jsx转换成普通的js代码的过程。
如jsx:


image.png

会被转换为:


image.png

看一下 React.createElement源码

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */
export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;
  // ... 中间省略
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

可以看到react源码里定义的 createElement 接收了3个参数(超过3个时会从arguments取),返回了ReactElement函数的运行结果 ,其实返回的就是vdom。
按照这个逻辑,可以实现一个简化版的createElement。

实现一个简单的createElement:

function createElement(type, props, ...children) {
    return {
        type,
        props,
        children
    }
}

看起来过于简洁了... 但是对于简单实现已经够用了 :)。

2、ReactDOM.render

ReactDOM.render源码

export function render(
  element: React$Element<any>,
  container: DOMContainer,
  callback: ?Function,
) {
  // ...省略
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

react里的render的功能还是非常复杂的,涉及比较多的代码,这里只能展示一小部分。
对于简化版的render, 接收2个参数:vnode、根节点,然后将vnode转换成真实节点插入根节点。对于vnode的类型,分为3类来处理:字符串、函数、原生html节点。

实现一个简单的render:

function render(vnode, container) {
  return container.appendChild(createDom(vnode));
}

// 将vdom转换为真实dom
function createDom(vnode) {
  // 纯字符 直接创建文件节点
  if (typeof vnode === 'string') {
    const node = document.createTextNode(vnode);
    return node;
  }

  // 处理 函数和类组件
  if (typeof vnode.tag === 'function') {
    return createComponentDom(vnode.tag, vnode.attrs)
  }

  // 处理原生节点
  const node = document.createElement(vnode);
  if (vnode.props) {
    Object.keys(node.props).forEach((k) => {
        setAttr(node, k, vnode.props[key]);
    })
  }
  // 递归处理所有的子节点
  vnode.children.forEach(child => {
    return render(child, node);
  })
  return node;
}

function setAttr(node, key, val) {
  // 还需要增加判断key的各种情况  如 style htmlFor等等
  if (key === 'className') {
    node.setAttribute('class', val);
  } else {
    node.setAttribute(key, val);
  }
}

3、实现component

在使用react的时候,类组件总是要继承component,并且经常要使用setState这个方法 下面先看一下 component源码

/**
 * Base class helpers for the updating state of a component.
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};
//... 省略部分代码和注释
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

可以看到这是一个构造函数,并且setState这个方法里最终执行了

this.updater.enqueueSetState(...)

实际上setState就是异步的。按照源码,下面来实现一个简单的component。

实现一个简单的component

class component {
  // 标识类组件
  static isClassComponent = true
  constructor(props) {
    this.props = props
    this.state = {}
  }
  setState(newState) {
    this.state = Object.assign({}, this.state, newState)
    renderComponent(this)
  }
}

// 返回组件渲染后的dom
function createComponentDom(component, props) {
  let node;
  if (component.isClassComponent) {
    // 类组件 创建实例
    const instance = new component();
    node = renderComponent(instance);
  } else {
    // 函数组件 直接运行得到vdom
    const vnode = component(props);
    node = createDom(vnode);
  }
  return node;
}

// 传入类组件的实例,渲染类组件
function renderComponent(componentObj) {
  let base;
  const vnode = componentObj.render();
  base = createDom(vnode);
  if (componentObj.base && componentObj.base.parentNode) {
    componentObj.base.parentNode.replaceChild(base, componentObj.base);
  }
  componentObj.base = base;
}

至此,一个简易的react算是完成了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352