大家好!昨天实作了小小专案,也写了一篇短短的介绍文,那今天跟大家分享怎麽用 Next.js 的各种 data fetching functions 串 API 抓取资料然後做成一个专案~
不过设计就这样随便做,请见谅Q
这专案用 Radix UI 的 Primitives 和 Stitches 做 styling
开始之前,先跟大家分享这小专案的所有功能:
用 Next.js 的 create-next-app
,在终端机输入以下指令建立专案:
npx create-next-app
# or
yarn create next-app
跑完上面的指令後进到该专案的资料夹,执行 npm run dev
或 yarn dev
,打开浏览器浏览 http://localhost:3000
,应该会看到以下这画面:
耶~ 恭喜!我们可以开始了!
pages/_app.js
这是什麽?!之前的文章应该没有提到这档案呢?Next.js 使用 App
去做每个页面的初始化,不过我们可以自己做 custom App
做客制化。因为每个页面都会用这 App
被 initialize,所以如果我们想加一些每一页必须有的 components 或 css,都可以加在这里:
// imports
import { globalCss } from "@stitches/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { Container } from "../components/container";
// 加 global CSS styles
const globalStyles = globalCss({
"html, body": {
padding: 0,
margin: 0,
fontFamily:
"'Open Sans', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif",
},
a: { color: "inherit" },
"*": { boxSizing: "border-box" },
});
// custom App
function MyApp({ Component, pageProps }) {
globalStyles();
const { pathname } = useRouter();
return (
<Container>
<main>
// 显示当页
<Component {...pageProps} />
</main>
// 每一页会有这 footer
<footer>
<p>
// 如果我们不在首页,让使用者看到 "Go back to Home"
{pathname === "/" ? "You are at " : "Go back to "}
<Link href="/">Home</Link>
</p>
</footer>
</Container>
);
}
// 记得用 export default 喔!
export default MyApp;
pages/index.js
先从 Home (首页) 开始吧!所谓的首页是我们的 pages/index.js
档案,因为像这篇提到,pages/index.js
会对应到我们的 /
路径。在这页我们完全不用显示任何资料,也就是我们不需要用到任合 data fetching 的 functions。不过,我们放了一个连结去 Next 页 (/next
),用 Link
做的:
<p>
See what is <Link href="/next">Next</Link>?
</p>
除了连结之外,这页最重要的功能是 input
!上方的 input
可以填 Github username,而下方的 input
是填 Github repository name。大家可以填填看,会观察到一件事,按钮一开始是 disabled
的状态,代表是不能点的 (或是点了没有用),等到有输入值之後,才能点喔:
// component state
const [user, setUser] = useState("");
const [repo, setRepo] = useState("");
// render inputs
<Flex css={{ marginBottom: 12 }}>
<UserInput value={user} onChange={handleChange("user")} />
// 当 user 没有值,按钮的 disabled 会等於 true
<Button disabled={!user} onClick={handleClick("user")}>
Go →
</Button>
</Flex>
<Flex css={{ marginBottom: 12 }}>
<RepoInput value={repo} onChange={handleChange("repo")} />
// 当 user 或 repo 没有值,按钮的 disabled 会等於 true
<Button disabled={!user || !repo} onClick={handleClick("repo")}>
Go →
</Button>
</Flex>
handleChange
会在每次 input
触发 change event
被执行,更新对应 state
的值。那 handleClick
是处理按按钮的事件,我们希望当我们点 UserInput
旁边的按钮而且 user
state 有值,我们会被导去 User 页,怎麽导呢?用 useRouter
回传的 push
method:
const { push } = useRouter();
const handleClick =
(type = "user") =>
() => {
switch (type) {
case "user":
if (user) {
push({ pathname: "/users/[user]", query: { user } });
}
break;
case "repo":
if (user && repo) {
push({ pathname: "/repos/[user]/[repo]", query: { user, repo } });
}
break;
default:
break;
}
};
RepoInput
旁边的按钮需要加一个条件,就是 user
和 repo
都不能是空的,才能导去 Repo 页~
pages/next.js
Next 页是采用 Static Generation 产生出来的,也就是使用 getStaticProps
去抓取该页所需的内容。透过 props
去传递 data
:
// 在服务器端在 build time 执行
export async function getStaticProps() {
// 抓取 vercel/next.js repository 的资料
const res = await fetch("https://api.github.com/repos/vercel/next.js");
const data = await res.json();
// 回传该 page 所需的 props
return {
props: { data },
};
}
Next 页会收到 props
而里面包含 data
:
// 记得把 page component 当 default 的 export 喔!
export default function Next({ data }) {
// 这里的 data 就是 getStaticProps 回传的~
return <RepoCard data={data} />;
}
pages/users/[user].js
User 页的路径是 /users/[user]
,user
为动态资讯,也就是使用 dynamic routes 的方法!在这里我用的是 getStaticProps
和 getStaticPaths
,而且还加了 revalidate
和 fallback = 'blocking'
,让页面会不断更新也不断产生 (生成),所以这页是采用 Incremental Static Regeneration 的模式:
// 抓取该 page 所需的资料
export async function getStaticProps(context) {
// 跟 Github 拿 user 的资料
const res = await fetch(
// context.params.user 就是路径中的 [user]
`https://api.github.com/users/${context.params.user}`
);
const data = await res.json();
return {
props: { data },
revalidate: 24 * 60 * 60, // 至少 24 小时後服务器会重新抓取资料而重新生成该页
};
}
// 抓取这 dynamic route 该产生的 paths
export async function getStaticPaths() {
// 抓取 Github 的所有 users
const res = await fetch("https://api.github.com/users");
const data = await res.json();
// 因为几个 user 代表几个 page,我不想要一次产生这麽多页面,所以只挑前 1000 名
const paths = data.slice(0, 1000).map((u) => ({ params: { user: u.login } }));
// 回传该在 build time 被产生的 paths,不在这清单里的页面,会采取 "blocking" 方式
return { paths, fallback: "blocking" };
}
现在我们来看看User 页的 component:
// 记得把 page component 当 default 的 export
export default function User({ data }) {
// 当 Github API 回传 Not Found 错误
if ("message" in data && data.message === "Not Found") {
// 应该要用 Next.js 的 404 page,可是我先坐在这里Q
return (
<Center column>
<h3>404 Not Found</h3>
<p>Try other user</p>
</Center>
);
}
return (
<>
<Head>
<title>{data.name || "A user"} | 2021 iTHome Day 07</title>
<meta name="description" content="2021 iTHome Day 07 by Jade" />
<link rel="icon" href="/favicon.ico" />
</Head>
<UserCard data={data} />
</>
);
}
pages/repos/[user]/[repo].js
Repo 页的路径是 /repos/[user]/[repo]
,user
和 repo
为动态资讯,一样是用 dynamic routes,不过这页是采用 getServerSideProps
实做出来的,也就是 Server-side Rendering:
// 在服务器端每次收到请求时会执行
export async function getServerSideProps(context) {
const res = await fetch(
// context.params.user 就是路径中的 [user]
// context.params.repo 就是路径中的 [repo]
`https://api.github.com/repos/${context.params.user}/${context.params.repo}`
);
const data = await res.json();
return {
props: { data },
};
}
Repo 页 component 其实长得跟 User 页 很像 (很懒Q),只差在显示的资料喔:
// 记得把 page component 当 default 的 export~
export default function Repo({ data }) {
// 当 Github API 回传 Not Found 错误
if ("message" in data && data.message === "Not Found") {
// 应该要用 Next.js 的 404 page,可是我先坐在这里Q
return (
<Center column>
<h3>404 Not Found</h3>
<p>Try other repo or user</p>
</Center>
);
}
return (
<>
<Head>
<title>
{data.name || "A repo"} by {data.owner.login || "someone"} | 2021
iTHome Day 07
</title>
<meta name="description" content="2021 iTHome Day 07 by Jade" />
<link rel="icon" href="/favicon.ico" />
</Head>
<RepoCard data={data} />
</>
);
}
哇!写完了~ 这个小专案应该有用到前几天学的东东,大家觉得如何呢?有没有什麽问题?欢迎发问喔~
目前还没有办法提供完整的 code,不过有任何问题都可以问我! (希望我回答得出来Q)
祝大家明天上班上课愉快!
晚安 <3
<<: 【Day 08】- 有着资料清洗功能的 Requests-HTML
前言 list 是 Python 中最常见的资料类型,有许多的应用都会用到 list 喔! 今天会先...
ES6 除了新增了上篇的let & const之外,也提供了新的模版字符串(Template...
Colab连结 今天要介绍处理不平衡资料的方法叫 SMOTE (Synthetic Minority...
前言 本来只是想简单带过 .Net , 但不知不觉就写了一大堆, 觉得内容有些混乱, 就在今天花些时...
Q1. 什麽是文件读取漏洞? 骇客可以透过一些手段读取无授权的档案,时常作为资讯收集的一种手段,例如...