实现一个简易版本的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,
  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算是完成了。

你可能感兴趣的:(实现一个简易版本的REACT)