马上到字节实习了~之前技术栈是vue就学习了一波react
对于redux,从一开始的createStore,connect函数以及各种配置到用RTK开发真的简化了太多,配置起来相当方便
不过个人认为对于初学者RTK封装太多不利于从头理解redux的各种概念
这篇文章主要是参照官网写的一个小示例,入门一下RTK
redux-toolkit
redux-toolkit围绕着Redux核心,包含了对构建Redux应用程序至关重要的包和函数。Redux Toolkit是redux的最佳实践,简化了大多数Redux任务,防止了常见的错误,并使编写Redux应用程序更加容易。
redux结合typescript
npx create-react-app my-app --template redux-typescript
组件中使用展示
import React, { useEffect } from 'react'
// import { useDispatch, useSelector } from 'react-redux'
import { useAppSelector, useAppDispatch } from './hooks'
import {
increment,
addN,
asyncIncrement,
asyncIncrementN,
} from './store/features/counterSlice'
import { loadData } from './store/features/movieSlice'
export default function App() {
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(loadData())
}, [dispatch])
const count = useAppSelector((state) => {
return state.counter.count
})
return (
<div>
<h2>App</h2>
<h2>{count}</h2>
{/* 普通使用action */}
<button onClick={(e) => dispatch(increment())}>+1</button>
{/* 传参,都会放到payload中 */}
<button onClick={(e) => dispatch(addN(5))}>+5</button>
<button onClick={(e) => dispatch(asyncIncrement())}>异步+1</button>
<button onClick={(e) => dispatch(asyncIncrementN(5))}>异步+5</button>
</div>
)
}
创建store
使用configureStore不需要任何额外的类型。
但为了方便RootState类型和Dispatch类型在需要时被引用。可以使用store推断出这些类型
import { AnyAction, configureStore } from '@reduxjs/toolkit'
import { ThunkAction } from 'redux-thunk'
import counterReducer from './features/counterSlice'
import movieReducer from './features/movieSlice'
const store = configureStore({
reducer: {
counter: counterReducer,
movie: movieReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
AnyAction
>
export default store
自定义useDispatch和useSelector
需要自定义的原因:
- 对于useSelector,不必每次都输入(state: RootState)。
- 对于useDispatch,默认的Dispatch类型并不了解thunks或其他中间件。为了正确地调度thunks,需要使用来自store的特定的AppDispatch类型(其中包括thunk中间件类型),与useDispatch一起使用。
//hooks.ts
import { useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
创建切片
在redux-toolkit中,一个切片表示一个子store,相当于原来的reducer+actionCreators+constants
- 每个切片文件都应该为其初始状态值定义一个类型,这样createSlice就可以正确推断出每个案例中reducer的state类型。
- 所有生成的action都应该使用 Redux Toolkit 中的
PayloadAction<T>类型来定义,T类型会检测action.payload的类型
import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { RootState } from '../index'
import { ThunkAction } from 'redux-thunk'
import { AppThunk } from '../index'
interface counterState {
count: number
}
const initialState: counterState = {
count: 0,
}
export const counterSlice = createSlice({
name: 'counter', //命名
initialState,
reducers: {
increment(state) {
state.count += 1
},
addN(state, action: PayloadAction<number>) {
state.count += action.payload
},
},
})
//异步action在下面讲述
export const { increment, addN } = counterSlice.actions
export default counterSlice.reducer
结合redux-thunk
thunk类型的action
export type ThunkAction<
R, // Return type of the thunk function 通常void
S, // RootState
E, // any "extra argument" injected into the thunk 通常是unknown
A extends Action// AnyAction
> = (dispatch: ThunkDispatch<S, E, A>, getState: () => S, extraArgument: E) => R
//ThunkAction是一个如下函数
//(dispatch,getState)=>{}
在编写异步action时定义类型
// 异步action传参
export const asyncIncrementN = (
num: number
): ThunkAction<void, RootState, unknown, AnyAction> => {
return (dispatch) => {
setTimeout(() => {
dispatch(addN(num))
}, 1000)
}
}
store中定义ThunkAction类型
// 默认异步action返回值为void,当有返回值时
// AppThunk<Promise<SomeReturnType>>
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
AnyAction
>
完整conterSlice
import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { RootState } from '../index'
import { ThunkAction } from 'redux-thunk'
import { AppThunk } from '../index'
interface counterState {
count: number
}
const initialState: counterState = {
count: 0,
}
export const counterSlice = createSlice({
name: 'counter', //命名
initialState,
reducers: {
increment(state) {
state.count += 1
},
addN(state, action: PayloadAction<number>) {
state.count += action.payload
},
},
})
// 异步action
export const asyncIncrement = (): AppThunk => {
return (dispatch) => {
setTimeout(() => {
dispatch(increment())
}, 2000)
}
}
// 异步action传参
export const asyncIncrementN = (
num: number
): ThunkAction<void, RootState, unknown, AnyAction> => {
return (dispatch) => {
setTimeout(() => {
dispatch(addN(num))
}, 1000)
}
}
export const { increment, addN } = counterSlice.actions
export default counterSlice.reducer
extraReducers
extraReducer字段可以监听触发指定action时执行reducer
在createSlice中添加extraReducers字段,要使用“builder callback”的 格式,因为“普通对象”无法正确推断action类型。builder将RTK生成的action type传递给生成器。addCase()将正确推断action/state 类型
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { increment } from './counterSlice'
type movieState = {
list: any[]
total: number
}
const initialState: movieState = {
list: [],
total: 0,
}
export const movieSlice = createSlice({
name: 'movie',
initialState,
reducers: {
changeData(state, { payload }) {
state.list = payload
state.total = payload.length
},
},
extraReducers: (builder) => {
builder
.addCase(increment, (state) => {
//当increment被触发时,以下函数执行
state.list.push(1)
state.total += 1
})
},
/* js中写法
extraReducers: {
// 当触发increment事件时,联动触发
// [increment](state, action) {
// state.list.push(1)
// state.total += 1
// }
}, */
})
export default movieSlice.reducer
createAsyncThunk
createAsyncThunk创建的action是可以直接dispatch的,用来处理异步操作获取数据,在extraReducer中可以对它的状态进行监听,当状态处于fulfilled时,会在action.payload中存放返回数据
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
// 发起网络请求获取数据
const loadMoviesAPI = () =>
fetch(
'https://pcw-api.iqiyi.com/search/recommend/list?channel_id=1&data_type=1&mode=11&page_id=2&ret_num=48'
).then((res) => res.json())
// 这个action是可以直接调用的,用来处理异步操作获取数据
export const loadData = createAsyncThunk('movie/loadData', async () => {
const res = await loadMoviesAPI()
return res // 此处的返回结果会在 .fulfilled中作为payload的值
})
type movieState = {
list: any[]
total: number
}
const initialState: movieState = {
list: [],
total: 0,
}
export const movieSlice = createSlice({
name: 'movie',
initialState,
reducers: {
changeData(state, { payload }) {
state.list = payload
state.total = payload.length
},
},
extraReducers: (builder) => {
builder
.addCase(loadData.fulfilled, (state, { payload }) => {
console.log(payload)
state.list = payload.data.list
})
.addCase(loadData.pending, () => {
console.log('loading')
})
.addCase(loadData.rejected, (err) => {
console.log(err)
})
},
/* js中
extraReducers: {
// 当触发increment事件时,联动触发
// [loadData.fulfilled](state, { payload }) {
// console.log(payload)
// state.list = payload.data.list
// },
// [loadData.rejected](state, err) {
// console.log(err)
// },
// [loadData.pending](state) {
// console.log('进行中')
// },
}, */
})
export default movieSlice.reducer