react更新很快,现在搜到的搭建react项目的博客,经常搭建到一半就卡住了,因此总结了一套从0到1构建react的方法,是对自己亲身实践项目的总结,也对研究学习使用react的小伙伴提供一点借鉴。
本文对大量博客进行研究,进行实际配置填坑后,整理修改完成,是全网难得的可以顺利跑通的项目,望且学且珍惜
整个项目包括以下内容:
- 安装NodeJS环境
- 安装webpack环境
- 新建项目目录和文件
- 配置babel将es6等高级语法转为es5
- 配置less/sass预处理器处理css
- 配置解析字体、图片等静态资源
- 配置压缩打包的js、css文件
- 配置抽离公共代码
- 配置热更新
- 配置删除上一次的打包结果及记录
- 集成React全家桶
- 集成react-router
- 集成react-redux
平时工作中一直在用React提供的脚手架工具搭建React项目,一行命令全都搞定,自己只管做需求开发即可,从来没仔细研究过各个模块代码怎么去配置,相互之间怎么去进行交互。这周正好有时间,所以决定仔细研究下React项目中的各个功能模块,所以我们来讲解下如何从零搭建一个完整的React项目,现在我们开始喽。
一、安装NodeJS环境
在开始之前,我们本机首先要安装部署Node环境。Node环境安装部署其实很简单,只需要去官网https://nodejs.org/zh-cn/下载安装包,然后双击安装即可。安装完后在命令行查看安装的版本信息:
node -v
npm -v
我这里的版本如下:
node环境搭建完成后,我们开始新建项目文件夹并且初始化一个基础项目框架:
mkdir react-project
cd ./react-project/
npm init
当运行npm init
命令后,会初始化一个基础的项目框架,命令行中会出现一些问答信息,我们可以根据个人喜好输入一些自定义的信息,每一项填完按回车即可。
现在在我们的reactproject项目文件夹中有了一个“packagejson
”文件
里面有我刚才配置的基本信息:
二. 安装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”文件中也多了些信息:
"devDependencies": {
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
三、新建项目目录和文件
项目根目录下新建”src”文件夹,用来存放后期的项目源码,然后里面新建一个“index.js”文件作为被webpack编译的文件,同时也是webpack配置的入口文件;项目根目录下再新建一个“build”文件夹,存放项目的webpack配置文件,然后在文件夹中新建”webpack.config.js”文件,用于编写webpack的核心配置代码;在项目根目录新建一个”index.html”文件,是后期我们的项目打包运行的主页面,同时项目打包后自动将打包的文件添加在该文件中。
文件目录新建完成后是如下所示的结构:
接下来我们在各个文件中添加基础代码,最后再测试一下搭建的基础环境是否成功。
首先在webpack.config.js文件,添加以下代码:
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
}
}
接下来在index.js文件中,添加测试代码,仅仅测试是否打包成功:
function sum (a, b) {
return a + b;
}
var result = sum (12, 23);
console.log(result);
最后将package.json文件中的script属性修改一下,使它能够让在我们命令行中输入启动命令后自动完成打包过程,如下:
"scripts": {
"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等高级语法转换成浏览器能解析运行的低级语法,所以我们要在项目根目录中安装这些插件:
1、安装babel-loader及插件
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
配置文件的方式。在配置文件中我们加入如下代码:
2、配置babel.config.js
module.exports = {
presets: [
[
// babel预设
'@babel/preset-env',
{
// 使用corejs 3的版本
corejs: 2,
// 按需加载
useBuiltIns: 'entry',
// 不使用模块化 交给其它打包工具处理
modules: false
}
]
],
plugins: []
};
3、配置webpack.config.js
在webpack.config.js配置文件中,我们配置一下babel-loader,代码如下:
module: {
rules: [
{
test: /\.js|jsx$/,
use: [
{
loader: 'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
],
include: path.join(__dirname, '../src'),
exclude:/node_modules/
}
]
}
配置完成之后,我们修改下index.js中的代码,来测试下是否可以成功打包我们的ES5+的代码文件:
let taoziUncle = () => {
console.log('测试箭头函数');
}
taoziUncle()
终端运行npm run build可以看到编译成功了。
以上的配置还存在两个问题,第一个首先是虽然我们打包成功了项目,这也表示着ES5+的代码我们可以顺利打包,但是我们在代码中用Promise、Set、Symbol等全局对象或者一些定义在全局对象上的方法时它都不会转换,这是因为我们的babel-loader只能转换高级语法,并不会转换新的API,所以这时候我们就需要用@babel/polyfill
来为当前环境提供一个垫片;还有第二个问题是,当我们执行打包后,打包的文件里会有大量的重复代码,那我们这时候就需要提供统一的模块化的helper来减少这些helper函数的重复输出。所以就需要我们安装以下插件模块:
4、安装@babel/polyfill
npm install @babel/polyfill --save-dev
@babel/polyfill安装完之后我们不再需要进行额外的配置,因为在上面babel的配置文件中我们已经指定了@babel/polyfill是按需引入。
5、安装babel/polyfill的插件
npm install @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import --save-dev
以上三个插件安装完成之后,我们需要在babel.config.js和webpack.config.js中进行相应的配置,如下:
6、babel.config.js文件中如下修改:
// babel.config.js文件中如下修改:
module.exports = {
presets: [
[
// babel预设
'@babel/preset-env',
{
// 使用corejs 3的版本
corejs: 2,
// 按需加载
useBuiltIns: 'entry',
// 不使用模块化 交给其它打包工具处理
modules: false
}
]
],
plugins: [
["@babel/plugin-transform-runtime"],//就是在此处添加了两个@babel/runtime中的插件
"@babel/plugin-syntax-dynamic-import"
]
};
7、 webpack.config.js文件中如下修改:
//webpack.config.js文件中如下修改:
{
test: /\.js|jsx$/,
use: [
{
loader: 'babel-loader',
options:{
presets:['@babel/preset-env','@babel/preset-react']
}
}
],
include: path.join(__dirname, '../src'),
exclude:/node_modules/
}
最后我们在index.js文件中添加Promise相关的代码来查看打包结果,如下:
let taoziUncle = () => {
console.log('测试箭头函数');
}
taoziUncle()
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
})
promise.then(res => {
console.log(res);
})
终端运行npm run build 测试没有问题,说明我们配置成功了。
五、less/sass等css预处理器代码转换为css
在项目中如果我们使用了css预处理器,那就需要在打包的时候将less、sass等预处理器编写的代码转换成浏览器可以执行的css代码,这就需要我们安装以下插件,此处介绍less预处理器代码的转换配置:
1、安装基本依赖
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的配置文件module–rules数组中添加代码如下:
2、配置webpack文件
{
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来自动添加各种浏览器前缀。首先我们先要安装以下依赖插件:
3、安装postcss依赖
npm install postcss-loader autoprefixer --save-dev
然后在webpack配置文件中添加postcss
的配置信息即可,如下:
4、配置文件中添加postcss
{
test: /\.less$/,
use: [
{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'postcss-loader'
}, {
loader: 'less-loader'
}
]
}
六、解析字体、图片等静态资源
在我们的项目中会使用到图片等静态资源,在此处我们来添加配置。首先是安装相关依赖,如下:
npm install file-loader url-loader --save-dev
以上依赖中:file-loader可以用来帮助webpack打包处理一系列的图片文件,比如:.png 、 .jpg 、.jepg等格式的图片。打包的图片会给每张图片都生成一个随机的hash值作为图片的名字;url-loader封装了file-loader,它的工作原理:1、文件大小小于limit参数,url-loader将会把文件转为Base64;2、文件大小大于limit,url-loader会调用file-loader进行处理,参数也会直接传给file-loader。
安装完依赖后,我们进行webpack.config.js文件中module–rules数组中添加代码如下:
{
//配置图片静态资源的打包信息
test: /\.(jpg|png|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
}, {
//配置多媒体资源的打包信息
test: /\.(mp4|webm|ogg|mp3|wav)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
}
然后我们在项目src目录下新建”assets”文件夹,里面放置两张图片,在index.js中引入这两张图片
运行启动命令来打包项目代码,最后查看结果,没有报错说明我们配置成功了。
七、压缩打包后的JS、CSS文件
我们打包后的JS和CSS文件中存在大量的空格和引号等,这些会严重影像我们打包后的文件体积,所以接下来我们通过安装配置相应的依赖插件来压缩我们打包后的代码文件。首先安装如下依赖:
npm install mini-css-extract-plugin --save-dev
然后在webpack配置文件中增加我们依赖插件的配置,在plugins和module–rules中都需要修改
//首先引入我们新安装的依赖插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [
new MiniCssExtractPlugin({
// 瘦身css
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
})
],
module: {
rules: [
{
test: /\.js|jsx$/,
use: [
{
loader: 'babel-loader',
options:{
presets:['@babel/preset-env','@babel/preset-react']
}
}
],
include: path.join(__dirname, '../src'),
exclude:/node_modules/
}, {
//最后添加这个依赖插件的配置信息
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
]
}, {
test: /\.less$/,
use: [
{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'postcss-loader'
}, {
loader: 'less-loader'
}
]
}, {
//配置图片静态资源的打包信息
test: /\.(jpg|png|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
}, {
//配置多媒体资源的打包信息
test: /\.(mp4|webm|ogg|mp3|wav)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
}
]
}
然后我们在src目录下新建”index.css”文件,在此文件中添加css测试代码,如下:
.test {
color: red
}
接下来在index.js文件中引入新建的这个index.css文件和我们之前新建的index.less文件,最后运行启动命令来进行打包,最后结果如下:
由结果可以看到,最后css文件被打包,重新在dist目录下生成了一个打包后的文件,但是我们的less文件依然是直接打包在了bundle.js的文件中,这是因为mini-css-extract-plugin这个依赖插件只针对于css文件的。
八、抽离公共代码
我们在打包后的JS或者CSS文件中会有很多公共代码,如果不将这些代码进行抽离,我们最后的打包文件会变得很大,所以抽离公共代码这件事情是由SplitChunksPlugin
插件来做,我们只需要在webpack配置文件中加入如下代码即可:
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
optimization: {
//添加抽离公共代码插件的配置
splitChunks: {
cacheGroups: {
//打包公共模块
commons: {
chunks: 'initial', //initial表示提取入口文件的公共部分
minChunks: 2, //表示提取公共部分最少的文件数
minSize: 0, //表示提取公共部分最小的大小
name: 'commons' //提取出来的文件命名
}
}
},
}
......
九、添加resolve选项
这个选项的作用是为了方便我们开发者,比如文件的别名、文件的扩展名等,webpack.config.js配置加如下代码:
resolve: {
//resolve核心配置
extensions: ['.js', '.jsx', '.json'],
alias: {
pages: path.join(__dirname, '../src/pages'),
components: path.join(__dirname, '../src/components'),
actions: path.join(__dirname, '../src/redux/actions'),
reducers: path.join(__dirname, '../src/redux/reducers'),
images: path.join(__dirname, '../src/images')
}
}
十、代码热更新
首先安装如下依赖:
npm install webpack-dev-server --save-dev
npm install html-webpack-plugin --save-dev
其中第一个依赖插件是热更新
插件,第二个是我们的html-webpack-plugin
插件,这个插件的作用是它可以每次动态的将我们打包后的js、css文件加入到index.html页面中。其中webpack的配置信息如下:
const path = require('path')
//首先引入我们新安装的依赖插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin'); //引入html模板插件
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
optimization: {
//添加抽离公共代码插件的配置
splitChunks: {
cacheGroups: {
//打包公共模块
commons: {
chunks: 'initial', //initial表示提取入口文件的公共部分
minChunks: 2, //表示提取公共部分最少的文件数
minSize: 0, //表示提取公共部分最小的大小
name: 'commons' //提取出来的文件命名
}
}
},
},
plugins: [
new MiniCssExtractPlugin({
// 瘦身css
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
}),
new HtmlWebpackPlugin({
//实例化Html模板模块,让html引用外部文件
template: path.resolve(__dirname, '../index.html')
})
],
module: {
rules: [
{
test: /\.js|jsx$/,
use: [
{
loader: 'babel-loader',
options:{
presets:['@babel/preset-env','@babel/preset-react']
}
}
],
include: path.join(__dirname, '../src'),
exclude:/node_modules/
}, {
//最后添加这个依赖插件的配置信息
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
]
}, {
test: /\.less$/,
use: [
{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'postcss-loader'
}, {
loader: 'less-loader'
}
]
}, {
//配置图片静态资源的打包信息
test: /\.(jpg|png|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
}, {
//配置多媒体资源的打包信息
test: /\.(mp4|webm|ogg|mp3|wav)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
}
]
},
resolve: {
//resolve核心配置
extensions: ['.js', '.jsx', '.json'],
alias: {
pages: path.join(__dirname, '../src/pages'),
components: path.join(__dirname, '../src/components'),
actions: path.join(__dirname, '../src/redux/actions'),
reducers: path.join(__dirname, '../src/redux/reducers'),
images: path.join(__dirname, '../src/images')
}
},
devServer: {
//配置热更新模块
compress: true, // gzip压缩
hot: true, // 热更新
historyApiFallback: true, // 解决启动后刷新404
open: true,
port: 3500,
static: {
directory: path.resolve(__dirname, "dist")
},
proxy: {
'/api': {
target: 'http://localhost:3500',
pathRewrite: {
'^/api': '/api'
},
changeOrigin: true
}
}
}
}
webpack配置好之后,我们在package.json中添加启动命令,这次的启动命令是真正意义的启动命令,如下所示:
"dev": "webpack-dev-server --config ./build/webpack.config.js"
在命令行中输入npm run dev
来启动项目,这时候浏览器会自动打开,我们打开浏览器控制台可以看到,输出了我们在index.js中编写的代码,此时也说明它是将我们的js、css文件自动加载到index.html页面中了,如下:
十一、删除上一次的打包结果及记录
我们每次运行打包命令之前都要手动删除dist文件夹,不然的话它每次打包都会在dist文件夹中加入新的打包内容,上一次的打包内容还存留着,所以我们要安装clean-webpack-plugin
插件来将我们上一次的打包记录及结果删除,安装配置如下:
npm install clean-webpack-plugin --save-dev
webpack中的配置信息如下:
const {
CleanWebpackPlugin } = require('clean-webpack-plugin'); //引入clean-webpack-plugin插件
plugins: [
new CleanWebpackPlugin(), //实例化clean-webpack-plugin插件,删除上次打包的文件
new MiniCssExtractPlugin({
// 瘦身css
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
}),
new HtmlWebpackPlugin({
//实例化Html模板模块,让html引用外部文件
template: path.resolve(__dirname, '../index.html')
})
],
集成react
集成react无非就是在项目框架中引入react和react-dom两个依赖插件,首先我们来进行安装,如下:
npm install react react-dom --save-dev
安装完成之后,我们在index.js中编写React代码,就是独具特色的JSX语法,因为在刚开始时我们已经配置了相应的loader,所以后面项目启动的时候,它是可以将我们里面的JSX代码编译成ES5代码的,如下:
import './index.less'
import './index.css'
import './assets/001.png'
import './assets/002.png'
import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(
<div>桃子叔叔</div>,
document.getElementById('root')
)
然后在项目根目录下的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>
<div id="root"></div>
</body>
</html>
现在我们通过npm run dev来启动项目,可以发现,此时我们将“桃子叔叔”字样渲染在了我们的前端页面,如下:
十二、集成react-router
配置好了基础的React环境之后,我们接下来配置路由。首先是安装依赖模块,如下:
npm install react-router-dom --save-dev
模块安装完成之后,我们在src目录下新建一个views文件夹,然后在此文件夹下新建两个react组件,名称分别为ComponentOne和ComponentTwo,在src下新建router文件夹,文件夹内新建routers.js文件,分别添加如下代码:
ComponentOne.js文件内添加代码如下:
//组件一代码
import React,{
Component } from 'react';
class ComponentOne extends Component {
render () {
return (
<div>
<h3>组件1</h3>
</div>
)
}
}
export default ComponentOne;
ComponentTwo.js文件内添加代码如下:
//组件二代码
import React,{
Component } from 'react';
class ComponentTwo extends Component {
render () {
return (
<div>
<h3>组件2</h3>
</div>
)
}
}
export default ComponentTwo;
routers.js文件内添加代码如下:
import ComponentOne from '../views/ComponentOne';
import ComponentTwo from '../views/ComponentTwo';
let routes = [
{
path: '/',
component: ComponentOne,
exact: true
}, {
path: '/two',
component: ComponentTwo
}
];
export default routes;
路由的基本配置做完之后,现在就可以在index.js中添加路由跳转了,index.js的代码如下:
import React from 'react';
import ReactDom from 'react-dom';
import {
BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import ComponentOne from './views/ComponentOne';
import ComponentTwo from './views/ComponentTwo';
const routerDom = () => (
<Router>
<div className="primary-layout">
<header>
<Link to="/">组件一</Link>
<br />
<Link to="/two">组件二</Link>
</header>
<main>
<Routes>
<Route path="/" exact element={
<ComponentOne/>} />
<Route path="/two" element={
<ComponentTwo/>} />
</Routes>
</main>
</div>
</Router>
)
ReactDom.render(routerDom(), document.getElementById('root'))
这里要格外注意<Route path=“/” exact element={} />的写法,当使用react router时,请确保将应该为特定路由渲染的组件作为,而不是Component。
打开页面,发现路由跳转已经ok了:
react-router现在这就是我们想要的样子了。
十三、集成react-redux
1、redux介绍
redux的核心代码可以理解为一个库,“redux”本身指redux这个npm包,它提供若干个API让我们使用reducer创建store,并能够更新store中的数据或获取store中最新的状态。
2、Redux的设计思想:
- React 只是 DOM 的一个抽象层,并不是web应用的完整解决方案。代码结构和组件间的通信,他没有涉及
- web应用是一个状态机,视图与状态是一一对应的
- 所有的状态,保存在一个对象里面(唯一数据源)、
目的:实现集中式状态管理 - 如果你不知道是否需要 Redux ,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要Redux
3、何时需要redux
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View 要从多个来源获取数据
4、redux三大原则
- 单一数据源
在redux思想中,一个应用只有唯一的一个数据源,整个应用的状态都在一个对象中,实现集中式状态管理。 - 状态是只读的
redux中,没有store,取而代之的是,定义一个reducer,作用的是根据出发的action对当前状态state进行迭代,这里没有直接修改应用的状态,而是返回一个新的状态。
redux提供的createStore方法会根据reducer生成store,最后,利用store.dispatch方法达到修改状态的目的。 - 状态修改由纯函数完成
redux中通过reducer来确定状态的修改,而每一个reducer都是一个纯函数。
5、动手实践
首先安装依赖包
npm install redux react-redux redux-thunk react-router-redux --save-dev
在src下新建redux文件夹,在redux文件夹内新建actions和store文件夹和configStore.js文件。
在actions文件夹内新建index.js文件,在store文件夹内新建index.js和rights.js文件
然后我们在这几个文件中添加代码,串联起store、reducer和action。
在actions/index.js文件中添加以下代码:
export const FUNC_RIGHTS = 'func_rights'
export function funcRights (rights) {
return {
type: FUNC_RIGHTS,
payload: rights
}
}
funcRights其实是一个action creator
,每次运行funcRights都会生成一个新的action,因此action creator之名恰如其分。
有了action之后我们编写store代码。
在store/rights.js文件中添加以下代码:
import {
FUNC_RIGHTS } from '../actions'
const initState = {
rights: 0
}
const rightsReducer = (state = initState, action) => {
let payload = action.payload
switch (action.type) {
case FUNC_RIGHTS:
state.rights = payload
return {
...state,
rights: state.rights
}
default:
return state
}
}
export default rightsActions
1、这里我们首先初始化了state的值,使用的initState
,并且将初始化state传入到rightsReducer函数中,初始化完成后,reducer会获得上次计算出的state作为参数,这时initState不在使用。
2、rightsReducer就是我们的reducer
了,这里可以看到reducer会响应1种类型的action–’FUNC_RIGHTS‘,这也是刚刚定义的action–funcRights可能触发的类型。
然后在store/index.js中添加以下代码:
import {
combineReducers } from "redux";
import rightsReducer from './rights'
const rootReducer = combineReducers({
rightsReducer
})
export default rootReducer
export * as rightsActions from '../actions/index'
store/index.js整合了相关的reducers和actions,在这里我们引入了redux官方的combineReducers
方法,通过这个方法我们可以简单的将多个reducer集合成一个。
然后修改redux/configStore.js文件:
import {
legacy_createStore as createStore, combineReducers, compose, applyMiddleware } from "redux";
import {
routerReducer } from "react-router-redux";
import thunk from "redux-thunk";
import rightsActions from "./store/rights"
const finalCreateStore = compose(
applyMiddleware(thunk)
)(createStore)
const reducer = combineReducers(Object.assign({
}, {
rightsActions}, {
routing: routerReducer
}))
export default function configStore(initialState){
const store = finalCreateStore(reducer, initialState)
return store
}
这里是生成redux store的关键文件,用到了redux核心API–createStore。这里没有直接用createStore创建store,而是使用了compose
对createStore方法进行了增强,并生成了新的方法finalCreateStore。
applyMiddleware
是redux的另一个API,使用applyMiddleware可以让redux解析各种action,除了最原始的对象外,还可以是方法、promise和任何你能想象的类型。
接下来修改src/index.js文件,这里我们需要用到Provider
:
import React from 'react';
import ReactDom from 'react-dom';
import {
BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import ComponentOne from './views/ComponentOne';
import ComponentTwo from './views/ComponentTwo';
import configStore from './redux/configStore';
import {
Provider } from 'react-redux';
const store = configStore()
const routerDom = () => (
<Provider store={
store}>
<Router>
<div className="primary-layout">
<header>
<Link to="/">组件一</Link>
<br />
<Link to="/two">组件二</Link>
</header>
<main>
<Routes>
<Route path="/" exact element={
<ComponentOne/>} />
<Route path="/two" element={
<ComponentTwo/>} />
</Routes>
</main>
</div>
</Router>
</Provider>
)
ReactDom.render(routerDom(), document.getElementById('root'))
React-Redux 提供Provider
组件,可以让容器组件拿到state。
上面代码中,Provider
在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state
了。
接下来就可以在组件内尝试使用react-redux了。
我们修改一下views/ComponentOne.js文件:
//组件一代码
import React,{
Component } from 'react';
import {
connect } from 'react-redux'
import {
bindActionCreators } from 'redux'
import * as IndexActions from '../redux/actions/index'
class ComponentOne extends Component {
state = {
right: 0
}
componentWillMount () {
}
componentWillReceiveProps (nextProps) {
let {
rights} = nextProps
this.setState({
right: rights})
}
change () {
let a = this.state.right + 1
this.props.funcRights(a)
}
render () {
let {
rights } = this.props
return (
<div>
{
rights }
<h3 onClick={
()=> this.change()}>标题组件1</h3>
</div>
)
}
}
export default connect((state) => {
console.log(state)
return {
rights: state.rightsActions.rights
}
}, (dispatch) => {
return bindActionCreators(IndexActions, dispatch)
} , (stateProps, actionProps, parentProps) => {
return {
...stateProps,
...actionProps,
...parentProps
}
})(ComponentOne)
在这里我们用到了:connect
connect()
是 react-redux 暴露的一个 API 。 它用来连接 容器组件和 UI 组件的 。
此方法需要传递两个函数参数 。并且都是必须参数
第一个参数mapStateToProps
:
字面意思是映射state
到 Props . 它是一个函数,此函数把需要的状态数据取出 ,最后返回一个对象 , 此对象里的键值会被传递到 UI 组件的 props属性 里
此函数会接收到一个参数 。 此参数的数据相当于 store.getState()
返回的数据。
第二个参数mapDispatchToProps
:
意思是 映射 dispatch
到 props 。 它是一个函数 , 此函数返回一个对象。对象的键对应的值是一个函数,此函数执行的是 dispatch 方法。dispatch 是由 react-redux 传进来的参数 。
这时运行项目,页面上已经获取到了redux的state,初始化值为0,并且点击“标题组件1”,数字不断增加,这说明我们完成了react-redux的配置。
完成以上操作,一个完整的react项目环境就搭建完成了,如果还需要ui模版如antdesign可以直接引入使用了。
转载:https://blog.csdn.net/franktaoge/article/details/127647239