上次我们分析了无状态组件生成 DOM 的过程,无状态组件其实就是纯函数,它不维护内部的状态,只是根据外部输入,输出一份视图数据。而今天我们介绍的有状态组件,它有内部的状态,因此在组件的内部,可以自行对状态进行更改,进而渲染出新的视图。下面我们就来分析有状态组件生成真实 DOM 结点的过程。

我们先来写的一个 Greeting 组件,每次点击问候按钮,文字部分会更新问候的次数:

class Greeting extends React.Component {
constructor() {
super(); this.state = {
count: 0,
};
} componentDidMount() {
console.log('did mount');
} greet = () => {
let {count} = this.state; this.setState({
count: ++count,
});
}; render() {
let {name} = this.props; return (
<div className="container">
<div>hello {name} {this.state.count} times</div>
<button onClick={this.greet}>greet</button>
</div>
)
}
} const App = <Greeting name="scott"/>; console.log(App); ReactDOM.render(App, document.getElementById('root'));

编译之后的代码如下:

// 自执行函数变量 _createClass实际上是用来定义props的
var _createClass = function () { // 定义属性 props是数组类型 [{key, val}]
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true; if ("value" in descriptor) {
descriptor.writable = true;
} Object.defineProperty(target, descriptor.key, descriptor);
}
} return function (Constructor, protoProps, staticProps) {
// 定义原型props
if (protoProps) {
defineProperties(Constructor.prototype, protoProps);
} // 定义静态props
if (staticProps) {
defineProperties(Constructor, staticProps);
} return Constructor;
}; }(); function _possibleConstructorReturn(self, call) {
return call && (typeof call === "object" || typeof call === "function") ? call : self;
} // 继承
function _inherits(subClass, superClass) { // 使用Object.create(prototype, {constructor})来实现继承
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
}); if (superClass) {
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
} // 自执行函数变量 表示用户自定义组件
var Greeting = function (_React$Component) { // 自定义Greeting组件
function Greeting() {
var _this = _possibleConstructorReturn(this, (Greeting.__proto__ || Object.getPrototypeOf(Greeting)).call(this)); // 组件内部state
_this.state = {
count: 1
}; // 组件内部greet方法
_this.greet = function () {
var count = _this.state.count; _this.setState({
count: ++count
});
}; return _this;
} // 继承ReactComponent
_inherits(Greeting, _React$Component); // 给Greeting定义生命周期方法
_createClass(Greeting, [
{
key: "componentDidMount",
value: function componentDidMount() {
console.log('did mount');
}
},
{
key: "render",
value: function render() {
var name = this.props.name; return React.createElement(
"div",
{ className: "container" },
React.createElement(
"div",
null,
"hello ",
name,
" ",
this.state.count,
" times"
),
React.createElement(
"button",
{ onClick: this.greet },
"greet"
)
);
}
}
]); return Greeting; }(React.Component); var App = React.createElement(Greeting, { name: "scott" }); console.log(App); ReactDOM.render(App, document.getElementById('root'));

模拟组件渲染:

const React = {
// 创建DOM描述对象 即虚拟DOM
createElement(type, props, ...children) {
let propsChildren = children; // 组件参数的props.children本身是数组
// 所以调用组件函数时这里需要特殊处理
if (Array.isArray(children[0])) {
propsChildren = children[0];
} // 结点
let vnode = {
type,
props: {
...props,
children: propsChildren,
}
}; // 挂载组件函数体的虚拟DOM
if (typeof type === 'function') {
let componentProps = {
...props,
children,
}; // 有状态组件
if (type.prototype && type.prototype.render) {
let component = new type(); component.props = componentProps;
component.vnode = vnode; vnode.body = component.render();
} // 无状态组件
else {
vnode.body = type(componentProps);
}
} return vnode;
}
}; // ReactComponent基类
function ReactComponent(props) {} // 实现setState方法
ReactComponent.prototype.setState = function (partialSate) {
Object.assign(this.state, partialSate); let oldDom = this.vnode.dom;
let newDom = ReactDOM.generateDOM(this.render()); this.vnode.dom = newDom; // 替换DOM结点
oldDom.parentNode.replaceChild(newDom, oldDom);
} // 模拟React.Component基类
React.Component = ReactComponent; const ReactDOM = {
// 渲染真实DOM
render(vnode, container) {
container.appendChild(this.generateDOM(vnode));
},
// 获取真实DOM结点
generateDOM(vnode) {
if (typeof vnode.type === 'function') {
// 将组件函数体的虚拟DOM生成真实DOM
let elem = this.generateDOM(vnode.body); vnode.dom = elem; return elem;
} let elem = document.createElement(vnode.type); vnode.dom = elem; // 特殊key值映射
let specialKeyMap = {
className: 'class',
fontSize: 'font-size',
};
let {props} = vnode; // 设置DOM属性
props && Object.keys(props).forEach(key => {
if (key === 'children') {
// 处理子节点
props.children.forEach(child => {
if (['string', 'number'].includes(typeof child)) {
// 纯内容节点
elem.appendChild(document.createTextNode(child));
} else {
// DOM节点
elem.appendChild(this.generateDOM(child));
}
});
} else if (key === 'style') {
// 设置样式属性
let styleObj = props.style;
let styleItems = []; Object.keys(styleObj).forEach(styleKey => {
styleItems.push(`${specialKeyMap[styleKey] || styleKey}:${styleObj[styleKey]}`);
}); elem.setAttribute('style', styleItems.join(';'));
} else if (['onClick'].includes(key)) {
let eventName = key.replace(/^on/, '').toLowerCase(); // 绑定事件
elem.addEventListener(eventName, function () {
props[key]();
});
} else {
// 设置其他属性
elem.setAttribute(specialKeyMap[key] || key, props[key]);
}
}); return elem;
}
};

