最近用 Next.js 和 typeorm 完成了前端小白的第一个全栈项目,本文会记录我在做项目的过程中学习到的一些知识点,和遇到的那些奇奇怪怪的 Bug

博客系统 - 献上预览地址,喜欢的话就留下一篇博客吧

先说一下,项目的开发流程

  • 确定需求
  • 初始化项目
  • 前后端开发
  • 部署

确定需求

做博客系统的初衷,是创造一个小空间,让每个人可以发布博客,评论的地方

基于上面的理念,产品需求就出来了

Next.js + typerom 实践 - 博客系统(上)_Next.js

初始化项目

聪明的同学,看了文章标题,就可以推理出,项目使用什么技术了

那当然是 Next.js + Typeorm+ TS

TS 相信大家已经很熟悉了,但是 Next.js 和 Typeorm 又是什么鬼

那就介绍一下

Next.js

Next.js 的定位是 Node.js 全栈框架,优点有很多

  • 页面预渲染 SSR SSG
  • 前后的同构
  • 支持 React
  • 支持 Typescript

缺点

  • 完全没有提供数据操作的功能,解决办法:搭配 Typeorm 或 Sequelize
  • 完全没有提供测试相关的功能,解决办法:搭配 Jest 和 Cypress

想对 Next,js 的渲染方式有更深的了解,可以看看我写的这篇博客 Next.js 的三种渲染方式(BSR、SSG、SSR)

Typeorm

Typeorm 我们可以这样理解 Type + orm

Type 我们可以理解成 Typescript,支持 Typescript 就是我选用这个框架的原因

ORM 是 Object–relational mapping 的简称,这个技术的作用是通过实例对象的语法,完成关系型数据库的操作的技术

从这个例子可以看出,ORM 的写法,明显更方便简洁

SELECT id, first_name, last_name, phone, birth_date, sex
 FROM persons 
 WHERE id = 10
------ 改成 ORM 的写法
p = Person.get(10);
name = p.first_name;复制代码

那么下面就开干吧

Next.js + typerom 实践 - 博客系统(上)_Next.js_02

使用脚手架创建项目

npm init next-app nextjs-blog // next-js-blog 是项目名称复制代码

选择 Default starter app

进入 next-js-blog ,运行 yarn dev

看到这个页面,就代表成功了

Next.js + typerom 实践 - 博客系统(上)_Next.js_03

启动 Typescript

yarn add --dev typescript @types/react @types/node
yarn dev复制代码

然后我们将文件名 index.js 改为 index.tsx

将 index.tsx 改为

import React from "react"
import {NextPage} from 'next';

const Index: NextPage = () => {
  return (
    <div>
      首页
    </div>
  )
}
export default Index;复制代码

tsconfig 加强

在 tsconfig.json 里添加

"noImplicitAny": true

禁用隐式的 any,想学会 TS 就加上吧

知识点:Link 快速导航

详情请参考官网介绍

一般我们实现页面跳转都是使用 a 标签,而 Next 则推荐我们使用  标签,难道是有黑魔法吗?我们一起来揭晓吧

先在项目分别使用 a 标签和 Link 标签,实现首页和第一篇文章的互相跳转

pages/index.tsx

import React from "react"
import {NextPage} from 'next';

const Index: NextPage = () => {
  return (
    <div>
      <a href="/posts/first-post">a 点击这里</a>
      <Link href="/posts/first-post"><a >link 点击这里</a></Link>
    </div>
  )
}
export default Index;复制代码

pages/posts/first-post.tsx

// 第一篇文章
import React from "react"
import {NextPage} from 'next';

const FirstPost: NextPage = () => {
  return (
    <div>First Post</div>
  )
}
export default FirstPost;复制代码

实验开始

首先我们通过 a 标签,进行两个页面之间的跳转,从控制台可以看到,浏览器重新请求了 html css js

Next.js + typerom 实践 - 博客系统(上)_typerom_04

那接下来,我们使用 Link 重复上面的操作,神奇的事情发送了,浏览器只发送了两个请求

其中第二个请求时 webpack, 所以实际上只发送了一个请求,就是 first-post.js

