「4」next-shopping:接入RTK Query做请求

这篇文章我们需要用到的知识点:Redux 基础教程, 第七节: RTK Query 基础 | Redux 中文官网

首先我们安装依赖:

pnpm i @reduxjs/toolkit react-redux

请求的fetchApiSlice

新建store/slices/fetchApiSlice.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

我们可以看到从开头引入了createApifetchBaseQuery下面是这两个函数的解释:

「4」next-shopping:接入RTK Query做请求

reducerPath只是一个key,到时候我们在某些地方需要拿值或者调函数的时候会用到

BASE_URL的话是我们原先在next.config.mjs里面定义的: 需要和当前运行的项目的地址一致,比如我端口是9999那么BASE_URL也需要改为对应的http://localhost:9999

「4」next-shopping:接入RTK Query做请求

然后我们就可以在endPoints里面定义接口了,接口有两类:

  1. query接口:即从服务端拿数据,get
  2. mutation接口:即发数据让服务端更新数据 post、put、patch、delete等

添加到全局store里面

新建store/index.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

'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

import StoreProvider from '@/app/StoreProvider'

export default function Layout({ children }) {
  return <StoreProvider>{children}</StoreProvider>
}

新建app/(main)/page.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

「4」next-shopping:接入RTK Query做请求

authSlice、cartSlice

这两个slice主要实现,利用cookie保存用户信息、购物车信息等

所以我们先安装依赖:

pnpm i js-cookie

新建store/slices/authSlice.js

import { createSlice } from '@reduxjs/toolkit'
import Cookies from 'js-cookie'

const initialState = {
  userInfo: Cookies.get('userInfo') ? JSON.parse(Cookies.get('userInfo')) : {},
}

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    userLogin: (state, action) => {
      const { user, access_token: token } = action.payload

      state.token = token
      state.user = user

      Cookies.set('userInfo', JSON.stringify({ token, user }))
    },

    userLogout: (state, action) => {
      state = {}
      Cookies.remove('userInfo')
    },
  },
})

export const { userLogin, userLogout } = authSlice.actions

export default authSlice.reducer

新建store/slice/cartSlice.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

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),
})

目前我们暂时用不上authSlicecartSlice,相关的本篇就告一段落了

重新获取token

我们定义一个刷新token的api app/api/auth/accessToken/route.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,结果如下:

「4」next-shopping:接入RTK Query做请求

注册即登录

我们需要实现注册即登录,那么需要在registerapi中返回accessToken和refreshToken,修改app/api/auth/register/route.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

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@9956d31 (github.com)

原文链接:https://juejin.cn/post/7356049143955505206 作者:伯nulee

(0)
上一篇 2024年4月11日 上午10:59
下一篇 2024年4月11日 上午11:10

相关推荐

发表回复

登录后才能评论