React源码浅析

180 阅读3分钟

0、虚拟dom与diff算法

什么是虚拟dom

用JavaScript对象表示DOM信息和结构,当状态变更的时候,重新渲染这个JavaScript的对象结构,这个JavaScript对象称为virtual dom;

为什么使用虚拟dom

DOM操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能.相对于DOM对象,js对象处理起来更快,而且更贱哒.通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行dom操作,从而提高性能.

虚拟dom如何工作

react中用jsx语法描述试图,通过babel-loader转译后他们变成React.c re a te E le ment()形式,该函数将生成vdom来描述真是dom.将来如果有状态变化,vdom将作出相应的变化,再通过diff算法对比新旧vdom区别,从而作出最终dom操作

diff算法策略

1、同级比较,web ui中dom节点跨层级的移动操作特别少,可以忽略不计 0 2、拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的属性结构 例如div->p, CompA->CompB 3、对于同一层级的一组子节点,通过唯一的key进行区分

1、JSX与JS对比

代码来源 JSX写法

class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}

ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById('hello-example')
);

JS写法

class HelloMessage extends React.Component {
  render() {
    return React.createElement(
      "div",
      null,
      "Hello ",
      this.props.name
    );
  }
}

ReactDOM.render(React.createElement(HelloMessage, { name: "Taylor" }), document.getElementById('hello-example'));

其中对比我们发现Reac转换t中比较重要的两个方法是React.createElement、ReactDOM.render,下面我就就分别对两个方法进行分析.

2、React.createElement

创建kreact.js代码如下,代码中打印数据如图1

// kreact.js
function createElement(type, props, ...children) {
    props.children = children;
    delete props._source;
    delete props._self;
    // type: 标签类型如div
    // vtype: 组件的类型
    let vtype;
    if (typeof type === "string") {
      vtype = 1;
    } else if (typeof type === "functio") {
      if (type.isClassComponent) {
        // 类组件
        vtype = 2;
      } else {
        // 函数组件
        vtype = 3;
      }
    } 
    return createVNode(vtype, type, props)
}
export default {createElement}
export class Component {
  // 区分组件是function还是class
  static isClassComponent = true;
  constructor(props) {
    this.props = props;
    this.state = {};
  }
  setState() {

  }
}

图1

3、ReactDOM.render

我们把其中虚拟dom转dom的代码单独提取出来,那这里面的源码稍微简单

// kreact-domjs
function render(vnode, container) {
  const node = initVNode(vnode);
  container.appendChild(node);
}
export default {render};

虚拟dom单独提取成initVNode


// kvdom.js
// vtype元素类型: 1-html元素  2-function组件  3-class组件
export function createVNode(vtype, type, props) {
  const vnode = {vtype, type, props}
  return vnode
}

// 转换虚拟dom为dom
export function initVNode(vnode) {
  const {vtype} = vnode;
  if(!vtype) {
    // 文本节点
    return document.createTextNode(vnode);
  }
  if (vtype === 1) {
    // 原生元素
    return createElement(vnode)
  } else if (vtype === 2) {
    // class元素
    return createClassComp(vnode)
  } else if (vtype === 3) {
    // function元素
    return createFuncComp(vnode)
  }
}


function createElement() {
  // 根据type创建元素
  const { type, props } = vnode;
  const node = document.createElement(type);

  // 处理属性
  const { key, chidren, ...rest } = props;
  Object.keys(rest).forEach((k) => {
    // 处理特别属性名: className
    if (k === "className") {
      node.setAttribute("class", rest[k]);
    } else if (k === "style" && typeof rest[k] === "object") {
      const style = Object.keys(rest[k])
        .map((s) => s + ":" + rest[k][s])
        .join(";");
      node.setAttribute("style", style);
    } else if (k.startsWith("on")) {
      // conClick
      const event = k.toLowerCase();
      node[event] = rest[k];
    } else {
      node.setAttribute(k, rest[k]);
    }
  });
  // 递归自元素
  chidren.forEach((c) => {
    if (Array.isArray(c)) {
      c.forEach((n) => node.appendChild(initVNode(n)));
    } else {
      node.appendChild(initVNode(c));
    }
    node.appendChild(initVNode(v));
  });
  return node;
}
// class组件转换
function createClassComp() {
  // type是class组件声明
  const {type, props} = vnode;
  const component = new type(props);
  const vdom = component.render();
  return initVNode(vdom)
}
function createFuncComp() {
  // type是函数
  const {type, props} = vnode;
  const vdom = type(props);
  return initVNode(vdom);
}

那么到这里一个简易版的react解析就完成了,后面有机会再补充下...