上次我们分析了无状态组件生成 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. Python——IO多路复用之select模块select方法

    Python——IO多路复用之select模块select方法 使用select模块的select方法实现Python——IO多路复用 实现同时将终端输入的文本以及客户端传输的文本写入文本文件中: w ...

  2. IE浏览器卡死提示是否停止运行此脚本的解决办法

    IE浏览器经常卡死,报是否停止运行此脚本,严重影响使用体验,下面小编教大家怎么解决这个问题,供大家参考! 1.启动IE浏览器,点击上方菜单栏位的工具,如下图所示 2.在工具栏位选择internet选项 ...

  3. asp.net调用c++的dll

    只需要把dll文件拷贝到windows的system32目录下(64位系统为SysWOW64目录),如果操作系统为64位而dll为32位,还需在进程池启用32位支持.

  4. 非mvn项目转为mvn项目并构建mvn私服

    非mvn项目转为mvn项目并构建mvn私服 一.背景 公司里的系统是老系统,没有使用mvn,但是现在准备使用持续集成(CI),就要用到mvn,所以现在需要将老项目转为mvn项目,并且非mvn项目也是不 ...

  5. 关于liveness服务依赖用法整理

    一.生产环境中部分服务的使用场景有前置条件 使用initContainers,做一些前置服务的检测动作,以确定前置服务已正常运行且能对外提供服务(若检测未通过则本pod无法启动) 使用liveness ...

  6. IO流——字节流

    文件输出流 FileOutputStream:文件输出流是用于将数据写入 File,每次运行,都会覆盖之前文件中的数据 FileOutputStream(File file):创建一个向指定 File ...

  7. JAVA读写CSV文件

    最近工作需要,需要读写CSV文件的数据,简单封装了一下 依赖读写CSV文件只需引用`javacsv`这个依赖就可以了 <dependency> <groupId>net.sou ...

  8. golang学习笔记--函数和方法

    在go中,函数类型是一等类型,这意味着可以吧函数当做一个值来传递和使用. func divide(dividend int,divisor int)(int,error){ //省略部分代码 } 参数 ...

  9. centOS 在线安装lnmp

    CentOS7源码安装最新版LNMP环境   lnmp环境版本如下: 系统:CentOS 7 x86_64 NGINX:nginx-1.7.12 数据库:mariadb-10.0.13 PHP:php ...

  10. C# vb .net图像合成-合成矩形

    在.net中,如何简单快捷地实现图像合成呢,比如合成文字,合成艺术字,多张图片叠加合成等等?答案是调用SharpImage!专业图像特效滤镜和合成类库.下面开始演示关键代码,您也可以在文末下载全部源码 ...