React: 有状态组件生成真实DOM结点的更多相关文章

  1. React: 无状态组件生成真实DOM结点

    在上一篇文章中,我们总结并模拟了 JSX 生成真实 DOM 结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({ ...

  2. React系列文章:无状态组件生成真实DOM结点

    在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name ...

  3. React系列文章:JSX生成真实DOM结点

    在上一篇文章中,我们介绍了Babel是如何将JSX代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了Babel编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const style ...

  4. React: JSX生成真实DOM结点

    在上一篇文章中,我们介绍了 Babel 是如何将 JSX 代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了 Babel 编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const ...

  5. react篇章-React State(状态)-组件都是真正隔离的

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title&g ...

  6. [react] 什么是虚拟dom?虚拟dom比操作原生dom要快吗?虚拟dom是如何转变成真实dom并渲染到页面的?

    壹 ❀ 引 虚拟DOM(Virtual DOM)在前端领域也算是老生常谈的话题了,若你了解过vue或者react一定避不开这个话题,因此虚拟DOM也算是面试中常问的一个点,那么通过本文,你将了解到如下 ...

  7. React之父子组件传递和其它一些要点

    react是R系技术栈中最基础同时也是最核心的一环,2年不到获取了62.5k star(截止到目前),足可见其给力程度.下面对一些react日常开发中的注意事项进行罗列. React的组件生命周期 r ...

  8. Vue视图渲染原理解析,从构建VNode到生成真实节点树

    前言 在 Vue 核心中除了响应式原理外,视图渲染也是重中之重.我们都知道每次更新数据,都会走视图渲染的逻辑,而这当中牵扯的逻辑也是十分繁琐. 本文主要解析的是初始化视图渲染流程,你将会了解到从挂载组 ...

  9. 从DOM操作看Vue&React的前端组件化,顺带补齐React的demo

    前言 接上文:谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo 上次写完博客后,有朋友反应第一内容有点深,看着迷迷糊糊:第二是感觉没什么使用场景,太过业务化,还不如直接写Vue ...

随机推荐

  1. 实验三:Linux进程管理(HDU)

    2.设计内容 把下面的几个网址的内容看懂,关于Linux的通信机制就会有个基本的了解了,后面的这几个代码也应该可以看得懂了. 管道通信:https://blog.csdn.net/ljianhui/a ...

  2. luoguP2039 [AHOI2009]跳棋 巧妙的dp

    设\(f[i]\)表示在第\(i\)个格子上弄一个棋子的最小代价,前后扫两遍dp后统计答案即可. 代码 #include<bits/stdc++.h> using namespace st ...

  3. shell编程题(五)

    打印root可以使用可执行文件数. echo "root's bins: $(find ./ -type f | xargs ls -l | sed '/-..x/p' | wc -l)&q ...

  4. docker 部署 jenkins

    建议使用的Docker映像是jenkinsci/blueocean image(来自 the Docker Hub repository). 该镜像包含当前的长期支持 (LTS) 的Jenkins版本 ...

  5. udev只能修改Linux 网口名称,无法修改/dev目录下的设备名称

    NAME The name to use for a network interface. The name of a device node cannot be changed by udev, o ...

  6. Servlet 4.0 入门

    Java™ Servlet API 是主流服务器端 Java 的基本构建块,也是 Java EE 技术的一部分,例如,用于 Web 服务的 JAX - RS.JSF (JavaServer Faces ...

  7. Django学习----js传参给view.py

    需求: 散点图中每选择一个点,获取到id之后传给view.py,根据这个id进行sql语句的查询. 问题: 要求实时查询 解决办法: ajax查询 js页面 .on("mousedown&q ...

  8. OCR识别的Android端实现

    1.OCR简介OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗.亮的模式确定其形状,然后用字符识别 ...

  9. 记一次网络故障——pod间无法通信

    一.背景 集群是二进制部署 部署完成后一起正常,各种资源对象均可正常创建. 部署应用后发现无法跨节点通信,且pod的ip都是172.17.0.0段的 二.排查过程层 查看节点路由,发现docker0网 ...

  10. Python【每日一问】21

    问: [基础题]输入某年某月某日,判断这一天是这一年的第几天? [提高题]用 *号输出字母 C的图案 答: [基础题]输入某年某月某日,判断这一天是这一年的第几天? 方法1: import time ...