没有用到React,为什么我需要import引入React?
没有用到React,为什么我需要import引入React?
本质上来说JSX是React.createElement(component, props, ...children)方法的语法糖。
所以我们如果使用了JSX,我们其实就是在使用React,所以我们就需要引入React
前言
React是前端最受欢迎的框架之一,解读其源码的文章非常多,但是我想从另一个角度去解读React:从零开始实现一个React,从API层面实现React的大部分功能,在这个过程中去探索为什么有虚拟DOM、diff、为什么setState这样设计等问题。
提起React,总是免不了和Vue做一番对比
Vue的API设计非常简洁,但是其实现方式却让人感觉是“魔法”,开发者虽然能马上上手,但其原理却很难说清楚。
相比之下React的设计哲学非常简单,虽然有很多需要自己处理的细节问题,但它没有引入任何新的概念,相对更加的干净和简单。
关于jsx
在开始之前,我们有必要搞清楚一些概念。
我们来看一下这样一段代码:
const title = <h1 className="title">Hello, world!</h1>;
这段代码并不是合法的js代码,它是一种被称为jsx的语法扩展,通过它我们就可以很方便的在js代码中书写html片段。
本质上,jsx是语法糖,上面这段代码会被babel转换成如下代码:
const title = React.createElement(
'h1',
{ className: 'title' },
'Hello, world!'
);
React.createElement和虚拟DOM
前文提到,jsx片段会被转译成用React.createElement方法包裹的代码。所以第一步,我们来实现这个React.createElement方法
从jsx转译结果来看,createElement方法的参数是这样:
createElement( tag, attrs, child1, child2, child3 );
第一个参数是DOM节点的标签名,它的值可能是div,h1,span等等
第二个参数是一个对象,里面包含了所有的属性,可能包含了className,id等等
从第三个参数开始,就是它的子节点
我们对createElement的实现非常简单,只需要返回一个对象来保存它的信息就行了。
function createElement( tag, attrs, ...children ) {
return {
tag,
attrs,
children
}
}
函数的参数 ...children使用了ES6的rest参数,它的作用是将后面child1,child2等参数合并成一个数组children。
现在我们来试试调用它
// 将上文定义的createElement方法放到对象React中
const React = {
createElement
} const element = (
<div>
hello<span>world!</span>
</div>
);
console.log( element );
打开调试工具,我们可以看到输出的对象和我们预想的一致

我们的createElement方法返回的对象记录了这个DOM节点所有的信息,换言之,通过它我们就可以生成真正的DOM,这个记录信息的对象我们称之为虚拟DOM。
ReactDOM.render
接下来是ReactDOM.render方法,我们再来看这段代码
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
经过转换,这段代码变成了这样
ReactDOM.render(
React.createElement( 'h1', null, 'Hello, world!' ),
document.getElementById('root')
);
所以render的第一个参数实际上接受的是createElement返回的对象,也就是虚拟DOM
而第二个参数则是挂载的目标DOM
总而言之,render方法的作用就是将虚拟DOM渲染成真实的DOM,下面是它的实现:
function render( vnode, container ) {
// 当vnode为字符串时,渲染结果是一段文本
if ( typeof vnode === 'string' ) {
const textNode = document.createTextNode( vnode );
return container.appendChild( textNode );
}
const dom = document.createElement( vnode.tag );
if ( vnode.attrs ) {
Object.keys( vnode.attrs ).forEach( key => {
const value = vnode.attrs[ key ];
setAttribute( dom, key, value ); // 设置属性
} );
}
vnode.children.forEach( child => render( child, dom ) ); // 递归渲染子节点
return container.appendChild( dom ); // 将渲染结果挂载到真正的DOM上
}
设置属性需要考虑一些特殊情况,我们单独将其拿出来作为一个方法setAttribute
function setAttribute( dom, name, value ) {
// 如果属性名是className,则改回class
if ( name === 'className' ) name = 'class';
// 如果属性名是onXXX,则是一个事件监听方法
if ( /on\w+/.test( name ) ) {
name = name.toLowerCase();
dom[ name ] = value || '';
// 如果属性名是style,则更新style对象
} else if ( name === 'style' ) {
if ( !value || typeof value === 'string' ) {
dom.style.cssText = value || '';
} else if ( value && typeof value === 'object' ) {
for ( let name in value ) {
// 可以通过style={ width: 20 }这种形式来设置样式,可以省略掉单位px
dom.style[ name ] = typeof value[ name ] === 'number' ? value[ name ] + 'px' : value[ name ];
}
}
// 普通属性则直接更新属性
} else {
if ( name in dom ) {
dom[ name ] = value || '';
}
if ( value ) {
dom.setAttribute( name, value );
} else {
dom.removeAttribute( name );
}
}
}
这里其实还有个小问题:当多次调用render函数时,不会清除原来的内容。所以我们将其附加到ReactDOM对象上时,先清除一下挂载目标DOM的内容:
const ReactDOM = {
render: ( vnode, container ) => {
container.innerHTML = '';
return render( vnode, container );
}
}
渲染和更新
到这里我们已经实现了React最为基础的功能,可以用它来做一些事了。
我们先在index.html中添加一个根节点
<div id="root"></div>
我们先来试试官方文档中的Hello,World
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
可以看到结果:

