如何使用 Next.js 和 Typescript 动态过滤在 API 中获取的帖子?
How to dynamically filter posts fetched in a API using Next.js and Typescript?
我正在开发一个具有过滤功能的投资组合网站。所以我目前正在努力使用 useStates、useEffect,并且不确定它们是如何工作的。我已经为此工作了 5 天,并且有 watch/read 不同的概念和 how-tos/other 的答案,但仍在努力解决如何通过 useState 来获取和过滤 api 数据。
这个想法是当用户点击 index.tsx
中的 link 时,例如 苹果、橙子或香蕉 ,网站会根据他们点击了什么,但我目前只能通过将帖子硬编码到函数 getPosts('tag:apple')
.
中来过滤帖子
index.tsx
import { getPosts, PostType } from "./api/ghostCMS"
import moment from 'moment'
//import { useState, useEffect } from 'react'
export const getServerSideProps = async ({params}) => {
const posts = await getPosts('tag:apple')
return {
props: {
revalidate: 10,
posts }
}
}
const Home: React.FC<{ posts: PostType[] }> = (props) => {
const { posts } = props
return (
<div>
<div> {/* links to filter posts */}
<button onClick={() => getPosts('tag:apple')}>apple</button>
<button>orange</button>
<button>banana</button>
</div>
<div className={styles.gridContainer}>
{posts.map((post, index) => {
return (
<li className={styles.postitem} key={post.slug}>
<Link href="/post/[slug]" as={`/post/${post.slug}`}>
<a>{post.title}</a>
</Link>
<p>{moment(post.created_at).format('MMMM D, YYYY [●] h:mm a')}
</p>
<p>{post.custom_excerpt}</p>
{!!post.feature_image && <img src={post.feature_image} />}
</li>
)
})}
</div> {/*End of Post Container */}
</div>
然后对于这个 ghostCMS.tsx,这是我获取 api 数据并在函数 getPost
.
中添加过滤字符串的地方
./api/ghostCMS.tsx
const { BLOG_URL, CONTENT_API_KEY } = process.env
export interface PostType {
title: string
slug: string
custom_excerpt: string
feature_image: string
created_at: string
tag: string
}
export async function getPosts(setFilter?: string) {
if (typeof setFilter !== 'undefined' ) {
const res = await fetch(
`${BLOG_URL}/ghost/api/v4/content/posts/?key=${CONTENT_API_KEY}
&fields=title,slug,custom_excerpt,feature_image,created_at,tag
&limit=all&filter=${setFilter}`
).then((res) => res.json())
const posts = res.posts
return posts
} else {
const res = await fetch(
`${BLOG_URL}/ghost/api/v4/content/posts/?key=${CONTENT_API_KEY}
&fields=title,slug,custom_excerpt,feature_image,created_at,tag
&limit=all`
).then((res) => res.json())
const posts = res.posts
return posts
}
}
export default getPosts
我不确定我的逻辑是否正确,但总而言之,我仍在学习如何根据用户希望看到的内容进行过滤。如果您有任何建议或资源,请随时告诉我并解释。抱歉,如果这听起来像是一个愚蠢的问题,请尽我最大努力学习这个投资组合网站,非常感谢您的帮助。谢谢。
已编辑:
在收到@wongz 的建议后,我将锚点更改为按钮,但出现错误:未处理的运行时错误参考错误:未定义进程
出现错误是因为我调用了错误的函数吗?我使用 getPosts()
,所以我尝试用 setServerSideProps(setFilter)
替换它,但显示 服务器错误
错误:序列化从“/”中的 getServerSideProps
返回的 .posts
时出错。
原因:undefined
无法序列化为 JSON。请使用 null
或省略此值。 所以我只是恢复到只放下按钮而没有功能...
我在想是否可以将const getServerSideProps
放在index.tsx的const Home
里面?或者 useEffect 是可能的吗?如果是这样,我该怎么做?谢谢你的时间。
有很多东西需要改变一下。
1:从概念上将您的 index.tsx 视为主要组件,因此我删除了传递给它的参数,因为每次参数更新时它都会不断更改 Home
2:按钮应该有一个值。当您单击按钮时,它会转到 handleClick 函数。该 handleClick 函数然后通过 e?.currentTarget?.value
确定所需的水果值。问号意味着如果它不存在,整个事情将是未定义的——只是一种更安全的调用对象中的项目的方法。然后我们调用您的 getPosts 函数,传入过滤器标签,然后通过 setPosts
函数将其设置为 posts 变量。
useState 的工作方式是它为您提供值和 setter 函数来设置该变量的值。所以 posts
是变量名, setPosts
是一个函数,你可以在其中传入一个新值来确定帖子的值。
所以在这里我们将帖子设置为从 api 调用返回的内容。我们还使用 .then().catch()
语法,因为它是异步的。
根据收到数据时的外观,根据需要更改 res.data。
3:这里添加了useEffect
钩子,因为我们需要在页面加载时运行发帖一次。 useEffect 中的空方括号是您放置变量的地方,只要其中一个变量发生变化,useEffect 函数就会 运行 。所以在这种情况下,我们希望它在组件加载时仅 运行 一次,因此我们将其留空,以便在组件加载时它仅 运行 一次。
我们在 useEffect 钩子中调用的是 getPosts 函数,然后我们调用 setPosts
来更新 posts
变量。然后你的 JSX 上写着 {posts && } 的地方就会是真的,这样帖子就会出现。这应该会在一秒钟左右发生。
4: 在帖子部分,你只需要在帖子可用时渲染它,否则浏览器会因为帖子未定义而出错。所以这就是需要 {posts && <div>,<div>}
的地方。
import React from 'react';
import type {PostType} from "./ghostCMS";
import { getPosts } from './ghostCMS';
const Home:React.FC = () => {
const [posts, setPosts] = React.useState<PostType[] | null>(null);
React.useEffect(()=> {
getPosts().then(res=>{
setPosts(res.data);
}).catch(err=>console.log(err));
},[]);
const handleClick = (e:React.MouseEvent<HTMLButtonElement>) => {
const fruit = e?.currentTarget.value;
getPosts("tag:"+fruit).then(res=>setPosts(res.data)).catch(err=>console.log(err));
}
return (
<div>
<div>
<button value="apple" onClick={(e) => handleClick(e)}>apple</button>
<button value="orange" onClick={(e) => handleClick(e)}>orange</button>
<button value="banana" onClick={(e) => handleClick(e)}>banana</button>
</div>
{posts && <div>
{posts?.map((post, index) => {
return (
<li key={post?.slug}>
<p>{post?.custom_excerpt}</p>
</li>
)
})}
</div>}
</div>
);
};
export default Home;
5: 根据你的配置,错误是因为它不知道 process
是什么。在 React 中,为了获取变量,您需要在它们前面加上 REACT_APP_。因此,在 .env 文件的变量名中添加 REACT_APP_ 并这样调用它们。除此之外,根据错误,您可能需要下载包 dotenv
,然后在获取环境变量之前调用它,如下所示。所以npm install dotenv
然后添加下面的代码。
import dotenv from 'dotenv';
dotenv.config();
const BLOG_URL = process.env.REACT_APP_BLOG_URL;
const CONTENT_API_KEY = process.env.REACT_APP_CONTENT_API_KEY;
// your other code
您的 .env 文件现在看起来像这样
REACT_APP_BLOG_URL="example.com"
REACT_APP_API_KEY="apiKeyExample"
基于让您的应用正常运行,我会先做#5 来解决您的问题,然后根据您自己的理解一点一点地更新其余部分。
感谢@wongz,我能够动态过滤我获取的 API 帖子。最初,我想我对使用 getStaticProps/getStaticServer 的需要感到困惑,因此我意识到我可以只调用函数 getPosts
和 setPosts
.
我开始修复 process.env
,包括必须在我的变量前面添加 NEXT_PUBLIC_(而不是 REACT_APP_,因为我的项目基于 Next.js)。
NEXT_PUBLIC_CONTENT_API_KEY = apikey
NEXT_PUBLIC_BLOG_URL = example.com
index.tsx
const Home:React.FC = () => {
const [posts, setPosts] = React.useState<PostType[] | null>(null);
const [filter, setFilter] = React.useState('')
// when the page loads for the first time
React.useEffect(() => {
getPosts().then(res=>{
setPosts(res.posts);
}).catch(err=>console.log(err));
},[]);
const handleClick = (e:React.MouseEvent<HTMLButtonElement>) => {
const dataFilter = e?.currentTarget.value
getPosts("tag:"+dataFilter, page).then(res=>{
setPosts(res.posts);
}).catch(err=>console.log(err));
setFilter(dataFilter);
}
return (
<div>
<button value="apple" onClick={(e) => handleClick(e)}>apple</button>
<button value="orange" onClick={(e) => handleClick(e)}>orange</button>
<button value="banana" onClick={(e) => handleClick(e)}>banana</button>
</div>
{posts && <div>
{posts?.map((p, index) => {
return (
<li key={p?.slug}>
<p>{p?.custom_excerpt}</p>
</li>
)
})}
</div>}
</div>
);
};
export default Home
我正在开发一个具有过滤功能的投资组合网站。所以我目前正在努力使用 useStates、useEffect,并且不确定它们是如何工作的。我已经为此工作了 5 天,并且有 watch/read 不同的概念和 how-tos/other 的答案,但仍在努力解决如何通过 useState 来获取和过滤 api 数据。
这个想法是当用户点击 index.tsx
中的 link 时,例如 苹果、橙子或香蕉 ,网站会根据他们点击了什么,但我目前只能通过将帖子硬编码到函数 getPosts('tag:apple')
.
index.tsx
import { getPosts, PostType } from "./api/ghostCMS"
import moment from 'moment'
//import { useState, useEffect } from 'react'
export const getServerSideProps = async ({params}) => {
const posts = await getPosts('tag:apple')
return {
props: {
revalidate: 10,
posts }
}
}
const Home: React.FC<{ posts: PostType[] }> = (props) => {
const { posts } = props
return (
<div>
<div> {/* links to filter posts */}
<button onClick={() => getPosts('tag:apple')}>apple</button>
<button>orange</button>
<button>banana</button>
</div>
<div className={styles.gridContainer}>
{posts.map((post, index) => {
return (
<li className={styles.postitem} key={post.slug}>
<Link href="/post/[slug]" as={`/post/${post.slug}`}>
<a>{post.title}</a>
</Link>
<p>{moment(post.created_at).format('MMMM D, YYYY [●] h:mm a')}
</p>
<p>{post.custom_excerpt}</p>
{!!post.feature_image && <img src={post.feature_image} />}
</li>
)
})}
</div> {/*End of Post Container */}
</div>
然后对于这个 ghostCMS.tsx,这是我获取 api 数据并在函数 getPost
.
./api/ghostCMS.tsx
const { BLOG_URL, CONTENT_API_KEY } = process.env
export interface PostType {
title: string
slug: string
custom_excerpt: string
feature_image: string
created_at: string
tag: string
}
export async function getPosts(setFilter?: string) {
if (typeof setFilter !== 'undefined' ) {
const res = await fetch(
`${BLOG_URL}/ghost/api/v4/content/posts/?key=${CONTENT_API_KEY}
&fields=title,slug,custom_excerpt,feature_image,created_at,tag
&limit=all&filter=${setFilter}`
).then((res) => res.json())
const posts = res.posts
return posts
} else {
const res = await fetch(
`${BLOG_URL}/ghost/api/v4/content/posts/?key=${CONTENT_API_KEY}
&fields=title,slug,custom_excerpt,feature_image,created_at,tag
&limit=all`
).then((res) => res.json())
const posts = res.posts
return posts
}
}
export default getPosts
我不确定我的逻辑是否正确,但总而言之,我仍在学习如何根据用户希望看到的内容进行过滤。如果您有任何建议或资源,请随时告诉我并解释。抱歉,如果这听起来像是一个愚蠢的问题,请尽我最大努力学习这个投资组合网站,非常感谢您的帮助。谢谢。
已编辑:
在收到@wongz 的建议后,我将锚点更改为按钮,但出现错误:未处理的运行时错误参考错误:未定义进程
出现错误是因为我调用了错误的函数吗?我使用 getPosts()
,所以我尝试用 setServerSideProps(setFilter)
替换它,但显示 服务器错误
错误:序列化从“/”中的 getServerSideProps
返回的 .posts
时出错。
原因:undefined
无法序列化为 JSON。请使用 null
或省略此值。 所以我只是恢复到只放下按钮而没有功能...
我在想是否可以将const getServerSideProps
放在index.tsx的const Home
里面?或者 useEffect 是可能的吗?如果是这样,我该怎么做?谢谢你的时间。
有很多东西需要改变一下。
1:从概念上将您的 index.tsx 视为主要组件,因此我删除了传递给它的参数,因为每次参数更新时它都会不断更改 Home
2:按钮应该有一个值。当您单击按钮时,它会转到 handleClick 函数。该 handleClick 函数然后通过 e?.currentTarget?.value
确定所需的水果值。问号意味着如果它不存在,整个事情将是未定义的——只是一种更安全的调用对象中的项目的方法。然后我们调用您的 getPosts 函数,传入过滤器标签,然后通过 setPosts
函数将其设置为 posts 变量。
useState 的工作方式是它为您提供值和 setter 函数来设置该变量的值。所以 posts
是变量名, setPosts
是一个函数,你可以在其中传入一个新值来确定帖子的值。
所以在这里我们将帖子设置为从 api 调用返回的内容。我们还使用 .then().catch()
语法,因为它是异步的。
根据收到数据时的外观,根据需要更改 res.data。
3:这里添加了useEffect
钩子,因为我们需要在页面加载时运行发帖一次。 useEffect 中的空方括号是您放置变量的地方,只要其中一个变量发生变化,useEffect 函数就会 运行 。所以在这种情况下,我们希望它在组件加载时仅 运行 一次,因此我们将其留空,以便在组件加载时它仅 运行 一次。
我们在 useEffect 钩子中调用的是 getPosts 函数,然后我们调用 setPosts
来更新 posts
变量。然后你的 JSX 上写着 {posts && } 的地方就会是真的,这样帖子就会出现。这应该会在一秒钟左右发生。
4: 在帖子部分,你只需要在帖子可用时渲染它,否则浏览器会因为帖子未定义而出错。所以这就是需要 {posts && <div>,<div>}
的地方。
import React from 'react';
import type {PostType} from "./ghostCMS";
import { getPosts } from './ghostCMS';
const Home:React.FC = () => {
const [posts, setPosts] = React.useState<PostType[] | null>(null);
React.useEffect(()=> {
getPosts().then(res=>{
setPosts(res.data);
}).catch(err=>console.log(err));
},[]);
const handleClick = (e:React.MouseEvent<HTMLButtonElement>) => {
const fruit = e?.currentTarget.value;
getPosts("tag:"+fruit).then(res=>setPosts(res.data)).catch(err=>console.log(err));
}
return (
<div>
<div>
<button value="apple" onClick={(e) => handleClick(e)}>apple</button>
<button value="orange" onClick={(e) => handleClick(e)}>orange</button>
<button value="banana" onClick={(e) => handleClick(e)}>banana</button>
</div>
{posts && <div>
{posts?.map((post, index) => {
return (
<li key={post?.slug}>
<p>{post?.custom_excerpt}</p>
</li>
)
})}
</div>}
</div>
);
};
export default Home;
5: 根据你的配置,错误是因为它不知道 process
是什么。在 React 中,为了获取变量,您需要在它们前面加上 REACT_APP_。因此,在 .env 文件的变量名中添加 REACT_APP_ 并这样调用它们。除此之外,根据错误,您可能需要下载包 dotenv
,然后在获取环境变量之前调用它,如下所示。所以npm install dotenv
然后添加下面的代码。
import dotenv from 'dotenv';
dotenv.config();
const BLOG_URL = process.env.REACT_APP_BLOG_URL;
const CONTENT_API_KEY = process.env.REACT_APP_CONTENT_API_KEY;
// your other code
您的 .env 文件现在看起来像这样
REACT_APP_BLOG_URL="example.com"
REACT_APP_API_KEY="apiKeyExample"
基于让您的应用正常运行,我会先做#5 来解决您的问题,然后根据您自己的理解一点一点地更新其余部分。
感谢@wongz,我能够动态过滤我获取的 API 帖子。最初,我想我对使用 getStaticProps/getStaticServer 的需要感到困惑,因此我意识到我可以只调用函数 getPosts
和 setPosts
.
我开始修复 process.env
,包括必须在我的变量前面添加 NEXT_PUBLIC_(而不是 REACT_APP_,因为我的项目基于 Next.js)。
NEXT_PUBLIC_CONTENT_API_KEY = apikey
NEXT_PUBLIC_BLOG_URL = example.com
index.tsx
const Home:React.FC = () => {
const [posts, setPosts] = React.useState<PostType[] | null>(null);
const [filter, setFilter] = React.useState('')
// when the page loads for the first time
React.useEffect(() => {
getPosts().then(res=>{
setPosts(res.posts);
}).catch(err=>console.log(err));
},[]);
const handleClick = (e:React.MouseEvent<HTMLButtonElement>) => {
const dataFilter = e?.currentTarget.value
getPosts("tag:"+dataFilter, page).then(res=>{
setPosts(res.posts);
}).catch(err=>console.log(err));
setFilter(dataFilter);
}
return (
<div>
<button value="apple" onClick={(e) => handleClick(e)}>apple</button>
<button value="orange" onClick={(e) => handleClick(e)}>orange</button>
<button value="banana" onClick={(e) => handleClick(e)}>banana</button>
</div>
{posts && <div>
{posts?.map((p, index) => {
return (
<li key={p?.slug}>
<p>{p?.custom_excerpt}</p>
</li>
)
})}
</div>}
</div>
);
};
export default Home