2021-02-08 React原理解析01

一、深入虚拟DOM

React本身只是一个DOM的抽象层,使用组件构建虚拟DOM

什么是Virtual DOM、为什么用以及何处使用

what:用Javascript对象表示DOM信息和结构,当状态变更的时候,重新渲染这个Javascript对象结构。这个Javascript对象称为Virtual DOM

传统的dom渲染过程:
dom渲染

  • 将html解析成dom树
  • 将CSS解析成css tree
  • 合并二者为render tree
  • 调用浏览器相关的API绘制渲染

why:之所以用虚拟dom,是因为DOM操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能。相对于DOM对象,JS对象处理起来更快,而且更简单。通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行dom操作,从而提升性能

where:React中使用JSX语法描述视图,通过babel-loader转译后它们变为React。createElement(…)形式 ,该函数将生成vdom来描述真是dom。将来如果状态发生变化,vdom将作出相应变化,在通过diff算法(协调的核心)对比新老vdom区别从而作出最终dom操作

二、JSX

1、什么是JSX

  • 语法糖
  • React使用JSX代替常规的Javascript
  • JSX是一个看起来很像XML的Javascript语法扩展

2、为什么需要JSX

  • 开发效率:使用JSX编写模板简单快速
  • 执行效率:JSX编译为Javascript代码后进行了优化,执行更快
  • 类型安全:在编译过程中就能发现错误

3、React16原理:babel-loader会预编译JSX为React.createElement(…)

4、React17原理:React17中的JSX转换不会将JSX转换为React.createElement,而是自动从React的package中引入新的入口函数并调用。另外此次升级不会改变JSX语法,旧的JSX转换也将继续工作

5、与Vue的异同:

	- react中虚拟dom+jsx的设计一开始就有,vue则是演讲过程中才出现的
	- jsx本来就是js扩展,转移过程简单直接的多;vue把template编译为render函数的过程需要复杂的编译器转换字符串-ast-js函数字符串 

三、React核心API

核心精简后:

const React = {
	createElement,
	Component
}

核心的api:

React.Component:react\packages\react\src\ReactBaseClasses.js实现自定义组件

ReactDOM.render:react\packages\react-dom\src\client\ReactDOMLegacy.js渲染真实DOM

render()

ReactDOM.render(element, container, callback)

当首次调用时,容器节点的所有DOM元素都会被替换,后续的调用则会使用React的DOM差分算法(DOM diffing algorithm)进行高效的更新
如果提供了可选的回调函数callback,该回调将在组件被渲染或更新之后被执行

节点类型
注意节点类型:

  • 文本节点
  • HTML标签节点
  • 函数组件
  • 类组件
  • <>&&Fragment节点

调试文件index.js

// import React from "react";
// import ReactDOM from "react-dom";
import ReactDOM from "./kreact/react-dom";
import Component from "./kreact/Component";
import "./index.css";

function FunctionComponent(props) {
  return (
    <div className="border">
      <p>{props.name}</p>
    </div>
  );
}

class ClassComponent extends Component {
  render() {
    return (
      <div className="border">
        <p>{this.props.name}</p>
      </div>
    );
  }
}

/* function FragmentComponent(props) {
  return [1, 2, 3].map(item => <h1>哈哈</h1>);
} */

const jsx = (
  <div className="border">
    <h1>React原理解析-01</h1>
    <a href="https://www.kaikeba.com">kkb</a>
    <FunctionComponent name="function" />
    <ClassComponent name="class" />
    {/* <FragmentComponent /> */}
    <>
      <h2>哈哈</h2>
      <h3>呵呵</h3>
    </>
  </div>
);

ReactDOM.render(jsx, document.getElementById("root"));

// console.log(React.version);
// 文本节点
// 原生标签
// 函数组件
// 类组件
// 空节点&&Fragment组件

渲染函数react-dom.js

/**
 * vnode 虚拟DOM
 * node 真实DOM
 */

function render(vnode, container) {
  console.log("vnode", vnode);

  // vnode->node
  const node = createNode(vnode);

  // node 插入到container中
  container.appendChild(node);
}

function isString(str) {
  return typeof str === "string";
}

// 根据vnode,生成node
function createNode(vnode) {
  let node;
  const { type } = vnode;

  // todo 生成node
  // 原生标签 div a span
  if (isString(type)) {
    node = updateHostComponent(vnode);
  } else if (typeof type === "function") {
    node = type.prototype.isReactComponent
      ? updateClassComponent(vnode)
      : updateFunctionComponent(vnode);
  } else if (type === undefined) {
    // 文本
    node = updateTextComponent(vnode);
  } else {
    // <>空节点Fragment节点
    node = updateFragmentComponent(vnode);
  }

  return node;
}

