Nuxt3-useFetch陷阱

1,013 阅读2分钟

在使用Nuxt3 框架开发的时候,我们会经常要从接口请求数据, 必然会遇到 useFetch

但是会有一些使用上的注意事项

环境

"nuxt": "^3.11.2",
"vue": "^3.4.27",
node: 'v20.12.2'

测试环境,截止2024-06-07 Nuxtvue 均为最新版本

code

案例1

<template>
  <div>
    <h3>useFetch陷阱</h3>

    <hr />

    <div>
      <input type="text" v-model="name" />
      <button @click="onSubmit">提交</button>
    </div>
  </div>
</template>

<script setup>
const name = ref('')

const onSubmit = async () => {
  const res = await useFetch('/api/checkName', {
    method: 'post',
    body: {
      name
    }
  })
}
</script>

代码看上去没啥毛病, 很基础,很简单,

但是, 我们在 input 中持续输入, 查看 network 发现,一直在调用接口

image.png

啥情况?

因为 useFetch 是响应式的, 会监听他的 options的值 和 url, 值发生变化,就会触发他重新执行,也就导致,输入的时候, 一直在发请求

案例2


<script setup>
    let age = ref(12)

    let testUrl = computed(() => {
      return '/api/checkName?age='+ age.value
    })
    
    // url在下面变动
    let r2 = await useFetch(testUrl, {
      method: 'post'
    })

    onMounted(() => {
      setInterval(() => {
        age.value++
        console.log('r2 定时器里面的', r2);
      }, 2000);
    })
</script>

url的改变,导致请求也会自动触发

image.png


现在解决一下 案例1 里面的问题

既然 useFetch 有坑,我们在使用的时候,要分清楚场景

解决方式一:

$fetch 替代 useFetch

<template>
  <div>
    <h3>useFetch陷阱</h3>

    <hr />

    <div>
      <input type="text" v-model="name" />
      <button @click="onSubmit">提交</button>
    </div>
  </div>
</template>

<script setup>
const name = ref('')

const onSubmit = async () => {
  // 解决方式, 使用 $fetch
  const res = await $fetch('/api/checkName', {
    method: 'post',
    body: {
      name: name.value
    }
  })
}
</script>

$fetch 不会去监听 url 和 options的改变

解决方式2:

手动调用 execute 或者 refresh, 这2个是普通函数, 效果一样。

<template>
  <div>
    <h3>useFetch陷阱</h3>

    <hr />

    <div>
      <input type="text" v-model="name" />
      <button @click="onSubmit">提交</button>
    </div>
  </div>
</template>

<script setup>
const name = ref('')

const {error, execute, refresh} = useFetch('/api/checkName', {
  method: 'post',
  // 不立刻发出请求
  immediate: false,
  // 关闭监听
  watch: false,
  body: {
    name
  }
})

const onSubmit = async () => {
   // 手动调用
  await execute()
  // await refresh()

  // console.log('错误', error);
}
</script>
  • 复杂了点,不方便,单个SFC 组件代码复杂的时候,不利于维护

注意事项

useFetch 调用场景是有要求的

  • 顶层 setup函数 (上面 案例1 就不是在顶层调用 useFetch, 在函数里面调用, 不行。)
  • plugin 插件
  • route middleware 路由中间件

useFetch 这个组合函数,只能在上面的场景中调用。

$fetch 注意事项

<script setup>
    // 有问题的写法
    await $fetch('/api/getXXX')
</script>

会触发2次调用,一次在服务端,一次在客户端, 所以 $fetch 适合 手动触发,与用户交互的场景调用接口, 不在 顶层 setup上下文 的场景调用接口

const btn = document.querySelector('.btn')

btn.addEventListener('click', async() => {
    // 适合
    const res = await $fetch('/api/xxxxx')
})
<script setup>
     // 不行, 这里就在 setup 的上下文中。
    // $fetch('/api/xxxx')
    
    // 不在setup 顶层上下文-适合
    onMounted(async () => {
        // 适合
        await $fetch('/api/xxxx')
    })
</script>

也提供一个在 plugin中调用的案例

plugins/check.ts

export default defineNuxtPlugin(async (nuxtApp) => {
  const token = useCookie('token')

  const {data, error} = await useFetch('/api/ping', {
    query: {
      token: token.value
    }
  })

  if (error.value) {
    console.log('请求报错了');
    console.log(error.value);

  } else{
    console.log('请求得到的数据');
    console.log(data.value);
  }
})

express 另外启的一个服务,提供一个基础接口。

router.get('/api/ping', (req, res) => {
    console.log('解析一下参数');
    console.log(req.query.token);
    res.json({
        code: 0,
        data: {
            token: req.query.token,
            txt: req.query.token ? '有值' : '没值'
        },
        message: 'suceess'
    })
})