Redux传统流程:
- 安装redux,使用createStore(rootReducer、initState、middleWares)创建根store
- 使用combineReducers({}) 合并多个子reducer
- 使用compose(applyMiddleWare(thunk), applyMiddleWare(logger))使用中间件,尤其是解决redux不支持异步action的问题。
export default createStore(
combineReducers({
test,
counter,
cnode
}),
compose(applyMiddleWare(thunk), applyMiddleWare(logger))
)
- 如何创建子reducer呢?function reducer(initState,aciton) {本质上是一堆switch语句},在编写子reducer时,核心逻辑是深复制(immer),根据不同的case分支来修改子store(action={type,payload}是信号,像一封邮件)
reducers -> counter.js
import produce from 'immer'
const initState = {
num: 0
}
// reducer是纯函数, 不能修改入参
// 本质是由switch语句构成的函数 -> 深拷贝state,修改拷贝后的值,抛出(使用immer中间件做深复制)
export default function(state=initState, {type,payload}) {
//state -> 要深复制的值
// newState -> 深复制后的结果
return produce(state, newState=>{
switch (type) {
case COUNTER_NUM_ADD:
// 修改深复制之后的结果
newState.num += payload
break
case COUNTER_NUM_SUB:
newState.num -= payload
break;
default:
}
// 将修改后的结果抛出
return newState
})
}
reducers -> counter.js
import produce from 'immer'
import {
GET_CNODE_LIST
} from '../types'
const initState = {
list: []
}
export default function(state=initState, {type,payload}){
return produce(state, newState=>{
switch (type) {
case GET_CNODE_LIST:
// 调接口后获得的数据
console.log('cnode list', payload);
newState.list = payload
break
default:
}
return newState
})
}
- 在App.jsx中,安装react-redux这个库,使用 ,React组件树中有了store上下文。
- 在React组件中,如果是16.8以前,只能使用 connect(mapState, mapDispatch)(Component),然后在props上就能访问这些store数据、以及那些修改store的action逻辑。
//类组件写法,使用修饰符@
import React from "react";
import { connect } from ".react-redux";
import { updateMsg } from '@/store/actions'
function mapStateToProps({test}) {
return {
msg: test.msg
}
}
function mapDispatchToProps(dispatch) {
return {
updateMsg: payload => dispatch(updateMsg(payload))
}
}
// mapStateToProps 映射state放到props上
// mapDispatchToProps 映射dispath放到props上
@connect(mapStateToProps, mapDispatchToProps)
class PanelA extends React.Component {
updateMsg() {
this.props.updateMsg(Math.random())
}
render() {
console.log('PanelA 类组件的props', this.props);
return (
<div>
<h1>在类组件中使用Redux数据</h1>
<h1>{this.props.msg}</h1>
<button onClick={()=>this.updateMsg()}>change MSG</button>
</div>
)
}
}
export default PanelA
//函数式组件写法
import React from 'react'
import { connect } from 'react-redux'
import { updateMsg } from '@/store/actions'
export default connect(
({test})=>({
msg: test.msg
}),
dispatch=>({
// updateMsg(payload)返回一个action
updateMsg: payload=>dispatch(updateMsg(payload))
})
)(props => {
return (
<div>
<h1>在函数式组件中使用redux数据</h1>
<h1>{props.msg}</h1>
<button onClick={()=>props.updateMsg('hello 朱晓玥')}>修改msg</button>
</div>
)
}
)
- 在React组件中,如果是16.8以后,除了connect可以用,建议使用更好的 useDispatch、useSelector。
// 标准做法 hooks写法
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { updateMsg } from '@/store/actions'
export default () => {
const msg = useSelector(({test})=>test.msg)
const dispatch = useDispatch()
return (
<div>
<h1>在函数式组件中使用redux数据</h1>
<h1>{msg}</h1>
<button onClick={()=>dispatch(updateMsg(Math.random()))}>修改msg</button>
</div>
)
}
- 在传统的redux架构中,为了让action更好地维护和复用,我们一般建议封装action生成器方法。
import {
COUNTER_NUM_ADD,
COUNTER_NUM_SUB,
} from '../types'
import { fetchCnode } from '@/api'
// 返回整体action type+payload
// action生成器(creator)
export function addNum(payload) {
return {
type: COUNTER_NUM_ADD,
payload
}
}
export function subNum(payload) {
return {
type: COUNTER_NUM_SUB,
payload
}
}
// 使用thunk插件
// 调接口结束后再触发dispatch方法,将调接口获取到的值,传到reducer中,更新状态管理中的数据
export function getList(payload) {
return dispatch => {
// 调接口
问题:
在这里有一个坑,这里只是获得了调接口后的数据,但是还没有改变状态管理中的数据
初始化时,在视图中dispatch(fetchCnode(values)),接着在后面使用状态管理中调接口获取到的数据
会是undefined,这时候很可能状态管理还没有运行到改变数据时,视图就已经更新
解决方法:
在状态管理中设置一个标记success,初始值为false,状态管理中数据更新后,同时将success改为true
在视图中判断数据是否在状态管理中更新,使用success判断即可
fetchCnode(payload).then(list=>{
console.log('actions list data', list);
dispatch({
type: GET_CNODE_LIST,
payload: list
})
})
}
}
- 在传统的redux架构中,为了避免协同开发时大家滥用type或者type冲突,我们一般建议封装一个type字典。
// actions type 的字典,视图中和reducer中使用
export const COUNTER_NUM_ADD = 'COUNTER_NUM_ADD'
export const COUNTER_NUM_SUB = 'COUNTER_NUM_SUB'
export const GET_CNODE_LIST = 'GET_CNODE_LIST '
redux tookit
- 安装@reduxjs/toolkit这个库,使用configureStore({reducer,middleware})创建store。
import thunk from 'redux-thunk'
import login from './reducer/login'
import { configureStore } from '@reduxjs/toolkit'
import logger from 'redux-logger'
export const store = configureStore({
reducer: {
login
},
// 不要直接写数组,否则会把toolkit中封装的默认middleware覆盖
// 可以不加thunk,redux-toolkit默认安装了thunk
middleware: getDefaultMiddleware => [...getDefaultMiddleware(), thunk, logger ]
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export default store
- 使用createSlice({name,initialState,reducers,extraReducers})创建子reducer,最后抛出子reducer给根store合并。
export const loginSlice = createSlice({
name: 'login',
initialState: {
token : localStorage.getItem('token'),
user: { },
success: false,
color: ''
},
// 同步reducer(不需要调接口的reducer)
reducers: {
addColor(state, action) {
console.log(action.payload,'addcolor-----')
state.cardColor = action.payload
}
},
// toolkit封装,将同步reducer变为异步reducer
extraReducers: (builder) => {
builder.addCase (login.fulfilled, (state, action)=>{
// 直接修改state(tookit中封装了immer,在内部已经做过深复制了)
console.log('----login', action);
state.token = action.payload
})
// fulfilled 表示调接口成功的状态,总共有三种状态
.addCase(getUserInfo.fulfilled, (state, action)=>{
state.user = action.payload
})
// .addCase(...)
}
})
// 在子reducer中,一定要抛出一个reducer(实际上是一个由switch构成的函数)
export default loginSlice.reducer
// 抛出异步reducer
export { getUserInfo }
// 抛出同步reducer
export const { addColor } = cardSlice.actions
- 使用createAsyncThunk('user/login', async (入参)=>(await fetchLogin())),给到createSlice.extraReducers中addCase添加异步成功状态,在成功状态中直接修改子store。这些由createAsyncThunk创建action方法,也要抛出,给React组件进行触发使用 dispatch(login(入参))。
// 在视图中通过dispatch(login(params))
// 成功时,执行对应的case,并修改state,更新视图
const login = createAsyncThunk(
// 信号
'login/tologin', //type
// 函数(action)
async (data) => {
const res = await loginApi(data)
const token = res.token
localStorage.setItem('token', token)
return token //payload
}
)
const getUserInfo = createAsyncThunk(
'login/getUserInfo', //type
async (params) => {
const res = await getUserInfoApi(params)
return res //payload
}
)
- 在App中,安装react-redux,使用注入上下文。
import { Provider } from 'react-redux'
function App() {
return (
<Provider store={store}>
<DashBoard />
</Provider>
)
}
export default App;
- 在React组件中,只能使用 @reduxjs/toolkit官方推荐的 useAppSelector来使用store数据、只能使用useAppDispatch来触那些子store中抛出的action。
import { useAppSelector, useAppDispatch } from '@/hooks'
export default () => {
const dispatch = useAppDispatch()
const token = useAppSelector(({login})=>login.token)
useEffect(()=>{
if(token) {
dispatch(getUserInfo(token))
}
},[token])
}
ps:需要自己封装hooks
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from '@/store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
网友评论