前言:
今天开始了解一个前端语言框架React
代码之前:
什么是React:
React 是一个用于构建用户界面的 JAVASCRIPT 库。
React 主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图)。
React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。
- Framework(框架):大而全,提供了一整套的解决方案。
- library(库):小而巧,可以很方便的切换库,只提供特定的API
核心概念:
虚拟DOM:
何为DOM?
DOM(Document Object Model),DOM全拼为Document Object Model(文档对象模型)是一种用于HTML和XML文档的编程接口,它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式,是一种浏览器中的概念。
换句话说,DOM是针对HTML和XML的API。 可以理解为DOM就是一系列功能集合。
HTML DOM 将 HTML 文档视作树结构。这种结构被称为节点树:
通过 HTML DOM,树中的所有节点均可通过 JavaScript 进行访问。所有 HTML 元素(节点)均可被修改,也可以创建或删除节点。
HTML DOM将html元素定义为对象,API以对象方法和对象属性的形式实现。
可直接调用DOM实现的方法,进行DOM操作,例如:
getElementById()
返回带有指定 ID 的元素。
getElementsByTagName()
返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组)。
getElementsByClassName()
返回包含带有指定类名的所有元素的节点列表。
appendChild()
把新的子节点添加到指定节点。
removeChild()
删除子节点。
replaceChild()
替换子节点。
insertBefore()
在指定的子节点前面插入新的子节点。
createAttribute()
创建属性节点。
createElement()
创建元素节点。
createTextNode()
创建文本节点。
getAttribute()
返回指定的属性值。
setAttribute()
把指定属性设置或修改为指定的值。
何为虚拟DOM?
虚拟DOM是框架中的概念。
DOM中的API是由浏览器来提供的,而虚拟DOM中的API是由框架来提供的,用来模拟页面上DOM元素和DOM元素的嵌套。
为什么要用虚拟DOM:为了实现页面上DOM元素的高效更新。
一个网页呈现的过程:
- 浏览器请求服务器获取页面HTML代码
- 浏览器在缓存中解析DOM结构,并在浏览器缓存中,渲染出一颗DOM数
- 浏览器把DOM数呈现在页面上
用我们传统的开发模式,原生JS或JQ操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。在一次操作中,我需要更新10个DOM节点,浏览器收到第一个DOM请求后并不知道还有9次更新操作,因此会马上执行流程,最终执行10次。例如,第一次计算完,紧接着下一个DOM更新请求,这个节点的坐标值就变了,前一次计算为无用功。计算DOM节点坐标值等都是白白浪费的性能。即使计算机硬件一直在迭代更新,操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验。
为了解决性能问题,我们可以手动创造一颗DOM树,并与旧DOM树做对比,最后只变化一部分。
手动创建DOM树的思路示例:
DOM结构
<ul id="list">
<li class="item">item1</li>
<li class="item">item2</li>
</ul>
用js表示
{
tag:'ul',
attrs:{
id:'list'
},
children:{
{
tag:'li',
attrs:{className:'item'}, //class是js保留字,所以只能叫className
children:['item1']
},
{
tag:'li',
attrs:{className:'item'},
children:['item2']
}
}
}
我们用JS对象的形式,来模拟页面上DOM嵌套关系(虚拟DOM是以JS对象的形式存在的),这就是虚拟DOM
- 本质:用JS对象来模拟DOM元素和嵌套关系
- 目的:实现页面元素的高效更新
diff算法:
我们会有两个虚拟DOM(js对象,new/old进行比较diff),用户交互我们操作数据变化new虚拟DOM,old虚拟DOM会映射成实际DOM(js对象生成的DOM文档)通过DOM fragment操作给浏览器渲染。当修改new虚拟DOM,会把newDOM和oldDOM通过diff算法比较,得出diff结果数据表(用4种变换情况表示)。再把diff结果表通过DOM fragment更新到浏览器DOM中。
创建基本的webpack项目
关于webpack,我以前聊过,可以先复习一下。
- 运行
npm init -y
快速初始化项目 - 新建目录src用来存放所有的项目源代码,创建目录dist(build) 项目发布目录
- 在src下创建一个页面,index.html,并且使用 !+Tab 快捷生成标准 html,再新建一个index.js一会作为webpack的入口文件。
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>这是首页!</h1>
</body>
</html>
//index.js
console.log('OK!')
然后,我们使用 npm i webpack -D
或者 cnpm i webpack -D(需要配置好淘宝源)
来安装webpack,并且使用同样的 npm i webpack-cli -D
来安装webpack-cli,现在的webpack都是4以上的版本,需要这个cli来帮助打包(在4.x版本后,webpack的命令行进行打包由webpack-cli负责)
安装好之后我们创建一个 webpack.config.js 来配置webpack
//下面有node的语法,因为webpack是基于node构建的,支持node API和语法
//向外暴露一个打包的配置对象
module.exports={
mode:'development'
}
设置好模式为development
- 模式问题:通过选择
development
,production
或none
之中的一个,来设置mode
参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为production
,最直观的区别就是production下的dist/main.js的代码会被压缩,development不会。- node:Node.js是一个基于Chrome V8引擎的JavaScript运行环境,Node.js使用了一个事件驱动、非阻塞式I/O模型,使其轻量又高效。Chrome之所以能够运行js代码,是因为有V8引擎,而node,就好比是V8引擎的化身。
现在就可以打包了,很多人可能奇怪,为什么没有在配置文件中定好entry入口,这是因为在webpack 4.x中,有一个很大的特性,就是 约定大于配置,webpack默认打包入口路径是 src/index.js。如果想要修改也很简单,手动在配置文件中指定好entry即可。
接下来在命令行启动webpack就可以进行打包,但是很多人会遇到不能识别webpack的问题,这是因为webpack没有全局安装导致的,可以全局安装一下来解决,我使用另一个方法:
接下来在package.json中的script下加入如下一行
"dev":"webpack",
然后启动webpack时,就使用命令:
npm run dev
使用命令之后成功执行,在我们的dist下就生成了打包文件
接下来我们在index.html文件中加入引用打包后的js文件
<script src="../dist/main.js"></script>
最后整个index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../dist/main.js"></script>
</head>
<body>
<h1>这是首页!</h1>
</body>
</html>
在浏览器打开
成功。
webpack-dev-server
类似于nodejs的nodemon,webpack也有一个可以实时监测文件改变实时更新打包文件的工具——webpack-dev-server
使用命令安装:
npm i webpack-dev-server -D
安装完成之后打开 package.json,写入新内容
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}
执行的时候使用 npm run dev
即可启动
启动成功后可以看到输出:
i 「wds」: Project is running at http://localhost:8080/
i 「wds」: webpack output is served from /
i 「wds」: Content not from webpack is served from F:\reacttt\test
i 「wdm」: Hash: a9f2f24cf9f4090022ff
可以理解为webpack被托管在这个服务器上,开放于本机8080端口,我们访问本机8080端口,会发现我们访问了根目录
注意,我们的webpack-dev-server 打包好的 出口文件,就比如此例中的main.js,不是dist中的main.js,是一个存在于内存中的,看不到的main.js,它存在于根目录中。
所以,这个存在于内存中的隐身的 main.js,才是可以实时更新的出口文件,而/dist/main.js不是,所以我们要让 index.html 指向这个根目录的 main.js。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="/main.js"></script>
</head>
<body>
<h1>这是首页!</h1>
</body>
</html>
然后启动,然后更改index.js文件内容,页面及打包文件也可以同步更新了。
另外补充一点,在package.json中的dev值里面可以加一些参数,例如:
"dev": "webpack-dev-server --open --port 3000 ...",
常见参数有:
- –open:自动打开浏览器(后面可以加浏览器名称来指定哪一个,否则就是默认)
- –port:指定访问端口
配置html-webpack-plugin
webpack-dev-server是将我们的打包出口文件放到内存中了,那么我们可不可以将首页文件——index.html也放到内存中呢?可以的,这里需要借助一个插件——html-webpack-plugin
npm i html-webpack-plugin -D
cnpm i html-webpack-plugin -D
安装好之后,package.json中会有记录
接下来在webpack.config.js中修改一下配置:
const path=require('path')
//导入 “在内存中自动生成index页面” 的插件
const HtmlWebPackPlugin=require('html-webpack-plugin')
//创建一个插件的实例对象
const htmlPlugin=new HtmlWebPackPlugin({
//源文件
template:path.join(__dirname,"./src/index.html"),
//生成的内存中首页的名称
filename:'index.html'
})
//下面有node的语法,因为webpack是基于node构建的,支持node API和语法
//向外暴露一个打包的配置对象
module.exports={
mode:'development',
//在webpack 4.x中,有一个很大的特性,就是 约定大于配置
//默认打包入口路径是 src/index.js
plugins:[
htmlPlugin
]
}
这样一来,就根据/src/index.html在内存中生成了一个index.html,我们直接启动,打开对应IP端口,就直接访问到了主页,这就是那个内存中的文件。
查看他的源代码,我们发现
它自动生成了一句话:
<script type="text/javascript" src="main.js"></script></body>
没错,这个内存中的index.html自动找到了webpack-dev-server生成的内存中的main.js,这样一来就不需要我们的那句手动添加的<script>了。
Hello,React!
使用React渲染最基本的虚拟DOM到页面上
运行 npm i react react-dom -S
安装包
- –save (简写为: -S),安装模块后,模块的名称将加入到dependencies(生产阶段的依赖)如:执行以下命令后npm install gulp –save 或 npm install gulp –S在package.json文件中dependencies 属性里就会有如下体现:
“dependencies”: {
“gulp”: “^3.9.1”
}
- –save-dev (简称为:-D), 安装模块后,模块名称将加入到devDependencies(开发阶段的依赖),所以开发阶段一般使用它如:执行如下命令npm install gulp –save-dev 或 npm install gulp –D
package.json 文件的 devDependencies属性:
“devDependencies”: {
“gulp”: “^3.9.1”
}
- react:专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中
- 专门进行DOM操作的,最主要的应用场景,就是 ReactDOM.render()
接下来我们来使用React
代码奉上:
#index.js
//这两个导入的时候,接收的成员名称(React/ReactDOM)必须这么写
import React from 'react' //创建组件、虚拟DOM元素,生命周期
import ReactDOM from 'react-dom' //把创建好的组件和虚拟DOM放到页面上
//创建虚拟DOM元素
//参数1:创建元素类型,字符串,表示元素的名称
//参数2:是一个对象或null,表示 当前这个DOM元素的属性
//参数3:子节点(包括其他虚拟DOM或文本子节点)
//参数n:其他子节点
//<h1>这是一个h1</h1>
const myh1=React.createElement('h1',null,'这是一个h1')
//使用ReactDOM将虚拟dom渲染(render)到页面上
//参数1:要渲染的那个虚拟DOM元素
//参数2:DOM元素
ReactDOM.render(myh1,document.getElementById('app'))
#index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--创建一个容器,渲染的虚拟DOM会放到容器内显示-->
<div id="app"></div>
</body>
</html>
可以看到审查元素中就有我们在index.js中定义的h1
如果我要给h1标签加一些属性呢?
修改createElement即可
//<h1 id="myh1" title="This is a h1">这是一个h1</h1>
const myh1=React.createElement('h1',{id:'myh1',title:'This is a h1'},'这是一个h1')
总结一下上面的步骤:
- 导入react和reactDOM包
- 创建虚拟DOM并返回给一个对象(在react中,一切都是由js对象来表现的)
- 将虚拟DOM渲染到指定页面上的DOM元素,当做对象
虚拟DOM嵌套:
很简单,代码奉上:
#index.js
//这两个导入的时候,接收的成员名称(React/ReactDOM)必须这么写
import React from 'react' //创建组件、虚拟DOM元素,生命周期
import ReactDOM from 'react-dom' //把创建好的组件和虚拟DOM放到页面上
//创建虚拟DOM元素
//参数1:创建元素类型,字符串,表示元素的名称
//参数2:是一个对象或null,表示 当前这个DOM元素的属性
//参数3:子节点(包括其他虚拟DOM或文本子节点)
//参数n:其他子节点
//<h1 id="myh1" title="This is a h1">这是一个h1</h1>
const myh1=React.createElement('h1',{id:'myh1',title:'This is a h1'},'这是一个h1')
const mydiv=React.createElement('div',null,'这是一个div元素',myh1)
//使用ReactDOM将虚拟dom渲染(render)到页面上
//参数1:要渲染的那个虚拟DOM元素
//参数2:DOM元素
ReactDOM.render(mydiv,document.getElementById('app'))
index.html的代码不变
在React项目中启用JSX语法
上面的React.createElement可以创造虚拟DOM元素,但是效率却不客观。
不得不说,HTML是最优秀的标记语言。
用户可以直接在js中写html代码,通过babel来转换JS中的标签。这种在JS中,混合写入类似于HTML的语法,叫做JSX语法:符合XML规范的JS。
JSX语法的本质,还是在运行的时候,被转换成 React.createElement 的形式来执行
安装babel插件
好,我们先来安装 babel 插件:
npm i babel-core babel-loader babel-plugin-transform-runtime -D
npm i babel-preset-env babel-preset-stage-0 -D
npm i babel-preset-react -D
上面推荐使用淘宝源——cnpm 安装,会快点
上面是6个包
- babel-core:babel的内核
- babel-loader:babel 转换工具
- babel-plugin-transform-runtime:插件
- babel-preset-env:语法 env
- babel-preset-stage-0:语法 stage-0
-
babel-preset-react:能够识别转换jsx语法
进行配置:
#webpack.config.js
const path=require('path')
//导入 “在内存中自动生成index页面” 的插件
const HtmlWebPackPlugin=require('html-webpack-plugin')
//创建一个插件的实例对象
const htmlPlugin=new HtmlWebPackPlugin({
//源文件
template:path.join(__dirname,"./src/index.html"),
//生成的内存中首页的名称
filename:'index.html'
})
//下面有node的语法,因为webpack是基于node构建的,支持node API和语法
/*webpack默认只能打包处理.js后缀名类型的文件;像 .png、.vue 无法主动处理,
所以要配置第三方的loader*/
//向外暴露一个打包的配置对象
module.exports={
mode:'development',
//在webpack 4.x中,有一个很大的特性,就是 约定大于配置
//默认打包入口路径是 src/index.js
plugins:[
htmlPlugin
],
module:{ //所有第三方模块的配置规则
rules:[ //第三方匹配规则
//千万别忘记exclude排除项
{test: /\.js|jsx$/,use:'babel-loader',exclude:/node_modules/}
]
}
}
然后新建一个文件,.babelrc
#.babelrc
{
"presets": ["env","stage-0","react"],
"plugins": ["transform-runtime"]
}
babel配置完成
JS中直接使用HTML
有了babel,就可以直接在JS中写HTML了,我们修改index.js如下:
#index.js
//这两个导入的时候,接收的成员名称(React/ReactDOM)必须这么写
import React from 'react' //创建组件、虚拟DOM元素,生命周期
import ReactDOM from 'react-dom' //把创建好的组件和虚拟DOM放到页面上
//创建虚拟DOM元素
//const mydiv=React.createElement('div',{id:'mydiv',title:'div aaa'},'这是一个div元素')
//直接写HTML
//使用babel来转换这些JS中的标签
//这种在JS中,混合写入类似于HTML的语法,叫做JSX语法:符合XML规范的JS
//JSX语法的本质,还是在运行的时候,被转换成 React.createElement 的形式来执行
const mydiv=<div id="mydiv" title="div aaa" >这是一个div元素</div>
//使用ReactDOM将虚拟dom渲染(render)到页面上
ReactDOM.render(mydiv,document.getElementById('app'))
使用npm run dev启动,即可发现mydiv语句成功执行,即我们成功在JS中使用了HTML!
有的朋友是不是报错了?关于babel-loader不能用?对,这是个大坑,因为babel-loader版本过高的原因导致不相配,解决方法就是使用命令
npm install babel-loader@7.1.5 -D
,安装一个较低版本的babel-loader即可(会自动覆盖已经安装的babel-loader)
欢迎访问我的博客:is-hash.com