从零开始学前端,React框架背后的核心机制和原理JSX
什么是React
React是起源于Facebook的一个前端框架,用于构建用户界面的JavaScript库,Facebook用来探索一种更加高效优雅的Javascript MVC框架来架设Instagram网站用的,后来觉得还不错,于是开源出来。
- 官方开源地址:https://github.com/facebook/react
- 官方案例地址:https://reactjs.org
- FaceBook开源官网:https://opensource.facebook.com/projects/
React特性
- 声明式
React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。
以声明式编写 UI,可以让你的代码更加可靠,且方便调试。
- 组件化
创建拥有各自状态的组件,再由这些组件构成更加复杂的 UI。
组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离。
- 一次学习,随处编写
无论你现在正在使用什么技术栈,你都可以随时引入 React 来开发新特性,而不需要重写现有代码。
React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。
安装最基础的环境NPM
React依赖NPM(Node.js Package Manager)来安装,所以我们可以先安装Node.Js环境。
Node.Js会自动带NPM组件和自动安装配套的可选组件,非常简便。
v14.15.1 LTS长期支持版:https://nodejs.org/dist/v14.15.1/node-v14.15.1-x64.msi
安装推荐的集成编辑器Visual Studio Code
最新Stable版:https://aka.ms/win32-x64-user-stable
官网:https://code.visualstudio.com/
创建项目目录并进行NPM初始化
创建一个名为zeroreact
的文件夹,用Visual Studio Code来进行打开,在终端界面,执行如下命令,进行NPM初始化。
npm init
一路回车就行了,创建后还能继续编辑的。
初始化完成之后,会看到当前项目根目录会新建一个叫package.json
的文件,其内容如下:
{
"name": "zeroreact",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
其实中main
是一个Node项目指向的开始文件,但是没有也可以。
创建Visual Studio Code的调式配置Launch.json
切换到Visual Studio Code左侧的运行菜单,点击创建Launch.json文件
,选择你中意的可选浏览器平台即可。
当前项目根目录会自动生成一个Launch.json配置文件,这个配置就是调式配置,它指向了调式项目时启动哪个平台。
其内容如下(以Edge:Launch为例):
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-msedge",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}
配置好了之后,启动调式即可打开对应的浏览器,并且自动打开其中url
配置所对应的地址值。
关于React实际必选项JSX
虽然React官方说,React可以不依赖JSX去运行,但是实际上JSX可以说是必选项。
而JSX是什么呢?
JSX是一种JavaScript的语法扩展,运用于React架构中,其格式比较像是模版语言,但事实上完全是在JavaScript内部实现的。元素是构成React应用的最小单位,JSX就是用来声明React当中的元素,React使用JSX来描述用户界面。
注意的是,JSX的特性更接近JavaScript而不是HTML,所以React DOM使用camelCase(小驼峰)命名来定义属性的名称,而不是使用HTML的属性名称。例如:class变成了className,而tableindex则对应着tableIndex
JSX基本格式:
- 简单闭合
const element = <img src={user.avatarUrl} />;
- 嵌套闭合
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
这些有趣的标签语法既不是字符串也不是 HTML。
它被称为JSX,是一个JavaScript的语法扩展。建议在React中配合使用JSX,JSX可以很好地描述UI应该呈现出它应有交互的本质形式。JSX可能会使人联想到模版语言,但它具有JavaScript的全部功能。
JSX可以生成React“元素”。
安装JSX所需的WebPack和Babel组件
React中的JSX实际上是通过Babel组件,把JSX的代码最终翻译成普通的Javascript代码的。
Babel转译器会把JSX转换成一个名为React.createElement()的方法调用
安装WebPack
什么是WebPack呢?
WebPack其核心在于让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用,webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作,接着我们只要处理最后那个js文件即可。
为什么要用WebPack呢?
WebPack其实它是一个Javascript的打包工具,它的输入和产出在正常情况下都是Javascript的文件,它最大的作用就是帮助我们把Javascript里面的import和require,把多文件打包成一个单个的Js文件。所以说呢,webpack往往是由一个文件作为入口,这个文件可能会import一些东西,可能会require一些东西,不管你用哪种写法都是可以的,然后它最终把它变成一个单个的大的文件,这样呢,比较符合我们在Web上的性能还有发布各方面的一些需求,当然WebPack它还同时承载了很多工具,其中就包括接下来会说到的前端重要的工具Babel。
npm install webpack webpack-cli --save-dev
通过以上命令,同时安装webpack
和webpack-cli
两个组件,并且--save-dev
表示这两个组件会被加到package.json
中的devDependencies
节点中去。
注意:这时候,如果你想直接使用webpack
指令,是不可行的,因为它没有被全局安装,如果你需要可以通过npm install xxx -g
这种形式去安装,但是目前已经不被推荐了。
我们可以采用被推荐的如下命令来调用WebPack
:
npx webpack
然后你会看到有个报错。
其实,webpack命令是执行了,但是因为我们没有给webpack做对应的配置及入口文件,所以它最终执行失败。
这时候,我们可以在根目录新建一个叫webpack.config.js
的文件,根据Node的标准,我们可以用module.exports
的写法,内如如下:
module.exports={
entry:{
main: './main.js'
}
}
其中entry
就是入口的意思,然后main
是默认入口,main
的指向我们可以暂时先给一个main.js
文件,同时我们需要在根目录新建一个空的main.js
文件。
完成以上配置之后,我们可以再次执行
npx webpack
即可看到打包成功的信息了,同时你会发现会新建一个dist
目录来输出WebPack
最终打包好的Js文件。
打包后的main.js是一段被压缩的JS代码,可阅读性呢不是很好,如果我们想看到更加可阅读性的JS代码,可以在webpack.config.js
增加开发阶段的配置:
module.exports={
entry:{
main: './main.js'
},
mode: 'development',
optimization:{
minimize: false
}
}
新增配置项mode
=development
,optimization
.minimize
被设置成false
之后,再次执行npx webpack
会看到main.js
中会得到更加可阅读性的JS代码。
安装Babel
什么是Babel呢?
Babel是一个工具链,主要用于将ECMAScript 2015+版本的代码转换为向后兼容的JavaScript语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
为什么使用Babel呢?
Babel这个工具是一个把新版本JS文件翻译成老版本JS文件的这样一种工具。
Babel在WebPack
里面是以Loader
的形式去使用的,WebPack
允许我们使用Loader
去定制各种各样的文件,比如说,原则上WebPack
只能打包普通的JS文件,但是我们如果想把CSS文件以某种形式打包成JS文件的话,那么我们就可以写一个CSS-Loader
,如果我们想把HTML当作一个JS文件去打包进来,那我们就可以写一个HTML-Loader
,而这个Loader
也可以是独立的包,我们只要在WebPack
的配置里面配一下就可以了。
接下来我们,安装Babel组件,执行如下命令:
npm install --save-dev babel-loader @babel/core @babel/preset-env
这里安装三个组件babel-loader
、@babel/core
、@babel/preset-env
,以空格隔开即可。
配置并使用Babel
我们可以通过WebPack
的Module
来配置Babel组件,其中Module
中重要的概念就是Rules
,它是一个数组,里面可以是一个对象,如下配置新增一个关于babel-loader
的rule规则配置。
module:{
rules:[
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options:{
presets: ['@babel/preset-env']
}
}
}
]
}
如配置所示,test
中是一个针对所有JS文件的正则表达式,会匹配所有的JS文件,那么遇到JS文件会执行use
中的loader
应用,这里指定了loader
为Babel-Loader
,这样所有的JS文件都会走Babel-Loader
来完成一次翻译。同时,我们还需要给Babel-Loader
配置一个presets
(注意presets也是一个数组值),其值是前面我们安装的@babel/preset-env
,这里的presets
可以理解为它是一系列Babel的config的一种快捷方式。
完成上诉Babel-Loader
配置之后,我们可以来实现下真正的翻译。
在main.js
中,我们可以加入一段JS代码,如下:
for(let i of [1,2,3])
{
console.log(i);
}
再次执行npx webpack
之后,我们将看到打包之后的main.js
翻译后的JS代码变成了一个eval
的for方法,这将是最终被执行的JS代码。
为了看到这段JS最终执行的效果,我们在dist目录里面新建一个main.html,来引用最终生成main.js
,并且F12看下输入效果。
可以看到,如预期结果,依次输出了1,2,3
配置并启用用于翻译JSX的Babel插件
默认Babel组件是没有能力来处理JSX的。
如果我们此时在main.js
里面写一个JSX,那么它会报错。
let a = <div />
但是有一个Babel插件是可以的,叫babel/plugin-transform-react-jsx
,接下来我们安装并配置它。
npm install @babel/plugin-transform-react-jsx --save-dev
然后我们还需要在Babel-Loader
中配置这个Plugin
,在Options
配置节点中,新建一个名为plugins
的节点,这也是一个数字,里面填入我们刚刚安装的Babel插件@babel/plugin-transform-react-jsx
。
module:{
rules:[
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options:{
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-react-jsx']
}
}
}
]
}
再次执行npx webpack
,就会发现,带JSX语法的JS已经可以正常翻译了,得到后的JS如图:
这里我们看到<div/>
被翻译为一个React.createElement(\"div\", null);
的方法了。
备注:
可以看到,默认它会被一个叫React
的函数来调用createElement
方法。
这里我们也可以通过配置来修改调React.createElement
这个名字,这里@babel/plugin-transform-react-jsx
支持一个叫pragma
的参数,这里可以指定你喜欢的名字,比如我这里的zero_react_create
plugins: [['@babel/plugin-transform-react-jsx', { pragma: 'zero_react_create'}]]
这样翻译后会变成你自定义的名字:
探索JSX语法糖机制
带属性的JSX
let a = <div id="a" class="c"/>
翻译后:
var a = zero_react_create("div", {
id: "a",
"class": "c"
});
带子节点的JSX
let a = <div id="a" class="c">
<div/>
<div/>
<div/>
</div>
翻译后:
var a = zero_react_create("div", {
id: "a",
"class": "c"
}, zero_react_create("div", null), zero_react_create("div", null), zero_react_create("div", null));
发现子节点都会追加到后面了。
构造zero_react_create
函数,并且输出它
function zero_react_create(tagName, attributes, ...children){
return document.createElement(tagName);
}
window.a = <div id="a" class="c">
<div/>
<div/>
<div/>
</div>
翻译执行后,可以在浏览器console里面输入a,回车看到a的输入值:
应用子节点,并且输出它
function zero_react_create(tagName, attributes, ...children){
let e = document.createElement(tagName);
for(let p in attributes){
e.setAttribute(p, attributes[p]);
}
for(let child of children){
e.appendChild(child);
}
return e;
}
window.a = <div id="a" class="c">
<div/>
<div/>
<div/>
</div>
翻译后执行:
子节点中包含值,并且输出它
function zero_react_create(tagName, attributes, ...children){
let e = document.createElement(tagName);
for(let p in attributes){
e.setAttribute(p, attributes[p]);
}
for(let child of children){
if(typeof child === 'string'){
child = document.createTextNode(child);
}
e.appendChild(child);
}
return e;
}
window.a = <div id="a" class="c">
<div>abc</div>
<div/>
<div/>
</div>
这里的变化是,如果子节点类型是个字符串,那我们就把Child变成一个文本节点。
翻译后:
将输出挂载到Body里面
为了挂载到Body,我们现在main.html
新建好Body标签:
<body>
<script src="main.js"></script>
</body>
然后修改JSX代码如下:
document.body.appendChild(<div id="a" class="c">
<div>abc</div>
<div/>
<div/>
</div>);
翻译后执行,就可以把我们JSX描述的HTML及数据,成功的以DOM形式挂载到Boby里面了
这样我们就实现了一种完全基于实DOM的zero_react_create
方法。
探索JSX自定义组件机制
初探自定义标签
在JSX中有个规定,如果你的Tag是小写,比如div
,它就认为这是一种原生的标签,如果是大写开头的,那就认为是自定义组件,比如我们将div
改成MyComponent
document.body.appendChild(<MyComponent id="a" class="c">
<div>abc</div>
<div/>
<div/>
</MyComponent>);
翻译后运行,会得到一个报错。
在这里,MyComponent
变成了我们自定义的一个对象,或者Class或者函数
这里我们先做Class
处理。
class MyComponent{
}
翻译后执行,会得到一个报错
这是因为zero_react_create
方法中TagName
这时候已经不是一个原始的标签字符串了,在执行let e = document.createElement(tagName);
会报错,因为这时候TagName
已经变成了一个对象。
这里,我们修改下zero_react_create
方法的判断。
function zero_react_create(tagName, attributes, ...children){
let e;
if(typeof tagName === 'string'){
e = document.createElement(tagName);
}
else
{
e = new tagName;
}
for(let p in attributes){
e.setAttribute(p, attributes[p]);
}
for(let child of children){
if(typeof child === 'string'){
child = document.createTextNode(child);
}
e.appendChild(child);
}
return e;
}
翻译运行后发现报错如下:
这里是因为,e这时候已经不是一个原生对象了,那我们自然有很多原始对象可以支持的就运行不了了,所以这里e.setAttribute(p, attributes[p]);
自然会报错。
这时候,我们可以给所有原生的DOM对象,都加一个Wrapper,让它可以正确的执行下去。
开启自定义组件之路
我们新建一个名为zero-react.js
的文件,把前面main.js
那个zero_react_create
函数搬过来,并且Export
出来。
export function zero_react_create(tagName, attributes, ...children){
let e;
if(typeof tagName === 'string'){
e = document.createElement(tagName);
}
else
{
e = new tagName;
}
for(let p in attributes){
e.setAttribute(p, attributes[p]);
}
for(let child of children){
if(typeof child === 'string'){
child = document.createTextNode(child);
}
e.appendChild(child);
}
return e;
}
并且在main.js
中import
文件zero-react.js
。
import { zero_react_create } from './zero-react.js'
重新编写zero-react.js
对外公开一个Component
组件,所有自定义组件继承它
export class Component{
constructor(){
this.props = Object.create(null);
this.children = [];
this._root = null;
}
setAttribute(name, value){
this.props[name] = value;
}
appendChild(component){
this.children.push(component);
}
get root(){
if(!this._root){
this._root = this.render().root;
}
return this._root;
}
}
新建一个TextWrapper
来处理纯文本子节点。
class TextWrapper{
constructor(content){
this.root = document.createTextNode(content);
}
}
新建一个ElementWrapper
来处理原生标签节点。
class ElementWrapper{
constructor(tagName){
this.root = document.createElement(tagName);
}
setAttribute(name, value){
this.root.setAttribute(name, value);
}
appendChild(component){
this.root.appendChild(component.root);
}
}
替换原来zero_react_create
函数中的两个实现
其中把e = document.createElement(tagName);
替换成e = new ElementWrapper(tagName);
,把child = document.createTextNode(child);
替换成child = new TextWrapper(child);
,得到如下新的zero_react_create
函数
export function zero_react_create(tagName, attributes, ...children){
let e;
if(typeof tagName === 'string'){
e = new ElementWrapper(tagName);
}
else
{
e = new tagName;
}
for(let p in attributes){
e.setAttribute(p, attributes[p]);
}
for(let child of children){
if(typeof child === 'string'){
child = new TextWrapper(child);
}
e.appendChild(child);
}
return e;
}
最后对外公开一个render
函数
export function render(component, parentElement){
parentElement.appendChild(component.root);
}
修改main.js
中MyComponent
的实现。
import { render, Component, zero_react_create } from './zero-react.js'
class MyComponent extends Component{
render(){
return <div>zero component</div>
}
}
render(<MyComponent id="a" class="c">
<div>abc</div>
<div/>
<div/>
</MyComponent>, document.body);
翻译后,运行可得:
但是我们还看不到子节点元素,这时候如果把子节点元素传进来:
class MyComponent extends Component{
render(){
return <div><h1>zero component</h1>
{this.children}
</div>
}
}
运行会报错:
因为这时候子节点会把当作一个数组传进去,但是原来child = new TextWrapper(child);
处是没有能力处理这种情况的。
这时候,我们需要改造下这里。
export function zero_react_create(tagName, attributes, ...children){
let e;
if(typeof tagName === 'string'){
e = new ElementWrapper(tagName);
}
else
{
e = new tagName;
}
for(let p in attributes){
e.setAttribute(p, attributes[p]);
}
let insertChilder = (children) => {
for(let child of children){
if(typeof child === 'string'){
child = new TextWrapper(child);
}
if((typeof child === 'object') && (child instanceof Array)){
insertChilder(child);
}
else{
e.appendChild(child);
}
}
}
insertChilder(children);
return e;
}
翻译执行后,就可以看到成功的处理了带多个子节点的情况了。
于是我们就打造了一个属于自己的自定义组件处理引擎了。
附件
从零开始学前端,React框架背后的核心机制和原理JSX的更多相关文章
- Qt核心机制与原理
转: https://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★ ...
- Qt核心机制和原理
转:http://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★了解Q ...
- 从零开始搭建Webpack+react框架
1.下载node.js Node.js官网下载 , 安装: 安装成功后在控制台输入node -v 可查看当前版本: $ node -v v10.15.0 输入npm -v查看npm版本: $ npm ...
- Spring框架两大核心机制(IoC、AOP)
IoC(控制反转)/ DI(依赖注入) AOP(面向切面编程) Spring 是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件. MVC:Struts ...
- 从零开始学spring cloud(八) -------- Eureka 高可用机制
一.Eureka高可用机制介绍 Eureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以使其注册保持最新(因此可以在内存中完成). 客户端还有一个Eureka注册的内存缓存(因此,他们不必 ...
- zookeeper 负载均衡 核心机制-实现原理 包含ZAB协议(滴滴,阿里面试)
面试也经常问kafka的原理,以及zookeeper与kafka原理的区别:kafka 数据一致性-leader,follower机制与zookeeper的区别: zookeeper是如何实现负载均衡 ...
- Redis的几个核心机制底层原理
#### 1.S_DOWN和O_DOWN ###### S_DOWN和O_DOWN两种宕机状态 (1).S_DOWN是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机 s ...
- 前端三大框架Angular & React & Vue
前端三大框架: Angular[Google]:一套框架,多种平台移动端 & 桌面端.学会用Angular构建应用,然后把这些代码和能力复用在多种多种不同平台的应用上 —— Web.移动 We ...
- 从零开始学 Web 之 HTML(一)认识前端
大家好,这里是 Daotin 从零开始学 Web 系列教程.此文首发于「 Daotin的梦呓 」,欢迎大家订阅关注.在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享 ...
随机推荐
- mybatis-plus批量插入saveBatch太慢?我愿意称rewriteBatchedStatements为神
最近在做项目优化,代码优化之后,测试接口,好家伙.一个定时任务接口执行要10秒左右. 一点点追踪,给每个方法打上执行时间,一点点缩小范围.好家伙,终于让我锁定了目标. 这是mybatis-plus的批 ...
- Linux——CentOS7添加/删除用户和用户组1
Linux--CentOS7添加/删除用户和用户组 2017.05.02 19:58 23012浏览 前言 今天又重新装了centos7突然有关用户和用户组有关的命令记不清了,所以记一下,也方便你 ...
- IT菜鸟之虚拟机VMware的使用
虚拟机安装完成了,以下是虚拟机的使用. 双击快捷方式,打开vmware虚拟机. 点击创建新虚拟机,这里可以选择创建方式,可以点击典型并一路下一步创建,我们这里讲自定义创建. 这里选择兼容版本,大家可以 ...
- TEB 系统综合误差
TEB 系统综合误差 和森世籍 聊天得知 该TEB 包括 传感器误差 温度 系统误差等等
- python基础之psutil模块和发邮件(smtplib和yagmail)
除了内建的模块外,Python还有大量的第三方模块. 基本上,所有的第三方模块都会在PyPI - the Python Package Index上注册,只要找到对应的模块名字,即可用pip安装. 此 ...
- BXL文件怎样转换为AD LIB文件
https://jingyan.baidu.com/article/48b558e326e1b17f39c09a57.html
- redis 处理缓存穿透
1. 缓存穿透简述 举例说明,redis中确实没有key值为"redis"数据,并且数据库里面也没有,那么每一次都会穿过缓存层,会将请求打到数据库查询,然后数据库进行查询,造成了不 ...
- MYSQL导入/迁移后事件不执行
mysql迁移后事件不执行 查看数据库是否开启事件支持 mysql> show variables like 'event_scheduler'; +-----------------+---- ...
- LogBack 日志等级设置无效,原因竟然是因为这个?!
Hello,大家好,我是楼下小黑哥~ 最近被公司派去北京出差,本以为是个轻松的差事,北京一周游~ 但是没想到第一天就是九点半下班, 大意了~ 好了,回到正题,今天来讲下最近调试项目的时候发现的一个 L ...
- Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
On branch master Your branch and 'origin/master' have diverged, and have 1 and 1 different commits e ...