Less is more
- 我们反对繁琐且不必要的各种模板
- redux的核心功能必须使用大量的模板
- 为了降低redux使用的复杂性,减少模板代码的使用,redux-toolkit( RTX )应运而生
redux-toolkit的基本使用
安装
yarn add @reduxjs/toolkit
创建slice
import {createSlice, PayloadAction} from '@reduxjs/toolkit'
interface ProductDetailState {
loading: boolean,
error: string | null,
data: any
}
const initialState: ProductDetailState = {
loading: true,
error: null,
data: null
}
export const productDetailSlice = createSlice({
name: 'productDetail',
initialState,
reducers: {
fetchStart: (state) => {
// return {...state, loading: true}
state.loading = true
},
fetchSuccess: (state, action) => {
state.loading = false
state.data = action.payload
state.error = null
},
fetchFail: (state, action: PayloadAction<string|null>) => {
state.error = action.payload
state.loading = false
}
}
})
- reducers 将 action 和 reducer 结合在一起了
- 因为RTK使用了 immer 这个依赖,在 reducer 中可以直接修改 state
改用RTK的combineReducer
```tsx import { createStore,applyMiddleware } from ‘redux’ import languageReducer from ‘./language/languageReducer’ import recommendProductsReducer from ‘./recommendProducts/recommendProductsReducer’ import thunk from ‘redux-thunk’ import { actionLog } from ‘./middlewares/actionLog’
import { productDetailSlice } from ‘./productDetail/slice’ import { combineReducers } from ‘@reduxjs/toolkit’
const rootReducer = combineReducers({ language: languageReducer, recommendProducts: recommendProductsReducer, productDetail: productDetailSlice.reducer })
const store = createStore(rootReducer, applyMiddleware(thunk, actionLog))
export type RootState = ReturnType
<a name="zWFut"></a>
#### 在组件中使用state和dispatch
```tsx
// ...
import { useSelector, useDispatch } from "../../redux/hooks";
import { productDetailSlice } from "../../redux/productDetail/slice";
export const DetailPage: React.FC = () => {
const { touristRouteId } = useParams();
// const [loading, setLoading] = useState<boolean>(true);
// const [product, setProduct] = useState<any>(null);
// const [error, setError] = useState<string | null>(null);
const loading = useSelector(state => state.productDetail.loading)
const error = useSelector(state => state.productDetail.error)
const product = useSelector(state => state.productDetail.data)
const dispatch = useDispatch()
useEffect(() => {
const fetchData = async () => {
dispatch(productDetailSlice.actions.fetchStart())
try {
const { data } = await axios.get(
`/api/touristRoutes/${touristRouteId}`
);
dispatch(productDetailSlice.actions.fetchSuccess(data))
} catch (error:any) {
dispatch(productDetailSlice.actions.fetchFail(error.message))
}
};
fetchData();
}, []);
// ...
}
RTK中使用thunk处理异步请求
在RTK中使用thunk处理异步请求,必须使用 configureStore 和 createAsyncThunk
使用 configureStore 代替 createStore
import { createStore,applyMiddleware } from 'redux'
import languageReducer from './language/languageReducer'
import recommendProductsReducer from './recommendProducts/recommendProductsReducer'
import thunk from 'redux-thunk'
import { actionLog } from './middlewares/actionLog'
import { productDetailSlice } from './productDetail/slice'
import { combineReducers, configureStore } from '@reduxjs/toolkit'
const rootReducer = combineReducers({
language: languageReducer,
recommendProducts: recommendProductsReducer,
productDetail: productDetailSlice.reducer
})
// const store = createStore(rootReducer, applyMiddleware(thunk, actionLog))
// 接受一个对象作为参数
const store = configureStore({
reducer: rootReducer,
// getDefaultMiddleware获取默认中间件
middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), actionLog],
// 浏览器使用devTools插件可以查看redux状态
devTools: true,
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export default store
使用 createAsyncThunk 定义异步action creator
import axios from 'axios';
import {createAsyncThunk, createSlice, PayloadAction} from '@reduxjs/toolkit'
interface ProductDetailState {
loading: boolean,
error: string | null,
data: any
}
const initialState: ProductDetailState = {
loading: true,
error: null,
data: null
}
export const getProductDetail = createAsyncThunk(
'productDetailSlice/getProductDetail', // type
async (touristRouteId: string, thunkAPI) => {
thunkAPI.dispatch(productDetailSlice.actions.fetchStart())
try {
const { data } = await axios.get(
`/api/touristRoutes/${touristRouteId}`
);
thunkAPI.dispatch(productDetailSlice.actions.fetchSuccess(data))
} catch (error:any) {
thunkAPI.dispatch(productDetailSlice.actions.fetchFail(error.message))
}
}
)
export const productDetailSlice = createSlice({
name: 'productDetail',
initialState,
reducers: {
fetchStart: (state) => {
// return {...state, loading: true}
state.loading = true
},
fetchSuccess: (state, action) => {
state.loading = false
state.data = action.payload
state.error = null
},
fetchFail: (state, action: PayloadAction<string|null>) => {
state.error = action.payload
state.loading = false
}
}
})
在组件中 dispatch
useEffect(() => {
const fetchData = async () => {
dispatch(getProductDetail(touristRouteId))
};
fetchData();
}, []);
使用 promise 生命周期
createAsyncThunk会返回 3 种状态,将这三种状态对应的操作写在 slice 的 extraReducers 中,在对应的 promise生命周期就会执行对应的操作, createAsyncThunk中只需要有返回值(promise), 不需要有dispatch操作
import axios from 'axios';
import {createAsyncThunk, createSlice, PayloadAction} from '@reduxjs/toolkit'
interface ProductDetailState {
loading: boolean,
error: string | null,
data: any
}
const initialState: ProductDetailState = {
loading: true,
error: null,
data: null
}
export const getProductDetail = createAsyncThunk(
'productDetailSlice/getProductDetail', // type
async (touristRouteId: string, thunkAPI) => {
const { data } = await axios.get(
`/api/touristRoutes/${touristRouteId}`
);
return data
}
)
export const productDetailSlice = createSlice({
name: 'productDetail',
initialState,
reducers: {
// fetchStart: (state) => {
// // return {...state, loading: true}
// state.loading = true
// },
// fetchSuccess: (state, action) => {
// state.loading = false
// state.data = action.payload
// state.error = null
// },
// fetchFail: (state, action: PayloadAction<string|null>) => {
// state.error = action.payload
// state.loading = false
// }
},
extraReducers: {
[getProductDetail.pending.type]: (state) => {
// return {...state, loading: true}
state.loading = true
},
[getProductDetail.fulfilled.type]: (state, action) => {
state.loading = false
state.data = action.payload
state.error = null
},
[getProductDetail.rejected.type]: (state, action: PayloadAction<string|null>) => {
state.error = action.payload
state.loading = false
}
}
})