这篇文章带领大家从零开始手动撸一个React项目的基础框架,集成React全家桶。万字长文,请各位有足够的时间时再来阅读和学习。
概述
平时工作中一直在用React提供的脚手架工具搭建React项目,一行命令全都搞定,自己只管做需求开发即可,从来没仔细研究过各个模块代码怎么去配置,相互之间怎么去进行交互。这周正好有时间,所以决定仔细研究下React项目中的各个功能模块,所以我们来讲解下如何从零搭建一个完整的React项目。
目录
项目初始化
安装NodeJS环境和初始化项目
安装webpack
新建项目目录和文件
核心配置
ES6、ES7、ES8、ES9等高级语法转换成ES5
less/sass等css预处理器代码转换为css
解析字体、图片等静态资源
压缩打包后的JS、CSS文件
抽理公共代码
添加resolve选项
代码热更新
删除上一次的打包结果及记录
集成React全家桶
集成react
集成react-router-dom
集成redux
集成Antd
添加express服务接口,用axios打通前后端
操作步骤
项目初始化
安装NodeJS环境和初始化项目
在开始之前,我们本机首先要安装部署Node环境。Node环境安装部署其实很简单,只需要去官网https://nodejs.org/zh-cn/下载安装包,然后双击安装即可,中间并没有太大的坑,安装过程中会自动将node安装路径添加至操作系统的环境变量中,所以安装完成后我们直接可以在命令行中使用ndoe、npm这些命令,不像java一样还要自己手动去配环境变量。
安装完成后,我们打开命令行工具或者win10的powershell窗口,输入以下命令,如果成功输入信息,即表明Node环境安装部署成功:
node -v
npm -v
Node环境部署成功后,我们在he’sh合适的目录文件夹中,打开powershell窗口,输入以下命令来创建我们的项目文件夹,并且进入到这个新建的文件夹中,最后通过”npm init”来初始化一个最基础的项目框架:
mkdir myreactproject
cd .\myreactproject\
npm init
当我们进入到新建的项目文件夹的时候,通过”npm init”来初始化一个基础的项目框架时,命令行中会出现有一些问答信息,这些信息是在询问我们关于初始化的这个项目的一些基础信息,包括项目名称、项目描述、项目版本、入口文件、测试命令、git库地址、关键字、项目作者、项目遵循的版权信息等,在这里我们直接按回车即可,它会将默认信息填入相应的属性中,同样的,我们也可以输入自定义的一些信息,看自己喜好吧。
到此时呢,我们的一个基础项目框架已经完成了,打开这个项目文件夹,大家可以看到,在此文件夹下生成了一个”package.json”文件,里面就是我们刚才创建项目时候指定的一些基础信息,如下:
安装webpack
webpack是一个模块打包工具,它会自动分析我们项目中的依赖以及项目编码中所用的高级语法这些东西,然后将它们打包编译成浏览器可以解析运行的js和css等文件。我们可以将webpack的API和CLI配合使用,API不用过多解释,这是webpack提供给我们调用和配置的接口,CLI是webpack提供的一个类似于脚手架的东西,它允许我们在命令行中可以使用webpack命令、运行webpack,所以在此处我们安装两个东西。
在项目根目录下运行命令行或powershell工具,然后通过以下命令安装webpack和webpack-cli工具:
npm install webpack webpack-cli --save-dev
安装完成之后可以在项目根目录下看到,多了一个”node_modules”文件夹和一个”package-lock.json”文件,同时在我们的”package.json”文件中也多了些信息。
其中”node_modules”文件夹中存放的是我们整个项目代码中安装的一些插件模块的代码;”package-lock.json”文件是我们执行”npm install”命令后生成的,它锁定所有模块的版本号,包括主模块和所有依赖子模块。当我们执行npm install的时候,node从package.json文件读取模块名称,从package-lock.json文件中获取版本号,然后进行下载或者更新。因此,正因为有了package-lock.json文件锁定版本号,所以当我们执行npm install的时候,node不会自动更新package.json文件中的模块,必须用npm install packagename(自动更新小版本号)或者npm install packagename@x.x.x(指定版本号)来进行安装才会更新,package-lock.json文件中的版本号也会随着更新。当package.json与package-lock.json都不存在,执行”npm install”时,node会重新生成package-lock.json文件,然后把node_modules中的模块信息全部记入package-lock.json文件,但不会生成package.json文件,此时,我们可以通过”npm init –yes”或者”npm init”来初始化生成package.json文件;”package.json”文件中多出来的信息就是我们刚才安装的webpack的描述信息,它里面记录了安装的webpack的版本号和webpack-cli的版本号,如下:
新建项目目录和文件
项目根目录下新建”src”文件夹,用来存放后期的项目源码,然后里面新建一个“index.js”文件作为被webpack编译的文件,同时也是webpack配置的入口文件;项目根目录下再新建一个“build”文件夹,存放项目的webpack配置文件,然后在文件夹中新建”webpack.config.js”文件,用于编写webpack的核心配置代码;在项目根目录新建一个”index.html”文件,是后期我们的项目打包运行的主页面,同时项目打包后自动将打包的文件添加在该文件中。
文件目录新建完成后是如下所示的结构:
文件目录和文件都创建成功后,我们分别为各个文件添加上基础代码,最后测试一下搭建的基础环境是否成功。
首先是webpack.config.js文件,在此文件中我们根据官网文档,在里面添加如下代码,完成webpack的基础配置:
const path = require('path');
module.exports = {
mode: 'development', //此行表示我们webpack打包环境是开发环境
entry: './src/index.js', //项目的入口文件,是我们新建的index.js文件,它的路径是相对于项目根路径的,所以此处我们写的是“./src”,而不是“../src”
output: {
//配置输出信息
filename: 'bundle.js', //打包输出的文件名称,这里是一个写死的名称,后期可以改成按一定规则动态生成
path: path.resolve(__dirname, '../dist') //输出的路径,这里的路径针对的是当前目录,所以我们写成了"../dist",而不是"./dist"
}
};
接下来是新建的index.js文件,在此文件我们添加以下测试代码即可,仅仅是用来测试打包是否成功:
function sum (a, b) {
return a + b;
}
var result = sum (12, 23);
console.log(result);
最后将package.json文件中的script属性修改一下,使它能够让在我们命令行中输入启动命令后自动完成打包过程,如下:
"build": "webpack --config ./build/webpack.config.js"
到此为止呢,我们编辑和修改代码已经完成了,index.html文件中并没有增加任何代码,此时它只是一个空文件,我们后期再增加。在项目根目录下运行命令行或powershell工具,然后通过“npm run build”来启动我们的项目,此处其实并不叫启动,因为我们没有为项目配置调试服务器这些插件,准确点应该说是通过这个命令来进行项目打包,如下:
由上图可看到我们的项目已经顺利打包,这时候在我们项目根目录自动创建了“dist”文件夹,并且里面生成了结果文件bundle.js,如下:
打开bundle.js文件,我们可以看到打包后的代码,如下所示:
此时说明我们项目安装和配置的webpack是正确的。
核心配置
ES6、ES7、ES8、ES9等高级语法转换成ES5
ES6、ES7、ES8、ES9等这些高级语法在浏览器中是无法直接编译运行的,所以需要我们在编译运行之前对这些高级语法进行转换,将它们转换成ES5代码,以便在浏览器中进行编译运行。这个事情是babel-loader来做的,它主要是将ES6等高级语法转换成浏览器能解析运行的低级语法,所以我们要在项目根目录中安装这些插件:
npm install babel-loader @babel/core @babel/preset-env @babel/preset-react --save-dev
以上插件中babel-loader主要用于高级语法转换成低级语法,它是webpack的一个loader,用来预处理文件;@babel/core是babel的核心模块,提供转换的API;@babel/preset-env可以根据配置的目标浏览器或者运行环境将ES5+代码自动转换为ES5代码,也是babel的一个模块;@babel/preset-react用来解析React中的JSX语法,同样也是babel的模块。以上的模块写法中,前面加有”@”符号的写法是babel 7.0+版本的写法,具体的可以查看babel官网。
相关的babel依赖安装完成后,我们在项目根目录新建“babel.config.js”文件,用来配置babel。如果不创建此文件的话babel的配置信息我们直接写到webpack配置文件中的对应规则下的options属性中即可,在此处我们用babel.config.js配置文件的方式。在配置文件中我们加入如下代码:
const babelConfig = {
presets: [ //它的功能相当于是下面plugins的一个集合,即插件集。有了它我们不用在plugins中一个一个的配置插件了
["@babel/preset-env", {
useBuiltIns: "entry", //如果用了@babel/polyfill的话,配置这个属性可以将@babel/polyfill按需引入
corejs: 2
}], "@babel/preset-react"
],
plugins: []
};
module.exports = babelConfig;
然后在webpack.config.js配置文件中,我们配置一下babel-loader,代码如下:
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
module: {
//通过module属性配置babel-loader
rules: [
{
test: /\.js/,
use: ['babel-loader?cacheDirectory=true'],
include: path.join(__dirname, '../src')
}
]
}
};
配置完成之后,我们修改下index.js中的代码,来测试下是否可以成功打包我们的ES5+的代码文件:
let xbeichen = () => {
console.log('测试箭头函数');
}
xbeichen();
通过在命令行运行启动命令后发现,我们的项目打包成功,如下:
以上的配置还存在两个问题,第一个首先是虽然我们打包成功了项目,这也表示着ES5+的代码我们可以顺利打包,但是我们在代码中用Promise、Set、Symbol等全局对象或者一些定义在全局对象上的方法时它都不会转换,这是因为我们的babel-loader只能转换高级语法,并不会转换新的API,所以这时候我们就需要用@babel/polyfill来为当前环境提供一个垫片;还有第二个问题是,当我们执行打包后,打包的文件里会有大量的重复代码,那我们这时候就需要提供统一的模块化的helper来减少这些helper函数的重复输出。所以就需要我们安装以下插件模块:
npm install @babel/polyfill --save-dev
@babel/polyfill安装完之后我们不再需要进行额外的配置,因为在上面babel的配置文件中我们已经指定了@babel/polyfill是按需引入。
npm install @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import --save-dev
以上三个插件安装完成之后,我们需要在babel的配置文件中进行相应的配置,如下:
const babelConfig = {
presets: [
["@babel/preset-env", {
useBuiltIns: "entry",
corejs: 2
}], "@babel/preset-react"
],
plugins: ["@babel/plugin-syntax-dynamic-import", ["@babel/plugin-transform-runtime"]] //就是在此处添加了两个@babel/runtime中的插件
};
module.exports = babelConfig;
最后我们在index.js文件中添加Promise相关的代码来查看打包结果,如下:
let xbeichen = () => {
console.log('测试箭头函数');
}
xbeichen();
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
});
promise.then(res => {
console.log(res);
});
运行启动命令后打包结果如下所示:
less/sass等css预处理器代码转换为css
在项目中如果我们使用了css预处理器,那就需要在打包的时候将less、sass等预处理器编写的代码转换成浏览器可以执行的css代码,这就需要我们安装以下插件,此处介绍less预处理器代码的转换配置:
npm install stylus stylus-loader less less-loader sass-loader node-sass css-loader style-loader --save-dev
以上安装的依赖插件中:css-loader主要的作用是解析css文件, 像@import等动态语法;style-loader主要的作用是解析的css文件渲染到html的style标签内;stylus、less、sass是CSS的常见预处理器;stylus-loader、less-loader、sass-loader主要是将其对应的语法转换成css语法。
然后我们修改webpack的配置文件,代码如下:
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{
test: /\.js/,
use: ['babel-loader?cacheDirectory=true'],
include: path.join(__dirname, '../src')
}, {
//此处再添加一条rules,用于配置css预处理器信息
test: /\.less$/,
use: [
{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'less-loader'
}
]
}
]
}
};
在src目录下新建”index.less”文件,添加如下代码:
@color: red;
#div1 {
color: @color;
font-size: 23px;
}
然后在index.js文件中我们引入新建的index.less文件,运行启动命令来执行打包,结果如下:
但是如果我们使用CSS3的一些新特性时,需要为不同的浏览器在CSS代码中添加不同的前缀,在开发中手动添加太麻烦,所以我们可以通过postcss来自动添加各种浏览器前缀。首先我们先要安装以下依赖插件:
npm install postcss-loader autoprefixer --save-dev
然后在webpack配置文件中添加postcss的配置信息即可,如下:
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{
test: /\.js/,
use: ['babel-loader?cacheDirectory=true'],
include: path.join(__dirname, '../src')
}, {
test: /\.less$/,
use: [
{