写点什么

React 核心工作原理

作者:xiaofeng
  • 2022-10-25
    浙江
  • 本文字数:3347 字

    阅读完需:约 11 分钟

## 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 版本


相关 React 实战视频讲解:进入学习


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;}
// 遍历childrenfunction 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 或者 PureComponentfunction 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-vitalsreportWebVitals();
复制代码


整体代码就是这样,具体过程就不在这里细致说明了,大家好好品一下代码,有疑问的可以联系我。

小结

1、React17 中,React会自动替换JSX为js对象。
2、js对象即vdom,它能够完整描述dom结构。
3、ReactDOM.render(vdom, container)可以将vdom转换为dom并追加到container中。
4、实际上,转换过程需要经过一个diff过程。
复制代码


用户头像

xiaofeng

关注

努力写代码中 2022-08-18 加入

努力写代码中

评论

发布
暂无评论
React核心工作原理_React_xiaofeng_InfoQ写作社区