这篇文章主要是介绍redux的基本概念,以及redux如何使用!!
Redux简述
Redux 实际上是:
- 包含全局状态的单一仓库
- 当应用中发生某些事情时,分发普通对象(plain object) 动作(action)给仓库
- Pure reducer 函数查看这些动作(action)并且返回不可更新的状态。
简单来说Redux是一个可预测的状态容器
状态(State)
状态就是一个变量,用以记录程序执行的情况。组件可以根据不同的状态值切换为不同的显示,比如,用户登录和没登录看到页面应该是不同的,那么用户的登录与否就应该是一个状态。
容器(Container)
容器当然是用来装东西的,状态容器即用来存储状态的容器。状态多了,自然需要一个东西来存储,但是容器的功能却不是仅仅能存储状态,它实则是一个状态的管理器,除了存储状态外,它还可以用来对state进行查询、修改等所有操作。
可预测(Predictable)
可预测指我们在对state进行各种操作时,其结果是一定的。即以相同的顺序对state执行相同的操作会得到相同的结果。简单来说,Redux中对状态所有的操作都封装到了容器内部,外部只能通过调用容器提供的方法来操作state,而不能直接修改state。这就意味着外部对state的操作都被容器所限制,对state的操作都在容器的掌控之中,也就是可预测。
总的来说,Redux是一个稳定、安全的状态管理器。
react中使用Redux(旧的方式)
安装
js
npm install -S redux react-redux
举例:
现在有一个PageA组件,点击减少数字变小,点击增加数字变大,我想要通过redux来操作
js
import React from 'react'
export default function () {
return (
<div>
<button>增加</button>
<p>20</p>
<button>减少</button>
</div>
)
}
创建reducer
reducer用来整合关于state的所有操作,容器修改state时会自动调用该函数,函数调用时会接收到两个参数:state和action,
state表示当前的state,可以通过该state来计算新的state。state = {num: 20}这是在指定state的默认值,如果不指定,第一次调用时state的值会是undefined。也可以将该值指定为createStore()的第二个参数。
action是一个普通对象,用来存储操作信息。
js
const pageAReducer = ((state = {
num: 20
}, action) => {
switch (action.type) {
case 'ADD':
return {
...state,
num: state.num + action.num
};
case 'SUB':
return {
...state,
num: state.num - action.num
};
default:
return state;
}
})
创建store(createStore)
创建store是利用createStore
js
createStore(reducer, [preloadedState], [enhancer])
createStore用来创建一个Redux中的容器对象,它需要三个参数:reducer、preloadedState、enhancer。
- reducer是一个函数,是state操作的整合函数,每次修改state时都会触发该函数,它的返回值会成为新的state。
- preloadedState就是state的初始值,可以在这里指定也可以在reducer中指定。
- enhancer (Function): Store enhancer。你可以选择指定它以使用第三方功能,如middleware、时间旅行、持久化来增强 store。
js
const store=createStore(pageaReducer);
将pageReducer传递进createStore后,我们会得到一个store对象:
store对象创建后,对state的所有操作都需要通过它来进行
设置provider
创建store后,需要引入react-redux中提供的Provider组件,将其设置到所有组件的最外层,并且将刚刚创建的store设置为组件的store属性,只有这样才能使得Redux中的数据能被所有的组件访问到。
js
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
)
访问数据(useSelector)
useSelector,用于获取Redux中存储的数据,它需要一个回调函数作为参数,回调函数的第一个参数就是当前的state,回调函数的返回值,会作为useSelector的返回值返回,所以state => state表示直接将整个state作为返回值返回。现在就可以通过pagea来读取state中的数据了:
js
const pagea = useSelector(state => state);
<p>{pagea.num}</p>
操作数据(useDispatch)
useDispatch同样是react-redux提供的钩子函数,用来获取redux的派发器,对state的所有操作都需要通过派发器来进行。
通过派发器修改state:
js
const dispatch = useDispatch();
dispatch({type:'ADD', num:1})
dispatch({type:'SUB', num:2})
完整代码和目录结构
src\store\index.jsx
js
import { createStore } from "redux";
const pageAReducer = ((state = {
num: 20
}, action) => {
switch (action.type) {
case 'ADD':
return {
...state,
num: state.num + action.num
};
case 'SUB':
return {
...state,
num: state.num - action.num
};
default:
return state;
}
})
const store =createStore(pageAReducer)
export default store
src\components\PageA.jsx
js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export default function () {
const pagea = useSelector(state => state)
const dispatch = useDispatch()
const onClickOne = () => {
dispatch({ type: 'ADD', num: 1 })
}
const onClickTwo = () => {
dispatch({ type: 'SUB', num: 2 })
}
return (
<div>
<button onClick={onClickOne}>增加</button>
<p>{pagea.num}</p>
<button onClick={onClickTwo}>减少</button>
</div>
)
}
App.jsx
js
import PageA from './components/PageA';
function App() {
return (
<>
<PageA></PageA>
</>
)
}
export default App
main.jsx
js
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { Provider } from 'react-redux'
import store from './store/'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
)
多个reducer
比如有个PageB页面,显示数组的内容,点击变化按钮,数组push一个我们随便定义的数据,也要用redux进行操作
js
export default function Page2() {
const onClick=()=>{
}
return (
<div>
<div>Page2</div>
<div>{11}</div>
<button onClick={onClick}>变化</button>
</div>
)
}
跟PageA一样,先创建reducer
js
const pageBReducer = (state = { arr: [10,20,30] }, action) => {
switch (action.type) {
case 'pushNum':
state.arr.push(action.num)
return {
...state
}
default:
return state;
}
}
combineReducer
创建完reducer以后,因为是多个reducer,要使用Redux为我们提供的函数combineReducer将多个reducer进行合并,合并后才能传递进createStore来创建store。
combineReducer需要一个对象作为参数,对象的属性名可以根据需要指定,比如我们有两种reducer。读取数据时,直接通过state.pageAReducer读取PageA的数据,通过state.pageBReducer读取PageB的数据。
js
const reducers=combineReducers({
pageAReducer,
pageBReducer
})
const store = createStore(reducers)
export default store
完整代码
src\components\PageB.jsx
js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export default function PageB() {
const dispatch = useDispatch();
const pageb = useSelector(state => state.pageBReducer)
const onClick = () => {
dispatch({ type: 'pushNum', num: 50 })
}
return (
<div>
<button onClick={onClick}>变化</button>
<p>{pageb.arr.join(" ")}</p>
</div>
)
}
src\store\index.jsx
js
import { combineReducers, createStore } from "redux";
const pageAReducer = (state = { num: 20 }, action) => {
switch (action.type) {
case 'ADD':
return {
...state,
num: state.num + action.num
}
case 'SUB':
return {
...state,
num: state.num - action.num
}
default:
return state;
}
}
const pageBReducer = (state = { arr: [10,20,30] }, action) => {
switch (action.type) {
case 'pushNum':
state.arr.push(action.num)
return {
...state
}
default:
return state;
}
}
const reducers=combineReducers({
pageAReducer,
pageBReducer
})
const store = createStore(reducers)
export default store
模块化及优化
由上面可知,我是把store和reducer放在一个文件中的,当有多个reducer时,可以看到代码越来越多,结构就越来越复杂,代码的耦合性变高,不利于维护,因此进行模块化,将reducer和store进行拆分
拆分reducer
pageAReducer里面既有给数据设置默认值又有操作数据的动作,比如匹配到ADD以后,我们可以把对数据的操作封装成一个函数,在reducer里面直接调用,因为后续可能还有其他对数据的操作,会使得代码结构越来越复杂
因此我把pageAReducer拆分成两部分,一部分是reducer,负责根据匹配的case更新数据,一部分是status,负责去定义状态的初始值,以及更新状态的一些方法
在store文件下新建reducer文件夹,reducer下新建PageA文件夹
PageA文件夹下包含两个文件:reducer和status
src\store\reducers\PageA\status.jsx
在handlerStatus中对方法名映射为reducer中的action.type,这里的action.type也对应了dispatch中的type
js
let handlerStatus = {
state: {
num: 20
},
actions: {
ADD(newState, action) {
newState.num = newState.num + action.num
},
SUB(newState, action) {
newState.num = newState.num - action.num
}
},
ADD:"ADD",
SUB:"SUB"
}
export default handlerStatus
src\store\reducers\PageA\reducer.jsx
定义newState,对state进行深拷贝,统一返回newState
js
import handlerStatus from "./status";
const reducer = (state = { ...handlerStatus.state }, action) => {
let newState = JSON.parse(JSON.stringify(state))
switch (action.type) {
case handlerStatus.ADD:
handlerStatus.actions.ADD(newState, action)
break;
case handlerStatus.SUB:
handlerStatus.actions.SUB(newState, action)
break;
default:
break;
}
return newState
}
export default reducer
在reducers文件夹下新建index.jsx,为了引入多个reducer,并进行组合
src\store\reducers\index.jsx
js
import { combineReducers } from "redux";
import pageAReducer from './PageA/reducer'
const reducers = combineReducers({
pageAReducer
})
export default reducers
在store中引入reducers
js
import { createStore } from 'redux'
import reducers from './reducers'
const store = createStore(reducers)
export default store
关于PageB也是一样的,这里就不再重复,后面直接贴完整代码
优化reducer和status
因为我们对方法名和action.type进行了映射,所以我们可以把方法名统计用一个属性进行管理,通过遍历来将方法名和action.type进行映射
src\store\reducers\PageA\status.jsx
js
let handlerStatus = {
state: {
num: 20
},
actions: {
ADD(newState, action) {
newState.num = newState.num + action.num
},
SUB(newState, action) {
newState.num = newState.num - action.num
}
},
actionNames: {}
}
let actionNames = {}
Object.keys(handlerStatus.actions).forEach((key) => {
actionNames[key] = key
})
export default handlerStatus
同样的在reducer中通过遍历actionNames,根据匹配到action.type来调用具体的方法,优化了swtich结构,用some是为了减少不必要的遍历
js
import handlerStatus from "./status";
const reducer = (state = { ...handlerStatus.state }, action) => {
let newState = JSON.parse(JSON.stringify(state))
Object.keys(handlerStatus.actionNames).some((actionName) => {
if (actionName === action.type) {
handlerStatus.actions[actionName](newState, action)
return true
}
return false
})
return newState
}
export default reducer
PageB也是相似的写法,不再重复介绍
完整代码和目录结构
components
src\components\PageA.jsx
js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export default function PageA() {
const dispatch=useDispatch();
const pagea=useSelector(state=>state.pageAReducer)
const onClick1=()=>{
dispatch({type:'ADD',num:1})
}
const onClick2=()=>{
dispatch({type:'SUB',num:2})
}
return (
<div>
<button onClick={onClick1}>增加</button>
<p>{pagea.num}</p>
<button onClick={onClick2}>减少</button>
</div>
)
}
src\components\PageB.jsx
js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export default function PageB() {
const dispatch = useDispatch();
const pageb = useSelector(state => state.pageBReducer)
const onClick = () => {
dispatch({ type: 'pushNum', num: 50 })
}
return (
<div>
<button onClick={onClick}>变化</button>
<p>{pageb.arr.join(" ")}</p>
</div>
)
}
store
src\store\index.jsx
js
import { createStore } from 'redux'
import reducers from './reducers'
const store = createStore(reducers)
export default store
reducers
src\store\reducers\index.jsx
js
import { combineReducers } from "redux";
import pageAReducer from './PageA/reducer'
import pageBReducer from './PageB/reducer'
const reducers = combineReducers({
pageAReducer,
pageBReducer
})
export default reducers
PageA
src\store\reducers\PageA\reducer.jsx
js
import handlerStatus from "./status";
const reducer = (state = { ...handlerStatus.state }, action) => {
let newState = JSON.parse(JSON.stringify(state))
Object.keys(handlerStatus.actionNames).some((actionName) => {
if (actionName === action.type) {
handlerStatus.actions[actionName](newState, action)
return true
}
return false
})
return newState
}
export default reducer
src\store\reducers\PageA\status.jsx
js
let handlerStatus = {
state: {
num: 20
},
actions: {
ADD(newState, action) {
newState.num = newState.num + action.num
},
SUB(newState, action) {
newState.num = newState.num - action.num
}
},
actionNames: {}
}
let actionNames = {}
Object.keys(handlerStatus.actions).forEach((key) => {
actionNames[key] = key
})
handlerStatus.actionNames=actionNames
export default handlerStatus
PageB
src\store\reducers\PageB\reducer.jsx
js
import handlerStatus from "./status";
const reducer = (state = { ...handlerStatus.state }, action) => {
let newState = JSON.parse(JSON.stringify(state))
Object.keys(handlerStatus.actionNames).some((actionName) => {
if (actionName === action.type) {
handlerStatus.actions[actionName](newState, action)
return true
}
return false
})
return newState
}
export default reducer
src\store\reducers\PageB\status.jsx
js
let handlerStatus = {
state: {
arr: [10, 20, 30]
},
actions: {
pushNum(newState, action) {
newState.arr.push(action.num)
},
},
actionNames: {}
}
let actionNames = {}
Object.keys(handlerStatus.actions).forEach((key) => {
actionNames[key] = key
})
handlerStatus.actionNames=actionNames
export default handlerStatus
总结
使用redux,可以使用一套固定的模版
status
js
const handlerStatus = {
state: {
// 放数据
},
actions: {
// 放同步方法
},
asyncActions: {
// 放异步方法
},
actionNames: {}
}
let actionNames = {}
Object.keys(handlerStatus.actions).forEach((key) => {
actionNames[key] = key
})
handlerStatus.actionNames=actionNames
export default handlerStatus
reducer
js
import handlerStatus from "./status";
const reducer = (state = { ...handlerStatus.state }, action) => {
let newState = JSON.parse(JSON.stringify(state))
Object.keys(handlerStatus.actionNames).some((actionName) => {
if (actionName === action.type) {
handlerStatus.actions[actionName](newState, action)
return true
}
return false
})
return newState
}
export default reducer