предисловие
В процессе изучения React Hooks я увидел в интернете статью, запрашивающую данные через Hooks, и абстрагирующую эту логику в новый Hooks для повторного использования другими компонентами, я также перевел ее в своем блоге:Как запросить данные в React Hooks? 》, можете посмотреть, если интересно. Хотя это была прошлогодняя статья, после ее прочтения я сразу понял использование хуков, а запрос данных — очень распространенная логика в бизнес-коде.
Vue 3 был выпущен некоторое время назад, и его API композиции чем-то напоминает React Hooks.Сегодня я планирую изучать API композиции таким образом.
Инициализация проекта
Чтобы быстро запустить проект Vue 3, мы напрямую используем самый популярный инструмент Vite для инициализации проекта. Весь процесс прошел гладко и плавно.
npm init vite-app vue3-app
# 打开生成的项目文件夹
cd vue3-app
# 安装依赖
npm install
# 启动项目
npm run dev
мы открытыApp.vue
Сначала удалите сгенерированный код.
Вход в API композиции
Далее мы пройдемHacker News APIЧтобы получить некоторые популярные статьи, структура данных, возвращаемая Hacker News API, выглядит следующим образом:
{
"hits": [
{
"objectID": "24518295",
"title": "Vue.js 3",
"url": "https://github.com/vuejs/vue-next/releases/tag/v3.0.0",
},
{...},
{...},
]
}
мы проходимui > li
Отображение списка новостей на интерфейсе, данные новостей изhits
Получено во время обхода.
<template>
<ul>
<li
v-for="item of hits"
:key="item.objectID"
>
<a :href="item.url">{{item.title}}</a>
</li>
</ul>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
hits: []
})
return state
}
}
</script>
Прежде чем объяснять запросы данных, позвольте мне взглянутьsetup()
метод, API композиции должен пройтиsetup()
способ запуска,setup()
Возвращенные данные можно использовать в шаблоне, который можно просто понять, как в Vue 2.data()
Данные, возвращаемые методом, разница в том, что возвращаемые данные должны пройти черезreactive()
метод для переноса данных в отзывчивый.
Запросить данные в составном API
Во Vue 2, когда мы запрашиваем данные, нам обычно нужно поместить код, инициирующий запрос, в определенный жизненный цикл (created
илиmounted
). существуетsetup()
метод, мы можем использовать предоставленный Vue 3крючки жизненного циклаПоместите запрос в определенный жизненный цикл.Сравнение между методом ловушки жизненного цикла и предыдущим жизненным циклом выглядит следующим образом:
Как видите, в основном метод добавляется перед именем предыдущего метода.on
, и не обеспечиваетonCreated
крючок, потому что вsetup()
внутреннее исполнение эквивалентноcreated
сценическое исполнение. Ниже мыmounted
Этап запроса данных:
import { reactive, onMounted } from 'vue'
export default {
setup() {
const state = reactive({
hits: []
})
onMounted(async () => {
const data = await fetch(
'https://hn.algolia.com/api/v1/search?query=vue'
).then(rsp => rsp.json())
state.hits = data.hits
})
return state
}
}
Окончательный эффект выглядит следующим образом:
Мониторинг изменений данных
В интерфейсе запроса Hacker News есть параметр запроса, в предыдущем случае мы зафиксировали этот параметр, а теперь определяем эту переменную через Response data.
<template>
<input type="text" v-model="query" />
<ul>
<li
v-for="item of hits"
:key="item.objectID"
>
<a :href="item.url">{{item.title}}</a>
</li>
</ul>
</template>
<script>
import { reactive, onMounted } from 'vue'
export default {
setup() {
const state = reactive({
query: 'vue',
hits: []
})
onMounted((async () => {
const data = await fetch(
`https://hn.algolia.com/api/v1/search?query=${state.query}`
).then(rsp => rsp.json())
state.hits = data.hits
})
return state
}
}
</script>
Теперь мы модифицируем поле ввода, чтобы вызватьstate.query
Синхронное обновление, но не вызывает выборку, поэтому нам нужно пройтиwatchEffect()
отслеживать изменения в ответных данных.
import { reactive, onMounted, watchEffect } from 'vue'
export default {
setup() {
const state = reactive({
query: 'vue',
hits: []
})
const fetchData = async (query) => {
const data = await fetch(
`https://hn.algolia.com/api/v1/search?query=${query}`
).then(rsp => rsp.json())
state.hits = data.hits
}
onMounted(() => {
fetchData(state.query)
watchEffect(() => {
fetchData(state.query)
})
})
return state
}
}
так какwatchEffect()
Когда он вызывается в первый раз, его обратный вызов будет выполнен один раз, в результате чего интерфейс будет запрашиваться дважды во время инициализации, поэтому нам нужно поместитьonMounted
серединаfetchData
Удалить.
onMounted(() => {
- fetchData(state.query)
watchEffect(() => {
fetchData(state.query)
})
})
watchEffect()
Он будет отслеживать все ответные данные во входящей функции, и как только один из данных изменится, функция будет выполнена повторно. Если вы хотите отменить прослушивание, вы можете позвонитьwatchEffect()
Возвращаемое значение , возвращаемое значение которого является функцией. Вот пример:
const stop = watchEffect(() => {
if (state.query === 'vue3') {
// 当 query 为 vue3 时,停止监听
stop()
}
fetchData(state.query)
})
Когда мы вводим в поле ввода"vue3"
После этого запросы больше делаться не будут.
метод возврата события
Теперь есть проблема, что каждый раз, когда значение на входе изменяется, будет запускаться запрос.Мы можем добавить кнопку и запускать ее после нажатия кнопки.state.query
обновление.
<template>
<input type="text" v-model="input" />
<button @click="setQuery">搜索</button>
<ul>
<li
v-for="item of hits"
:key="item.objectID"
>
<a :href="item.url">{{item.title}}</a>
</li>
</ul>
</template>
<script>
import { reactive, onMounted, watchEffect } from 'vue'
export default {
setup() {
const state = reactive({
input: 'vue',
query: 'vue',
hits: []
})
const fetchData = async (query) => {
const data = await fetch(
`https://hn.algolia.com/api/v1/search?query=${query}`
).then(rsp => rsp.json())
state.hits = data.hits
}
onMounted(() => {
watchEffect(() => {
fetchData(state.query)
})
})
const setQuery = () => {
state.query = state.input
}
return { setQuery, state }
}
}
</script>
Вы можете заметить, что метод события щелчка, привязанного к кнопке, также проходит черезsetup()
метод возвращает, мы можем использоватьsetup()
Возвращаемое значение метода понимается как в Vue2data()
Методы иmethods
слияние объектов.
Исходное состояние возвращаемого значения стало атрибутом текущего возвращаемого значения, поэтому, когда мы извлекаем данные на уровне шаблона, нам нужно внести некоторые изменения, добавив передstate.
.
<template>
<input type="text" v-model="state.input" />
<button @click="setQuery">搜索</button>
<ul>
<li
v-for="item of state.hits"
:key="item.objectID"
>
<a :href="item.url">{{item.title}}</a>
</li>
</ul>
</template>
модификация возвращаемых данных
Как обсессивно-компульсивный пациент, пройти на шаблонном слоеstate.xxx
Получать данные на пути действительно неудобно, так что можем ли мы деконструировать объект?state
данные вернулись?
<template>
<input type="text" v-model="input" />
<button class="search-btn" @click="setQuery">搜索</button>
<ul class="results">
<li
v-for="item of hits"
:key="item.objectID"
>
<a :href="item.url">{{item.title}}</a>
</li>
</ul>
</template>
<script>
import { reactive, onMounted, watchEffect } from 'vue'
export default {
setup(props, ctx) {
const state = reactive({
input: 'vue',
query: 'vue',
hits: []
})
// 省略部分代码...
return {
...state,
setQuery,
}
}
}
</script>
Ответ - нет". После изменения кода вы можете увидеть, что хотя страница инициирует запрос, страница не отображает данные.
state
После деструктуризации данные становятся статическими и их больше нельзя отслеживать, а возвращаемое значение похоже на:
export default {
setup(props, ctx) {
// 省略部分代码...
return {
input: 'vue',
query: 'vue',
hits: [],
setQuery,
}
}
}
Для отслеживания данных примитивных типов (т.е. необъектных данных) Vue3 также предлагает решение:ref()
.
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Выше приведен официальный случай Vue 3,ref()
Метод возвращает объект.Будь то изменение или получение, необходимо получить возвращенный объект.value
Атрибуты.
мы будемstate
Измените объект ответа на простой объект, затем все свойства используютref
package, чтобы после модификации могла вступить в силу последующая деконструкция. Недостатком этого является то, чтоstate
Каждое свойство , будучи измененным, должно принимать своеvalue
Атрибуты. Но нет необходимости добавлять в шаблон.value
, который обрабатывается внутри Vue 3.
import { ref, onMounted, watchEffect } from 'vue'
export default {
setup() {
const state = {
input: ref('vue'),
query: ref('vue'),
hits: ref([])
}
const fetchData = async (query) => {
const data = await fetch(
`https://hn.algolia.com/api/v1/search?query=${query}`
).then(rsp => rsp.json())
state.hits.value = data.hits
}
onMounted(() => {
watchEffect(() => {
fetchData(state.query.value)
})
})
const setQuery = () => {
state.query.value = state.input.value
}
return {
...state,
setQuery,
}
}
}
Есть ли способ сохранитьstate
Для объекта ответа, поддерживая деструктуризацию его объекта? Конечно, есть, и Vue 3 также предоставляет решение:toRefs()
.toRefs()
метод может превратить объект ответа в обычный объект и добавить к каждому свойствуref()
.
import { toRefs, reactive, onMounted, watchEffect } from 'vue'
export default {
setup() {
const state = reactive({
input: 'vue',
query: 'vue',
hits: []
})
const fetchData = async (query) => {
const data = await fetch(
`https://hn.algolia.com/api/v1/search?query=${query}`
).then(rsp => rsp.json())
state.hits = data.hits
}
onMounted(() => {
watchEffect(() => {
fetchData(state.query)
})
})
const setQuery = () => {
state.query = state.input
}
return {
...toRefs(state),
setQuery,
}
}
}
Состояние загрузки и ошибки
Обычно, когда мы инициируем запрос, нам нужно добавить к запросу состояния загрузки и ошибки, нам нужно толькоstate
Добавьте две переменные для управления этими двумя состояниями.
export default {
setup() {
const state = reactive({
input: 'vue',
query: 'vue',
hits: [],
error: false,
loading: false,
})
const fetchData = async (query) => {
state.error = false
state.loading = true
try {
const data = await fetch(
`https://hn.algolia.com/api/v1/search?query=${query}`
).then(rsp => rsp.json())
state.hits = data.hits
} catch {
state.error = true
}
state.loading = false
}
onMounted(() => {
watchEffect(() => {
fetchData(state.query)
})
})
const setQuery = () => {
state.query = state.input
}
return {
...toRefs(state),
setQuery,
}
}
}
Используйте обе переменные в шаблоне одновременно:
<template>
<input type="text" v-model="input" />
<button @click="setQuery">搜索</button>
<div v-if="loading">Loading ...</div>
<div v-else-if="error">Something went wrong ...</div>
<ul v-else>
<li
v-for="item of hits"
:key="item.objectID"
>
<a :href="item.url">{{item.title}}</a>
</li>
</ul>
</template>
Отображение загрузки, статус ошибки:
Абстрактная логика запроса данных
Студенты, которые использовали umi, должны знать, что umi предоставляет хуки, называемые useRequest, которые очень удобны для запроса данных, поэтому мы также можем абстрагировать общедоступный метод, аналогичный useRequest, через комбинированный API Vue.
Далее создаем новый файлuseRequest.js
:
import {
toRefs,
reactive,
} from 'vue'
export default (options) => {
const { url } = options
const state = reactive({
data: {},
error: false,
loading: false,
})
const run = async () => {
state.error = false
state.loading = true
try {
const result = await fetch(url).then(res => res.json())
state.data = result
} catch(e) {
state.error = true
}
state.loading = false
}
return {
run,
...toRefs(state)
}
}
затем вApp.vue
Представлен в:
<template>
<input type="text" v-model="query" />
<button @click="search">搜索</button>
<div v-if="loading">Loading ...</div>
<div v-else-if="error">Something went wrong ...</div>
<ul v-else>
<li
v-for="item of data.hits"
:key="item.objectID"
>
<a :href="item.url">{{item.title}}</a>
</li>
</ul>
</template>
<script>
import { ref, onMounted } from 'vue'
import useRequest from './useRequest'
export default {
setup() {
const query = ref('vue')
const { data, loading, error, run } = useRequest({
url: 'https://hn.algolia.com/api/v1/search'
})
onMounted(() => {
run()
})
return {
data,
query,
error,
loading,
search: run,
}
}
}
</script>
ТекущийuseRequest
Есть также два недостатка:
- Входящий URL зафиксирован, после модифицированного запроса он не может быть отражено на URL-адрес во времени;
- Его нельзя запросить автоматически, вам нужно вручную вызвать метод запуска;
import {
isRef,
toRefs,
reactive,
onMounted,
} from 'vue'
export default (options) => {
const { url, manual = false, params = {} } = options
const state = reactive({
data: {},
error: false,
loading: false,
})
const run = async () => {
// 拼接查询参数
let query = ''
Object.keys(params).forEach(key => {
const val = params[key]
// 如果去 ref 对象,需要取 .value 属性
const value = isRef(val) ? val.value : val
query += `${key}=${value}&`
})
state.error = false
state.loading = true
try {
const result = await fetch(`${url}?${query}`)
.then(res => res.json())
state.data = result
} catch(e) {
state.error = true
}
state.loading = false
}
onMounted(() => {
// 第一次是否需要手动调用
!manual && run()
})
return {
run,
...toRefs(state)
}
}
После модификации наша логика становится очень простой.
import useRequest from './useRequest'
export default {
setup() {
const query = ref('vue')
const { data, loading, error, run } = useRequest(
{
url: 'https://hn.algolia.com/api/v1/search',
params: {
query
}
}
)
return {
data,
query,
error,
loading,
search: run,
}
}
}
Конечно, этоuseRequest
Есть еще много вещей, которые можно улучшить, например: не поддерживает модификацию http-методов, не поддерживает троттлинг и анти-шейк, не поддерживает таймаут и так далее. Наконец, я надеюсь, что вы сможете что-то получить после прочтения статьи.