上次我们分析了无状态组件生成 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. 洛谷P1531 I Hate It题解

    题目背景 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少.这让很多学生很反感. 题目描述 不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的 ...

  2. 【CSP-S膜你考】那23个路口

    那23个路口 题面 故事的起源不加赘述,那23个路口. 单刀直入,我直接说题的意思. 蚊子和疯子在做一件事,就是他们要在茫茫的大街上找一个出发点,然后从出发点开始,经过上下左右23次拐弯,到达一个他们 ...

  3. 【CF10D】 LCIS

    题目链接 最长公共上升子序列 \(f[i][j]\)表示\(A\)的前\(i\)个数,匹配\(B\)的第\(j\)个数,且\(B[j]\)必选时的最长公共上升子序列长度 转移: if(A[i]==B[ ...

  4. 《BLACK HAT PYTHON3》

    Black Hat Python3 kali 安装新版本python kali中自带的pyhton是2.7版本,显然2019年了,python2.x的版本已经逐渐过时,好多第三方库都逐步宣布不再支持p ...

  5. Django单元测试总结

    title: Django单元测试总结 date: 2019/6/18 17:50:00 body: [article] description: "  在本文中,笔者大致对Django单元 ...

  6. Golang微服务实践

    背景 在之前的文章<漫谈微服务>我已经简单的介绍过微服务,微服务特性是轻量级跨平台和跨语言的服务,也列举了比较了集中微服务通信的手段的利弊,本文将通过RPC通信的方式实现一个增删查Redi ...

  7. 树模型常见面试题(以XGBoost为主)

    参考资料: 珍藏版 | 20道XGBoost面试题 推荐系统面试题之机器学习(一) -----树模型 1. 简单介绍一下XGBoost2. XGBoost与GBDT有什么不同3. XGBoost为什么 ...

  8. c++ 数值计算库Eigen

    http://eigen.tuxfamily.org/index.php?title=Main_Page

  9. SSL密钥协商过程分析

    一.说明 尽管做过证书生成.双向认证.SSL通信编程等事情,但一直不清楚SSL如何完成密钥交换.看网上的资料则众说纷纭,最近和朋友学习时聊到了这个问题,然后正巧上周处理客户反馈SSL版本过低时领导也想 ...

  10. 【LeetCode】缺失的第一个正数【原地HashMap】

    给定一个未排序的整数数组,找出其中没有出现的最小的正整数. 示例 1: 输入: [1,2,0] 输出: 3 示例 2: 输入: [3,4,-1,1] 输出: 2 示例 3: 输入: [7,8,9,11 ...