如何使用RTK in nextjs 13 a year ago
RTK简介
传统要想跑通redux需要借助很多第三方包,而且还要写很多重复性的代码。为了降低学习门槛,react推出了自己的工具包 - redux toolkit(简称RTK
)。
RTK
集成了很多第三方库, 让初学者可以开箱即用。
来看看RTK
的核心api:
- 1,
configureStore()
: 底层包裹的是createStore
,把一个个小的reducer自动合并成一个大的reducer,可以添加任何中间件,原生支持redux-thunk
和Redux DevTools Extension - 2,
createReducer()
: 以前操作reducer,需要频繁的去copy state, eg{...state.xxx}
; 现在不用了,RTK
集成了immer, 对任意对象的修改会自动返回一个新的state, 如现在修改completed
属性可以直接这么写:state.todos[3].completed = true
,对state的操作不用写return语句 - 3,
createAction(type, prepareAction?)
: 用action type字符串自动生成一个action, eg:const increment = createAction<number>('counter/increment')
- 4,
createSlice()
: 接收一个包含reducer functions,a string slice name, an initial state value的object,自动生成一个reducer和对应的action creators 及 action types. - 5,
createAsyncThunk
: 接收一个string的action type 和 一个返回Promise对象的function,基于这个Promise,createAsyncThunk
会自动生成并派发pending/fulfilled/rejected
这3种action type的thunk
如何使用RTK
安装
#react-redux提供Provider
npm i @reduxjs/toolkit react-redux -S
创建store
这里我选择在lib
目录下创建store
, 在store
的features
目录下创建reducer,以countSlice为例:
store/index.tsx
import { configureStore } from '@reduxjs/toolkit'
import countSlice from './features/countSlice'
export const makeStore = () => {
return configureStore({
reducer: {
count: countSlice
},
})
}
// Infer the type of makeStore
export type AppStore = ReturnType<typeof makeStore>
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
创建reducer
reducer统一放在store/features
目录下:
features/counterSlice.tsx
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import userAPI from '@/lib/api/user'
// First, create the thunk
export const fetchSummary = createAsyncThunk(
'users/getSummary',
async () => {
const response = await userAPI.getSummary()
return response.data
}
)
export const incrementAsync = createAsyncThunk(
"count/incrementAsync",
async (amount:number)=>{
await new Promise((resolve)=> setTimeout(resolve,2000))
return amount
}
)
export interface CounterState {
value: number,
fetchStatus:string;
summary:{
blog_all_count:number,
blog_count_public:number;
tag_count_all:number;
visited:number;
}
}
const initialState: CounterState = {
value: 0,
fetchStatus:'',
summary: {
blog_all_count:0,
blog_count_public:0,
tag_count_all:0,
visited:0
}
}
export const counterSlice = createSlice({
name: 'counter', //will be used for prefix of actionCrators
initialState,
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
extraReducers(builder) {
builder.addCase(fetchSummary.pending,(state, _)=>{
state.fetchStatus = 'pending'
})
.addCase(fetchSummary.fulfilled,(state, action)=>{
// console.log(action.payload)
// http response: action.payload
state.fetchStatus = 'success'
state.summary = action.payload.data
}).addCase(fetchSummary.rejected,(state,_)=>{
state.fetchStatus = 'rejected'
})
.addCase(incrementAsync.pending,()=>{
console.log("ncremenAsync.pending")
})
.addCase(incrementAsync.fulfilled,(state,action:PayloadAction<number>)=>{
state.value += action.payload
})
},
})
// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
创建Provider
,并用Provider
包裹根组件
根组件在哪: 对nextjs来说根组件就是最外层的layout.tsx
1, 首先我们来创建一个Provider
'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore, AppStore } from '@/lib/store'
export default function StoreProvider({
children,
}: {
children: React.ReactNode
}) {
const storeRef = useRef<AppStore>()
if (!storeRef.current) {
// Create the store instance the first time this renders
storeRef.current = makeStore()
}
return <Provider store={storeRef.current}>{children}</Provider>
}
2, 用Provide
包裹根组件
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import StoreProvider from './StoreProvider'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<StoreProvider>
{children}
</StoreProvider>
</body>
</html>
)
}
使用Reducer
"use client"
import { decrement, fetchSummary, increment, incrementAsync } from '@/lib/store/features/countSlice'
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks'
import React, { memo } from 'react'
const Component1 = memo(() => {
const count = useAppSelector(state=>state.count.value)
const dispatch = useAppDispatch()
const summary = useAppSelector(state=>state.count.summary)
return (
<div className='flex flex-col border'>
<div className='flex gap-2'>
<button className='btn' onClick={()=>dispatch(increment())}>+1</button>
<button className='btn' onClick={()=>dispatch(decrement())}>-1</button>
<button className='btn' onClick={()=>dispatch(incrementAsync(10))}>异步+10</button>
</div>
<h3>{count}</h3>
<div>
<button className='btn' onClick={()=>dispatch(fetchSummary())}>发送http请求:</button>
<ul>
<li>blog_all_count:{summary.blog_all_count}</li>
<li>blog_count_public:{summary.blog_count_public}</li>
<li>tag_count_all:{summary.tag_count_all}</li>
<li>visited:{summary.visited}</li>
</ul>
</div>
</div>
)
})
export default Component1
"use client"
import { AppDispatch, RootState } from '@/lib/store'
import { decrement, fetchSummary, increment, incrementAsync } from '@/lib/store/features/countSlice'
import React, { memo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
const Component2 = memo(() => {
const count = useSelector((state:RootState)=>state.count.value)
const summary = useSelector((state:RootState)=>state.count.summary)
const dispatch = useDispatch<AppDispatch>()
return (
<div className='flex flex-col border'>
<div className='flex gap-2'>
<button className='btn' onClick={()=>dispatch(increment())}>+1</button>
<button className='btn' onClick={()=>dispatch(decrement())}>-1</button>
<button className='btn' onClick={()=>dispatch(incrementAsync(10))}>异步+10</button>
</div>
<h3>{count}</h3>
<div>
<button className='btn' onClick={()=>dispatch(fetchSummary())}>发送http请求:</button>
<ul>
<li>blog_all_count:{summary.blog_all_count}</li>
<li>blog_count_public:{summary.blog_count_public}</li>
<li>tag_count_all:{summary.tag_count_all}</li>
<li>visited:{summary.visited}</li>
</ul>
</div>
</div>
)
})
export default Component2
可以看到,除了react-redux
,RTK不用额外使用第三方库,就能使用rudux。如果你怀念老项目的话,也可以使用react-redux
来连接store, 写法还是和以前一样(connect(mapStateToProps,mapDispatchToPorps)(Componet)
), 需要注意的RTK操作是客户端行为
完整demo见 rtk_nextjs
建议使用rtk
command来创建store
- 下一篇: flutter常见基础组件