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

这篇文章我们需要用到的知识点: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

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

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

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

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

  1. query接口:即从服务端拿数据,get
  2. mutation接口:即发数据让服务端更新数据 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),
})

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

重新获取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,结果如下:

注册即登录

我们需要实现注册即登录,那么需要在registerapi中返回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)

相关推荐
yangqii1 分钟前
工作中可以用到的前端小知识(不定时更新)
javascript
Dragon Wu6 分钟前
TailwindCss 总结
前端·css·前端框架
bpmf_fff16 分钟前
十、事件类型(鼠标事件、焦点.. 、键盘.. 、文本.. 、滚动..)、事件对象、事件流(事件捕获、事件冒泡、阻止冒泡和默认行为、事件委托)
前端·javascript
泰山小张只吃荷园29 分钟前
期末Python复习-输入输出
java·前端·spring boot·python·spring cloud·docker·容器
悦涵仙子1 小时前
vueuse中的useTemplateRefsList
前端·javascript·vue.js
萧萧玉树1 小时前
分布式在线评测系统
前端·c++·后端·负载均衡
haima952 小时前
ubuntu安装chrome无法打开问题
前端·chrome
放逐者-保持本心,方可放逐2 小时前
XSS 与 CSRF 记录
前端·xss·csrf·浏览器安全
徊忆羽菲2 小时前
利用HTML5和CSS来实现一个漂亮的表格样式
前端·css·html5
不爱说话郭德纲2 小时前
Stylus、Less 和 Sass 的使用与区别
前端·css·面试·less·sass·stylus