React: 无状态组件生成真实DOM结点
在上一篇文章中,我们总结并模拟了 JSX 生成真实 DOM 结点的过程,今天接着来介绍一下无状态组件的生成过程。
先以下面一段简单的代码举例:
const Greeting = function ({name}) {
return <div>{`hello ${name}`}</div>;
};
const App = <Greeting name="scott"/>;
console.log(App);
ReactDOM.render(App, document.getElementById('root'));
可以看出,Greeting 是一个无状态组件,我们来看看编译过后的可执行代码:
var Greeting = function Greeting(_ref) {
var name = _ref.name;
return React.createElement(
"div",
null,
"hello " + name
);
};
var App = React.createElement(Greeting, { name: "scott" });
console.log(App);
ReactDOM.render(App, document.getElementById('root'));
我们看到,调用 Greeting 组件时传入的 name 属性,出现在 React.createElement() 方法的第二个参数中,这和前面介绍的 JSX 是一致的,不同的是,React.createElement() 方法的第一个参数不再是一个标签名,而是一个函数引用,指向了我们声明的 Greeting 组件,而 name 属性也作为参数的成员出现在组件内部,这个参数名为 _ref,实则是我们熟知的 props。
下图是我们运行上面代码之后,打印出的 App 数据结构,即虚拟 DOM 结构:
我们再来看一个稍微复杂些的例子:
const Greeting = function ({name}) {
return (
<div>{`hello ${name}`}</div>
);
};
const Container = function ({children}) {
return (
<div className="container">
{children}
</div>
);
};
const App = (
<Container>
<Greeting name="scott"/>
<Greeting name="jack"/>
<Greeting name="john"/>
</Container>
);
console.log(App);
ReactDOM.render(App, document.getElementById('root'));
在上面代码中,我们定义了两个无状态组件,其中 Container 用来作为外层的容器,Greeting 则用来显示实际的业务视图。
现在再来看看编译后的代码结构:
var Greeting = function Greeting(_ref) {
var name = _ref.name;
return React.createElement(
"div",
null,
"hello " + name
);
};
var Container = function Container(_ref2) {
var children = _ref2.children;
return React.createElement(
"div",
{ className: "container" },
children
);
};
var App = React.createElement(
Container,
null,
React.createElement(Greeting, { name: "scott" }),
React.createElement(Greeting, { name: "jack" }),
React.createElement(Greeting, { name: "john" })
);
console.log(App);
ReactDOM.render(App, document.getElementById('root'));
这次我们主要观察 Container 的结构,它实际上是将 React.createElement() 方法的第三个参数作为 props.children 传递到了组件内部,而这个 children 是一个 Greeting,最终是将 Greeting 渲染在 Container 组件内部。
接下来,我们要改进一下之前实现的 React.createElement() 和 ReactDOM.render() 方法,使它们支持组件的形式,模拟生成虚拟 DOM 和真实 DOM。
先来看看 React.createElement() 方法:
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') {
vnode.body = type({
...props,
children,
});
}
return vnode;
}
};
上面的代码主要对组件做了特殊处理。如果当前处理对象是组件,则对应的 type 就是函数的引用,我们会调用这个组件函数,然后将函数体的结果作为 body 属性挂载到该结点上。需要注意的是,我们在上面方法参数中使用了可变参数的形式,如果直接引用这个 children,它本身就是一个变参数组,如果组件体内使用了 props.children,那么在调用 React.createElement() 时,变参数组的形式将会是 [[...]],所以我们需要特殊处理一下。
现在,我们运行程序,看看上面代码生成的虚拟 DOM 结构:
最后,再来看看 ReactDOM.render() 方法:
const ReactDOM = {
// 渲染真实DOM
render(vnode, container) {
let realDOM = this.generateDOM(vnode);
container.appendChild(realDOM);
},
// 获取真实DOM
generateDOM(vnode) {
if (typeof vnode.type === 'function') {
// 将组件函数体的虚拟DOM生成真实DOM
return this.generateDOM(vnode.body);
}
let elem = document.createElement(vnode.type);
// 特殊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 (typeof child === 'string') {
// 纯内容节点
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 {
// 设置其他属性
elem.setAttribute(specialKeyMap[key] || key, props[key]);
}
});
return elem;
}
};
上面代码中改动较小,我们只添加了几行针对组件的处理逻辑,如果是组件函数,则将函数体的虚拟 DOM 生成真实 DOM。
最后,我们来看看最终生成的 DOM 结构:
React: 无状态组件生成真实DOM结点的更多相关文章
- React: 有状态组件生成真实DOM结点
上次我们分析了无状态组件生成 DOM 的过程,无状态组件其实就是纯函数,它不维护内部的状态,只是根据外部输入,输出一份视图数据.而今天我们介绍的有状态组件,它有内部的状态,因此在组件的内部,可以自行对 ...
- React系列文章:无状态组件生成真实DOM结点
在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name ...
- React系列文章:JSX生成真实DOM结点
在上一篇文章中,我们介绍了Babel是如何将JSX代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了Babel编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const style ...
- React: JSX生成真实DOM结点
在上一篇文章中,我们介绍了 Babel 是如何将 JSX 代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了 Babel 编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const ...
- React 中的 Component、PureComponent、无状态组件 之间的比较
React 中的 Component.PureComponent.无状态组件之间的比较 table th:first-of-type { width: 150px; } 组件类型 说明 React.c ...
- React中的高阶组件,无状态组件,PureComponent
1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...
- react的redux无状态组件
Provider功能主要为以下两点: 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件 接收Redux的store作为props,通过context对象传递给子孙组件上的connec ...
- 15. react UI组件和容器组件的拆分 及 无状态组件
1.组件的拆分 组件拆分的前提 当所有的逻辑都出现在一个组件内时 组件会变得非常复杂 不便与代码的维护 所以对组件进行拆分 IU组件 进行页面渲染 容器组件 进行逻辑操作 UI组件的拆分 新建一个 ...
- Blazor中的无状态组件
声明:本文将RenderFragment称之为组件DOM树或者是组件DOM节点,将*.razor称之为组件. 1. 什么是无状态组件 如果了解React,那就应该清楚,React中存在着一种组件,它只 ...
随机推荐
- flink ---- 系统内部消息传递的exactly once语义
At Most once,At Least once和Exactly once 在分布式系统中,组成系统的各个计算机是独立的.这些计算机有可能fail. 一个sender发送一条message到rec ...
- (原)使用ass字幕文件通过ffmpeg给视频添加字幕的一些研究
使用ass字幕文件通过ffmpeg给视频添加字幕的一些研究 Author:lihaiping1603@aliyun.com Create:2019-09-04 最近对ffmpeg给视频文件添加字幕效果 ...
- ASP.NET LinqDataSource数据绑定后,遇到[MissingMethodException: 没有为该对象定义无参数的构造函数。]问题。
问题出现的情形:LinqDataSource数据绑定到DetailsView或GridView均出错,错误如下: “/”应用程序中的服务器错误. 没有为该对象定义无参数的构造函数. 说明: 执行当前 ...
- java js ur特殊格式处理 json 特殊格式处理
url特殊格式处理: js中使用 encodeURIComponent() 编码对应的value $.ajax({ type: "post", url: "/tb_are ...
- EasyNVR摄像机网页无插件直播方案H5前端构建之:关于接口调用常见的一些问题(401 Unauthorized)
背景分析 最近在使用EasyNVR的过程中,很多小伙伴咨询关于接口调用的问题,初步判断应该是遇到权限问题(401 Unauthorized).EasyNVR为第三方系统和应用提供了标准的API接口,方 ...
- K3Wise插件开发实战教程(全套)持续更新中。。。
这是林枫山自己编写制作的全套K3wise插件教程,欢迎下载学习. 下载目录链接如下(如果链接下载不了,请加QQ:714259796获取教程): 进度01-K3Wise数据表详解 下载学习文档 ...
- 【miscellaneous】编码格式简介(ANSI、GBK、GB2312、UTF-8、GB18030和 UNICODE)
转发:http://blog.jobbole.com/30526/ 来源:潜行者m 的博客 编码一直是让新手头疼的问题,特别是 GBK.GB2312.UTF-8 这三个比较常见的网页编码的区别,更是让 ...
- 【sqlserver】【mysql】用NavicatPremium12把sqlserver的数据迁移到mysql上
1.在NavicatPremium的mysql,选择到迁移到的表,然后右键[导入向导]
- 聚焦JavaScript面向对象的思想
面向对象是一种软件开发方法,是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物.随着时代的发展,计算机被用于解决越来越复杂的问题.一切事物皆对象,通过面向对象的方式,将现实世界的 ...
- 【转帖】Linux命令行操作json神器jq
Linux命令行操作json神器jq https://www.cnblogs.com/chenqionghe/p/11736942.html jq类似一个awk或grep一样的神器,可以方便地在命令行 ...