上次我们分析了无状态组件生成 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. SSM回顾

    1.mybatis 认识MyBatis:持久化 第一个MyBatis程序(重点) CRUD 配置 ResultMap结果集映射(重点) 一对多 多对一 Log4j 分页 注解开发 动态SQL 缓存 2 ...

  2. 【题解】CF161B Discounts

    目录 题目 思路 \(Code\) 题目 CF161B Discounts 思路 贪心.很显然对于一个板凳(价格为c)所能使我们最多少花费\(\frac{c}{2}\)的金钱. 原因如下: 如果你将一 ...

  3. k均值聚类算法原理和(TensorFlow)实现

    顾名思义,k均值聚类是一种对数据进行聚类的技术,即将数据分割成指定数量的几个类,揭示数据的内在性质及规律. 我们知道,在机器学习中,有三种不同的学习模式:监督学习.无监督学习和强化学习: 监督学习,也 ...

  4. Spring Boot 构造器参数绑定,越来越强大了!

    在之前的文章:Spring Boot读取配置的几种方式,我介绍到 Spring Boot 中基于 Java Bean 的参数绑定,在一个 Java Bean 类上用 @ConfigurationPro ...

  5. Leetcode 1262. 可被三整除的最大和

    题目:给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和. 示例 1: 输入:nums = [3,6,5,1,8] 输出:18 解释:选出数字 3, 6, 1 和 8,它们的和是 18( ...

  6. 安卓 App 性能专项测试之流畅度深度解析-中篇

    背景 毫无疑问,流畅度在Android众多性能指标中其重要程度不言而喻,而且也是最为复杂的一个.为了描述这样的一个维度,业界大佬纷纷提出了各种指标来对其进行衡量.在上篇文章中给大家介绍了FPS这一项指 ...

  7. Docker容器内部端口映射到外部宿主机端口 - 运维笔记

    Docker允许通过外部访问容器或者容器之间互联的方式来提供网络服务.容器启动之后,容器中可以运行一些网络应用,通过-p或-P参数来指定端口映射. 注意:宿主机的一个端口只能映射到容器内部的某一个端口 ...

  8. [原创]小巧免杀的端口转发工具PortTran(附.net源码)

    0x001 简介 PortTran by k8gege.NET版端口转发工具,支持任意权限下转发 0x002 背景工具在2016年左右写的,当时某个内网不知何原故LCX用不了 由于Win2003才刚停 ...

  9. JavaSE 面试题: 成员变量与局部变量

    JavaSE 面试题 成员变量与局部变量 public class Test { static int s; int i; int j; { int i = 1; i++; j++; s++; } p ...

  10. 【译】RAID的概念和RAID对于SQL性能的影响

    简介 我们都听说过RAID,也经常作为SQL DBA.开发人员或构架师在工作中讨论RAID.但是,其实我们很多人都对RAID的原理,等级,以及RAID是如何影响SQL Server性能并不甚了解. 本 ...