内容介绍
本专题假设你已经具备了基本的 HTML 5 、Javascript(ES6) 和 CSS 3 知识。本专题是为了补全 AXUM 全栈开发所需要的技术栈,而不是从零开始的前端课程,如果你还不具备以上知识,请通过其它途径学习完成之后,再来学习本专题。React: 简介与安装
本章将通过编程世界通用的“Hello World”程序,告诉你:创建 React 应用的几种方法、虚拟DOM、JSX以及将 React 挂载到真实 DOM 的步骤。- 支持试读
React:类式组件、函数式组件及state和props
本章将讨论 React 的类式组件、函数式组件以及 React 两个最重要的属性:`state` 和 `props`。 React: 组件的生命周期
本章将讨论 React 组件的常用生命周期。- 支持试读
React: 事件处理、表单处理及受控组件与非受控组件
本章讨论 React 的事件处理,以及表单处理时涉及两个策略:受控组件与非受控组件。 React: 流程控制与key
本章将讨论 React(JSX) 的条件判断和循环。React: 自定义组件及组件通讯
本章我们将正式开启 React 组件之路。虽然我们之前章节的都叫组件,但整个应用只有一个组件,略显单薄。同时,我们还将讨论组件之间如何进行通讯。React: 路由
本章将讨论 React 的路由:通过路由,你可以制作出“多页面”的系统。React: 状态共享
本章将通过一个非常典型的案例来讨论 React 的状态共享:只有登录用户才能看到某些内容。早期的 React 完全依靠其生态中的 redux 等第三方库来实现状态共享;现在 React 提供了官方的 `Context` 来实现这一目的。React: SEO挑战、服务端渲染及本地存储
经过紧张的学习,React 课程终于暂告一个段落了。本章将是一个相对轻松的内容,我们一起探讨一下 React 应用的 SEO 以及为什么需要服务端渲染,同时对 React 课程做个简单的小结。NextJS: 简介与安装
NextJS 是一个 React 框架,它提供了很多有用的功能把 React 的力量发挥地淋漓尽致。本章我们将开始 NextJS 之旅,首先自然是安装它,然后来一个「你好,NextJS」NextJS: 渲染模式和数据获取
趁你现在对我们刚刚讨论的有关 React 和 SEO 的问题还保持有较强的印象,我们先来讨论 NextJS 是如何利用多种渲染模式来应用 SEO 挑战的。NextJS: 内置组件及自动路由
NextJS 既然是 React 的框架,自然提供了一些内置组件来扩展 React。本章将介绍几个常用的 NextJS 内置组件,同时也将介绍 NextJS 的路由系统。NextJS: 开发博客系统
本章我们将使用 NextJS 开发一个小型的博客系统,你将学习到如何从远程服务器获取数据以及数据过滤、NextJS 常用组件的用法、NextJS 的自动路由等功能。Tailwind:简介与响应式设计的基本原则
TailwindCSS 是一款响应式的、移动设备优先的 CSS 工具类框架。本章对响应式布局、移动设备优先、断点等基本概念进行简要说明;并讨论几种安装 tailwind 的方式。Tailwind: 通过小示例体验它
本章通过两个小示例来体验一下 tailwind 的魔力。Tailwind: 默认配置
Tailwind 定义了一系列变量,比如颜色、大小、间距等。本章将介绍 tailwind 的一些默认设置。Tailwind: 撸一个按钮
本章将带你使用 tailwind 撸一个按钮Tailwind: 使用 flex 和 grid 进行响应式布局
本章我们将讨论使用 `flex` 和 `grid` 进行响应式布局,以及为什么不建议再使用 `float` 进行布局。- 支持试读
Tailwind: 撸一个带图标和动画效果的下拉框
本章将使用 tailwind 实现一个没有任何 Javscript 代码的纯 CSS 的下拉框,把应用到导航栏、菜单栏时,也被称为下拉菜单。同时我们将讨论如何在 tailwind 中使用图标,包括图标的进化史:从字体文件到SVG。 Tailwind: 撸一个报价卡片
本章我们将使用 tailwind 撸一个报价卡片。Tailwind: 撸一个响应式的纯CSS导航栏
本章将使用 tailwind 撸一个响应式的、纯 CSS 的导航栏。Tailwind: 集成到React/NextJS
本章将介绍如何将 Tailwind 集成到 React 或 NextJS 项目中。Tailwind: 复用
本章通过将之前撸的按钮改成 NextJS 版,进而讨论 Tailwind 的复用原则。Tailwind: 配置和插件
本章将讨论如何配置 tailwind,以及几个 tailwind 官方插件。Tailwind: 制作响应式博客
本章我们使用 tailwind 将之前课程中 NextJS 迷你博客改造为响应式的布局。
NextJS: 开发博客系统
本章我们将使用 NextJS 开发一个小型的博客系统,你将学习到如何从远程服务器获取数据以及数据过滤、NextJS 常用组件的用法、NextJS 的自动路由等功能。
我们使用json placeholder提供的模拟数据来实现一个小型的、只读的博客系统(因为这个网站虽然提供了写操作的API,但它只是单纯的返回状态码,并不会把数据真实的写入服务器)。
开发之前,我们先熟悉一下 API 及数据结构
json placeholder API | 说明 |
---|---|
/posts | 100条文章的列表 |
/posts/:id | 指定ID的文章详情 |
/posts/:id/comments | 指定ID的文章的评论 |
/users | 10个用户的列表 |
/users/:id | 指定ID的用户详情 |
模块
模块 | 说明 |
---|---|
首页 | 分成两部分:上面部分显示用户列表;下半部分显示文章列表 |
文章详情 | 显示文章内容及该文章的作者和所有评论 |
用户详情 | 显示该用户的详情信息及发表的文章 |
实现
目录结构
- components -- 组件目录
- PostItems.jsx -- 列表中,单个文章组件
- PostList.jsx -- 文章列表组件
- pages
- posts
- [id].jsx
- users
- [id].jsx
使用客户端渲染实现
本节代码:axum-rs-next-lite-blog-client-side-rendering,你可以先点开这个链接,既能看到执行结果,又能方便的查看代码。
首页
import { useState, useEffect } from 'react';
import Head from 'next/head';
import axios from 'axios';
import Link from 'next/link';
import PostList from '../components/PostList';
export default function Home() {
const [userList, setUserList] = useState([]); // 第 8 行
const [postList, setPostList] = useState([]); // 第 9 行
useEffect(() => { // 第10行
Promise.all([ // 第11行
axios.get('https://jsonplaceholder.typicode.com/users'),
axios.get('https://jsonplaceholder.typicode.com/posts'),
]).then(([{ data: userListRemote }, { data: postListRemote }]) => { // 第14行
setUserList(userListRemote);
setPostList(postListRemote);
});
}, []); // 第18行
return (
<>
<Head> {/* 第21行*/}
<title>AXUM中文网</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head> {/* 第24行*/}
<main>
<section>
<h2>用户列表</h2>
<ul>
{userList.map((user) => ( {/* 第29行*/}
<li key={`user-item-${user.id}`}>
<Link href={`/users/${user.id}`}>{user.name}</Link>
<small>({user.username})</small>
</li>
))} {/* 第34行*/}
</ul>
</section>
<section>
<h2>最新文章</h2>
<PostList userList={userList} postList={postList} limit={5} /> {/* 第39行*/}
</section>
</main>
</>
);
}
-
第8、9行:分别创建两个状态和设置函数。
userList
用于维护用户列表,postList
用于维护文章列表 -
第10~18行:使用
useEffect
hook模拟compontentDidMount
生命周期,从远程服务器获取数据- 第11行:由于我们要同时获取两个接口的数据,所以这里使用
Promise.all
来并发地发起两个 HTTP 请求 - 第14行:这里我们结合了数组解构和对象解构的写法
- 第11行:由于我们要同时获取两个接口的数据,所以这里使用
-
第21~24行:设置所需要的
Head
-
第29~34行:遍历用户列表,这里使用了
Link
组件,跳转到/user/:id
路由上 -
第39行:将
userList
和postList
传递给PostList
组件思考:代码中还有一个额外的
limit
,可以用来控制显示的条数,你有思路实现吗。👉 可以使用Array.slice()
第10~18行:使用 useEffect
hook模拟 compontentDidMount
生命周期,从远程服务器获取数据
- 第11行:由于我们要同时获取两个接口的数据,所以这里使用
Promise.all
来并发地发起两个 HTTP 请求 - 第14行:这里我们结合了数组解构和对象解构的写法
第21~24行:设置所需要的 Head
第29~34行:遍历用户列表,这里使用了 Link
组件,跳转到 /user/:id
路由上
第39行:将 userList
和 postList
传递给 PostList
组件
思考:代码中还有一个额外的
limit
,可以用来控制显示的条数,你有思路实现吗。👉 可以使用Array.slice()
思考:代码中还有一个额外的 limit
,可以用来控制显示的条数,你有思路实现吗。👉 可以使用 Array.slice()
文章列表、文章项组件
除了第8行,其它没什么值得说的:
- 第8行,使用
Array.find()
来查找符合条件的元素。此例中,是要在所有用户列表中,查找当前文章的作者。
文章详情页
import axios from 'axios';
import Link from 'next/link';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router'; // 第4行
export default function PostDetail() {
const { id } = useRouter().query; // 第7行
const [post, setPost] = useState({});
const [comments, setComments] = useState([]);
const [author, setAuthor] = useState({});
useEffect(() => {
Promise.all([ //第12行
axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`),
axios.get(`https://jsonplaceholder.typicode.com/posts/${id}/comments`),
]).then(([{ data: post }, { data: comments }]) => {
setPost(post);
setComments(comments);
axios // 第18行
.get(`https://jsonplaceholder.typicode.com/users/${post.userId}`)
.then(({ data: author }) => {
setAuthor(author);
});//第22行
}); // 第23行
}, []);
return (
<div>
<h1>{post.title}</h1>
<div>
作者:<Link href={`/users/${author.id}`}>{author.name}</Link> {/*第29行*/}
</div>
<p>{post.body}</p>
<hr />
<h3>评论列表</h3>
{comments.map((c) => (
<div key={`comments-${post.id}-${c.id}`}>
<div>
<a href={`mailto:${c.email}`}>{c.name}</a> 说:{/*第37行*/}
</div>
<div>{c.body}</div>
</div>
))}
</div>
);
}
- 第4行:导入
useRouter
hook - 第7行:通过
useRoter
hook,获取动态路由中的参数 - 第12~23行:从远程服务端获取数据
- 第12行:并行发起2个 HTTP 请求,当这两个 HTTP请求成功返回之后,设置状态并在18行又一次发起 HTTP 请求
- 第18行:由于这个请求依赖于12行发起的 HTTP请求的返回结果,所以这里要在上一步HTTP请求拿到结果后再发一次 HTTP 请求
- 第29行:使用
Link
组件跳转到指定路由 - 第37行:直接使用原始的 HTML 的
<a>
,这里因为这个链接并不是路由跳转,所以不能使用Link
- 第12行:并行发起2个 HTTP 请求,当这两个 HTTP请求成功返回之后,设置状态并在18行又一次发起 HTTP 请求
- 第18行:由于这个请求依赖于12行发起的 HTTP请求的返回结果,所以这里要在上一步HTTP请求拿到结果后再发一次 HTTP 请求
用户详情页
import axios from 'axios';
import PostItem from '../../components/PostItem';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
export default function UserDetail() {
const { id } = useRouter().query;
const [user, setUser] = useState({});
const [postList, setPostList] = useState([]);
useEffect(() => {
Promise.all([
axios.get(`https://jsonplaceholder.typicode.com/users/${id}`),
axios.get('https://jsonplaceholder.typicode.com/posts'),
]).then(([{ data: user }, { data: postList }]) => {
setUser(user);
setPostList(postList);
});
}, []);
return (
<div>
<h1>用户详情</h1>
<div>用户名:{user.username}</div>
<div>姓名:{user.name}</div>
<div>邮箱:{user.email}</div>
<div>
{/* 第26行 */}地址:{user.address?.street} {user.address?.suite}, {user.address?.city}{' '}
({user.address?.zipcode})
</div>
<div>电话:{user.phone}</div>
<div>
网站:
<a href="http://{user.website}" target="_blank"> {/* 第32行 */}
{user.website}
</a>
</div>
<hr />
<div>发表的文章:</div>
<ul>
{postList
.filter((post) => post.userId === user.id) {/* 第40行 */}
.map((post) => (
<PostItem post={post} author={user} /> {/* 第42行 */}
))}
</ul>
</div>
);
}
- 第26行:为了确保在未拿到 HTTP 请求的结果之前,页面能正常渲染,这里使用
?
对user.address
进行容错处理 - 第32行:
- 因为是链接到外部地址,所以直接使用
<a>
标签 - 这里存在一个笔误,请读者自行修改:
<a href={...}
——这里应该用字符串模板拼接
- 因为是链接到外部地址,所以直接使用
- 第40行:使用
Array.filter()
对文章列表进行过滤:此处只需要该用户发表的文章 - 第42行:复用
PostItem
——这里漏了key
,请读者自行修改:<PostItem key={...} ... />
- 因为是链接到外部地址,所以直接使用
<a>
标签 - 这里存在一个笔误,请读者自行修改:
<a href={...}
——这里应该用字符串模板拼接
页顶
import Link from 'next/link';
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
return (
<> {/*第6行*/}
<div> {/*第7行*/}
<Link href="/">首页</Link> {/*第8行*/}
</div>{/*第9行*/}
<Component {...pageProps} />
</>{/*第11行*/}
);
}
export default MyApp;
- 为了让每个页面都能快速回到首页,我们在
pages/_app.js
中添加了一个类似于导航栏的组件- 在布局时,这是一种很常用的方式
- 为了满足 React 只能有一个根元素的规范,第6行添加了
<>
——还记得它是什么吗?不记得的话,找找以前的章节吧。
- 在布局时,这是一种很常用的方式
使用服务端渲染实现
本节代码:axum-rs-next-lite-blog,你可以先点开这个链接,既能看到执行结果,又能方便的查看代码。
服务端渲染和客户端渲染几乎一样,除了获取数据的方式和获取动态路由的参数不同。我们以文章详情为例进行分析。其它内容请点击上面的链接直接查看源码。
import axios from 'axios';
import Link from 'next/link';
export default function PostDetail({ post, comments, author }) { // 第4行
return (
<div>
<h1>{post.title}</h1>
<div>
作者:<Link href={`/users/${author.id}`}>{author.name}</Link>
</div>
<p>{post.body}</p>
<hr />
<h3>评论列表</h3>
{comments.map((c) => (
<div key={`comments-${post.id}-${c.id}`}>
<div>
<a href={`mailto:${c.email}`}>{c.name}</a> 说:
</div>
<div>{c.body}</div>
</div>
))}
</div>
);
}
export async function getServerSideProps({ params }) { // 第26行
const { id } = params; // 第27行
const [{ data: post }, { data: comments }] = await Promise.all([
axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`),
axios.get(`https://jsonplaceholder.typicode.com/posts/${id}/comments`),
]);
const { data: author } = await axios.get(
`https://jsonplaceholder.typicode.com/users/${post.userId}`
);
return { props: { post, comments, author } };
}
- 它只要在形参里声明、接收即可
- 它连ID都不用获取:
- 一是不需要
- 二是 如果确实需要,还是可以通过
getServerSideProps()
来传递
- 一是不需要
- 二是 如果确实需要,还是可以通过
getServerSideProps()
来传递
- 它可以接收参数,其中就有
params
,nextjs 会自动把动态路由里的参数通过它传递过来
试一试
-
参照页顶“首页”链接的方法,将
pages/index.js
下的<Head>
组件放到pages/_app.js
里,以便让给所有页面统一设置Head
。 -
参照“文章列表”的方式,试着将“用户列表”也封装成组件
-
让组件目录更规范化,比如:
- components -- 组件目录 - Post - Items.jsx -- 列表中,单个文章组件 - List.jsx -- 文章列表组件
参照页顶“首页”链接的方法,将 pages/index.js
下的 <Head>
组件放到 pages/_app.js
里,以便让给所有页面统一设置 Head
。
参照“文章列表”的方式,试着将“用户列表”也封装成组件
让组件目录更规范化,比如:
- components -- 组件目录
- Post
- Items.jsx -- 列表中,单个文章组件
- List.jsx -- 文章列表组件
本章小结
如何获取动态路由中的参数
方式一:所有渲染模式都能用,且客户端渲染模式只能用这种方式
在 getServerSideProps/getStaticProps
函数里,通过 params
接收。
如何同时(并发的)发起多个 HTTP 请求
使用 Promise.all
可以并发地执行多个异步任务。
对于外部链接,NextJS可能会有警告
可以按照它的警告信息,给<a>
加上noreferrer
等内容。
Tailwind 在召唤你
通过本章的示例,你应该能感受到 NextJS (包括 React)开发的爽快感觉了,回头看一下本章案例——好丑呀。不要慌,马上进入 Tailwind 的学习,有了它之后,不愁它不能美化你的网页,只恨你没有那么多创意。
我和 Tailwind 在下一章等你。