# 基于 NextJS + AI 前端开发指南

By [0xpowpos.eth](https://paragraph.com/@0xpowpos) · 2023-03-20

---

动机
--

最近开发了一款多钱包的资产管理平台——Walletee。旨在更快捷、方便地掌握自己在区块链上所拥有的资产，防止日常在钱包应用中频繁切换地址浪费精力。Walletee 的前端采用 NextJs + Tailwind + Framer Motion 实现，前者为前端开发的基础框架，后两者负责前端样式和动画效果。这是我第一次开发前端应用，因此从一个后端开发人员的角度，记录下整个开发脉络以及我认为核心的要点。

要点知识
----

### 要点一：数据请求链路

首先，我们从一个最常规的问题出发，由浅入深。

**问题：当我们在浏览器中输入一个网址后，页面数据是如何传递的？**

我们（后端工程师）固有认知是：前端向后端发送 HTTP 请求，后端监听、处理并返回最终的结果。我们通常认为的前端就是浏览器，然而对于浏览器是如何拿到前端的资源（包括 HTML、样式、Js 脚本）的过程是模糊的。

**前端服务划分**

事实上，前端服务可以分为两块：

*   服务端
    

请注意和后端服务端区分，不过二者本质上都是存放、运行代码的服务器。试想一下，前端工程师开发完的代码总需要部署吧，如果没有承载前端代码的服务器，总不可能直接将代码部署到大家的浏览器。因此，对于前端项目其实和后端一样，开发人员需要将自己的代码打包部署到服务器上，我们称服务器上运行的代码为服务端。

那么，当你在浏览器上输入一个网址按住回车键时，浏览器对于需要呈现的内容一无所知，它本质上是发送一个 HTTP 请求，然而这个请求不是到达后端服务器，而是转向前端服务器。前端服务器本质上也是一个后端服务，监听 HTTP 请求，并通过路由，拿到这个 HTTP 请求对应页面的前端资源。

*   客户端（浏览器）
    

当浏览器通过上文的 HTTP 请求获取到前端资源之后，然后才开始渲染整个页面的数据，这时候如果有需要一些动态的数据（存储在后端服务器），在浏览器的内部（本质上是从前端服务器拿到的 JS 脚本），才会再次发送 HTTP 请求从 后端服务器 获取，这就回到我们了最早的认知中来。

**初级架构**

![](https://storage.googleapis.com/papyrus_images/87c0c66ed81ab8267698e7793c8df4d513dc98daa0c52ed0f797039c8c5b3afc.png)

### 要点二：服务端 or 客户端渲染

我们继续从一个问题出发。

**问题一：服务器之间可以互相通信，为什么前端服务器不把动态内容组装好，然后输出给客户端？**

![](https://storage.googleapis.com/papyrus_images/d87687865ecc9de45ba48fc7e0b8f54edbe61839a215dce643126abbddb7efa9.png)

主要是从以下几点考虑的：

*   性能
    

将内容部分部分输出可以提高网站性能，因为客户端可以边下载边渲染页面，而不用等待整个页面下载完成。这样可以缩短网页加载时间，提高用户体验。

*   动态性
    

如果所有内容都是提前组装好的，那么前端服务器就无法根据客户端请求动态生成内容。

*   减少前端服务器负担
    

如果前端服务器一次性将所有内容组装好并输出给客户端，则服务器需要处理大量数据和请求。而将内容分成小部分输出可以帮助减轻服务器负担，提高网站的可用性。

**问题二：那是不是说所有的动态组件都交由浏览器处理？**

尽管前端服务器将内容分成小部分输出给客户端优化了网站的性能，但在某些情况下，这并不是最优解，此处不细究了。

可以看出，任何事情不能走向极端，这又是一个权衡的问题。

**终极架构**

![](https://storage.googleapis.com/papyrus_images/525b20a3c6a0288c6d0c743f12e047da9d4536357ab87d43271e36ad54658435.png)

浏览器在初始请求页面资源的时候，**服务端渲染的资源** 会在前端服务器直接生成（通过直接请求后端服务器获取数据）。剩下的资源返回给浏览器后，由浏览器向后端服务器直接索取动态资源，称为 **客户端渲染**。

因此，在开发前端项目时，一定要定位清楚：**_所写的代码究竟是在 前端服务器 运行还是在 浏览器 运行，这很重要_**\*。\*

### 要点三：组件状态

**场景：多钱包资产管理时，用户可以选择「添加钱包」的功能。假设用户数据库中记载的钱包数为 3，登陆后，资产页面已经将 3 个钱包的资产全部加载完毕。这时用户添加一个新的钱包，弹出添加表单输入信息并提交。**

**问题：提交以后需要重新刷新整个页面嘛？能否做到只有资产组件的重新加载和渲染？**

答案是肯定的。要实现局部组件的动态加载和渲染，就需要用到 NextJS 前端框架为我们提供的组件状态。

**状态**

针对上述场景，我们定义了两个组件（理解为 Class ）：Wallet 和 Asset，前者负责渲染用户的钱包信息，后者负责渲染用户钱包的资产信息，后者的输出依赖于前者。

两者的信息，都定义成了状态，如下所示：

*   Wallet 组件定义的一个状态
    

    import { useState } from "react";
    // 仅示例
    const Wallet = () => {
      // 组件定义的状态，初始化为一个空的列表
        const [wallets, setWallets] = useState([]);
      ...
        return (
            <div>
             {// Wallet 组件 HTML 代码}
            </div>
        );
    }
    

*   Asset 组件定义的一个状态
    

    import { useState } from "react";
    // 仅示例
    const Asset = () => {
        // 组件定义的状态，初始化为一个空的列表
        const [assets, setAssets] = useState([]);
      ...
        return (
            <div>
             {// Asset 组件 HTML 代码}
            </div>
        );
    }
    

当钱包增加时，逻辑代码（比如表单的 onSubmit 方法）会触发 wallets 状态的变更。

**重新渲染**

**_组件状态的变更会导致该组件在页面上重新渲染_**。因此可以看到钱包组件页面模块中新增了一个钱包地址。

### 要点四：Hook

然而，资产组件的状态更新比钱包组件要复杂。当用户新增钱包时，资产组件是需要重新向后端服务器发送 HTTP 请求才能拿到对应钱包的资产。与钱包组件状态变更不同的是，钱包组件的状态变更是直接由「用户行为」触发的，即它是在钱包组件内部表单的 OnSubmit 方法中直接 set 的。然而，我们不太可能在 OnSubmit 方法中，再去发起 HTTP 请求获取最新的 Asset 信息，然后更改 Assent 组件的状态，这样两者耦合就太严重，需要一种更优雅的机制。

那么就要引出一个称为 useEffect 的 React 钩子，这是一个重大神器，细节部分此处不做阐述。它让开发人员可以在组件渲染后完成各种操作。**_useEffect 钩子具有两个参数：一个是要执行的函数，另一个是一个依赖项数组。当依赖项数组中的任何值发生变化时，该函数将再次运行。如果依赖项数组为空，则该函数仅在组件第一次渲染时运行_**。

因此，我们可以将 Wallet 组件中的 wallets 状态传递到 Asset 组件中来，当 wallets 状态变更时，触发 useEffect 函数的执行，获取最新的 Asset 信息后，继而触发 assets 状态的变更，从而重新渲染 Asset 组件。

    import { useState } from "react";
    // 仅示例
    const Asset = ({wallets}) => {
        // 组件定义的状态
        const [assets, setAssets] = useState([]);
      ...
      // 动态加载
        useEffect(() => {
        getAssetsAPI(chain.id, DataType.PROTO).then((assets) =>
          setAssets(assets)
        );
      }, [wallets]);
      ...
        return (
            <div>
             {// Asset 组件 HTML 代码}
            </div>
        );
    }
    

要点总结
----

以上是基于 NextJS 前端框架开发时，需要掌握的几大核心思想，其他的无非是项目自身的逻辑或者是一些琐碎的配置。明确你所写的代码运行在哪里，定义好组件的状态，分析清楚组件之间的依赖关系，并利用好 Hook，基本可以让我们在基于 NextJS 前端框架的开发过程中达到事半功倍的效果。

页面布局及样式
-------

对于后端程序员来说这其实是最大的一个门槛，css 样式这些太琐碎了，以往通过检索文档、试验效果不断尝试的效率太低了。所幸 AI 的出现，让我们有了一个强大的助理，对于页面布局和样式，甚至是动画效果这些完全可以去咨询 ChatGPT。对于 Walletee 项目，我采用的是 Tailwind 样式，动画效果使用的是 Framer Motion，然后大部分的代码都是由 AI 生成，我只是一个 prompt 工程师。

Quick Start
-----------

**步骤 1：安装 Node.js**

前往 [Node.js 官网](https://nodejs.org/)，按照提示下载并安装 Node.js。

**步骤 2：创建 Next.js 应用程序**

    npx create-next-app my-app
    cd my-app
    

**步骤 3：安装 tailwindcss 和 framer-motion**

    npm install tailwindcss framer-motion
    

**步骤 4：配置 tailwindcss**

在根目录下创建一个新的 tailwind.config.js 文件，并将以下内容复制粘贴到文件中：

    module.exports = {
      mode: 'jit',
      purge: [
        './pages/**/*.{js,ts,jsx,tsx}',
        './components/**/*.{js,ts,jsx,tsx}'
      ],
      darkMode: false, // or 'media' or 'class'
      theme: {
        extend: {},
      },
      variants: {
        extend: {},
      },
      plugins: [],
    }
    

然后，在 pages/\_app.js文件中，导入 tailwindcss：

    import "tailwindcss/tailwind.css";
    

**步骤 5：配置 framer-motion**

在 pages/\_app.js 文件中，导入 framer-motion：

    import { AnimatePresence } from "framer-motion";
    

然后，将 组件包装在 return 中： import "../styles/globals.css"; import "tailwindcss/tailwind.css"; import { AnimatePresence } from "framer-motion"; function MyApp({ Component, pageProps }) { return ( <AnimatePresence exitBeforeEnter> <div className="bg-gray-100 min-h-screen"> <Component {...pageProps} /> </div> </AnimatePresence> ); } export default MyApp; **步骤 6：开发环境运行** npm run dev **步骤 7：生产环境构建及运行** #构建 npm run build #运行 npm start

---

*Originally published on [0xpowpos.eth](https://paragraph.com/@0xpowpos/nextjs-ai)*