// 更新属性
function updateNode(node, nextVal) {
  Object.keys(nextVal)
    .filter(k => k !== "children")
    .forEach(k => (node[k] = nextVal[k]));
}

// 原生标签
function updateHostComponent(vnode) {
  const { type, props } = vnode;
  const node = document.createElement(type);
  // 更新属性
  updateNode(node, props);
  // 遍历子节点,插入到node上
  reconcileChildren(node, props.children);
  return node;
}

// 文本
function updateTextComponent(vnode) {
  const node = document.createTextNode(vnode);
  return node;
}

// 函数组件
function updateFunctionComponent(vnode) {
  const { type, props } = vnode;
  const vvnode = type(props);
  const node = createNode(vvnode);
  return node;
}

// 类组件
function updateClassComponent(vnode) {
  const { type, props } = vnode;
  // 先实例化
  const instance = new type(props);
  const vvnode = instance.render();
  const node = createNode(vvnode);
  return node;
}

// <>Fragment节点
function updateFragmentComponent(vnode) {
  const { props } = vnode;
  // 创建Fragment节点
  const node = document.createDocumentFragment();
  // 遍历子节点,插入到node上
  reconcileChildren(node, props.children);
  return node;
}

// 最假的吧,最简单的也是协调
function reconcileChildren(parentNode, children) {
  let newChildren = Array.isArray(children) ? children : [children];

  for (let i = 0; i < newChildren.length; i++) {
    let child = newChildren[i];
    // child是vnode
    // child->node, 插入到parentNode
    render(child, parentNode);
  }
}

// eslint-disable-next-line
export default { render };

组件Component.js

function Component(props) {
  this.props = props;
}

Component.prototype.isReactComponent = {};

export default Component;

分析,首先render(vnode, container)接收两个参数vnode和container,在render函数中就做两件事:
第一件事是把vnode->node
第二件事是把node插入到container中

function render(vnode, container) {
  console.log("vnode", vnode);

  // vnode->node
  const node = createNode(vnode);

  // node 插入到container中
  container.appendChild(node);
}

第一件事,把vnode变成node需要根据vnode.type类型来判断,不同类型使用不同的方法

function isString(str) {
  return typeof str === "string";
}
// 根据vnode,生成node
function createNode(vnode) {
  let node;
  const { type } = vnode;

  // todo 生成node
  // 原生标签 div a span
  if (isString(type)) {
    node = updateHostComponent(vnode);
  } else if (typeof type === "function") {
    node = type.prototype.isReactComponent
      ? updateClassComponent(vnode)
      : updateFunctionComponent(vnode);
  } else if (type === undefined) {
    // 文本
    node = updateTextComponent(vnode);
  } else {
    // <>空节点Fragment节点
    node = updateFragmentComponent(vnode);
  }

  return node;
}

type的情况有以下几种:
1、原生节点div、span、a等使用updateHostComponent方法

// 原生标签
function updateHostComponent(vnode) {
  const { type, props } = vnode;
  const node = document.createElement(type);
  // 更新属性
  updateNode(node, props);
  // 遍历子节点,插入到node上
  reconcileChildren(node, props.children);
  return node;
}

2、文本节点使用updateTextComponent方法

// 文本
function updateTextComponent(vnode) {
  const node = document.createTextNode(vnode);
  return node;
}

3、函数组件则返回函数的结果vnode,再调用createNode方法

// 函数组件
function updateFunctionComponent(vnode) {
  const { type, props } = vnode;
  const vvnode = type(props);
  const node = createNode(vvnode);
  return node;
}

4、类组件则先new一个实例,再调用实例的render方法返回vnode,再调用createNode方法

// 类组件
function updateClassComponent(vnode) {
  const { type, props } = vnode;
  // 先实例化
  const instance = new type(props);
  const vvnode = instance.render();
  const node = createNode(vvnode);
  return node;
}

5、<>节点和Fragment组件使用updateFragmentComponent方法

// <>Fragment节点
function updateFragmentComponent(vnode) {
  const { props } = vnode;
  // 创建Fragment节点
  const node = document.createDocumentFragment();
  // 遍历子节点,插入到node上
  reconcileChildren(node, props.children);
  return node;
}

最后,协调也是最重要的部分diff算法就使用在协调中,这里列出简单的方法

// 最假的吧,最简单的也是协调
function reconcileChildren(parentNode, children) {
  let newChildren = Array.isArray(children) ? children : [children];

  for (let i = 0; i < newChildren.length; i++) {
    let child = newChildren[i];
    // child是vnode
    // child->node, 插入到parentNode
    render(child, parentNode);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值