Next.js + typerom 实践 - 博客系统(上)_Next.js_05

我们来揭秘一下黑魔法吧

传统导航

Next.js + typerom 实践 - 博客系统(上)_Next.js_06

当用户点击 a 标签时,请求 html,通过解析 html,请求 html 中引用到的 css 和 js

Link 快速导航

Next.js + typerom 实践 - 博客系统(上)_typerom_07

首次加载,浏览器依次请求 html css js,和传统导航相同

当用户点击 Link 时,解析 Link 标签,通过 AJAX 请求 page2.js,而 page2.js 实际上就是 page2 的 HTML + CSS + JS

请求完 page2.js 之后,浏览器不需要访问 page2,而是通过 page2.js 将 page1 的内容,转换成 page2

优点

  • 页面不会刷新,用 AJAX 请求新页面内容
  • 不会请求重复的 HTML、CSS、JS
  • 自动在页面插入新内容,删除旧内容
  • 因为省了很多请求和解析过程,所以速度极快

知识点:同构代码

由于我们项目使用的时 SSR 渲染方式,也就是服务端渲染,所以写的就是同构代码

同构代码指的是同时在客户端和服务端运行的代码

比如,我们在组件里写一句 console.log('执行了')

你会发现 Node 控制台会输出这句话,Chrome 控制台也会输出这句话

注意差异

  • 不是所有的代码都会运行,有些需要用户触发
  • 不是所有的 API 都能用,比如 window 在 Node 里面就不存在

Next.js API

目前我们写的都是 HTML 页面

那实际开发中我们需要请求 /user /shops 等 API,返回的内容都是 JSON 格式的字符串

我们先来实现一个简单的 API 吧

使用 Next.js API

我们使用路径为 /api/v1/posts 以便和 /posts 区分开来

默认导出函数的类型为 NextApiHandle

该代码只运行在 Node.js 里,不运行在浏览器中

/pages/api/v1/posts.tsx

import {NextApiHandler} from 'next';

const Posts:NextApiHandler = (req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');
  res.write(JSON.stringify({name: 'Jacky'}));
  res.end();
};

export default Posts;复制代码

访问 localhost:3000/api/v1/posts得到

Next.js + typerom 实践 - 博客系统(上)_Next.js_08

表示api可以访问了 (图中的效果是因为我装了Chrome 的插件 JSON viewer)

改造 posts

目前的 posts api 还不能返回博客列表,接下来我们就改造一下吧

由于我们现在还没创建数据库,先在根目录下创建 markdown 目录,写入几篇 md 格式的博客

然后我们借助 gray-matter 从 md 文件中解析数据

lib/posts.tsx 这个文件导出 JSON 数据

import path from "path";
import fs, {promises as fsPromise} from "fs";
import matter from "gray-matter";

export const getPosts = async () => {
  const fileNames = await fsPromise.readdir(find('markdown'))
  return fileNames.map(fileName => {
    const id = fileName.replace(/\.md$/, '')
    const fullPath = find('markdown', fileName)
    const content = fs.readFileSync(fullPath, 'utf-8')
    const {title, date} = matter(content).data
    return {
      id, title, date
    }
  })
}复制代码

搞定了读取数据之后,下面就是完善 Posts API ,当接收到请求时,posts api 会从 lib/posts.tsx 中获取数据,然后返回给前端即可

import {NextApiHandler} from 'next';
import {getPosts} from 'lib/posts';


const Posts: NextApiHandler = async (req, res) => {
  const posts = await getPosts();
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');
  res.write(JSON.stringify(posts));
  res.end();
};
export default Posts;复制代码

此时,再次访问 localhost:3000/api/v1/posts 页面,就能得到 markdown 目录里写的文章了

Next.js + typerom 实践 - 博客系统(上)_Next.js_09

未完待续...

更多博客系统的实现和相关知识,请留意 - Next.js + typerom 实践 - 博客系统(中),还没创作出来(再等等吧)

如果对 Next.js 的三种渲染方式有兴趣,请移步查看 Next.js 的三种渲染方式(BSR、SSG、SSR)