如果此篇对您有所帮助,在此求一个star。项目地址: OrcasTeam/my-cli
react
react介绍
目前,国内主流的前端应用框架具有两个:vue.js和react.js,关于vue和react的优劣性,网上众说纷纭。在下就不在此引战。
而是直接介绍React
??? vue和React这种都是快速应用开发工具,可能也会像曾经如日中天的JQuery被市场淘汰,所以个人建议不要盲目只追求快速工具的使用,而是花时间去学习原点。例如设计思想和数据结构。快速应用框架(或语言)只不过是应用工具而已。
? 以前都说是“三大框架”,还有一个Google开发的Angular,但是国内Angular使用份额越来越少。 个人感觉Angular主要问题是上手成本。Angular比较偏向于后端,很多概念对于前端开发人员都是噩梦。不过对于前端工程化,个人认为Angular是集大成之作。个人建议,对于有经验的朋友,可以稍微学习下Angular中的思想。
React是一个用于构建用户界面的 JavaScript 库,
React本身是一个特别简单的库:将元素抽象为虚拟DOM,更新DOM时对比虚拟DOM,然后只更新那些真正需要更新的元素。
React.createElement()
使用Document构建DOM时,都是使用 document.createElement() 来构建标签
代码语言:javascript复制const li = document.createElement('li');
document.body.appendChild(li)
在React中, 也提供了这样一个自定义函数来React组件。
React.createElement() 返回的是一个React自定义的元素类型:ReactElement
代码语言:javascript复制const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React提供的React.createElement()和ReactElement提供了很好平台隔离性。
使用同一套代码编写的元素组件只需要对接不同平台的APi,就可以实现跨平台。
React能够跨平台的原因也在于此。
在日常开发中,也会经常写无关业务的通用封装,其思想与此类似。
虚拟DOM
在直接使用Document更新DOM元素时,很多时候会因为某些原因 对不必更新DOM进行更新 从而产生了性能浪费
解决这个问题一般想到的做法就是做一个DOM缓存。创建DOM时将DOM信息缓存,更新时对比新旧DOM。排除掉不必要的更新DOM。
这种缓存DOM数据的方案就叫虚拟DOM(Virtual DOM), 而排除算法叫做diff算法
React也使用了这种方案提升性能
虚拟DOM(Virtual DOM)和diff算法 是对数据结构和算法的考验。每一个人都可以模拟出简单的方案,但不是每一个人都可以写出优秀的解决方案。
在下愚钝,对于数据结构和算法掌握的不好。所以对虚拟DOM(Virtual DOM)和diff算法只有浅薄的认知。有兴趣的朋友可以看一下这篇文章:深度剖析:如何实现一个 Virtual DOM 算法
JSX
React是通过JS构建元素的,
我们都知道使用JS编写页面痛苦是没有结构性。
使用HTML两个标签能搞定的事,使用JS就能写一大堆代码。
React为了解决这个问题,提供了一个模板语言---JSX
JSX是一种JS扩展语言。允许在JS中以标签形式构建元素。并且JSX开发工具中还可以具有各种提示和快捷键。
能够极大的提高开发效率
代码语言:javascript复制const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
但JSX编写的组件只是React.createElement()语法糖,打包编译过程中会将JSX语法转换为React.createElement()
??? JSX编写的组件本质是 React.createElement() 语法糖。所以React还支持使用 React.createElement() 创建虚拟DOM(Virtual DOM)。
?? JSX是React提供构建代码方式的一种扩展语言,本质是一个语法糖。JSX定义的事件、style、class是JSX自身语法,并不是原生DOM。所以有些属性名称不一致。
?? JSX转换React.createElement()操作使用的是babel提供的一个plugin,在下面再介绍
? JSX目前被社区认可。Vue@3.X也支持JSX
添加 React
安装 react
React目前最新版本为17.0.1,在这里就直接引用此版本来介绍,对React有兴趣的朋友在从老版本循循渐进的学习。
yarn add react@17.0.1
react库是React的核心库,具有 React.createElement() 、虚拟DOM、JSX语法支持等一系列核心内容。
但是此库并不没有提供与真实DOM交互。与真实DOM交互的代码则由react-dom提供,
yarn add react-dom@17.0.1
react类似一个通用库,没有与任何平台具有相关性,只负责组织数据结构。
就像写React Native时,使用了react-native来做平台交互。
使用 react
接下来就仿照react-cli来组织代码。
根节点
第一步就是在HTML页面中创建一个元素作为React承载的根节点。
? vue-cli也具有这么一个根节点用来承载vue,只不过元素ID名称不一样,有兴趣的朋友可以自行查看。
接下来处理JS,在之前打包测试中都是使用 /src/index.js 文件作为源文件。
也是使用此文件作为源文件。
代码语言:javascript复制?? React只是承载在打包器中的一个应用框架。经过打包器打包将JSX转换为可运行的代码。
import React from 'react';
import ReactDOM from 'react-dom';
const root = (
<h1 className="greeting">
Hello, world!
</h1>
);
ReactDOM.render(root, document.getElementById('root'));
在 /src/index.js 文件中使用了JSX创建元素,然后使用
react-dom中的 ReactDOM.render() 添加到根节点中。
? vue-cli也同样如此,有兴趣的朋友可以自行查看
@babel/preset-react
不过如果此时执行yarn build
操作,会直接报错。
这是因为JSX无法被识别的问题。前面说过,JSX只是React提供的一种模板语言。本质上并不属于JS模块。
所以需要将JSX转换为 React.createElement() 形式
提供这个转换操作的是babel中提供的一个plugin:@Babel/plugin-syntax-jsx
不过不需要直接安装这个plugin
babel为React提供了一个preset:@babel/preset-react。
@babel/preset-react中封装了所有处理React的plugin
yarn add -D @babel/preset-react@7.12.13
? Babel官网提供了JSX转换为 React.createElement() 的测试,有兴趣的朋友可以测试测试
然后配置在 .babelrc 文件中
此时执行yarn build
便可以执行成功,并且查看生成代码可以看到JSX已经转换为了React.createElement()
在浏览器也可以正常运行代码
.jsx文件
app.jsx
React代码已经运行成功,接下来就组织React代码。
刚才,直接在 /src/index.js 文件中编写了JSX代码进行测试
但是真正开发中,需要将JSX代码编写在 .jsx 文件中,通过模块导入导入方式提供给 /src/index.js 文件。
将JSX提取到 /src/app.jsx 文件,在 /src/index.js 导入。
?? app.jsx作为React框架的根节点。用在承载React组件。
/src/app.jsx 文件中组件作为React的根节点。React也是以树的组织方式管理,/src/app.jsx 文件中组件就是树根。React框架代码就像 托管 在了 /src/app.jsx 之中
? ?
- React组件分为 函数组件 和 类组件 , 函数组件 方便,再加上 Hooks 的助力,在编写颗粒度较小组件时使用 函数组件 是个非常好的选择。类组件 封装性强,内部提供完善的钩子函数和一系列功能,再加上继承特性。比较适合使用在业务代码主干中。
- /src/app.jsx 中返回的 <></> 代表 空标签 ,React组件只允许返回一个元素,但有时候组件需要返回元素数组,可以在外部包一层空标签。与Vue中的template标签功能一致。
- React 组件名称约定为大写形式。
webpack配置
.jsx作为一种新的文件格式,需要在webpack进行配置使用babel
代码语言:javascript复制const modules = {
module:{
rules:[
{
// 所有的.js或者.jsx文件都走babel-loader
test: /.js(x?)$/,
include: path.join(config.root,'src'),
loader: "babel-loader"
}
]
}
}
并且可以提供引用时忽略后缀名称。
代码语言:javascript复制 resolve:{
// 可被忽略的后缀
extensions:['.jsx', '.js', '.json'],
}
此时就算成功将React使用在脚手架中了。
而对于React Router、Redux只是用于扩展React的开发库。在此就不再添加。
? vue-cli搭建方式与react-cli基本一致,只是各自框架暴露的API不同
browserslist
browserslist是什么
在介绍babel时使用过package.json文件中browserslist属性设置浏览器版本,那么browserslist属性到底是怎么回事呢?
前面介绍过,前端的运行环境(浏览器)版本是由用户决定的,不同的项目对于浏览器版本要求不一样。
而在打包过程中。需要指定支持的浏览器版本,以这些版本对开发代码做出适配。(CSS、JS都需要适配)。
browserslist属性就是提供指定浏览器版本功能。是由browserslist库提供的。
而这个简单的功能browserslist却做出了强大的效果,得到了社区的高度认可。很多库都直接依赖browserslist
browserslist配置方式
browserslist提供了两种配置方式。
一种就是配置在package.json文件中的browserslist属性。browserslist执行时会默认读取此属性。
另一种是使用约定文件。可以在项目根目录(package.json所在目录)创建一个约定文件 .browserslistrc.json ,将属性配置在此。.browserslistrc.json文件名称一般会省略后缀:.browserslistrc
两种方式不可同时设置,否则会直接报错。
个人推荐直接配置在package.json文件中,没必要创建一个文件了。在此也就直接使用此方案。
browserslist环境变量
browserslist可以使用不用属性来灵活的控制浏览器版本。
如下所示。可以设置在不同环境下设置不同浏览器版本。
代码语言:javascript复制"browserslist": {
"development": [
"chrome > 75"
],
"production": [
"ie 9"
]
}
属性值取自Node.js中环境变量。环境变量名称为BROWSERSLIST_ENV。所以需要设置环境变量。
注意:在此虽然设置在webpack.config.js文件中,但设置的是Node.js中的环境变量, 并不是webpack提供的环境变量。
browserslist属性值名称可以随意命名。只要与Node.js中BROWSERSLIST_ENV环境变量对应即可。
在此就不贴图测试了,有兴趣的朋友可以自行测试。
至于BROWSERSLIST_ENV 环境变量与 webpack中不同模式的关联,在下一篇介绍。
browserslist支持的浏览器
browserslist支持设置当前基本上所有的浏览器,在Github上作者说明了可以设置的浏览器
可以看到,browserslist几乎支持所有浏览器:PC、安卓、IOS 甚至还有国内浏览器。
?? 设置浏览器时名称不区分大小写
browserslist属性
browserslist能得到社区的认可,也就在于browserslist提供了强大的属性设置。
如前面使用的 指定 区间浏览器(chrome > 75) 也只是browserslist简单的属性配置
下面简单列举部分browserslist属性配置,想了解更多的朋友请参考Github
一般只需要简单的设置即可。
总结
???
- React是一个快速构建高性能网站的开发框架
- React使用了虚拟DOM(Virtual DOM)和diff 算法优化了DOM操作
- React利用自定义DOM类型解耦平台限制,以此实现了跨平台
- JSX只是一个JS扩展语法。React使用JSX作为构建元素的模板语言
- browserslist是一个强大的设置浏览器版本库。
本文参考
- vue核心之虚拟DOM(vdom)
- 深度剖析:如何实现一个 Virtual DOM 算法
- browserslist Github
- babel-preset-react
本文依赖
- react@17.0.1
- react-dom@17.0.1
- @babel/preset-react@7.12.3
package.json
代码语言:javascript复制{
"name": "my-cli",
"version": "1.0.0",
"main": "index.js",
"author": "mowenjinzhao<yanzhangshuai@126.com>",
"license": "MIT",
"devDependencies": {
"@babel/core": "7.13.1",
"@babel/plugin-transform-runtime": "7.13.7",
"@babel/preset-env": "7.13.5",
"@babel/preset-react": "7.12.13",
"@babel/runtime-corejs3": "7.13.7",
"babel-loader": "8.2.2",
"clean-webpack-plugin": "3.0.0",
"html-webpack-plugin": "5.2.0",
"webpack": "5.24.0",
"webpack-cli": "4.5.0"
},
"dependencies": {
"jquery": "3.5.1",
"react": "17.0.1",
"react-dom": "17.0.1"
},
"scripts": {
"start": "webpack --mode=development --config webpack.config.js",
"build": "webpack --mode=production --config webpack.config.js"
},
"browserslist": [
"ie 9",
"Chrome > 75"
]
}
webpack.config.js
代码语言:javascript复制const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
// browserslist环境变量
process.env.BROWSERSLIST_ENV = 'development'
const config = {
root: path.join(__dirname, './'),
}
const modules = {
// 入口文件
// 字符串形式
entry: path.join(config.root, 'src/index.js'),
// 对象形式
// entry:{
// 'index': path.join(config.root, 'src/index.js'),
// },
// 输出文件
// 字符串形式
// output:path.join(config.root, './dist/[name].js')
//对象形式
output: {
// 输出文件的目录地址
path: path.join(config.root, 'dist'),
// 输出文件名称,contenthash代表一种缓存,只有文件更改才会更新hash值,重新打包
filename: '[name]_[contenthash].js'
},
//devtool:false, //'eval'
module:{
rules:[
{
// 所有的.js(x?)文件都走babel-loader
test: /.js(x?)$/,
include: path.join(config.root,'src'),
loader: "babel-loader"
}
]
},
optimization: {
minimize: false,
minimizer: [
new TerserPlugin({
// 指定压缩的文件
include: /.js(?.*)?$/i,
// 排除压缩的文件
// exclude:/.js(?.*)?$/i,
// 是否启用多线程运行,默认为true,开启,默认并发数量为os.cpus()-1
// 可以设置为false(不使用多线程)或者数值(并发数量)
parallel: true,
// 可以设置一个function,使用其它压缩插件覆盖默认的压缩插件,默认为undefined,
minify: undefined,
// 是否将代码注释提取到一个单独的文件。
// 属性值:Boolean | String | RegExp | Function<(node, comment) -> Boolean|Object> | Object
// 默认为true, 只提取/^**!|@preserve|@license|@cc_on/i注释
// 感觉没什么特殊情况直接设置为false即可
extractComments: false,
// 压缩时的选项设置
terserOptions: {
// 是否保留原始函数名称,true代表保留,false即保留
// 此属性对使用Function.prototype.name
// 默认为false
keep_fnames: false,
// 是否保留原始类名称
keep_classnames: false,
// format和output是同一个属性值,,名称不一致,output不建议使用了,被放弃
// 指定压缩格式。例如是否保留*注释*,是否始终为*if*、*for*等设置大括号。
format: {
comments: false,
},
output: undefined,
// 是否支持IE8,默认不支持
ie8: false,
compress: {
// 是否使用默认配置项,这个属性当只启用指定某些选项时可以设置为false
defaults: false,
// 是否移除无法访问的代码
dead_code: false,
// 是否优化只使用一次的变量
collapse_vars: true,
warnings: true,
// 是否删除所有 console.*语句,默认为false,这个可以在线上设置为true
drop_console: false,
// 是否删除所有debugger语句,默认为true
drop_debugger: true,
// 移除指定func,这个属性假定函数没有任何副作用,可以使用此属性移除所有指定func
// pure_funcs: ['console.log'], //移除console
},
},
})
]
},
plugins: [
new HtmlWebpackPlugin({
// HTML的标题,
// template的title优先级大于当前数据
title: 'my-cli',
// 输出的html文件名称
filename: 'index.html',
// 本地HTML模板文件地址
template: path.join(config.root, 'src/index.html'),
// 引用JS文件的目录路径
publicPath: './',
// 引用JS文件的位置
// true或者body将打包后的js脚本放入body元素下,head则将脚本放到中
// 默认为true
inject: 'body',
// 加载js方式,值为defer/blocking
// 默认为blocking, 如果设置了defer,则在js引用标签上加上此属性,进行异步加载
scriptLoading: 'blocking',
// 是否进行缓存,默认为true,在开发环境可以设置成false
cache: false,
// 添加mate属性
meta: {}
}),
new CleanWebpackPlugin({
// 是否假装删除文件
// 如果为false则代表真实删除,如果为true,则代表不删除
dry: false,
// 是否将删除日志打印到控制台 默认为false
verbose: true,
// 允许保留本次打包的文件
// true为允许,false为不允许,保留本次打包结果,也就是会删除本次打包的文件
// 默认为true
protectWebpackAssets: true,
// 每次打包之前删除匹配的文件
cleanOnceBeforeBuildPatterns: ['**/*'],
// 每次打包之后删除匹配的文件
cleanAfterEveryBuildPatterns:["*.js"],
}),
new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一个打包配置的全局变量") }),
],
resolve: {
alias:{
// 设置路径别名
'@': path.join(config.root, 'src') ,
'~': path.join(config.root, './src/assets') ,
},
// 可互忽略的后缀
extensions:['.JSX', '.js', '.json'],
// 默认读取的文件名
mainFiles:['index', 'main'],
}
}
// 使用node.js的导出,将配置进行导出
module.exports = modules
.babelrc
代码语言:javascript复制{
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
"modules":false
// 移除useBuiltIns设置
// "targets": "chrome > 75",
// "useBuiltIns": "usage",
// "corejs": {
// "version": 3,
// "proposals":true
// }
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
}
}
]
]
}