什么是React

React是起源于Facebook的一个前端框架,用于构建用户界面的JavaScript库,Facebook用来探索一种更加高效优雅的Javascript MVC框架来架设Instagram网站用的,后来觉得还不错,于是开源出来。

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

官网:https://nodejs.org/zh-cn/

安装推荐的集成编辑器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是什么呢?

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

通过以上命令,同时安装webpackwebpack-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=developmentoptimization.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

我们可以通过WebPackModule来配置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应用,这里指定了loaderBabel-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.jsimport文件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.jsMyComponent的实现。

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的更多相关文章

  1. Qt核心机制与原理

    转:  https://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★ ...

  2. Qt核心机制和原理

    转:http://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★了解Q ...

  3. 从零开始搭建Webpack+react框架

    1.下载node.js Node.js官网下载 , 安装: 安装成功后在控制台输入node -v 可查看当前版本: $ node -v v10.15.0 输入npm -v查看npm版本: $ npm ...

  4. Spring框架两大核心机制(IoC、AOP)

    IoC(控制反转)/ DI(依赖注入) AOP(面向切面编程) Spring 是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件. MVC:Struts ...

  5. 从零开始学spring cloud(八) -------- Eureka 高可用机制

    一.Eureka高可用机制介绍 Eureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以使其注册保持最新(因此可以在内存中完成). 客户端还有一个Eureka注册的内存缓存(因此,他们不必 ...

  6. zookeeper 负载均衡 核心机制-实现原理 包含ZAB协议(滴滴,阿里面试)

    面试也经常问kafka的原理,以及zookeeper与kafka原理的区别:kafka 数据一致性-leader,follower机制与zookeeper的区别: zookeeper是如何实现负载均衡 ...

  7. Redis的几个核心机制底层原理

    #### 1.S_DOWN和O_DOWN ######   S_DOWN和O_DOWN两种宕机状态  (1).S_DOWN是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机 s ...

  8. 前端三大框架Angular & React & Vue

    前端三大框架: Angular[Google]:一套框架,多种平台移动端 & 桌面端.学会用Angular构建应用,然后把这些代码和能力复用在多种多种不同平台的应用上 —— Web.移动 We ...

  9. 从零开始学 Web 之 HTML(一)认识前端

    大家好,这里是 Daotin 从零开始学 Web 系列教程.此文首发于「 Daotin的梦呓 」,欢迎大家订阅关注.在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享 ...

随机推荐

  1. mybatis-plus批量插入saveBatch太慢?我愿意称rewriteBatchedStatements为神

    最近在做项目优化,代码优化之后,测试接口,好家伙.一个定时任务接口执行要10秒左右. 一点点追踪,给每个方法打上执行时间,一点点缩小范围.好家伙,终于让我锁定了目标. 这是mybatis-plus的批 ...

  2. Linux——CentOS7添加/删除用户和用户组1

    Linux--CentOS7添加/删除用户和用户组 2017.05.02 19:58 23012浏览   前言 今天又重新装了centos7突然有关用户和用户组有关的命令记不清了,所以记一下,也方便你 ...

  3. IT菜鸟之虚拟机VMware的使用

    虚拟机安装完成了,以下是虚拟机的使用. 双击快捷方式,打开vmware虚拟机. 点击创建新虚拟机,这里可以选择创建方式,可以点击典型并一路下一步创建,我们这里讲自定义创建. 这里选择兼容版本,大家可以 ...

  4. TEB 系统综合误差

    TEB  系统综合误差  和森世籍  聊天得知 该TEB  包括  传感器误差  温度  系统误差等等

  5. python基础之psutil模块和发邮件(smtplib和yagmail)

    除了内建的模块外,Python还有大量的第三方模块. 基本上,所有的第三方模块都会在PyPI - the Python Package Index上注册,只要找到对应的模块名字,即可用pip安装. 此 ...

  6. BXL文件怎样转换为AD LIB文件

    https://jingyan.baidu.com/article/48b558e326e1b17f39c09a57.html

  7. redis 处理缓存穿透

    1. 缓存穿透简述 举例说明,redis中确实没有key值为"redis"数据,并且数据库里面也没有,那么每一次都会穿过缓存层,会将请求打到数据库查询,然后数据库进行查询,造成了不 ...

  8. MYSQL导入/迁移后事件不执行

    mysql迁移后事件不执行 查看数据库是否开启事件支持 mysql> show variables like 'event_scheduler'; +-----------------+---- ...

  9. LogBack 日志等级设置无效,原因竟然是因为这个?!

    Hello,大家好,我是楼下小黑哥~ 最近被公司派去北京出差,本以为是个轻松的差事,北京一周游~ 但是没想到第一天就是九点半下班, 大意了~ 好了,回到正题,今天来讲下最近调试项目的时候发现的一个 L ...

  10. 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 ...