NextJS Course For React Developers (2022)

1. Introduction




2. File Based Routing

2.1. Installation and Folder Sttructure

创建的时候可以不选 src,不会生成 src 目录

npx create-next-app myapp

2.2. Creating Routes


import React from "react";

const about = () => {
  return <div>about</div>;
};

export default about;
export default function Home() {
  return (
    <>
      <div>Hello World</div>
    </>
  );
}

2.3. Nested Routes


2.4. Dynamic Routes



2.5. Slug (Long Routes)

...表示任意数量的参数

2.6. Extracting Values of Route Params

import { useRouter } from "next/router";
const BookDetails = () => {
  const router = useRouter();
  console.log(router);
  return <div>BookDetails - {router.query["id"]}</div>;
};

export default BookDetails;

import { useRouter } from "next/router";
const Slug = () => {
  let slug = [];
  const router = useRouter();
  slug = router.query.slug;
  return (
    <div>
      <ul>{slug ? slug.map((slug, i) => <li key={i}>{slug}</li>) : <></>}</ul>
    </div>
  );
};

export default Slug;

3. PROJECT - File Based Routing

3.1. Getting Started and Rendering Books

// pages/books/index.js
import React from "react";
import { books } from "../../data/utils";
const index = () => {
  console.log(books);
  return (
    <div className="">
      {books.map((book, i) => (
        <div className="" key={i}>
          <h1>{book.name}</h1>
          <p>{book.description}</p>
          <article>Link</article>
        </div>
      ))}
    </div>
  );
};

export default index;
const books = [
  {
    id: "1",
    name: "Mindset",
    description: "this is my first book",
  },
  {
    id: "1",
    name: "Mindset",
    description: "this is my first book",
  },
  {
    id: "2",
    name: "Mindset2",
    description: "this is my 2 book",
  },
  {
    id: "3",
    name: "Mindset3",
    description: "this is my 3 book",
  },
  {
    id: "4",
    name: "Mindset4",
    description: "this is my 4 book",
  },
  {
    id: "5",
    name: "Mindset5",
    description: "this is my 5 book",
  },
];

const fecthBookFromID = (id) => {
  const fetchedBook = books.find((book) => book.id === id);
  return fetchedBook;
};

export { books, fecthBookFromID };

3.2. Adding CSS Styles to the Pages

import React from "react";
import { books } from "../../data/utils";
const index = () => {
  console.log(books);
  return (
    <div className="">
      {books.map((book, i) => (
        <div
          style={{
            width: 300,
            background: "withesmoke",
            margin: "auto",
            display: "flex",
            flexDirection: "column",
            justifyContent: "center",
            alignItems: "center",
          }}
          key={i}
        >
          <h1>{book.name}</h1>
          <p>{book.description}</p>
          <article
            style={{
              border: "1px solid black",
              padding: 12,
              background: "#ccc",
            }}
          >
            Link
          </article>
        </div>
      ))}
    </div>
  );
};

export default index;
import React from "react";
import { books } from "../../data/utils";
import Link from "next/link";
const index = () => {
  console.log(books);
  return (
    <div className="">
      {books.map((book, i) => (
        <div
          style={{
            width: 300,
            background: "withesmoke",
            margin: "auto",
            display: "flex",
            flexDirection: "column",
            justifyContent: "center",
            alignItems: "center",
          }}
          key={i}
        >
          <h1>{book.name}</h1>
          <p>{book.description}</p>
          <article
            style={{
              border: "1px solid black",
              padding: 12,
              background: "#ccc",
            }}
          >
            <Link href={`/books/${book.id}`}>Detail</Link>
          </article>
        </div>
      ))}
    </div>
  );
};

export default index;

3.4. Creating Book Detail Page (Getting book from ID)

import { useRouter } from "next/router";
import { fecthBookFromID } from "@/data/utils";
const BookDetails = () => {
  const { query } = useRouter();
  const bid = query.id;
  const book = fecthBookFromID(bid);
  return (
    <div
      style={{
        width: 300,
        background: "withesmoke",
        margin: "auto",
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "center",
      }}
      key={book.id}
    >
      <h1>{book.name}</h1>
      <p>{book.description}</p>
    </div>
  );
};