没有用到React,为什么我需要import引入React?的更多相关文章
- Pycharm中 import 引入同级文件失败问题
Pycharm中 import 引入同级文件失败,如下所示: “This inspection detects names that should resolve but don't. Due to ...
- css样式中@import引入样式
css样式中@import引入样式 学习了:http://www.cnblogs.com/zbo/archive/2010/11/17/1879590.html
- Vue style里面使用@import引入外部css, 作用域是全局的解决方案
问题描述 使用@import引入外部css,作用域却是全局的 <template> </template> <script> export default { na ...
- php Yaf_Loader::import引入文件报错的解决方法
php Yaf_Loader::import引入文件报错的解决方法 改下配置文件就行<pre>yaf.use_spl_autoload=1</pre> 也可以PHP动态修改 毕 ...
- Vue-Cli 3.x 创建的项目中对 import 引入的 CSS 样式启用 autoprefixer
问题描述: Vue-Cli 3.x 默认开启了 autoprefixer,但对于在 main.js 中通过 import 引入的 CSS 并没有自动添加前缀 分析原因: autoprefixer 在项 ...
- 普通页面引入React(使用和不使用JSX)
1. 不使用JSX 优点: 不用配置有关JSX的编译. 依赖语法: React.createElement(component/type, props, ...chilidren); //第一个参数可 ...
- vue 各种 import 引入
vue 各种 import 引入: https://www.jianshu.com/p/784e51ec68ce 阮一峰:http://es6.ruanyifeng.com/#docs/module
- link和@import引入css 区别,不建议使用@import
众多周知,有两种方法可以在页面中导入样式文件. <link href="a.css" rel="stylesheet"> <style> ...
- React学习笔记-1-什么是react,react环境搭建以及第一个react实例
什么是react?react的官方网站:https://facebook.github.io/react/下图这个就是就是react的标志,非常巧合的是他和我们的github的编辑器Atom非常相似. ...
随机推荐
- 小学四则运算口算练习app---No.1
因为对app不是很了解,对环境的配置也不是很舒心,今天主要配置了环境,了解了一些相关app的简单操作以及安卓stdiuo的使用!如下: 我自己连接的自己的手机(还是不要拿自己的手机做测试哦!模拟器虽然 ...
- 前端html页面,手机查看
在写前端页面中,经常会在浏览器运行HTML页面,从本地文件夹中直接打开的一般都是file协议,当代码中存在http或https的链接时,HTML页面就无法正常打开,为了解决这种情况,需要在在本地开启一 ...
- .Net反射-基础1-Assembly、Type
Assembly:封装程序集信息,可以动态加载程序集 获取Assembly的几种方式: 1.var ass1 = Assembly.Load("ClassLibrary1");// ...
- 复制excel表中的数据
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- ElementUI_NodeJS环境搭建
ElementUI简介 我们学习VUE,知道它的核心思想式组件和数据驱动,但是每一个组件都需要自己编写模板,样式,添加事件,数据等是非常麻烦的, 所以饿了吗推出了基于VUE2.0的组件库,它的名称叫做 ...
- 联想笔记本上Ubuntu无线网卡问题
可能有两个问题: 1.无无线网卡驱动 2.无线网卡驱动不能自动加载 问题1:无线网卡驱动 百度出网卡驱动iwlwifi-9000,如iwlwifi-9000-pu-b0-jf-b0-34.618819 ...
- haproxy 配置文件详解 之 配置文件示例
此示例文件在haproxy1.8.20 测试没有问题: global log 127.0.0.1 local0 info maxconn user nobody group nobody daemon ...
- List 拆分集合与 读写XML配置文件
有时候会出现需要将一个集合分成所干个集合,依次再对每组集合进行处理,想了想,用 Linq 处理就很方便 ); ).Take(); //第一组 ).Take(); //第二组 ; i < time ...
- redis为何单线程 效率还这么高 为何使用跳表不使用B+树做索引(阿里)
如果想了解 redis 与Memcache的区别参考:Redis和Memcache的区别总结 阿里的面试官问问我为何redis 使用跳表做索引,却不是用B+树做索引 因为B+树的原理是 叶子节点存储数 ...
- jenkins pipeline使用方式
pipeline 使用 使用groovy的一种DSL语言,流程控制 pipeline脚本同其他脚本语言一样,从上到下顺序执行,它的流程控制取决于Groovy表达式,为jenkins用户提供了更巨大的灵 ...