这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天
一、本堂课重点内容:
- 什么是Next.js
- Next.js客户端开发
- Next.js服务端开发
- 核心功能讲解
二、详细知识点介绍:
1. 什么是Next.js
Next.js是开发C端应用的框架。B端应用是挂在内网上,提供给管理员使用,如学生管理系统等,C端应用则是挂在外网上,提供给整个互联网上的用户使用。
Next.js是一个构建于Node.js之上的开源Web开发框架,支持基于React的Web应用程序功能,例如服务端渲染和生成静态网站。
优点:上手快,能力集全,而且覆盖了足够多的性能优化和生态。对于新同学掌握前后端一体化的开发模式很友好。
2. Next.js客户端开发
初始化:npx create-next-app@latest --typescript
CMS仓库地址:github.com/czm12904337…
Demo仓库地址:github.com/czm12904337…
next-env.d.ts用于确保TypeScript编译器选择Next.js类型,可以放到.gitignore中,不需要变更。 next.config.js保存着nextjs的配置,可以补充webpack的一些配置,例如补充别名等。
- 数据注入方式:
- CSR请求数据(客户端数据注入):useEffect钩子
useEffect(() => {
axios.get ...(() => {
// 传递数据
})
}, [])
在SSR和SSG中用这种方式,与CSR的最直接区别在于无法在network查询到请求接口,因此一般不这么使用。
- getInitialProps
在服务器端执行,只能在页面层面进行绑定,采用同构,首次渲染服务器端渲染,路由跳转使用客户端路由。意味着如果使用router跳转当前页,会在客户端执行这部分逻辑。
这个api是走在服务器端的注水过程,因为需要优化SEO,这个请求必须在访问掘金这个路由之前就注入HTML流中,如果走在客户端,是无法获取这些数据的。但是如果存在一些内部跳转,他就会走客户端的路由;而跳转后刷新页面,即直接访问这个页面,就会走服务器端路由,来保证SEO优化直接请求可以拿到所有数据。
对于初学者来说,很容易弄混,于是拆分为getServerSideProps和getStaticProps。
Demo中pages\article[articleId].tsx:
Article.getInitialProps = async (context): Promise<IArticleProps> => {
// debugger; //加入debugger后,内部跳转触发,跳转后刷新页面不触发
const { articleId } = context.query;
const { data } = await axios.get(`${LOCALDOMAIN}/api/articleInfo`, {
params: {
articleId,
},
});
return data;
};
- getServerSideProps
SSR,一定会走服务端,与getInitialProps不同的是即使用route跳转当前页,也只会在服务端执行这部分逻辑。即内部跳转也会在服务器端执行。
与getInitialProps效果非常相似,但返回data时要注意用props包裹。
Demo中pages\article[articleId].tsx:
export const getServerSideProps: GetServerSideProps = async context => {
// debugger; //加入debugger后,无论如何也不触发
const { articleId } = context.query;
const { data } = await axios.get(`${LOCALDOMAIN}/api/articleInfo`, {
params: {
articleId,
},
});
return {
props: data, // 需要拿props包裹
};
};
- getStaticProps
SSG,在服务器端构建时执行,如果设计动态路由(带参数),需要使用getStaticPaths配置所有可能的参数情况。
如果说SSR是点菜现做,SSG就是自助餐,厨师做好了菜,客户自己拿。
getStaticPaths的作用是把所有可能性都列出来,然后把这些可能性都存到CDN中,然后再去访问。但是这种方法还是比较呆,所以建议在比较固定且可能性较少的情况下使用。
Demo中pages\article[articleId].tsx:
ssg;
export const getStaticPaths: GetStaticPaths = async () => ({
paths: [{ params: { articleId: '1' } }],
fallback: false,
});
export const getStaticProps: GetStaticProps = async context => {
const { articleId } = context.params as any;
const { data } = await axios.get(`${LOCALDOMAIN}/api/articleInfo`, {
params: {
articleId,
},
});
return {
props: data,
};
};
3. Next.js服务端开发
- BFF层开发
BFF层不生产数据,只搬运数据
服务端开发是api开发部分,即BFF层开发,和Express等开发类似,区别是并没有参数可以直接区别请求类型。
没有中间件,无论是GET还是POST都可以请求,如果需要添加限制,可以取出request.method进行判断。
- Strapi - headless CMS
CMS层则负责数据的生产
CMS:一个后台管理平台。一个网站的内容不可能一直不更新,但是如果只是修改一下文案,其实不需要研发人员来修改,因此需要一个数据管理平台来维护整个网站上的数据,相当于给运营团队使用的数据库。
Strapi是一个开源项目,可以帮助我们快速地搭建一个CMS平台
Strapi仓库:github.com/strapi/stra…
初始化:npx create-strapi-app my-project --quickstart
创建完打开就会进入网站,就可以进行接口的创建。
一个接口的生成有以下几个过程:
1.content-type builder编辑结构体
2.content manager配置数据源,并且发布
3.setting roles里选择对应角色并勾选要发布的接口类型
4.如果涉及嵌套,要在接口后加上populate=deep参数(需要安装依赖npm install strapi-plugin-populate-deep --save),没安装加参数populate=*,但只能嵌套一层。
如果没有加populate=deep参数,就只会展示基础数据,不会把结构体中的层级遍历出来。
比较重要的是Relation结构体,表示不同的结构体之间存在关联,比如某个结构体是另一个结构体的子结构体,
- 调试方式
在代码中添加debugger,使用JavaScript Debug Terminal进行调试,这个终端是服务器端的控制台。然后在这个终端执行npm run dev,就可以运行并触发断点。
如果希望使用浏览器控制台进行调试,可以在终端执行npm run debugger,这是next.js自己提供的命令。运行后出现的控制台是客户端的控制台,而点击这个控制台页面左上角的Nodejs标志,可以打开服务端的控制台。
4. 核心功能讲解
- 首页功能实现
-
首页 & 动画 & 多媒体适配
-
BFF
-
Strapi
Strapi唯一需要注意的一点是需要populate=deep才能显示子结构体。
- 文章页实现
-
首页 & 动画 & 多媒体适配
-
BFF
-
Strapi分页(/api/articles?pagination[page]=1&pagination[pageSize]=10 //按10个/页分页,返回第一页的数据)
把pageNo和pageSize作为query存入并处理,就可以返回对应数据。
CMS中src\api\article-introduction\controllers\article-introduction.js:
module.exports = createCoreController(
"api::article-introduction.article-introduction",
({ strapi }) => ({
async find(ctx) {
const { pageNo, pageSize, ...params } = ctx.query;
if (pageNo && pageSize) {
ctx.query = {
...params,
"pagination[page]": Number(pageNo),
"pagination[": Number(pageSize),
};
}
const { data, meta } = await super.find(ctx);
return { data: removeAttrsAndId(removeTime(data)), meta };
},
})
);
- 多媒体格式的转换
-markdown 转 html:npm install showdown --save
-html转dom:dangerouslySetlnnerHTML
-公共样式的定义
Demo中pages\article[articleId].tsx:
const Article: NextPage<IArticleProps> = ({ title, author, description, createTime, content }) => {
const converter = new showdown.Converter();
return (
<div className={styles.article}>
<h1 className={styles.title}>{title}</h1>
<div className={styles.info}>
作者:{author} | 创建时间: {createTime}
</div>
<div className={styles.description}>{description}</div>
<div dangerouslySetInnerHTML={{ __html: converter.makeHtml(content) }} className={styles.content} />
</div>
);
};
showdown.Converter()把markdown转成html,再用dangerouslySetInnerHTML={{ __html: converter.makeHtml(content) }} 转化成dom,就可以给他设置className属性了。
- 主题化功能实现
1 .基础样式和背景的抽离
-
主题化context全局注入
-
从注入数据中取出theme和setTheme
-
多进程间的主题同步
Demo中stores\theme.tsx:
export const ThemeContext = createContext<IThemeContextProps>({} as IThemeContextProps);
export const ThemeContextProvider = ({ children }: IProps): JSX.Element => {
const [theme, setTheme] = useState<Themes>(Themes.light);
// 监听本地缓存来同步不同页面间的主题
useEffect(() => {
const checkTheme = (): void => {
const item = (localStorage.getItem('theme') as Themes) || Themes.light;
setTheme(item);
document.getElementsByTagName('html')[0].dataset.theme = item;
};
checkTheme();
window.addEventListener('storage', checkTheme);
return (): void => {
window.removeEventListener('storage', checkTheme);
};
}, []);
return (
<ThemeContext.Provider
value={{
theme,
setTheme: (currentTheme): void => {
setTheme(currentTheme);
localStorage.setItem('theme', currentTheme);
document.getElementsByTagName('html')[0].dataset.theme = currentTheme;
},
}}
>
{children}
</ThemeContext.Provider>
);
};
document.getElementsByTagName('html')[0].dataset.theme = currentTheme;在html流中加入了当前属性。
设置全局scss(global.scss)来控制全局的属性值,在使用时只需要调用即可。
多进程同步:设置不同进程下,页面样式一致。window.addEventListener('storage', checkTheme);监听storage变化,则改变当前页面主题。
将theme和useAgent等逻辑放在单独的文件中,更易于维护。
- http://localhost:3000 和 http://127.0.0.1:3000 的主题无法共享
因为这个使用的是localStorage进行共享,同一个域名可以共用一个localStorage,但是这两个东西是跨域的,所以并不同源,无法共享
三、课后个人总结:
通过本节课程,我明白了Next.js的原理和优点;了解到Next.js的客户端开发方式,包括四种数据注入方式的区别和实现;并且理解了Next.js的客户端开发方式,包括BFF层开发和CMS层开发和客户端与服务端的调试方式;还了解到了首页、文章页和主题化功能的实现。