通常,在构建JavaScript应用程序时,您需要从远程源获取数据或使用API。可以使用许多公开的API中的数据来完成很多很酷的工作。
使用Vue.js,您可以从字面上围绕这些服务之一构建应用程序,并在几分钟之内开始向用户提供内容。
我将演示如何构建一个简单的新闻应用程序,该应用程序将显示当天的热门新闻,并允许用户按兴趣类别进行过滤,并从API中获取数据。你可以找到这个教程的完整代码在这里[https://github.com/sitepoint-editors/vue-news-app],并最终应用的现场演示这里[https://vue-news-app.stackblitz.io/]。
要继续学习本教程,您需要在计算机上安装Node.js和(可选)Yarn。要安装Node,您可以转到官方下载页面并获取系统的Node二进制文件。
安装Node后,要拉入Yarn,请运行:
npm i -g yarn
您还需要Vue.js的基础知识。您可以在这里[https://v3.vuejs.org/guide/introduction.html]找到一个很好的入门指南。
获取API密钥
要使用NYTimes API,您需要获取一个API密钥。因此,如果您还没有一个,请转到其注册页面[https://developer.nytimes.com/signup]并注册以获取Top Stories API[https://developer.nytimes.com/top_stories_v2.json]的API密钥。
我们将使用的New York Times API端点[https://developer.nytimes.com/],从获取数据。请注意,此API提供了多个部分,例如"家","旅行","艺术"和"科学"。我们需要构建一个过滤器,使用户可以选择一个部分并在其中加载故事。
以下是示例调用:
https://api.nytimes.com/svc/topstories/v2/arts.json?api-key=yourkey
https://api.nytimes.com/svc/topstories/v2/home.json?api-key=yourkey
https://api.nytimes.com/svc/topstories/v2/science.json?api-key=yourkey
https://api.nytimes.com/svc/topstories/v2/us.json?api-key=yourkey
https://api.nytimes.com/svc/topstories/v2/world.json?api-key=yourkey
随意使用您喜欢的REST客户端(例如Hoppscotch或Insomnia)来测试您的API调用。
项目结构
让我们使用Vite快速启动Vue 3项目,Vite是一个运行速度比Vue CLI更快的开发服务器:
yarn create @vitejs/app vue-news-app --template vue
# Install package dependencies
cd vue-news-app
yarn install
# Confirm app can run
yarn dev
localhost:3000
在浏览器中打开。您应该具有以下视图:
接下来,让我们安装TailwindCSS[https://tailwindcss.com/]框架以提供一些基本样式。您需要停止服务器才能执行此操作:
yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
# Generate tailwind.config.js and postcss.config.js files
npx tailwindcss init -p
我们将需要一些其他的软件包实用程序来帮助我们格式化日期并确定abstract
字段的行数:
yarn add @tailwindcss/line-clamp date-fns
@tailwindcss/line-clamp
是需要包含在中的插件tailwind.config.js
。下面是完整的配置:
module.exports = {
purge: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [require("@tailwindcss/line-clamp")],
}
接下来,index.css
在src
文件夹中创建一个文件并添加以下代码:
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply antialiased text-green-900 bg-green-50;
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
}
#app {
@apply flex flex-col min-h-screen overflow-x-hidden;
}
除了导入所需的Tailwind CSS类之外,我们还包括一些CSS设置以帮助我们定义应用程序的默认主题。我们还实现了Flex布局系统,以帮助我们为应用程序创建粘性的页眉和页脚。
我们需要导入index.css
的src/main.js
:
import { createApp } from "vue"
import App from "./App.vue"
import "./index.css"
createApp(App).mount("#app")
现在让我们继续定义应用程序布局。首先,清除中的任何现有组件src/components
。接下来,在同一文件夹中,创建以下三个文件:
-
Layout.vue
-
Header.vue
-
Footer.vue
为每个文件复制以下代码:
src / components / Footer.vue:
<template>
<footer class="px-4 py-8 text-sm font-bold text-center text-green-100 bg-green-900">
<p class="text-sm tracking-wide">Copyright (c) 2021 SitePoint</p>
</footer></template>
src / components / Header.vue:
<template>
<header class="flex justify-center py-6 bg-green-900 place-items-center">
<img alt="Vue logo" src="../assets/logo.png" width="32" />
<span class="ml-4 text-lg font-bold text-green-100 md:text-xl">
Vue News | NYTimes Edition
</span>
</header></template>
src / components / Layout.vue:
<template>
<Header />
<main class="container flex-grow px-4 mx-auto my-12">
<slot />
</main>
<Footer /></template><script>import Header from "./Header.vue"import Footer from "./Footer.vue"export default {
components: {
Header,
Footer,
},}</script>
最后,更新src/App.vue
:
<template>
<Layout>
<p>Main content goes here</p>
</Layout></template><script>import Layout from "./components/Layout.vue"export default {
components: {
Layout,
},}</script>
执行yarn dev
。浏览器应自动刷新。
完成应用程序布局后,我们现在就可以开始构建新闻应用程序的核心逻辑。
建筑新闻应用程序组件
我们的应用程序结构将由三个News组件和一个容器组成src/App.vue
。该容器将负责获取发布数据并填充组件。
首先,我们需要设计布局并寻找这些组件。因此,我们首先需要一些模拟数据。创建文件src/posts.json
并使用以下数据填充它:
{
"posts": [
{
"title": "Stay Healthy When Exercising Outdoors",
"abstract": "Cold weather workouts do bring unique risks, but a little planning and preparation can help whether you’re going for a winter walk, trekking in snowshoes or sledding with the kids.",
"url": "https://www.nytimes.com/2021/02/06/at-home/exercise-outdoors-cold-weather.html",
"byline": "By Kelly DiNardo",
"published_date": "2021-02-06T23:40:05-05:00",
"thumbnail": "https://static01.nyt.com/images/2021/02/07/multimedia/07ah-OUTDOOREXERCISE/07ah-OUTDOOREXERCISE-mediumThreeByTwo210.jpg",
"caption": ""
},
{
"title": "4 Skiers Killed in Avalanche in Utah, Officials Say",
"abstract": "It was the third such deadly episode in days and the deadliest avalanche in the United States since May 2014, according to the authorities.",
"url": "https://www.nytimes.com/2021/02/06/us/avalanche-salt-lake-city.html",
"byline": "By Michael Levenson",
"published_date": "2021-02-06T20:22:39-05:00",
"thumbnail": "https://static01.nyt.com/images/2021/02/06/lens/06xp-avalanche-photo2/06xp-avalanche-photo2-mediumThreeByTwo210.jpg",
"caption": "A helicopter returning to Millcreek Canyon after rescuing one of the four avalanche survivors on Saturday."
}
]}
我鼓励您重复记录以更好地测试我们的组件设计布局,但是由于篇幅所限,在此我不做这些。
现在让我们开始构建新闻组件。在src/components
文件夹中,创建以下文件:
-
NewsCard.vue
-
NewsList.vue
-
NewsFilter.vue
只是为了可视化所有这些组件如何组合在一起,将它们导入src/App.vue
并按如下所示进行设置:
<template>
<Layout>
<h2 class="mb-8 text-4xl font-bold text-center capitalize">
News Section : <span class="text-green-700">{{ section }}</span>
</h2>
<NewsFilter v-model="section" />
<NewsList :posts="posts" />
</Layout></template><script>import Layout from "./components/Layout.vue"import NewsFilter from "./components/NewsFilter.vue"import NewsList from "./components/NewsList.vue"import data from "./posts.json"export default {
components: {
Layout,
NewsFilter,
NewsList,
},
data() {
return {
section: "home",
posts: data.posts,
}
},}</script>
让我们分解上面的代码:
-
该
header
标签是我们显示的当前状态值section
。 -
该
NewsFilter
组件将包含一个下拉输入,供用户选择其他部分。他们将必须单击一个按钮才能执行提取。我们已经将组件绑定到状态section
以允许状态同步。 -
该
NewsList
组件将NewsCard
在响应网格中使用该组件显示帖子。
现在让我们开始处理每个新闻组件。该NewsCard.vue
组件将显示单个帖子的数据。它需要一个道具post
:
<template>
<section class="p-4 rounded-lg shadow-lg bg-gray-50 w-80">
<div class="h-96">
<a class="text-xl font-bold text-center text-green-800 hover:text-green-600 hover:underline"
:href="post.url"
target="_blank"
rel="noreferrer"
>
{{ post.title }}
</a>
<img class="w-full mt-2 rounded"
:src="post.thumbnail"
:alt="post.caption"
height="140"
width="210"
/>
<p class="mt-2 text-justify text-gray-700 line-clamp-4">
{{ post.abstract }}
</p>
</div>
<div>
<p class="mt-4 font-bold text-gray-600">{{ post.byline }}</p>
<p class="font-light text-gray-600">
{{ formatDate(post.published_date) }}
</p>
</div>
</section></template><script>import { format } from "date-fns"export default {
props: {
post: {
type: Object,
required: true,
},
},
methods: {
formatDate(strDate) {
return format(new Date(strDate), "MMMM do, yyyy")
},
},}</script>
在NewsList.vue
经过一个职位阵列和填充将循环NewsCards
跨越响应格:
<template>
<div class="grid grid-cols-1 gap-6 mt-4 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 justify-items-center"
>
<NewsCard v-for="(post, index) in posts" :key="index" :post="post" />
</div></template><script>import NewsCard from "./NewsCard.vue"export default {
props: {
posts: {
type: Array,
required: true,
},
},
components: {
NewsCard,
},}</script>
接下来,我们有一个NewsFilter
组件,它将允许用户加载来自不同部分的帖子。首先,我们需要一个内容文件来存储Top Stories API端点支持的所有部分。创建文件src/components/sections.js
:
const sections = [
"home",
"arts",
"automobiles",
"books",
"business",
"fashion",
"food",
"health",
"insider",
"magazine",
"movies",
"nyregion",
"obituaries",
"opinion",
"politics",
"realestate",
"science",
"sports",
"sundayreview",
"technology",
"theater",
"magazine",
"travel",
"upshot",
"us",
"world",]export default sections
现在NewsFilter.vue
,让我们创建,其中包含一个下拉选择输入和一个按钮。我们需要使用v-model
与状态section
中的状态同步的方式来绑定状态App.vue
:
<template>
<div class="flex justify-center p-4 rounded">
<!-- Start of select dropdown -->
<div class="relative inline-flex">
<svg class="absolute top-0 right-0 w-2 h-2 m-4 pointer-events-none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 412 232"
>
<path
d="M206 171.144L42.678 7.822c-9.763-9.763-25.592-9.763-35.355 0-9.763 9.764-9.763 25.592 0 35.355l181 181c4.88 4.882 11.279 7.323 17.677 7.323s12.796-2.441 17.678-7.322l181-181c9.763-9.764 9.763-25.592 0-35.355-9.763-9.763-25.592-9.763-35.355 0L206 171.144z"
fill="#648299"
fill-rule="nonzero"
/>
</svg>
<select class="h-10 pl-5 pr-10 text-gray-600 bg-white border border-gray-300 rounded-lg appearance-none hover:border-gray-400 focus:outline-none"
v-model="section"
>
<option
v-for="(section, index) in sections"
:key="index"
:value="section"
>
{{ capitalize(section) }}
</option>
</select>
</div>
<!-- End of select dropdown -->
<div class="self-center ml-8">
<button class="px-6 py-2 text-white bg-green-700 rounded hover:bg-green-900"
>
Retrieve
</button>
</div>
</div></template><script>import { computed } from "vue"import sectionsData from "./sections"export default {
props: {
modelValue: String,
},
setup(props, { emit }) {
const section = computed({
get: () => props.modelValue,
set: value => emit("update:modelValue", value),
})
return {
section,
}
},
data() {
return {
sections: sectionsData,
}
},
methods: {
capitalize(value) {
if (!value) return ""
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
},
},}</script>
我们用于将section
状态绑定到NewsFilter
组件的策略。基本上,这允许子组件更新道具并与父组件同步。
下面是该应用程序当前状态的屏幕截图:
使用Axios提取远程数据
Axios是用于发出Ajax请求的基于诺言的HTTP客户端,将非常适合我们的目的。它提供了一个简单而丰富的API。它与fetch
API十分相似,但无需为旧版浏览器添加polyfill和其他一些细微之处。
要安装axios,请运行:
yarn add axios
我们的应用程序的UI开发已完成。现在,我们只需要实现远程获取逻辑。以下是NYTimes API服务期望的完整URL格式的示例:
https://api.nytimes.com/svc/topstories/v2/home.json?api-key=your_api_key
首先,让我们将API密钥存储在.env
项目根目录下的文件中。以以下格式保存:
VITE_NYT_API_KEY=####
将哈希替换为您的实际API密钥。
由于我们使用的是Vite,因此需要遵守Vite的有关加载环境变量的手册。Vue / CLI有其自己的说明来执行此操作。
现在,让我们实现从NYTimes REST API端点获取实际帖子的逻辑。只需src/App.vue
相应地更新:
<template>
<Layout>
<h2 class="mb-8 text-4xl font-bold text-center capitalize">
News Section : <span class="text-green-700">{{ section }}</span>
</h2>
<NewsFilter v-model="section" :fetch="fetchNews" />
<NewsList :posts="posts" />
</Layout></template><script>import Layout from "./components/Layout.vue"import NewsFilter from "./components/NewsFilter.vue"import NewsList from "./components/NewsList.vue"import axios from "axios"const api = import.meta.env.VITE_NYT_API_KEYexport default {
components: {
Layout,
NewsFilter,
NewsList,
},
data() {
return {
section: "home",
posts: [],
}
},
methods: {
// Helper function for extracting a nested image object
extractImage(post) {
const defaultImg = {
url: "http://placehold.it/210x140?text=N/A",
caption: post.title,
}
if (!post.multimedia) {
return defaultImg }
let imgObj = post.multimedia.find(
media => media.format === "mediumThreeByTwo210"
)
return imgObj ? imgObj : defaultImg },
async fetchNews() {
try {
const url = `https://api.nytimes.com/svc/topstories/v2/${this.section}.json?api-key=${api}`
const response = await axios.get(url)
const results = response.data.results
this.posts = results.map(post => ({
title: post.title,
abstract: post.abstract,
url: post.url,
thumbnail: this.extractImage(post).url,
caption: this.extractImage(post).caption,
byline: post.byline,
published_date: post.published_date,
}))
} catch (err) {
if (err.response) {
// client received an error response (5xx, 4xx)
console.log("Server Error:", err)
} else if (err.request) {
// client never received a response, or request never left
console.log("Network Error:", err)
} else {
console.log("Client Error:", err)
}
}
},
},
mounted() {
this.fetchNews()
},}</script>
在这里,我们创建了一个名为的函数fetchNews
,其中包含用于执行提取逻辑的逻辑。该函数将从两个地方调用:
-
在
mounted()
生命周期事件 -
该
NewsFilter
组件
让我们分解一下函数以确保我们了解发生了什么:
-
我们正在使用async语法,因为它比使用常规的
Promise
回调语法更干净。 -
由于我们将要进行网络呼叫,因此许多事情可能会出错。我们已经将函数的代码包装在一个
try...catch
块中。否则,如果发生一个非描述性的Promise错误,将会遇到用户。 -
使用ES6模板文字,我们能够构建一个URL字符串,只要用户
section
通过该NewsFilter
组件更改新闻,该URL字符串就会自动更新。请注意,API密钥也已包含在URL字符串中。 -
使用
axios.get()
函数获取结果后,我们需要解析结果并以与我们的UI(特别是NewsCard
组件)兼容的方式对结果进行格式设置。我们使用JavaScript的Array.map
功能执行此操作,以使用格式化后的数据创建一个新数组。 -
提取图像数据有些棘手。有些帖子缺少该
multimedia
字段,即使有,也不能保证我们需要的媒体格式存在。在这种情况下,我们将返回默认的图像URLhttp://placehold.it/210x140?text=N/A
---并将帖子的标题用作标题。 -
在错误块中,我们正在检查特定错误属性的存在,以确定发生了哪种类型的错误。您可以使用此信息来构造有用的错误消息。
现在,看一下模板部分,观察到我们已经包含了一个名为的新道具fetch
,该道具链接到该fetchNews
函数。我们需要更新src/components/NewsFilter.vue
以接受该道具。在下面,我仅突出显示了您应更改的代码部分:
<template>
...
<button class="px-6 py-2 text-white bg-green-700 rounded hover:bg-green-900"
@click="fetch"
>
Retrieve
</button>
...</template><script>export default {
props: {
modelValue: String,
fetch: Function,
},}</script>
您可能需要重新启动开发服务器才能正确加载axios库和API密钥。完成此操作后,您应该可以查看实际的帖子。以下是应用程序的当前状态。
您应该能够切换和加载不同的新闻栏目。
最后的接触和演示
我决定添加一些较小的(可选)修饰,以使应用程序体验更好一些,例如引入加载图像。
您可以在下面的StackBlitz中看到一个演示(功能受限):
或者,您可以在此处查看实时版本。
结论
在本教程中,我们学习了如何从头开始Vue.js项目,如何使用axios从API获取数据,以及如何使用组件和计算属性来处理响应以及处理数据。
现在,我们有一个功能强大的Vue.js 3.0应用程序,该应用程序围绕API服务构建。通过插入其他一些API可以进行很多改进。例如,我们可以:
-
使用Buffer API自动将类别中的社交媒体帖子排队
-
使用Pocket API将帖子标记为以后阅读