这篇文章我们需要用到的知识点:Redux 基础教程, 第七节: RTK Query 基础 | Redux 中文官网
首先我们安装依赖:
shell
pnpm i @reduxjs/toolkit react-redux
请求的fetchApiSlice
新建store/slices/fetchApiSlice.js
js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
const fetchApi = createApi({
reducerPath: 'fetchApi',
baseQuery: fetchBaseQuery({ baseUrl: process.env.BASE_URL }),
endpoints: builder => ({
getData: builder.query({
query: ({ url, token }) => ({
url,
method: 'GET',
headers: { 'Content-Type': 'application/json', Authorization: token },
}),
}),
postData: builder.mutation({
query: ({ url, data, token }) => ({
url,
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: token },
body: data,
}),
}),
patchData: builder.mutation({
query: ({ url, data, token }) => ({
url,
method: 'PATCH',
headers: { 'Content-Type': 'application/json', Authorization: token },
body: data,
}),
}),
putData: builder.mutation({
query: ({ url, data, token }) => ({
url,
method: 'PUT',
headers: { 'Content-Type': 'application/json', Authorization: token },
body: data,
}),
}),
deleteData: builder.mutation({
query: ({ url, token }) => ({
url,
method: 'DELETE',
headers: { 'Content-Type': 'application/json', Authorization: token },
}),
}),
}),
})
export const {
useGetDataQuery,
usePostDataMutation,
usePatchDataMutation,
usePutDataMutation,
useDeleteDataMutation,
} = fetchApi
export default fetchApi
我们可以看到从开头引入了createApi
和fetchBaseQuery
下面是这两个函数的解释:
reducerPath只是一个key,到时候我们在某些地方需要拿值或者调函数的时候会用到
BASE_URL的话是我们原先在next.config.mjs
里面定义的: 需要和当前运行的项目的地址一致,比如我端口是9999
那么BASE_URL
也需要改为对应的http://localhost:9999
然后我们就可以在endPoints
里面定义接口了,接口有两类:
query
接口:即从服务端拿数据,getmutation
接口:即发数据让服务端更新数据 post、put、patch、delete等
添加到全局store里面
新建store/index.js
js
import { configureStore } from '@reduxjs/toolkit'
import fetchApi from '@/store/slices/fetchApiSlice'
export const store = configureStore({
reducer: {
[fetchApi.reducerPath]: fetchApi.reducer,
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(fetchApi.middleware),
})
apiSlice
会生成需要添加到store的自定义middleware
,这个middleware
必须被添加,它可以管理缓存的生命周期和控制是否过期
将store注入到页面
由于我们需要为主流程页面
注入,也需要为not-found
注入,所以我们可以将自定义一个Provider
:
新建app/StoreProvider.js
js
'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { store } from '@/store'
export default function StoreProvider({ children }) {
const storeRef = useRef()
if (!storeRef.current) {
storeRef.current = store
}
return <Provider store={storeRef.current}>{children}</Provider>
}
使用ref可以确保store的单例性,无论StoreProvider
何时重新渲染,storeRef.current
都将保持为首次赋值的store
页面引入store
由于我们需要为页面注入store
,但是服务端组件是没有办法使用useRef
的,所以我们需要在为main
路线创建特定布局,即(main)
下面的比如app/(main)/user
,请求路径为localhost:3000/user
,但是他的布局是main下面特定的,而不是全局共享的app/layout.js
的
新建app/(main)/layout.js
js
import StoreProvider from '@/app/StoreProvider'
export default function Layout({ children }) {
return <StoreProvider>{children}</StoreProvider>
}
新建app/(main)/page.js
js
'use client'
import { useSelector } from 'react-redux'
export default function Page() {
const res = useSelector(store => store.fetchApi)
console.log('res', res)
return <div>hello</div>
}
我们可以看到控制台打印了这个slice
authSlice、cartSlice
这两个slice
主要实现,利用cookie
保存用户信息、购物车信息等
所以我们先安装依赖:
shell
pnpm i js-cookie
新建store/slices/authSlice.js
js
import { createSlice } from '@reduxjs/toolkit'
import Cookies from 'js-cookie'
const initialState = {
userInfo: Cookies.get('userInfo') ? JSON.parse(Cookies.get('userInfo')) : null,
}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
userLogin: (state, action) => {
state.userInfo = action.payload
Cookies.set('userInfo', JSON.stringify(action.payload))
},
userLogout: (state, action) => {
state.userInfo = null
Cookies.remove('userInfo')
},
},
})
export const { userLogin, userLogout } = authSlice.actions
export default authSlice.reducer
新建store/slice/cartSlice.js
js
import { createSlice } from '@reduxjs/toolkit'
import Cookies from 'js-cookie'
const initialState = {
cartItems: Cookies.get('cartItems') ? JSON.parse(Cookies.get('cartItems')) : null,
}
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addToCart: (state, action) => {
const itemExist = state.cartItems.find(item => item._id === action.payload._id)
if (itemExist) {
itemExist.quantity += 1
Cookies.set('cartItems', JSON.stringify(state.cart))
} else {
state.cartItems.push(action.payload)
Cookies.set('cartItems', JSON.stringify(state.cart))
}
},
removeFromCart: (state, action) => {
const index = state.cartItems.find(item => item._id === action.payload._id)
if (index !== -1) {
state.cartItems.splice(index, 1)
Cookies.set('cartItems'.JSON.stringify(state.cart))
}
},
increase: (state, action) => {
state.cartItems.forEach(item => {
if (item._id === action.payload._id) item.quantity += 1
})
},
decrease: (state, action) => {
state.cartItems.forEach(item => {
if (item._id === action.payload._id) item.quantity -= 1
})
},
clearCart: (state, action) => {
state.cartItems = []
Cookies.remove('cartItems')
},
},
})
export const { addToCart, removeFromCart, clearCart, decrease, increase } = cartSlice.actions
export default cartSlice.reducer
在store/index.js
中引入这两个slice
js
import { configureStore } from '@reduxjs/toolkit'
import fetchApi from '@/store/slices/fetchApiSlice'
import authSlice from '@/store/slices/authSlice'
import cartSlice from '@/store/slices/cartSlice'
export const store = configureStore({
reducer: {
auth: authSlice,
cart: cartSlice,
[fetchApi.reducerPath]: fetchApi.reducer,
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(fetchApi.middleware),
})
目前我们暂时用不上authSlice
和cartSlice
,相关的本篇就告一段落了
重新获取token
我们定义一个刷新token的api app/api/auth/accessToken/route.js
js
import { NextResponse } from 'next/server'
import jwt from 'jsonwebtoken'
import db from '@/lib/db'
import User from '@/models/User'
import sendError from '@/utils/sendError'
import { createAccessToken } from '@/utils/generateToken'
const accessToken = async req => {
try {
const { value: rf_token } = req.cookies.get('refreshtoken')
if (!rf_token) return sendError(400, '无刷新token')
const result = jwt.verify(rf_token, process.env.REFRESH_TOKEN_SECRET)
if (!result) return sendError(400, '刷新登录异常')
await db.connect()
const user = await User.findById(result.id)
if (!user) return sendError(res, 400, '此用户不存在')
await db.disconnect()
const access_token = createAccessToken({ id: user._id })
return NextResponse.json(
{
access_token,
user: {
name: user.name,
email: user.email,
role: user.role,
avatar: user.avatar,
root: user.root,
},
},
{ statue: 200 }
)
} catch (error) {
return sendError(500, error.message)
}
}
export const GET = accessToken
这样在不用重新登录的情况下,依靠refresh_token来换取新的access_token
我们在请求客户端里面设置cookie的refreshtoken,并获取新的access_token,结果如下:
注册即登录
我们需要实现注册即登录,那么需要在register
api中返回accessToken和refreshToken,修改app/api/auth/register/route.js
js
import { NextResponse } from 'next/server'
import bcrypt from 'bcrypt'
import db from '@/lib/db'
import User from '@/models/user'
import sendError from '@/utils/sendError'
import { createAccessToken, createRefreshToken } from '@/utils/generateToken'
export async function POST(req, { params }) {
try {
await await db.connect()
const { name, email, password } = await req.json()
const user = await User.findOne({ email })
if (user) return sendError(400, '该账户已存在')
const hashPassword = await bcrypt.hash(password, 12)
const newUser = new User({ name, email, password: hashPassword })
await newUser.save()
await db.disconnect()
const access_token = createAccessToken({ id: newUser._id })
const refresh_token = createRefreshToken({ id: newUser._id })
return NextResponse.json(
{
msg: '注册成功',
data: {
refresh_token,
access_token,
user: {
name: newUser.name,
email: newUser.email,
role: newUser.role,
avatar: newUser.avatar,
root: newUser.root,
},
},
},
{
status: 201,
}
)
} catch (error) {
return sendError(500, error.message)
}
}
相应的login
返回格式需要改动一下app/api/auth/login/route.js
js
import { NextResponse } from 'next/server'
import bcrypt from 'bcrypt'
import db from '@/lib/db'
import User from '@/models/user'
import sendError from '@/utils/sendError'
import { createAccessToken, createRefreshToken } from '@/utils/generateToken'
export async function POST(req) {
try {
await db.connect()
const { email, password } = await req.json()
const user = await User.findOne({ email })
if (!user) return sendError(400, '找不到此电子邮件的应用程序')
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) return sendError(400, '电子邮件地址或密码不正确')
const access_token = createAccessToken({ id: user._id })
const refresh_token = createRefreshToken({ id: user._id })
return NextResponse.json(
{
msg: '登录成功',
data: {
refresh_token,
access_token,
user: {
name: user.name,
email: user.email,
role: user.role,
avatar: user.avatar,
root: user.root,
},
},
},
{ status: 200 }
)
} catch (error) {
console.log('====error====', error.message)
return sendError(500, error.message)
}
}
大功告成
感谢阅读,有帮助跪求点赞!!!
代码地址:feat: 接入redux · liyunfu1998/next-shopping@deaec38 (github.com)