watchEffect 可自动触发 API 请求,依赖响应式变量变化;需在回调中直接使用 ref 或 reactive 数据,支持 AbortController 防竞态,并可封装为 useApi 组合式函数统一管理请求、loading 与错误。

watchEffect 可以在响应式数据变化时自动触发 API 请求,无需手动监听或写一堆 watch 逻辑。关键在于把依赖的数据直接写进回调函数里,Vue 会自动追踪并响应变化。
让 API 请求随响应式变量自动执行
只要在 watchEffect 的回调中使用了某个响应式变量(比如 ref 或 reactive 的属性),它就会成为依赖项。变量一变,请求立刻重发。
例如:
const keyword = ref('')
const list = ref([])
watchEffect(async () => {
if (!keyword.value.trim()) return
const res = await fetch(`/api/search?q=${keyword.value}`)
list.value = await res.json()
})
</font>
<p>这里 keyword.value 是响应式依赖,输入框一改,请求就自动发出去——不用 watch、不用 onMounted 里手动调一次,也不用额外写防抖(可后续加)。</p>
<H3>控制执行时机:跳过首次或添加条件</H3>
<p>默认会在定义时立即执行一次。如果只想在变化后才请求(比如避免空搜索),加个判断即可;若需完全跳过初始执行,可用 <strong>lazy: true</strong> 选项(但注意这会让它变成纯“监听”,不主动触发)。</p>
<ul>
<li>推荐做法:在回调里加业务判断,如 <code>if (!keyword.value) return</code></li>
<li>慎用 lazy:开启后需手动调一次,否则第一次永远不发请求</li>
<li>想首次也请求?什么都不加,默认行为就满足</li>
</ul>
<H3>避免重复请求和竞态问题</H3>
<p>用户快速输入时,可能发出多个请求,旧请求返回后覆盖新结果。简单方案是用 <strong>AbortController</strong> 主动取消前序请求:</p>
<font color="#666">
<pre class="brush:php;toolbar:false;">
let controller
watchEffect(async () => {
controller?.abort()
controller = new AbortController()
try {
const res = await fetch(`/api/search?q=${keyword.value}`, {
signal: controller.signal
})
list.value = await res.json()
} catch (e) {
if (e.name !== 'AbortError') console.error(e)
}
})
</font>
<H3>封装成可复用的请求钩子(useApi)</H3>
<p>把上面逻辑抽成组合式函数,以后任何页面都能一键接入:</p>
<font color="#666">
<pre class="brush:php;toolbar:false;">
function useApi(urlFn, options = {}) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
watchEffect(async (onInvalidate) => {
const url = urlFn()
if (!url) return
loading.value = true
error.value = null
const controller = new AbortController()
onInvalidate(() => controller.abort())
try {
const res = await fetch(url, { signal: controller.signal })
data.value = await res.json()
} catch (e) {
if (e.name !== 'AbortError') error.value = e
} finally {
loading.value = false
}
})
return { data, loading, error }
}
// 使用
const { data, loading } = useApi(() => `/api/user/${id.value}`)
这样就把请求逻辑、loading、错误、取消全收拢了,组件里只管用,不操心细节。

















