React虚拟DOM和DIFF算法
Wscats opened this issue · 0 comments
VDOM
VDOM,也叫虚拟 DOM,它是仅存于内存中的 DOM,因为还未展示到页面中,所以称为 VDOM
var vdom = document.createElement("div");
上面这一句就是最简单的虚拟 DOM
var vdom = document.createElement("div");
document.body.append(vdom);
上面这两句就是把虚拟 DOM 转化为 真实 DOM,其实就是把节点 append 到页面中
常见DOM操作
常见DOM操作,就三类:增、删、改。对应的DOM操作如下:
DOM操作 | DOM方法 |
---|---|
增加一个节点 | appendChild |
删除一个节点 | removeChild |
更改一个节点 | replaceChild |
以前我们写代码经常会拼接完模板,简单粗暴的用$(el).html(template)
整块节点替换
这样做最大的问题在于性能,如果页面比较小,问题不大,但如果页面庞大,这样会出现卡顿,用户体验会很差,所以解决办法就是差量更新
差量更新
差量更新就是只对局面的 HTML 片段进行更新。比如你加了一个节点,那么我就只更新这个节点,我无需整个模板替换。这样做效率就会提高。但问题在于,不知道哪个节点更新了,哪个节点删除了,哪个节点替换了,所以我们需要对 DOM 建模
DOM 建模,简单点说就是用一个 JS 对象来表示 VDOM。
如果我们可以用一个JS对象来表示 VDOM,那么这个对象上多一个属性(增加节点),少一个属性(删除节点),或者属性值变了(更改节点),就很清醒了
DOM 也叫 DOM 树,是一个树形结构,DOM 树上有很多元素节点,要对 VDOM 进行建模,本质上就是对一个个元素节点进行建模,然后再把节点放回 DOM 树的指定位置
JSX建模
每个节点都是由以下三部分组成
- type : 元素类型
- props : 元素属性
- children : 子元素集合
{type:"div",props: null, children:[
{type:"img",props:{"src":"avatar.png", "className":"profile"},children:[],
{type:"h3",props: null, children:[{[user.firstName, user.lastName].join(' ')}],
]}
上面 VDOM 建模是用下面的 HTML 结构转出来的
var profile = <div>
<img src="avatar.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;
但这段代码并不是合法的 js 代码,它是一种被称为 jsx 的语法扩展,通过它我们就可以很方便的在 js 代码中书写 html 片段
本质上,jsx 是语法糖,上面这段代码会被 babel 转换成如下代码
pig("div", null, pig("img", {
src: "avatar.png",
className: "profile"
}), pig("h3", null, [user.firstName, user.lastName].join(" ")))
而上面的这段被转化的代码是 将我们的 VDOM 配合pig
(一般应该是createElement
函数)转化为真实 DOM
注意,如果是自定义组件<App />
会转化为pig(App, null)
,因为组件是class App extends React.Component {}
这样定义的,所以App进入createElement
函数里面就会变成是一个对象
这里我们可以把这个函数放进createElement()
里面生成一个 VDOM 对象,然后用生成的 VDOM 对象,配合render()
生成一个 DOM 插入页面,从而转变成真实 DOM 结构
createElement()
补充createElement()
方法的源代码
function createElement(type, props, ...childrens) {
return {
// 父标签类型,比如dev,ul等
type: type,
// 属性值
props: {
...props,
},
// 子节点,比如li,字符串等
children: childrens.length <= 1 ? childrens[0] : childrens
};
}
render()
补充render()
方法的源代码
//=>DOM的动态创建
function render(jsxObj, container, callback) {
let {
type,
props,
children
} = jsxObj;
let newElement = document.createElement(type);
//=>属性和子元素的处理
for (let attr in props) {
if (!props.hasOwnProperty(attr)) break;
switch (attr) {
case 'className':
newElement.setAttribute('class', props[attr]);
break;
case 'style':
let styleOBJ = props['style'];
for (let key in styleOBJ) {
if (styleOBJ.hasOwnProperty(key)) {
newElement['style'][key] = styleOBJ[key];
}
}
break;
// =>CHILDREN
case 'children':
// 如果children放在props里面的话,这句才会有意义
// renderChildren()
default:
newElement.setAttribute(attr, props[attr]);
}
}
renderChildren()
function renderChildren() {
let childrenAry = children;
childrenAry = childrenAry instanceof Array ? childrenAry : (childrenAry ? [childrenAry] : []);
childrenAry.forEach(item => {
// 如果子节点直接是字符串,进入这个分支
if (typeof item === 'string') {
// =>字符串:文本节点,直接增加到元素中
newElement.appendChild(document.createTextNode(item));
} else {
// 如果是标签节点比如<span><img />这些都进入这个分支
// =>字符串:新的JSX元素,递归调用RENDER,只不过此时的容器是当前新创建的newElement
render(item, newElement);
}
});
}
console.log(newElement);
container.appendChild(newElement);
callback && callback();
}
transform-react-jsx
安装transform-react-jsx来实现 jsx 和 js 之间的转换
npm install babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env webpack // 首先安装好 babel 环境
npm install --save-dev babel-plugin-transform-react-jsx //再安装 transform-react-jsx 插件
配置对应的 webpack 参数,如果这里把注释的那条 plugins 打开,那就不需要写另外配置 .babelrc 文件,这里默认会先使用 plugins 的配置的
const path = require('path');
const config = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
// "plugins": ["transform-react-jsx"]// 如果需要配置参数注释这条,在 .babelrc上面配置
}
}
}]
}
};
module.exports = config;
配置 .babelrc 文件,如果需要对应的自定义的函数名,可以设置 pragma 的参数,不设置默认返回 React.createElement
{
"plugins": [
["transform-react-jsx", {
"pragma": "pig.yao" // default pragma is React.createElement
}]
]
}