前面为redux基础笔记 项目使用看最终方案
redux的基本流程
流程大概就是创建一个公用的仓库store来存储我们的数据,通过state来渲染到UI页面上,我们在UI层面想要修改state数据需要通过dispatch来派发一个action对象,action对象的type会找到对应的reducer来修改state
搭建一个基本的store
第一步安装redux
npm i redux
在项目src/下创建store/index.ts
import { createStore } from 'redux'
function countReducer(state = { count: 1 }, action) {
switch (action.type) {
case 'add':
// return state.count + 1 错误写法 redux 不允许直接修改state 而是需要返回一个新值不影响之前传递的值
return { count: state.count + 1 } // 正确的写法
default:
return state
}
}
const store = createStore(countReducer)
export default store
这里我们定义了一个最基础的store声明count以及修改count的方法
在页面中如何使用
import React from 'react'
import store from '@/store'
export default function Layout() {
return (
<div>
{/* 通过store.getState()获取store中的数据 */}
{store.getState().count}
</div>
)
}
页面:
如何修改state
在最开始的流程图下说过UI如果想要修改state需要通过dispatch派发一个action对象来调用reducer来修改state
import React from 'react'
import store from '@/store'
export default function Layout() {
const setCount = () => {
store.dispatch({
type: 'add',
})
}
return (
<div>
{/* 通过store.getState()获取store中的数据 */}
{store.getState().count}
{/* 修改count */}
<button onClick={setCount}>+</button>
</div>
)
}
这时候我们点击button的时候,页面并不会更新数据,但是实际上state是发生变化了,但是页面没有监控的到,这时我们需要用redux提供的一个方法,并且需要使用useState来配合实现改变
import React from 'react'
import store from '@/store'
export default function Layout() {
const [count, setc] = React.useState(store.getState().count)
const setCount = () => {
store.dispatch({
type: 'add',
})
// 每次state发生变化的时候会执行
store.subscribe(() => {
setc(store.getState().count)
})
}
return (
<div>
{/* 通过store.getState()获取store中的数据 */}
{count}
{/* 修改count */}
<button onClick={setCount}>修改count</button>
</div>
)
}
页面:
这就算是完成一个redux的基本流程
使用react-redux来简化代码
安装react-redux
npm i react-redux
之后需要在main.ts中进行Provider包裹
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import router from './router'
import { RouterProvider } from 'react-router-dom'
// 引入你的store
import store from './store'
// 引入redux中的Provider
import { Provider } from 'react-redux'
ReactDOM.createRoot(document.getElementById('root')!).render(
// 严格模式
<React.StrictMode>
{/* store包裹 */}
<Provider store={store}>
{/* 路由组件 */}
<RouterProvider router={router} />
</Provider>
</React.StrictMode>,
)
使用Provider包裹你的ui组件 把store挂载到上面
简化页面代码
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// useSelector用于获取store中的数据
// usedispatch用于修改store中的数据
export default function Layout() {
// 获取count
const count = useSelector((state: any) => state.count)
const dispatch = useDispatch()
const setCount = () => {
dispatch({
type:'add'
})
}
return (
<div>
{count}
{/* 修改count */}
<button onClick={setCount}>修改count</button>
</div>
)
}
这是我们修改页面会直接响应 所使用的代码也简捷了许多
如何对reducer来进行传参
我们只需要在dispatch的对象中写入第二个参数
dispatch({
type: 'add',
payload:5
})
store中
import { createStore } from 'redux'
function countReducer(state = { count: 1 }, action) {
switch (action.type) {
case 'add':
// action:{} type:执行的操作 payload:操作的参数 现在每次点击count就会加5
return { count: state.count + action.payload }
default:
return state
}
}
const store = createStore(countReducer)
export default store
处理多个reducer
在vuex中我们可以通过module属性来处理多个store 在redux中我们可以通过combineReducers方法来处理多个reducer
第一步创建module文件夹 新建count.ts 把store中的countReducer抽离出来
// count.ts
export default function countReducer(state = { count: 1 }, action) {
switch (action.type) {
// 加入命名空间
case 'count/add':
// action:{} type:执行的操作 payload:操作的参数 现在每次点击count就会加5
return { count: state.count + action.payload }
default:
return state
}
}
store使用combineReducers(包裹)
// store
import { createStore, combineReducers } from 'redux'
// 引入抽离的countcountReducer
import countReducer from './module/count'
const store = createStore(combineReducers({
// 命名count
count: countReducer
// 模块2命名:模块2Reducer ....
}))
export default store
页面获取操作也需要加上命名空间
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// useSelector用于获取store中的数据
// usedispatch用于修改store中的数据
export default function Layout() {
// 获取count命名空间模块下的count数据
const count = useSelector((state: any) => state.count.count)
const dispatch = useDispatch()
const setCount = () => {
dispatch({
// 修改count命名模块下的count数据
type: 'count/add',
payload:5
})
}
return (
<div>
{count}
{/* 修改count */}
<button onClick={setCount}>修改count</button>
</div>
)
}
在redux中如何处理异步
上面的操作其实一直是针对同步修改,在实际场景中我们很多时候会需要异步修改state,例如http请求等,但是在redux中dispatch默认只支持action对象字面量,这时候我们可以通过redux-thunk来让dispatch支持回调函数形式
下载redux-thunk
npm i redux-thunk
使用applyMiddleware注册中间件
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk';
// 引入抽离的countcountReducer
import countReducer from './module/count'
// applyMiddleware(中间件,中间件2...)
const store = createStore(combineReducers({ count: countReducer }), applyMiddleware(thunk) as any)
export default store
页面调用异步代码
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// useSelector用于获取store中的数据
// usedispatch用于修改store中的数据
export default function Layout() {
// 获取count命名空间模块下的count数据
const count = useSelector((state: any) => state.count.count)
const dispatch = useDispatch()
const setCount = () => {
// 模仿异步 延迟2秒修改count
dispatch((dispatch) => {
// 使用thunk之后 可以传入一个回调函数
// 回调第一个参数也是dispatch可以用来执行完异步操作继续dispatch
setTimeout(() => {
dispatch({
type: 'count/add',
payload:5
})
}, 2000);
})
}
return (
<div>
{count}
{/* 修改count */}
<button onClick={setCount}>修改count</button>
</div>
)
}
异步的方法进行抽离
// count.ts
export default function countReducer(state = { count: 1 }, action) {
switch (action.type) {
// 加入命名空间
case 'count/add':
// action:{} type:执行的操作 payload:操作的参数 现在每次点击count就会加5
return { count: state.count + action.payload }
default:
return state
}
}
// 把异步操作放抽出来
export const asyncAddCount = () => {
return (dispatch) => {
setTimeout(() => {
dispatch({
type: 'count/add',
payload: 5
})
}, 2000);
}
}
这样在主页引入方法调用即可
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
// 引入抽离的异步方法
import {asyncAddCount} from '../../store/module/count'
export default function Layout() {
const count = useSelector((state: any) => state.count.count)
const dispatch = useDispatch()
const setCount = () => {
// 调用
dispatch(asyncAddCount())
}
return (
<div>
{count}
{/* 修改count */}
<button onClick={setCount}>修改count</button>
</div>
)
}
最终方案RTK
优点
可以自动跟redux devtools结合,不需要下载模块进行生效
数据不需要在通过返回值进行修改,可以跟vue一样直接进行修改
内置了redux-thunk异步插件 不需要额外安装
代码风格更好 采用选项式编程风格
安装@reduxjs/toolkit
npm i @reduxjs/toolkit
RTK官网:https://redux-toolkit.js.org/
基本使用
重建store
// store.ts
import { configureStore } from '@reduxjs/toolkit'
// 引入reducer
import countReducer from './module/count'
// 创建store
const store = configureStore({
reducer: {
// 命名空间 state.count.xxx
count: countReducer
// 多个reducer ...
}
})
export default store
module/count.ts
import { createSlice } from '@reduxjs/toolkit'
const countSlice = createSlice({
// 命名空间 dispatch(count/...)
name: 'count',
// 初始值
initialState: {
count: 0,
name: 'count'
},
reducers: {
add(state) {
state.count++
},
// 传递参数
setName(state, action) {
state.name = action.payload
}
}
})
export default countSlice.reducer
页面使用
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
export default function Layout() {
const count = useSelector((state: any) => state.count.count)
const name = useSelector((state: any) => state.count.name)
const dispatch = useDispatch()
const setCount = () => {
dispatch({
type: 'count/add',
})
dispatch({
type: 'count/setName',
payload: '张三',
})
}
return (
<div>
<div>数:{count}</div>
<div>名:{name }</div>
<button onClick={setCount}>修改count-name</button>
</div>
)
}
初始页面
点击按钮
使用异步方法
createAsyncThunk创建异步方法
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
// 使用createAsyncThunk来创建异步方法 第一个参数是命名空间 第二个参数是异步任务
export const asyncAdd = createAsyncThunk('count/asyncAdd', async () => {
// 模拟一个异步任务
let res = await new Promise(resolve => {
setTimeout(() => {
resolve({ code: 200 })
}, 1000)
})
return res
})
const countSlice = createSlice({
// 命名空间 dispatch(count/...)
name: 'count',
// 初始值
initialState: {
count: 0,
},
reducers: {
add(state, action) {
state.count = action.payload
},
}
})
export default countSlice.reducer
页面可以通过.then(res)拿到对应的结果进行操作
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {asyncAdd} from '@/store/module/count'
export default function Layout() {
const count = useSelector((state: any) => state.count.count)
const dispatch = useDispatch()
const setCount = () => {
dispatch(asyncAdd()).then(res => {
// 再使用dispatch调用同步方法
dispatch({
type: 'count/add',
payload:res.payload.code
})
})
}
return (
<div>
<div>数:{count}</div>
<button onClick={setCount}>异步修改count</button>
</div>
)
}
控制台打印结果为
除了.then形式也可以使用extraReducers配置来拿到结果处理
从控制台打印我们可以看到存在type属性
extraReducers会根据primise返回的状态来执行对应的操作
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
// 使用createAsyncThunk来创建异步方法 第一个参数是命名空间 第二个参数是异步任务
export const asyncAdd = createAsyncThunk('count/asyncAdd', async () => {
// 模拟一个异步任务
let res = await new Promise(resolve => {
setTimeout(() => {
resolve({ code: 200 })
}, 1000)
})
return res
})
const countSlice = createSlice({
// 命名空间 dispatch(count/...)
name: 'count',
// 初始值
initialState: {
count: 0,
},
reducers: {
add(state, action) {
state.count = action.payload
},
},
extraReducers(builder) {
// 根据返回的primise状态来修改state fulfilled:成功 pending:等待 rejected:失败
builder.addCase(asyncAdd.fulfilled, (state, action) => {
state.count = action.payload.code
})
},
})
export default countSlice.reducer
数据持久化处理
安装redux-persist
npm i redux-persist
配置如下
// store.ts
import { configureStore } from '@reduxjs/toolkit'
// 引入reducer
import countReducer from './module/count'
// 持久化引入......................................
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
// 持久化引入......................................
// 持久化配置1
const persistConfig = {
// 存储名称
key: 'root',
version: 1,
// 存储方式
storage,
// 白名单 那个需要存储
whitelist: ['count'],
// 黑名单 写在这块的数据不会存在storage
// blacklist: ['fuseIm']
}
// 创建store
const store = configureStore({
reducer: {
// 持久化配置2 包裹 参数一 持久化配置 参数二 reducer
count: persistReducer(persistConfig, countReducer)
},
// 持久化配置3
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
})
// 配置4
persistStore(store)
export default store