redux-toolkit结合typeScript入门

822 阅读4分钟

马上到字节实习了~之前技术栈是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>
  )
}

动画.gif

创建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

自定义useDispatchuseSelector

需要自定义的原因:

  • 对于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