什么是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;
}

翻译执行后,就可以看到成功的处理了带多个子节点的情况了。

于是我们就打造了一个属于自己的自定义组件处理引擎了。

附件

最新文章

  1. SQL注入攻防入门详解
  2. R包之间冲突带来的奇怪错误
  3. 16个时髦的扁平化设计的 HTML5 &amp; CSS3 网站模板
  4. arraylist与linkedlist的区别与性能测试
  5. Mongo对内嵌文档的CRUD
  6. [hadoop] hadoop “util.NativeCodeLoader: Unable to load native-hadoop library for your platform”
  7. 深入了解relative
  8. iOS应用内支付(内购)的个人开发过程及坑!
  9. Swift中面向协议的编程
  10. linux ftp批量上传和下载文件
  11. 使用cocapods报错 [!] Your Podfile has had smart quotes sanitised. To avoid issues in the future, you should not use TextEdit for editing it. If you are not using TextEdit, you should turn off smart quotes
  12. eclipse如何debug调试jdk源码(任何源码)并显示局部变量
  13. Kafka系列之-Kafka Protocol实例分析
  14. mongo 写分析
  15. 动态代理和CGlib
  16. EasyUI 分页 偶遇 问题
  17. [leetcode]984. 不含 AAA 或 BBB 的字符串
  18. Python之路,第三篇:Python入门与基础3
  19. D17——C语言基础学PYTHON
  20. Mysql数据库基础知识

热门文章

  1. 【二】Kubernetes 集群部署-kubeadm方式(亲测)
  2. [c++] 常犯错误
  3. CentOS7中下载RPM及其所有的依赖包
  4. /etc/ssh/sshd_config ssh自动断 cent7
  5. nginx 的常用模块
  6. C++知识点案例 笔记-2
  7. 如果你想设置无人自动升级,我们推荐你将这个值修改为security,它会告诉 yum 仅仅升级修复安全问题的软件包。
  8. shell基础之多功能nginx(安装、重启、停止等)
  9. STM32用FreeRTOS时任务优先级和中断优先级说明
  10. 在react中使用redux并实现计数器案例