export default BookDetails;

3.5. Summary

4. Pre-rendering and Data Fetching (Static Site Generation)

4.1. Introduction To Pre Rendering


页面先渲染然后呈现,而sap是用户先获取html,然后运行js来渲染

4.2. Two Forms of Pre Rendering

4.3. Creating NextJS App

npx create-next-app myapp

4.4. Creating Data

// public/dummy.json
{
  "books": [
    {
      "name": "Mindest",
      "description": "Mindest Book",
      "id": "1"
    },
    {
      "name": "Mindest2",
      "description": "Mindest Book2",
      "id": "2"
    },
    {
      "name": "Mindest3",
      "description": "Mindest Book3",
      "id": "3"
    },
    {
      "name": "Mindest4",
      "description": "Mindest Book4",
      "id": "4"
    },
    {
      "name": "Mindest5",
      "description": "Mindest Book5",
      "id": "5"
    }
  ]
}

4.5. Connecting and Configuring to Firebase

它在 firebase 里导入了 json,创建数据库

4.6. Creating Route Structure

4.8. Using getStaticProps (Static Site Generation)

后端服务我直接用 json-server 了

import { getBooks } from "@/utils/api";
import React from "react";
import Link from "next/link";
const BookName = ({ books }) => {
  return (
    <div>
      <ul>
        {books.map((book) => (
          <li>
            <div>
              <h1>{book.name}</h1>
              <p>{book.description}</p>
              <aritcle>
                <Link href={"books/" + book.id}>Go to book</Link>
              </aritcle>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default BookName;

/*
    getStaticProps 主要用于构建时落地一些静态数据,
    但不同于 getServerSideProps,
    getStaticProps 默认情况下只会在构建时执行一次,
    之后的每次请求都会使用构建时的数据
    在 ISR、SSG 等场景下还有不同的表现。
*/
export async function getStaticProps() {
  let books = await getBooks();
  return {
    props: {
      books: books,
    },
  };
}
// utils/api.js

// fetch是替代XMLHttpRequest的web api
export async function getBooks() {
  let res = await fetch("http://localhost:5555/books");
  return res.json();
}

4.10. Fetching Book From ID in Detail Page

import { getBookFormId } from "@/utils/api";
import React from "react";

const BookDetail = ({ book }) => {
  return (
    <div>
      <h1>{book.name}</h1>
      <p>{book.description}</p>
    </div>
  );
};

export default BookDetail;

export async function getStaticProps({ params }) {
  const book = await getBookFormId(params.id);
  return {
    props: {
      book,
    },
  };
}
export async function getBookFormId(id) {
  const books = await getBooks();
  const book = books.find((b) => b.id === id);
  return book;
}

4.11. What is getStaticPaths Error

next 不知道 id 到底有多少,不知道需要渲染多少/哪些静态页面

4.12. Using getStaticPaths

import { getBookFormId, getBooks } from "@/utils/api";
import React from "react";

const BookDetail = ({ book }) => {
  return (
    <div>
      <h1>{book.name}</h1>
      <p>{book.description}</p>
    </div>
  );
};

export default BookDetail;

export async function getStaticProps({ params }) {
  const book = await getBookFormId(params.id);
  return {
    props: {
      book,
    },
  };
}

// 提供要渲染的页面的path参数
export async function getStaticPaths() {
  const books = await getBooks();
  const paths = books.map((book) => ({ params: { id: book.id } }));

  return {
    paths: paths,
    fallback: false,
  };
}

4.13. What is Incremental Static Generation

这个方法生成静态页面,但是只在构建期间执行一次,后续数据发生了变化,但是页面不会动态更新数据

4.14. Using Incremental Static Generation

加上 revalidate 就可以指定多久重新获取数据

import { getBookFormId, getBooks } from '@/utils/api'
import React from 'react'

const BookDetail = ({ book }) => {
  return (
    // ...
  )
}

export default BookDetail

export async function getStaticProps({ params }) {
  const book = await getBookFormId(params.id)
  return {
    props: {
      book
    },
    // 10秒后重新获取数据
    revalidate: 10,
  }
}

// 提供要渲染的页面的path参数
export async function getStaticPaths() {
  // ...
}

好像需要手动刷新一下页面才会重新获取

ISR: 静态增量再生

4.15. The fallback key


5. Server Side Generation (Contd.)

5.1. Story Behind Server Side Generation

如果使用 getStaticProps 的 revalidate 来重新获取数据,如果数据量大,则整个页面重新渲染加上请求数据的时间会很长

5.2. What is getServerSideProps

5.3. Using getServerSideProps

import { getBookFormId, getBooks } from '@/utils/api'
import React from 'react'

const BookDetail = ({ book }) => {
  return (
    // ...
  )
}

export default BookDetail

export async function getServerSideProps({ params }) {
  const book = await getBookFormId(params.id)
  return {
    props: {
      book,
    }
  }
}
```

5.4. getStaticProps vs getServerSideProps (Which form to use and when)

5.5. Summary

6. Creating API and Full Stack Apps Using NextJS (API Routing)

6.1. Introduction to API Routing


6.2. Overview and Demo Of The Application That We Are Building

6.3. About API Folder

npx create-next-app book_crud
import { useState } from "react";

export default function Home() {
  const [name, setName] = useState("");
  const data = fetch("/api/hello/")
    .then((res) => res.json())
    .then((data) => setName(data.name));

  return <div className="">{name}</div>;
}
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

export default function handler(req, res) {
  res.status(200).json({ name: "John Doe" });
}

7. API Routing Project ( Full Stack NextJS)

7.1. Creating the API of our application

import fs from "fs";
import path from "path";
function handler(req, res) {
  if (req.method === "GET") {
    const filePath = path.join(process.cwd(), "data", "books.json");
    const fileData = fs.readFileSync(filePath);
    const data = JSON.parse(fileData);
    console.log(data);
    return res.status(200).json({ message: data });
  }
}
export default handler;

7.2. Creating Pages and Components to Call the API

// components/BookList.js
import React, { useEffect, useState } from "react";
import BookItem from "./BookItem";

const BookList = () => {
  const [data, setData] = useState();
  const sendRequest = () => {
    fetch("/api/books/")
      .then((res) => res.json())
      .then((data) => setData(data.message))
      .catch((e) => console.log(e));
  };
  useEffect(() => {
    sendRequest();
  }, []);
  return (
    <div>
      <ul>
        {data &&
          data.map((item, i) => (
            <BookItem
              description={item.description}
              name={item.name}
              id={item.id}
              imgUrl={item.imgUrl}
            />
          ))}
      </ul>
    </div>
  );
};

export default BookList;
// components/BookItem.js
import React from "react";

const BookItem = ({ name, description, id, imgUrl }) => {
  return (
    <li>
      <img src={imgUrl} alt={name} />
      <h3>{name}</h3>
      <p>{description}</p>
    </li>
  );
};

export default BookItem;
// data/books.json
[
  {
    "name": "Mindest",
    "description": "Mindest Book",
    "id": "1",
    "imgUrl": "https://img9.doubanio.com/view/subject/l/public/s34300626.jpg"
  },
  {
    "name": "Mindest2",
    "description": "Mindest Book2",
    "id": "2",
    "imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34192061.jpg"
  },
  {
    "name": "Mindest3",
    "description": "Mindest Book3",
    "id": "3",
    "imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34249411.jpg"
  },
  {
    "name": "Mindest4",
    "description": "Mindest Book4",
    "id": "4",
    "imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34201041.jpg"
  },
  {
    "name": "Mindest5",
    "description": "Mindest Book5",
    "id": "5",
    "imgUrl": "https://img2.doubanio.com/view/subject/l/public/s34072342.jpg"
  }
]
// pages/index.js
import BookList from "@/components/BookList";
export default function Home() {
  return <BookList />;
}

7.3. Adding custom CSS Styling to Books

.listContainer {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  list-style-type: none;
  flex: 1;
  align-content: flex-start;
}
.listItem {
  margin: 10px;
  border: 1px solid #ccc;
  width: 16rem;
  padding: 1rem;
  height: 22rem;
}
.listItem img {
  padding-bottom: 0.2rem;
  width: 100%;
  height: 80%;
}

@media screen and (max-width: 1000px) {
  .listContainer {
    margin: 0 3rem;
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
  }
  .listItem {
    margin: 2rem;
    width: 16rem;
    height: 18rem;
  }
}

@media screen and (max-width: 725px) {
  .listContainer {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
  }
  .listItem {
    margin: 2rem;
    width: 16rem;
    height: 18rem;
  }
}
import React, { useEffect, useState } from "react";
import BookItem from "./BookItem";
import classes from "../styles/Books.module.css";

const BookList = () => {
  // ...
  return (
    <div>
      <ul className={classes.listContainer}>// ...</ul>
    </div>
  );
};

export default BookList;
import React from "react";
import classes from "../styles/Books.module.css";

const BookItem = ({ name, description, id, imgUrl }) => {
  return <li className={classes.listItem}>// ...</li>;
};

export default BookItem;

7.4. Creating API for POST HTTP Request

import fs from "fs";
import path from "path";

function getData() {
  const filePath = path.join(process.cwd(), "data", "books.json");
  const fileData = fs.readFileSync(filePath);
  const data = JSON.parse(fileData);
  return data;
}

function handler(req, res) {
  if (req.method === "GET") {
    const data = getData();
    return res.status(200).json({ message: data });
  } else if (req.method === "POST") {
    const { name, description, imgUrl } = req.body;
    const data = getData();
    const newBook = {
      name,
      description,
      imgUrl,
      id: Date.now(),
    };
    data.push(newBook);
    return res.status(201).json({ message: "Added", book: newBook });
  }
}
export default handler;
.container {
  width: 50%;
  height: 50%;
  margin: auto;
  margin-top: 20px;
}
.formControl {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
const { default: AddBook } = require("@/components/AddBook");

export default function Add() {
  return <AddBook />;
}
import React from "react";
import classes from "../styles/Form.module.css";

const AddBook = () => {
  return (
    <div className={classes.container}>
      <form action="" className={classes.formControl}>
        <label htmlFor="name">name</label>
        <input type="text" name="name" />
        <label htmlFor="description">description</label>
        <input type="text" name="description" />
        <label htmlFor="imgUrl">imgUrl</label>
        <input type="text" name="imgUrl" />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

export default AddBook;

7.5. Creating AddBook Page to add new book

import React, { useState } from "react";
import classes from "../styles/Form.module.css";

const AddBook = () => {
  return (
    <div className={classes.container}>
      <form className={classes.formControl}>
        <label htmlFor="name">name</label>
        <input type="text" name="name" />
        <label htmlFor="description">description</label>
        <input type="text" name="description" />
        <label htmlFor="imgUrl">imgUrl</label>
        <input type="text" name="imgUrl" />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

export default AddBook;

7.6. Handling Form Data

import React, { useState } from "react";
import classes from "../styles/Form.module.css";

const AddBook = () => {
  const [inputs, setInputs] = useState({
    name: "",
    description: "",
    imgUrl: "",
  });
  const handleChange = (e) => {
    setInputs((prevState) => ({
      ...prevState,
      [e.target.name]: e.target.value,
    }));
  };
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(inputs);
  };
  return (
    <div className={classes.container}>
      <form onSubmit={handleSubmit} className={classes.formControl}>
        <label htmlFor="name">name</label>
        <input
          value={inputs.name}
          onChange={handleChange}
          type="text"
          name="name"
        />
        <label htmlFor="description">description</label>
        <input
          value={inputs.description}
          onChange={handleChange}
          type="text"
          name="description"
        />
        <label htmlFor="imgUrl">imgUrl</label>
        <input
          value={inputs.imgUrl}
          onChange={handleChange}
          type="text"
          name="imgUrl"
        />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

export default AddBook;

7.7. Sending POST HTTP Request to API

import React, { useState } from 'react'
import classes from '../styles/Form.module.css'

const AddBook = () => {
    const [inputs, setInputs] = useState(
        { name: "", description: "", imgUrl: '' }
    )
    const handleChange = (e) => {
        // ...
    }
    const sendRequest = () => {
        fetch('/api/books/', {
            method: 'POST',
            body: JSON.stringify({
                name: inputs.name,
                description: inputs.description,
                imgUrl: inputs.imgUrl
            }),
            headers: {
                "Content-Type": "application/json"
            }
        })
            .then(res => res.json())
            .then(data => console.log(data))
    }
    const handleSubmit = (e) => {
        e.preventDefault()
        if (!inputs.name || !inputs.description || !inputs.imgUrl) {
            return
        }
        sendRequest()
    }
    return (
        // ...
    )
}

export default AddBook
import fs from "fs";
import path from "path";

const filePath = path.join(process.cwd(), "data", "books.json");

function getData() {
  const fileData = fs.readFileSync(filePath);
  const data = JSON.parse(fileData);
  return data;
}

function handler(req, res) {
  if (req.method === "GET") {
    const data = getData();
    return res.status(200).json({ message: data });
  } else if (req.method === "POST") {
    const { name, description, imgUrl } = req.body;
    const data = getData();
    const newBook = {
      name,
      description,
      imgUrl,
      id: Date.now(),
    };
    data.push(newBook);

    fs.writeFileSync(filePath, JSON.stringify(data));
    return res.status(201).json({ message: "Added", book: newBook });
  }
}
export default handler;

7.8. Connecting to Real Database (MongoDB Database)

登录 mongodb 官网,点免费试用

npm i mongodb

7.9. Modifying API For MongoDB Operations

import fs from "fs";
import path from "path";
import mongodb, { MongoClient } from "mongodb";

async function handler(req, res) {
  const client = await MongoClient.connect(
    `mongodb+srv://malguy2022:${process.env.SECRET}@cluster0.umzcezh.mongodb.net/?retryWrites=true&w=majority`
  );
  // create db
  const db = client.db("books");

  if (req.method === "GET") {
    const books = await db.collection("books").find().sort().toArray();

    if (!books) {
      return res.status(500).json({ message: "Internal Server Error" });
    }
    return res.status(200).json({ message: books });
  } else if (req.method === "POST") {
    const { name, description, imgUrl } = req.body;
    const newBook = {
      name,
      description,
      imgUrl,
      id: Date.now(),
    };

    const generatedBook = await db.collection("books").insertOne(newBook);
    return res.status(201).json({ message: "Added", book: generatedBook });
  }
}
export default handler;

7.11. Final Optimizations and Validations

import fs from "fs";
import path from "path";
import mongodb, { MongoClient } from "mongodb";

async function handler(req, res) {
  const client = await MongoClient.connect(
    `mongodb+srv://malguy2022:${process.env.SECRET}@cluster0.umzcezh.mongodb.net/?retryWrites=true&w=majority`
  );
  // create db
  const db = client.db("books");

  if (req.method === "GET") {
    let books;
    try {
      books = await db.collection("books").find().sort().toArray();
    } catch (e) {
      console.log(e);
    }

    if (!books) {
      return res.status(500).json({ message: "Internal Server Error" });
    }
    return res.status(200).json({ message: books });
  } else if (req.method === "POST") {
    const { name, description, imgUrl } = req.body;
    if (
      !name ||
      name.trim() === "" ||
      !description ||
      description.trim() === "" ||
      !imgUrl ||
      imgUrl.trim() === ""
    ) {
      return res.status(422).json({ message: "Invalid data" });
    }

    const newBook = {
      name,
      description,
      imgUrl,
      id: Date.now(),
    };

    let generatedBook;
    try {
      generatedBook = await db.collection("books").insertOne(newBook);
    } catch (e) {
      console.log(e);
    }
    return res.status(201).json({ message: "Added", book: generatedBook });
  }
}
export default handler;

7.12. Summary


 上一篇
build your own redis build your own redis
开始02. Introduction to Socket Redis 是服务器/客户端系统的一个例子。多个客户端连接到单个服务器,服务器接收来自 TCP 连接的请求并发回响应。在开始套接字编程之前,我们需要学习几个 Linux 系统调用。
2023-07-15
下一篇 
日语基础语法整理 日语基础语法整理
第一章 日本语概说 世界上语言有几千种,根据形态特点分为孤立语、屈折语、粘着语、抱合语等四大类 孤立语无词形变化,主要依靠词序来表示词在语句中的作用、地位。如汉语、越南语、泰语、马来西亚语等 屈折语主要依靠词尾变化(屈折)来表示词在语句
2023-07-13
  目录