前言
本篇文章在尚未完善的星巴克App开发中使用了
redux
, 尽管在小项目中使用redux
确实有些牛刀杀鸡的感觉, 但是小项目的结构更为清晰简单, 是最适合我们用来给redux
的学习练手的, 如果你对redux
不太熟悉,这篇文章或许能解答你的一些疑问
这也是笔者写下这篇文章的目的, 一来是为了帮助自己理清
redux
的相关知识点, 二来是希望广大读者朋友在看到这篇文章之后能获得一些启发与帮助
项目
项目详情大家可以自行查看我的上一篇文章, 页面详情几乎没有改动, 仅仅使用redux
取代了React
自带的useState
来管理数据, 这里就不再赘述项目了
后续若有更新, 将会有新的文章给大家带来更详细的分享
前期准备
在项目中需要安装一些包 :
- npm i redux react-redux
- npm i redux-thunk
浏览器中需要安装扩展 :
- Redux DevTools
ps : 尽量使用Chrome
Redux
作用
当然是管理数据 !
在使用redux
之前, 我们通常使用useState
来管理数据, 再通过props
来进行父子组件或爷孙组件之间的数据传递
通过对子组件传入data
以及setData
的方法, 我们对数据进行读取和修改
但是
当数据需要在非父子组件之间传递时, 使用props
将成为不可能, 这个时候Redux
站了出来
它给我们提供了一种数据管理方式, 即为我们搭建了一个数据仓库, 这个数据仓库由一个主仓库和若干个子仓库组成
数据的传递在仓库和仓库之间完成, 需要用的时候就从仓库中取出来, 数据若在其中一个子仓库中被修改, 修改后的结果也会被同步到总仓库和每一个子仓库 !
也就是说在一个组件中被修改的数据, 通过Redux
可以向所有使用过该数据的组件同步数据 ! 不需要再向之前一样一层一层的报告, 大大提高的数据传输的效率和安全性 !
仓库结构
主仓库
在src
目录下, 其中包括index.js
和reducer.js
, 下面将对它们进行详细介绍
index.js
- createStore : 创建一个新的仓库
- compose : 可以用来组合中间件
- applyMiddleware : 中间件
- thunk : 延迟计算
- composeEnhancers : 当浏览器中存在Redux DevTools 时将前者赋值, 否则使用默认compose
import { createStore, compose, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
))
export default store
store
: 传入 reducer
作为数据, 再结合中间件作为管理数据的各项配置
可简单理解为 : 仓库中的货物和处理货物的工具
当有多个中间件需要使用时, 可以修改代码为 :
const store = createStore(reducer,
compose(
composeEnhancers(applyMiddleware(thunk)),
applyMiddleware(thunk),
// 其他中间件
)
)
reducer.js
import { combineReducers } from 'redux'
import { reducer as homeReducer } from '@/pages/Home/store/index'
export default combineReducers({
home: homeReducer
})
reducer.js
中的代码较为简单, 大致可以理解为 : 将子仓库中的数据传过来, 合并到主仓库中
如果需要添加多个子仓库时, 可以这样修改代码 :
import { combineReducers } from 'redux'
import { reducer as homeReducer } from '@/pages/Home/store/index'
import { reducer as accountReducer } from '@/pages/Account/store/index' // 导入
export default combineReducers({
home: homeReducer,
account: accountReducer, // 添加
})
结合上面, 在index.js
中将store
输入, 这样就将主仓库搭建好了
子仓库
哪里需要数据, 哪里就有子仓库
- 一般来说, 我们会在每一个独立的页面中使用一个子仓库来管理数据, 页面内的数据交流则通过
props
传值进行
下图为src/pages/Home
首页的store
:
可以看到, 子仓库中存在四个
js
文件, 下面我们将一个一个分析
- index.js
import * as actionCreator from './actionCreator'
import * as actionTypes from './common'
import reducer from './reducer'
export {
actionCreator,
actionTypes,
reducer
}
index.js
中的代码十分简短, 可以理解为 : 将其他三个文件中的内容整合, 然后整体向外输出
- reducer.js
import { actionTypes } from './index'
const defaultState = {
adsList: [],
goodsList: [],
cultureList: []
}
export default (state = defaultState, action) => {
switch (action.type) {
case actionTypes.SET_ADS_LIST:
return {
...state,
adsList: action.data
}
case actionTypes.SET_GOODS_LIST:
return {
...state,
goodsList: action.data
}
case actionTypes.SET_CULTURE_LIST:
return {
...state,
cultureList: action.data
}
default:
return state
}
}
reducer.js
用来储存数据, 并规定传过来的数据按什么方式处理 (这里是将更新数据, 也可以在 case
中进行其他各项操作)
它会向外输出一个reducer
对象, 该对象接受两个参数 state
(数据), action
(对象), 通过action
中 type
对 state
中相应的数据进行操作
其中,
action
对象包含两个属性 :type
与data
- type : 相当于
Key
, 用于标识该action
是谁的action
, 下面switch
选择中将会用到, 是谁的数据, 就将该数据给谁- data : 需要在匹配后, 给出的数据
简单的来说, 它的作用为 : 存储和修改数据
- actionCreator.js
import {
getAdsRequest,
getGoodsRequest,
getCultureRequest
} from '@/api/request'
import { actionTypes } from './index'
// 1
const changeAdsList = (data) => ({
type: actionTypes.SET_ADS_LIST,
data
})
const changeGoodsList = (data) => ({
type: actionTypes.SET_GOODS_LIST,
data
})
const changeCultureList = (data) => ({
type: actionTypes.SET_CULTURE_LIST,
data
})
// ------------------- 我是分割线 -------------------
// 2
export const getAdsList = () => {
return (dispatch) => {
getAdsRequest().then(data => {
dispatch(changeAdsList(data.ad))
})
}
}
export const getGoodsList = () => {
return (dispatch) => {
getGoodsRequest().then(data => {
dispatch(changeGoodsList(data.goods))
})
}
}
export const getCultureList = () => {
return (dispatch) => {
getCultureRequest().then(data => {
dispatch(changeCultureList(data.culture))
})
}
}
这里通过分割线, 将代码分成上面的第 1 部分和下面的第 2 部分
1 部分为同步代码, 用于创建
action
对象
2 部分为异步代码, 用于请求数据, 并dispatch
一个action
对象, 即将action
对象发送出去
ps : 一般来说 , API
的请求调用都在 actionCreator.js
中进行
- common.js
export const SET_ADS_LIST = 'SET_ADS_LIST'
export const SET_GOODS_LIST = 'SET_GOODS_LIST'
export const SET_CULTURE_LIST = 'SET_CULTURE_LIST'
common.js
中的代码也十分简洁, 用于声明常量, 集中到 common 文件中更方便全局的调用和管理
至此, 子仓库的创建也完成了......
Redux 使用
首先, 我们会在全局引入一个 Provider (这里是在 mian.jsx
下) , 并作为最外层组件, 将所有组件包裹起来, 传入参数 store = {store}
, 这意味着在全局引入 store
, 并使用它
- 紧接着, 因为我们需要在 Home 页面中使用数据(子仓库建立在
Home
目录下), 所以来到pages/Home/index.js
中使用数据 - 一般来说, 我们对数据的操作包括读数据和写数据, 下面将会在这两个方面对代码进行分析
- 先来看整体 :
- 在头部引入
connect
, 用于给当前页面组件传参connect()()
函数柯里化connect
的第一个括号内接受要传的参数mapStateToProps
: 将数据传给当前页面组件mapDispatchToProps
: 将操作数据的方法传给当前页面组件mapStateToProps
与mapDispatchToProps
中的属性将会合并到props
中, 我们只需要在Home
函数体中解构即可
- 这里可以对代码进行优化, 最后一行代码在输出
Home
时包裹一个React.memo(Home)
,像这样 :
export default connect(mapStateToProps, mapDispatchToProps)(React.memo(Home))
在分析 Home
中的代码之前, 我们先来看这两个函数究竟做了什么
const mapStateToProps = (state) => {
return ({
ads: state.home.adsList,
goods: state.home.goodsList,
culture: state.home.cultureList
})
}
const mapDispatchToProps = (dispatch) => {
return {
getAdsListDispatch() {
dispatch(actionCreator.getAdsList())
},
getGoodsListDispatch() {
dispatch(actionCreator.getGoodsList())
},
getCultureListDispatch() {
dispatch(actionCreator.getCultureList())
}
}
}
- 在 mapStateToProps 中 : 我们传入参数
state
, 即全部数据, 再将我们所需要的数据从state
取出来{ads, goods, culture}
, 把它们封装成一个对象, 最后返回到props
中- 在 mapDispatchToProps 中 : 我们传入
dispatch
函数, 它将会接收一个action
对象, 并传出, 根据action.type
的值来修改对应的data
- 可以看到, 对于不同的数据我们都使用了不同的函数来对
dispatch
进行封装, 并传入对应的action
对象, 以达到更新数据的效果
分析了数据的读写后, 我们再来看看它们中的属性是如何被使用的吧 !
const { ads, goods, culture } = props
const { getAdsListDispatch, getGoodsListDispatch, getCultureListDispatch } = props
useEffect(() => {
getAdsListDispatch()
getGoodsListDispatch()
getCultureListDispatch()
}, [])
通过 props 将我们需要的属性全部解构出来, 再通过 useEffect
生命周期函数, 等页面挂在后调用 Dispatch
函数, 达到更新数据的目的
在此之后, 我们就可以将获取到的数据分发到不同的组件 :
<Wrapper>
<Ads ads={ads}/>
<Card />
<Club />
<Goods goods={goods} />
<Culture culture={culture} />
</Wrapper>
后记 :
- 看到这里的朋友, 相信你对 redux 的框架已经有了基本的理解, 其实文中还有许多较为复杂的难点, 在这里并没有做出详细的解释 , 如 :
- dispatch 的具体作用原理
- store 目录下的文件之间的内在联系
- 数据完整的请求和传递路径 (它是从哪个文件哪行代码开始, 经过了哪些文件, 最后到达了哪里)
等等这些, 都需要对 redux 有着足够深厚的理解才能弄的明白, 讲得清楚
想要将 redux 完全掌握的读者朋友可以自行进行学习和思考, 这里笔者也需要更多的学习来对其进行更进一步的理解