Redux Toolkit旨在成为编写Redux逻辑的标准方式。解决Redux三个常见问题:
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
npm install redux react-redux @reduxjs/toolkit -D
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { fetchCount } from './counterAPI'
export interface CounterState {
value: number
status: 'idle' | 'loading' | 'failed'
}
const initialState: CounterState = {
value: 0,
status: 'idle'
}
export const incrementAsync = createAsyncThunk('counter/fetchCount', async (amount: number) => {
const response = await fetchCount(amount)
return response.data
})
export const counterSlice = createSlice({
name: 'counter',
initialState,
// 这里内置了immer,可以直接操作state
// reducers是reduces 和 actions 组合在一起的对象
reducers: {
increment: state => {
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
},
//这里对异步操作incrementAsync异步状态的处理
extraReducers: builder => {
builder
.addCase(incrementAsync.pending, state => {
state.status = 'loading'
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = 'idle'
state.value += action.payload
})
.addCase(incrementAsync.rejected, state => {
state.status = 'failed'
})
}
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import logger from 'redux-logger'
import rootReducer from './rootReducer'
import { useDispatch } from 'react-redux'
import CounterSlice from '@pages/counter/counterSlice'
const middleware = getDefaultMiddleware().concat(logger)
export const store = configureStore({
reducer: {
counter: CounterSlice
},
middleware,
devTools: process.env.NODE_ENV !== 'production'
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export const useAppDispatch = () => useDispatch<AppDispatch>()
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { useAppDispatch, RootState } from '@store'
import { decrement, increment, incrementByAmount, incrementAsync } from './counterSlice'
import styles from './Counter.module.less'
const Counter = () => {
//useSelector简化redux的模板代码
const { value } = useSelector((state: RootState) => state.counter)
const dispatch = useAppDispatch()
const [incrementAmount, setIncrementAmount] = useState('2')
const incrementValue = Number(incrementAmount) || 0
return (
<div>
<div className={styles.row}>
<button className={styles.button} aria-label="Decrement value" onClick={() => dispatch(decrement())}>
-
</button>
<span className={styles.value}>{value}</span>
<button className={styles.button} aria-label="Increment value" onClick={() => dispatch(increment())}>
+
</button>
</div>
<div className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={e => setIncrementAmount(e.target.value)}
/>
<button className={styles.button} onClick={() => dispatch(incrementByAmount(incrementValue))}>
Add Amount
</button>
<button className={styles.asyncButton} onClick={() => dispatch(incrementAsync(incrementValue))}>
Add Async
</button>
</div>
</div>
)
}
export default Counter
import React from 'react'
import { createRoot } from 'react-dom/client'
import { HashRouter, Routes, Route } from 'react-router-dom'
import { Provider } from 'react-redux'
import { store } from '@store'
import App from './app'
import './assets/style/index.less'
const container = document.getElementById('root')
const root = createRoot(container)
root.render(
<Provider store={store}>
<HashRouter>
<Routes>
<Route key={'counter-demo'} path="/counter" element={<CounterDemo />} />
</Routes>
</HashRouter>
</Provider>
)