共计 4766 个字符,预计需要花费 12 分钟才能阅读完成。
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}; |
参考 React 实战视频解说:进入学习
接着,还要在创立一个 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(); |
整体代码就是这样,具体过程就不在这里粗疏阐明了,大家好好品一下代码,有疑难的能够分割我。
小结
1、React17 中,React 会主动替换 JSX 为 js 对象。2、js 对象即 vdom,它可能残缺形容 dom 构造。3、ReactDOM.render(vdom, container) 能够将 vdom 转换为 dom 并追加到 container 中。4、实际上,转换过程须要通过一个 diff 过程。