## 1.1、虚拟 DOM
常见问题:react virtual dom 是什么?说一下 diff 算法?
拿到一个问题,一般回答都是是什么?为什么?怎么办?那就按照这个思路来吧!
what
用 JavaScript 对象表示 DOM 信息和结构,当状态变更的时候,重新渲染这个 JavaScript 的对象结构。这个 JavaScript 对象称为 virtual dom;
why
DOM 操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能。相对于 DOM 对象,js 对象处理起来更快,而且更简单。通过 diff 算法对比新旧 vdom 之间的差异,可以批量的、最小化的执行 dom 操作,从而提高性能。
where
React 中用 JSX 语法描述视图,通过 babel-loader 转译后它们变为 React.createElement(...)形式,该函数将生成 vdom 来描述真实 dom。将来如果状态变化,vdom 将作出相应变化,再通过 diff 算法对比新老 vdom 区别从而做出最终 dom 操作。
这里说到了 JSX,那就顺带大致说一下:
什么是 JSX
语法糖, React 使用 JSX 来替代常规的 JavaScript。
JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
复制代码
为什么需要 JSX
开发效率:使用 JSX 编写模板简单快速。
执行效率:JSX编译为 JavaScript 代码后进行了优化,执行更快。
类型安全:在编译过程中就能发现错误。
复制代码
React 16 原理
babel-loader会预编译JSX为React.createElement(...)
复制代码
React 17 原理
React 17中的 JSX 转换不会将 JSX 转换为 React.createElement,而是自动从 React 的 package 中引入新的入口函数并调用。另外此次升级不会改变 JSX 语法,旧的 JSX 转换也将继续工作。
复制代码
与 vue 的异同
react 中虚拟 dom+jsx 的设计一开始就有,vue 则是演进过程中才出现的,2.0 版本后出现。
jsx 本来就是 js 扩展,转义过程简单直接的多;vue 把 template 编译为 render 函数的过程需要复杂的编译器转换字符串-ast-js 函数字符串
1.2、render、Component 基础核心 api
render
ReactDOM.render(element, container[, callback]);
复制代码
当首次调用的时候,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 React 的 DOM 的差分算法(DOM diffing algorithm)进行高效的更新。
如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执行。
节点类型
1、文本节点
2、html 标签节点
3、函数组件
4、类组件
...
复制代码
函数组件
// 大些字母开头
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
复制代码
类组件
React 的组件可以定义为 class 或函数的形式,如需定义 class 组件,需要继承 React.Component 或 React.PureComponent:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
复制代码
1.3、手写简版 myreact
实现原生标签节点、文本节点、函数组件和类组件的初次渲染
先用 Create React App 创建一个 React 项目,安装依赖并运行;
接着在 src/index.js 里边加上 这段代码查看一下版本号,保证自己的是 17 版本
console.log("version", React.version);
复制代码
正是因为 React17 中,React 会自动替换 JSX 为 js 对象,所以我们主要需要注释掉 src/index.js 中:
// import React from "react";
// import ReactDOM from "react-dom";
复制代码
接着在 src 下创建一个 myreact 文件夹,在里边创建一个 react-dome.js
// vnode 虚拟dom对象
// node 真实dom节点
// ! 初次渲染
function render(vnode, container) {
// react17 可以自动转虚拟dom
console.log("vnode", vnode);
// vnode->node
const node = createNode(vnode);
// node->container
container.appendChild(node);
}
// 创建节点
function createNode(vnode) {
let node;
const {type} = vnode;
// todo 根据组件类型的不同创建不同的node节点
if (typeof type === "string") { // 原生标签节点
node = updateHostComponent(vnode);
} else if (typeof type == "function") { // 函数组件 再次区分一下类组件和函数组件
node = type.prototype.isReactComponent
? updateClassComponent(vnode)
: updateFunctionComponent(vnode);
} else { // 文本节点
node = updateTextComponent(vnode);
}
return node;
}
// 原生标签节点
function updateHostComponent(vnode) {
const {type, props} = vnode;
const node = document.createElement(type);
console.log('document.createElement', node)
// 更新节点部分
updateNode(node, props); // 属性
reconcileChildren(node, props.children); // 遍历children
return node;
}
// 更新属性
function updateNode(node, nextVal) {
Object.keys(nextVal)
.filter((k) => k !== "children") // 过滤一下 children
.forEach((k) => (node[k] = nextVal[k])); // 生成属性
}
// 文本节点
function updateTextComponent(vnode) {
const node = document.createTextNode(vnode);
return node;
}
// 函数组件
function updateFunctionComponent(vnode) {
const {type, props} = vnode;
// type 是一个 function
const vvnode = type(props);
// vvnode->node
const node = createNode(vvnode);
return node;
}
// 类组件
function updateClassComponent(vnode) {
const {type, props} = vnode;
// 类组件需要 new
const instance = new type(props);
console.log('instance', instance);
const vvnode = instance.render();
console.log('vvnode', vvnode);
// vvnode->node
const node = createNode(vvnode);
return node;
}
// 遍历children
function reconcileChildren(parentNode, children) {
// 和源码一点写法区别,但是也是为了判断是否是数组
const newChildren = Array.isArray(children) ? children : [children];
for (let i = 0; i < newChildren.length; i++) {
let child = newChildren[i];
// vnode
// vnode->node, node插入到parentNode
render(child, parentNode);
}
}
export default { render };
复制代码
接着,还要在创建一个 src/myreact/Component.js 文件:
// 类组件必须继承自 Component 或者 PureComponent
function Component(props) {
// 需要绑定一下this
this.props = props;
}
// 做了一个 类组件的标记
Component.prototype.isReactComponent = {};
export default Component;
复制代码
奥,不能忘了还要改动一下 src/index.js 文件内容:
// import React from 'react';
// import ReactDOM from 'react-dom';
import ReactDOM from './myreact/react-dom';
import Component from "./myreact/Component";
import './index.css';
// import App from './App';
import reportWebVitals from './reportWebVitals';
class ClassComponent extends Component {
render() {
return (
<div>
<p>类组件-{this.props.name}</p>
</div>
);
}
}
export default ClassComponent;
function FunctionComponent(props) {
return (
<div>
<p>函数组件-{props.name}</p>
</div>
);
}
const jsx = (
<div className="myjsx">
<h1>111111</h1>
<h2>222222</h2>
<h3>111111</h3>
<a href="https://www.baidu.com/">百度</a>
<FunctionComponent name="我是函数组件" />
<ClassComponent name="我是类组件" />
</div>
)
// 原生标签
// 文本节点
// 函数组件
// 类组件
ReactDOM.render(
jsx,
document.getElementById('root')
);
// console.log("version", React.version); // version 17.0.1
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
复制代码
参考 React面试题详细解答
整体代码就是这样,具体过程就不在这里细致说明了,大家好好品一下代码,有疑问的可以联系我。
小结
1、React17 中,React会自动替换JSX为js对象。
2、js对象即vdom,它能够完整描述dom结构。
3、ReactDOM.render(vdom, container)可以将vdom转换为dom并追加到container中。
4、实际上,转换过程需要经过一个diff过程。
复制代码
评论