<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>前端与跨端 on 扎塔-Zata</title><link>https://www.zata.cc/tags/%E5%89%8D%E7%AB%AF%E4%B8%8E%E8%B7%A8%E7%AB%AF/</link><description>Recent content in 前端与跨端 on 扎塔-Zata</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>扎塔-Zata</copyright><lastBuildDate>Thu, 24 Sep 2026 17:09:06 +0800</lastBuildDate><atom:link href="https://www.zata.cc/tags/%E5%89%8D%E7%AB%AF%E4%B8%8E%E8%B7%A8%E7%AB%AF/index.xml" rel="self" type="application/rss+xml"/><item><title>flutter_tutorial</title><link>https://www.zata.cc/p/flutter_tutorial/</link><pubDate>Thu, 14 Aug 2025 03:58:16 +0800</pubDate><guid>https://www.zata.cc/p/flutter_tutorial/</guid><description>&lt;img src="https://www.zata.cc/p/flutter_tutorial/images/index/index.png" alt="Featured image of post flutter_tutorial" />&lt;p>&lt;img src="https://www.zata.cc/image.png"
loading="lazy"
alt="alt text"
>&lt;/p>
&lt;h4 id="从零开始的fultter之路">从零开始的fultter之路
&lt;/h4>&lt;p>好的，非常乐意！从零开始学习一门新技术是令人兴奋的。我们将一步一步来，从你电脑上什么都没有，一直到运行起你的第一个 Flutter 应用。&lt;/p>
&lt;p>这个过程分为几个关键阶段：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>环境搭建&lt;/strong>：安装所有必要的工具。&lt;/li>
&lt;li>&lt;strong>创建项目&lt;/strong>：使用命令行创建一个新的 Flutter 应用。&lt;/li>
&lt;li>&lt;strong>运行应用&lt;/strong>：在模拟器或真实设备上把应用跑起来。&lt;/li>
&lt;li>&lt;strong>理解代码&lt;/strong>：弄懂项目中最重要的文件和代码是什么意思。&lt;/li>
&lt;li>&lt;strong>动手修改&lt;/strong>：亲手改动代码并立即看到变化。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>第 1 步：环境搭建 (最重要的一步)&lt;/p>
&lt;p>这是最关键也是最容易遇到问题的一步，请务必保持耐心。&lt;/p>
&lt;p>&lt;strong>1. 安装 Flutter SDK&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>前往 Flutter 官方网站：&lt;a class="link" href="https://flutter.dev" target="_blank" rel="noopener"
>&lt;strong>https://flutter.dev&lt;/strong>&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>点击 &amp;ldquo;Get Started&amp;rdquo; 按钮。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>根据你的操作系统 (Windows, macOS, or Linux) 选择相应的安装指南。官方文档是最新、最权威的，请严格按照上面的步骤操作。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>核心操作&lt;/strong>：下载 Flutter SDK 压缩包 -&amp;gt; 解压到你电脑上一个固定的位置 (例如，Windows 的 &lt;code>C:\src\flutter&lt;/code> 或 Mac 的 &lt;code>~/development/flutter&lt;/code>) -&amp;gt; 将 Flutter 的 &lt;code>bin&lt;/code> 目录添加到系统的环境变量 (PATH) 中。&lt;strong>这一步至关重要，否则后续命令会无法识别。&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>2. 安装代码编辑器 (IDE)&lt;/strong>&lt;/p>
&lt;p>我强烈推荐 &lt;strong>Visual Studio Code (VS Code)&lt;/strong>，它轻量且功能强大。&lt;/p>
&lt;ul>
&lt;li>下载并安装 &lt;a class="link" href="https://code.visualstudio.com/" target="_blank" rel="noopener"
>VS Code&lt;/a>。&lt;/li>
&lt;li>打开 VS Code，点击左侧的扩展图标 (Extensions)。&lt;/li>
&lt;li>搜索并安装两个最重要的扩展：
&lt;ul>
&lt;li>&lt;code>Dart&lt;/code>&lt;/li>
&lt;li>&lt;code>Flutter&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>3. 安装目标平台的工具&lt;/strong>&lt;/p>
&lt;p>你需要告诉 Flutter 在哪里运行你的应用。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>如果要开发 Android 应用&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>安装 &lt;a class="link" href="https://developer.android.com/studio" target="_blank" rel="noopener"
>Android Studio&lt;/a>。&lt;/li>
&lt;li>安装完成后，打开 Android Studio，根据向导完成设置。它会自动安装最新的 Android SDK、Android SDK 命令行工具和 Android SDK Build-Tools。&lt;/li>
&lt;li>在 Android Studio 中，打开 &lt;strong>Virtual Device Manager&lt;/strong> (AVD Manager) 并创建一个安卓模拟器 (Emulator)。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>如果要开发 iOS 应用 (仅限 macOS)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>从 Mac App Store 安装 &lt;a class="link" href="https://developer.apple.com/xcode/" target="_blank" rel="noopener"
>Xcode&lt;/a>。&lt;/li>
&lt;li>安装后，打开一次 Xcode 接受许可协议。&lt;/li>
&lt;li>Xcode 会自带 iOS 模拟器 (Simulator)。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>4. 运行 &lt;code>flutter doctor&lt;/code>&lt;/strong>&lt;/p>
&lt;p>这是 Flutter 的“诊断医生”。打开你的终端 (Terminal) 或命令行 (Command Prompt)，运行以下命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">flutter doctor
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个命令会检查你的环境是否准备就绪。第一次运行时，它可能会显示很多错误 (用 &lt;code>[X]&lt;/code> 表示)。&lt;strong>别担心&lt;/strong>，这是正常的。&lt;code>flutter doctor&lt;/code> 会清晰地告诉你缺少什么，以及如何修复它。&lt;/p>
&lt;p>&lt;strong>你的目标是让 &lt;code>flutter doctor&lt;/code> 的所有项目都打上绿色对勾 &lt;code>[✓]&lt;/code>。&lt;/strong> (至少 &amp;ldquo;Flutter&amp;rdquo;, &amp;ldquo;Android toolchain&amp;rdquo; 或 &amp;ldquo;Xcode&amp;rdquo;, 以及 &amp;ldquo;VS Code&amp;rdquo; 这几项要满足)。&lt;/p>
&lt;hr>
&lt;p>第 2 步：创建你的第一个 Flutter 项目&lt;/p>
&lt;p>当 &lt;code>flutter doctor&lt;/code> 显示一切就绪后，就可以创建项目了。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>打开你的终端/命令行。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用 &lt;code>cd&lt;/code> 命令进入一个你想要存放代码的目录，例如 &lt;code>Desktop&lt;/code> 或 &lt;code>Documents&lt;/code>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>运行以下命令来创建项目：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">flutter create my_first_app
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>my_first_app&lt;/code> 是你的项目名称，请使用小写字母和下划线。&lt;/li>
&lt;li>Flutter 会自动为你生成一个包含示例代码的项目文件夹。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>进入项目目录并用 VS Code 打开它：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> my_first_app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">code .
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>(命令 &lt;code>code .&lt;/code> 会用 VS Code 打开当前文件夹)&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>第 3 步：运行你的第一个应用&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>选择设备&lt;/strong>：在 VS Code 的右下角，你会看到一个状态栏。点击 &lt;code>&amp;lt;no device&amp;gt;&lt;/code> 区域，VS Code 会在顶部显示一个可选设备列表 (包括你已经启动的模拟器或连接的真机)。选择一个设备。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>启动调试&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>最简单的方式：按 &lt;code>F5&lt;/code> 键。&lt;/li>
&lt;li>或者，点击顶部菜单的 &amp;ldquo;Run&amp;rdquo; -&amp;gt; &amp;ldquo;Start Debugging&amp;rdquo;。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Flutter 会开始编译代码并安装应用到你选择的设备上。第一次启动会需要几分钟时间。成功后，你会在模拟器上看到 Flutter 的默认计数器应用。&lt;/p>
&lt;p>&lt;strong>恭喜你！你已经成功运行了你的第一个 Flutter 应用！&lt;/strong>&lt;/p>
&lt;hr>
&lt;p>第 4 步：理解项目结构和核心概念&lt;/p>
&lt;p>现在我们来看看项目里有什么。在 VS Code 的左侧文件浏览器中，最重要的两个东西是：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>lib/main.dart&lt;/code>&lt;/strong>: 这是你的应用入口文件，几乎你所有的 Dart 代码都会写在 &lt;code>lib&lt;/code> 文件夹里。默认的计数器应用代码就在这里。&lt;/li>
&lt;li>&lt;strong>&lt;code>pubspec.yaml&lt;/code>&lt;/strong>: 这是项目配置文件。你可以用它来管理项目依赖 (比如添加网络请求、图片缓存等第三方库) 和资源 (比如图片、字体)。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Flutter 核心概念：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>一切皆为 Widget (组件)&lt;/strong>：在 Flutter 中，按钮、文本、布局、甚至整个应用页面，都是 Widget。你通过像搭积木一样组合这些 Widget 来构建用户界面。&lt;/li>
&lt;li>&lt;strong>Widget 树&lt;/strong>：你的应用是由一个 Widget 嵌套另一个 Widget 构成的树状结构。例如，一个 &lt;code>Center&lt;/code> Widget 里面可以包含一个 &lt;code>Text&lt;/code> Widget。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>第 5 步：动手修改 (体验 Hot Reload)&lt;/p>
&lt;p>现在，我们将默认的计数器应用修改成我们之前那个简单的 &amp;ldquo;Hello, World&amp;rdquo; 应用。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>打开 &lt;code>lib/main.dart&lt;/code> 文件。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>删除里面的所有代码&lt;/strong>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将我们第一个回答中的 &amp;ldquo;Hello, World&amp;rdquo; 代码&lt;strong>完整地粘贴&lt;/strong>进去。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="cl">&lt;span class="k">import&lt;/span> &lt;span class="s1">&amp;#39;package:flutter/material.dart&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">void&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">runApp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">const&lt;/span> &lt;span class="n">MyApp&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">class&lt;/span> &lt;span class="nc">MyApp&lt;/span> &lt;span class="kd">extends&lt;/span> &lt;span class="n">StatelessWidget&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">const&lt;/span> &lt;span class="n">MyApp&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="k">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">@&lt;/span>&lt;span class="n">override&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Widget&lt;/span> &lt;span class="n">build&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BuildContext&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">MaterialApp&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nl">title:&lt;/span> &lt;span class="s1">&amp;#39;Welcome to Flutter&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nl">home:&lt;/span> &lt;span class="n">Scaffold&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nl">appBar:&lt;/span> &lt;span class="n">AppBar&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nl">title:&lt;/span> &lt;span class="kd">const&lt;/span> &lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;My First App&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nl">body:&lt;/span> &lt;span class="kd">const&lt;/span> &lt;span class="n">Center&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nl">child:&lt;/span> &lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Hello, World!&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>保存文件 (Ctrl + S 或 Cmd + S)&lt;/strong>。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>神奇的事情发生了！你几乎不需要等待，模拟器里的应用界面会瞬间从计数器更新为你刚刚编写的 &amp;ldquo;Hello, World&amp;rdquo; 页面。这就是 &lt;strong>Hot Reload (热重载)&lt;/strong>，Flutter 的王牌特性，它极大地提升了开发效率。&lt;/p>
&lt;p>下一步该做什么？&lt;/p>
&lt;p>你已经迈出了最坚实的一步。接下来：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>学习 Dart 语言&lt;/strong>：Flutter 是框架，Dart 是编程语言。花点时间学习 Dart 的基本语法（变量、函数、类等）。&lt;/li>
&lt;li>&lt;strong>探索常用 Widget&lt;/strong>：尝试使用更多的基础 Widget，例如 &lt;code>Container&lt;/code> (容器), &lt;code>Row&lt;/code> (水平布局), &lt;code>Column&lt;/code> (垂直布局), &lt;code>ListView&lt;/code> (列表), &lt;code>Image&lt;/code> (图片) 等。&lt;/li>
&lt;li>&lt;strong>跟着官方文档学习&lt;/strong>：Flutter 官方的 &lt;a class="link" href="https://docs.flutter.dev/codelabs" target="_blank" rel="noopener"
>Codelabs&lt;/a> 提供了非常棒的引导式教程。&lt;/li>
&lt;li>&lt;strong>犯错和搜索&lt;/strong>：遇到问题时，学会复制错误信息到 Google 或 Stack Overflow 搜索，这是每个开发者必备的技能。&lt;/li>
&lt;/ol>
&lt;p>编程之路就是不断学习、实践和解决问题的过程。祝你在 Flutter 的世界里玩得开心！&lt;/p></description></item><item><title>Refine: 当 API 即界面，CRUD 不再是体力活</title><link>https://www.zata.cc/p/refine-%E5%BD%93-api-%E5%8D%B3%E7%95%8C%E9%9D%A2crud-%E4%B8%8D%E5%86%8D%E6%98%AF%E4%BD%93%E5%8A%9B%E6%B4%BB/</link><pubDate>Thu, 05 Jun 2025 10:00:00 +0800</pubDate><guid>https://www.zata.cc/p/refine-%E5%BD%93-api-%E5%8D%B3%E7%95%8C%E9%9D%A2crud-%E4%B8%8D%E5%86%8D%E6%98%AF%E4%BD%93%E5%8A%9B%E6%B4%BB/</guid><description>&lt;img src="https://www.zata.cc/p/refine-%E5%BD%93-api-%E5%8D%B3%E7%95%8C%E9%9D%A2crud-%E4%B8%8D%E5%86%8D%E6%98%AF%E4%BD%93%E5%8A%9B%E6%B4%BB/images/index/index.png" alt="Featured image of post Refine: 当 API 即界面，CRUD 不再是体力活" />&lt;h2 id="问题后台管理界面为什么总是重复劳动">问题：后台管理界面，为什么总是重复劳动？
&lt;/h2>&lt;p>你有没有发现，每次写后台管理界面，都在做同样的事情：&lt;/p>
&lt;p>&lt;strong>项目列表页&lt;/strong>：表格 + 分页 + 搜索 + 排序 + 新建按钮 + 编辑按钮 + 删除按钮&lt;/p>
&lt;p>&lt;strong>项目详情页&lt;/strong>：表单 + 字段校验 + 提交按钮 + 错误提示&lt;/p>
&lt;p>&lt;strong>用户管理页&lt;/strong>：表格 + 分页 + 搜索 + 排序 + 新建按钮 + &amp;hellip;&lt;/p>
&lt;p>这些页面长得几乎一样，但你还是要：&lt;/p>
&lt;ol>
&lt;li>写表格组件&lt;/li>
&lt;li>写分页逻辑&lt;/li>
&lt;li>写搜索过滤&lt;/li>
&lt;li>写表单校验&lt;/li>
&lt;li>写 API 调用&lt;/li>
&lt;li>写错误处理&lt;/li>
&lt;li>写乐观更新&lt;/li>
&lt;li>写权限控制&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>一个 CRUD 页面，至少 200 行代码&lt;/strong>。10 个资源就是 2000 行。&lt;/p>
&lt;p>而且这些代码有个特点：&lt;strong>业务逻辑和 UI 混在一起&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 典型的后台页面代码
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">ProjectList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">projects&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setProjects&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">([]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">loading&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setLoading&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">page&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setPage&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">search&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setSearch&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">useEffect&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setLoading&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/api/projects?page=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">page&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;amp;search=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">search&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">res&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setProjects&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">items&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setLoading&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">toast&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;加载失败&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setLoading&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">page&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">search&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ... 还有 100 行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这段代码的问题：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>数据获取逻辑&lt;/strong>（fetch、loading、error）和 &lt;strong>UI 逻辑&lt;/strong>（表格、分页）混在一起&lt;/li>
&lt;li>换一个资源（tasks、users），这段代码几乎要重写一遍&lt;/li>
&lt;li>后端 API 改了，前端要改 N 个地方&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>这就是 Refine 要解决的问题&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="refine-是什么">Refine 是什么？
&lt;/h2>&lt;p>Refine 不是 UI 库，不是组件库，而是一个 &lt;strong>元框架（meta-framework）&lt;/strong>。&lt;/p>
&lt;p>它的核心思想：&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>你的后端 API 长什么样，前端 CRUD 就长什么样。&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;p>用一张图说明：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 后端 API 结构 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ GET /api/projects → 列表页（表格 + 分页 + 搜索） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ GET /api/projects/:id → 详情页（表单展示） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ POST /api/projects → 新建页（表单 + 校验） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ PUT /api/projects/:id → 编辑页（表单 + 校验） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ DELETE /api/projects/:id → 删除功能 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Refine 自动映射
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 前端页面结构 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ /projects → ProjectList 组件 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ /projects/:id → ProjectShow 组件 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ /projects/create → ProjectCreate 组件 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ /projects/edit/:id → ProjectEdit 组件 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>你只需要告诉 Refine：&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>你的 API 在哪里（Data Provider）&lt;/li>
&lt;li>你有哪些资源（Resource）&lt;/li>
&lt;li>用什么 UI（shadcn/ui、Material UI、Ant Design&amp;hellip;）&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Refine 会帮你生成：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>数据获取逻辑&lt;/li>
&lt;li>路由配置&lt;/li>
&lt;li>侧边栏菜单&lt;/li>
&lt;li>权限控制&lt;/li>
&lt;li>表单校验&lt;/li>
&lt;li>错误处理&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="核心概念-1headless无头">核心概念 1：Headless（无头）
&lt;/h2>&lt;p>这是 Refine 最关键的设计决策：&lt;strong>业务逻辑和 UI 完全分离&lt;/strong>。&lt;/p>
&lt;h3 id="refine-提供什么">Refine 提供什么？
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Refine 提供的核心能力
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">useList&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 列表数据获取
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">useOne&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 单条数据获取
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">useCreate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 新建
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">useUpdate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 更新
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">useDelete&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 删除
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">useForm&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 表单状态管理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">useNavigation&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="c1">// 路由跳转
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">useCan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 权限判断
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/core&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这些 hooks &lt;strong>不依赖任何 UI 库&lt;/strong>。它们只负责：&lt;/p>
&lt;ul>
&lt;li>发 API 请求&lt;/li>
&lt;li>管理请求状态（loading、error、data）&lt;/li>
&lt;li>缓存数据&lt;/li>
&lt;li>乐观更新&lt;/li>
&lt;/ul>
&lt;h3 id="你用什么-ui">你用什么 UI？
&lt;/h3>&lt;p>&lt;strong>完全自由&lt;/strong>。Refine 官方提供了集成：&lt;/p>
&lt;ul>
&lt;li>&lt;code>@refinedev/mui&lt;/code> — Material UI&lt;/li>
&lt;li>&lt;code>@refinedev/antd&lt;/code> — Ant Design&lt;/li>
&lt;li>&lt;code>@refinedev/mantine&lt;/code> — Mantine&lt;/li>
&lt;li>&lt;code>@refinedev/chakra&lt;/code> — Chakra UI&lt;/li>
&lt;/ul>
&lt;p>或者用你自己的组件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 用 shadcn/ui 的 Table
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Table&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@/components/ui/table&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useList&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/core&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">ProjectList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isLoading&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useList&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resource&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">project&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableRow&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableRow&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">))}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Table&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>对比 Ant Design Pro&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>维度&lt;/th>
&lt;th>Ant Design Pro&lt;/th>
&lt;th>Refine&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>UI 库&lt;/td>
&lt;td>强绑定 Ant Design&lt;/td>
&lt;td>任意 UI 库&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>业务逻辑&lt;/td>
&lt;td>和 UI 耦合&lt;/td>
&lt;td>完全分离&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>自定义&lt;/td>
&lt;td>要覆盖 Pro 组件&lt;/td>
&lt;td>直接用自己的组件&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>学习成本&lt;/td>
&lt;td>要学 Pro 组件体系&lt;/td>
&lt;td>只学 Refine hooks&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="核心概念-2resource资源">核心概念 2：Resource（资源）
&lt;/h2>&lt;p>Refine 把一切后台对象抽象成 &lt;strong>Resource&lt;/strong>。&lt;/p>
&lt;h3 id="资源的定义">资源的定义
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Refine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">resources&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// API 路径
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">list&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 列表页路由
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">create&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/projects/create&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 新建页路由
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">edit&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/projects/edit/:id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 编辑页路由
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">show&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/projects/:id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 详情页路由
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">meta&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">label&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;项目&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 侧边栏显示名称
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">icon&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ProjectIcon&lt;/span> &lt;span class="p">/&amp;gt;,&lt;/span> &lt;span class="c1">// 侧边栏图标
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;tasks&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">list&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/tasks&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">create&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/tasks/create&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">edit&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/tasks/edit/:id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;users&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">list&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/team&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 路由可以自定义
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="这段配置做了什么">这段配置做了什么？
&lt;/h3>&lt;p>&lt;strong>1. 自动生成侧边栏菜单&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 📁 项目 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ✅ 任务 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 👥 团队 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 自动生成路由表&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/projects → 项目列表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/projects/create → 新建项目
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/projects/edit/123 → 编辑项目 123
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/projects/123 → 项目详情
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/tasks → 任务列表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. 自动关联 Data Provider&lt;/strong>&lt;/p>
&lt;p>当你调用 &lt;code>useList({ resource: &amp;quot;projects&amp;quot; })&lt;/code>，Refine 知道要去 &lt;code>/api/projects&lt;/code> 取数据。&lt;/p>
&lt;p>&lt;strong>对比传统方式&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 传统方式：每个页面单独写
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Route&lt;/span> &lt;span class="na">path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/projects&amp;#34;&lt;/span> &lt;span class="na">element&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">ProjectList&lt;/span> &lt;span class="p">/&amp;gt;}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Route&lt;/span> &lt;span class="na">path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/projects/create&amp;#34;&lt;/span> &lt;span class="na">element&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">ProjectCreate&lt;/span> &lt;span class="p">/&amp;gt;}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Route&lt;/span> &lt;span class="na">path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/projects/edit/:id&amp;#34;&lt;/span> &lt;span class="na">element&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">ProjectEdit&lt;/span> &lt;span class="p">/&amp;gt;}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Route&lt;/span> &lt;span class="na">path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/tasks&amp;#34;&lt;/span> &lt;span class="na">element&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">TaskList&lt;/span> &lt;span class="p">/&amp;gt;}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Route&lt;/span> &lt;span class="na">path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/tasks/create&amp;#34;&lt;/span> &lt;span class="na">element&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&amp;lt;&lt;/span>&lt;span class="nt">TaskCreate&lt;/span> &lt;span class="p">/&amp;gt;}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ... 重复 N 次
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用 Refine，&lt;strong>配置一次，路由、菜单、数据源全部搞定&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="核心概念-3data-provider数据提供者">核心概念 3：Data Provider（数据提供者）
&lt;/h2>&lt;p>这是 Refine 最有价值的设计。&lt;/p>
&lt;h3 id="data-provider-的接口">Data Provider 的接口
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">dataProvider&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 获取列表（带分页、过滤、排序）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">getList&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pagination&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">filters&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">sorters&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">current&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pageSize&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">pagination&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`/api/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">?`&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`_start=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">current&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">pageSize&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;amp;`&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`_end=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">current&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">pageSize&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;amp;`&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">filters&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">f&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">f&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">f&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&amp;amp;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">parseInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">headers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;x-total-count&amp;#39;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 获取单条
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">getOne&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/api/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 新建
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">create&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">variables&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/api/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;POST&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">variables&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 更新
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">update&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">variables&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/api/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;PUT&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">variables&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 删除
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">deleteOne&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/api/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;DELETE&amp;#39;&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="这个接口的意义">这个接口的意义
&lt;/h3>&lt;p>&lt;strong>一次实现，所有页面复用&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 项目列表页
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">ProjectList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useList&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 自动调用 dataProvider.getList({ resource: &amp;#34;projects&amp;#34; })
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 任务列表页
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">TaskList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useList&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;tasks&amp;#34;&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 自动调用 dataProvider.getList({ resource: &amp;#34;tasks&amp;#34; })
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 项目编辑页
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">ProjectEdit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useOne&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">123&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 自动调用 dataProvider.getOne({ resource: &amp;#34;projects&amp;#34;, id: 123 })
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>你不用在每个页面写 fetch&lt;/strong>。Refine 帮你：&lt;/p>
&lt;ul>
&lt;li>拼接 URL&lt;/li>
&lt;li>处理分页参数&lt;/li>
&lt;li>处理过滤参数&lt;/li>
&lt;li>处理排序参数&lt;/li>
&lt;li>缓存响应&lt;/li>
&lt;li>处理错误&lt;/li>
&lt;/ul>
&lt;h3 id="内置的-data-provider">内置的 Data Provider
&lt;/h3>&lt;p>Refine 提供了 15+ 开箱即用的 Data Provider：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Provider&lt;/th>
&lt;th>用途&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>@refinedev/simple-rest&lt;/code>&lt;/td>
&lt;td>标准 REST API&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>@refinedev/graphql&lt;/code>&lt;/td>
&lt;td>GraphQL API&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>@refinedev/strapi&lt;/code>&lt;/td>
&lt;td>Strapi CMS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>@refinedev/supabase&lt;/code>&lt;/td>
&lt;td>Supabase&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>@refinedev/hasura&lt;/code>&lt;/td>
&lt;td>Hasura GraphQL&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>@refinedev/nestjs-crud&lt;/code>&lt;/td>
&lt;td>NestJS CRUD&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>@refinedev/airtable&lt;/code>&lt;/td>
&lt;td>Airtable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>@refinedev/firebase&lt;/code>&lt;/td>
&lt;td>Firebase&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>如果你的后端是 FastAPI&lt;/strong>，写一个自定义 Data Provider 即可：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// customDataProvider.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">dataProvider&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/simple-rest&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">customDataProvider&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">...&lt;/span>&lt;span class="nx">dataProvider&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://your-fastapi.com/api&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 自定义 getList，适配你的 FastAPI 分页格式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">getList&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pagination&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">filters&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">sorters&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`/api/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">?`&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`page=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">pagination&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">current&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;amp;`&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sb">`size=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">pagination&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pageSize&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">items&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span>: &lt;span class="kt">items&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="核心概念-4auth-provider认证提供者">核心概念 4：Auth Provider（认证提供者）
&lt;/h2>&lt;p>认证逻辑也抽象成 Provider：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">authProvider&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 登录
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">login&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">password&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/api/auth/login&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>: &lt;span class="kt">JSON.stringify&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">password&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ok&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">token&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">localStorage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">setItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;token&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">success&lt;/span>: &lt;span class="kt">true&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">success&lt;/span>: &lt;span class="kt">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">message&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;登录失败&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 登出
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">logout&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">localStorage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">removeItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;token&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">success&lt;/span>: &lt;span class="kt">true&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 检查登录状态
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">check&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">token&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">localStorage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;token&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">token&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">?&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">authenticated&lt;/span>: &lt;span class="kt">true&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">authenticated&lt;/span>: &lt;span class="kt">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">redirectTo&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/login&amp;#34;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 获取当前用户
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">getIdentity&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/api/auth/me&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 权限检查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">can&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">action&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">resource&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">authProvider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getIdentity&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">role&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s2">&amp;#34;admin&amp;#34;&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">permissions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">includes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">action&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">:&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用方式">使用方式
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 包裹整个应用
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Refine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">authProvider&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">authProvider&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">App&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Refine&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 在组件中使用
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useCan&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/core&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">DeleteButton&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">projectId&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span>: &lt;span class="kt">canDelete&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useCan&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">action&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;delete&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resource&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">params&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">id&lt;/span>: &lt;span class="kt">projectId&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">canDelete&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">删除&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Refine 会自动处理&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>未登录跳转到 &lt;code>/login&lt;/code>&lt;/li>
&lt;li>登录后跳回原页面&lt;/li>
&lt;li>侧边栏根据权限显示/隐藏菜单项&lt;/li>
&lt;li>按钮根据权限显示/隐藏&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="核心概念-5inferencer自动生成-crud-页面">核心概念 5：Inferencer（自动生成 CRUD 页面）
&lt;/h2>&lt;p>这是最惊艳的功能。&lt;/p>
&lt;h3 id="使用方式-1">使用方式
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 安装 Inferencer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm install @refinedev/inferencer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 运行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npx @refinedev/cli@latest inferencer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="inferencer-做了什么">Inferencer 做了什么？
&lt;/h3>&lt;ol>
&lt;li>&lt;strong>读取你的 API Schema&lt;/strong>（通过 Data Provider）&lt;/li>
&lt;li>&lt;strong>分析字段类型&lt;/strong>：
&lt;ul>
&lt;li>&lt;code>string&lt;/code> → 文本输入框&lt;/li>
&lt;li>&lt;code>number&lt;/code> → 数字输入框&lt;/li>
&lt;li>&lt;code>boolean&lt;/code> → 开关&lt;/li>
&lt;li>&lt;code>date&lt;/code> → 日期选择器&lt;/li>
&lt;li>&lt;code>enum&lt;/code> → 下拉选择&lt;/li>
&lt;li>&lt;code>relation&lt;/code> → 关联选择器&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>生成完整的 CRUD 页面代码&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="生成的代码示例">生成的代码示例
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Inferencer 生成的列表页（简化版）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">useTable&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/antd&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Table&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;antd&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">ProjectList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">tableProps&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useTable&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resource&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">List&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="na">...tableProps&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table.Column&lt;/span> &lt;span class="na">dataIndex&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span> &lt;span class="na">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;ID&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table.Column&lt;/span> &lt;span class="na">dataIndex&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;name&amp;#34;&lt;/span> &lt;span class="na">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;名称&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table.Column&lt;/span> &lt;span class="na">dataIndex&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;status&amp;#34;&lt;/span> &lt;span class="na">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;状态&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table.Column&lt;/span> &lt;span class="na">dataIndex&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;created_at&amp;#34;&lt;/span> &lt;span class="na">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;创建时间&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table.Column&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;操作&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">render&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{(&lt;/span>&lt;span class="nx">record&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">EditButton&lt;/span> &lt;span class="na">hideText&lt;/span> &lt;span class="na">recordItemId&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">DeleteButton&lt;/span> &lt;span class="na">hideText&lt;/span> &lt;span class="na">recordItemId&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Table&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">List&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>这个代码你可以&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>直接使用（功能完整）&lt;/li>
&lt;li>作为起点修改（符合你的需求）&lt;/li>
&lt;li>保留核心逻辑，换 UI（用 shadcn/ui 替换 Ant Design）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="实战用-refine--shadcnui-搭建后台">实战：用 Refine + shadcn/ui 搭建后台
&lt;/h2>&lt;h3 id="步骤-1创建项目">步骤 1：创建项目
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npm create refine-app@latest my-admin -- &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --preset next &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --ui shadcn
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步骤-2配置-data-provider">步骤 2：配置 Data Provider
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/dataProvider.ts
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">dataProvider&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/simple-rest&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">dataProvider&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">...&lt;/span>&lt;span class="nx">dataProvider&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://your-fastapi.com/api&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 适配 FastAPI 的分页格式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">getList&lt;/span>: &lt;span class="kt">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pagination&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">filters&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">sorters&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">URLSearchParams&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">page&lt;/span>: &lt;span class="kt">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pagination&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">current&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">size&lt;/span>: &lt;span class="kt">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pagination&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pageSize&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`/api/&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">?&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">params&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">items&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span>: &lt;span class="kt">items&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">total&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步骤-3配置资源">步骤 3：配置资源
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/app.tsx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Refine&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/core&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">dataProvider&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;./dataProvider&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">App() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Refine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">dataProvider&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">dataProvider&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">resources&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">list&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/projects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">create&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/projects/create&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">edit&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/projects/edit/:id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">meta&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">label&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;项目&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;tasks&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">list&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/tasks&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">create&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/tasks/create&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">edit&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/tasks/edit/:id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">meta&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">label&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;任务&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;users&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">list&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;/team&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">meta&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">label&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;团队&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* 你的路由配置 */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Refine&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步骤-4用-inferencer-生成页面">步骤 4：用 Inferencer 生成页面
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npx @refinedev/cli@latest inferencer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这会在 &lt;code>src/pages/&lt;/code> 下生成：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">pages/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> projects/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> list.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> show.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> create.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> edit.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> tasks/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> list.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> show.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> create.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> edit.tsx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> users/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> list.tsx
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步骤-5美化-ui">步骤 5：美化 UI
&lt;/h3>&lt;p>生成的代码用的是 shadcn/ui 组件。你可以直接修改：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-tsx" data-lang="tsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/pages/projects/list.tsx
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Table&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@/components/ui/table&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">Button&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@/components/ui/button&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useList&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@refinedev/core&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">ProjectList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isLoading&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useList&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">resource&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;projects&amp;#34;&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">isLoading&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">加载中&lt;/span>&lt;span class="p">...&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;p-6&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;flex justify-between mb-4&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text-2xl font-bold&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">项目列表&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">新建项目&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Table&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableHeader&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableRow&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableHead&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">名称&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableHead&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableHead&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">状态&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableHead&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableHead&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">创建时间&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableHead&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableRow&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableHeader&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableBody&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="o">?&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableRow&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">status&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">created_at&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">TableCell&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableRow&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">))}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">TableBody&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">Table&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="对比refine-vs-传统开发">对比：Refine vs 传统开发
&lt;/h2>&lt;p>假设你要开发一个包含 5 个资源的后台（projects、tasks、users、teams、settings）：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>能力&lt;/th>
&lt;th>传统开发&lt;/th>
&lt;th>用 Refine&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>列表分页&lt;/strong>&lt;/td>
&lt;td>每个页面手写（5 × 50 行）&lt;/td>
&lt;td>自动&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>搜索过滤&lt;/strong>&lt;/td>
&lt;td>每个页面手写（5 × 30 行）&lt;/td>
&lt;td>自动&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>排序&lt;/strong>&lt;/td>
&lt;td>每个页面手写（5 × 20 行）&lt;/td>
&lt;td>自动&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>新建表单&lt;/strong>&lt;/td>
&lt;td>每个页面手写（5 × 100 行）&lt;/td>
&lt;td>自动生成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>编辑表单&lt;/strong>&lt;/td>
&lt;td>每个页面手写（5 × 100 行）&lt;/td>
&lt;td>自动生成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>API 错误处理&lt;/strong>&lt;/td>
&lt;td>每个调用手写（N × 10 行）&lt;/td>
&lt;td>统一处理&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>乐观更新&lt;/strong>&lt;/td>
&lt;td>手写（复杂）&lt;/td>
&lt;td>内置&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>权限控制&lt;/strong>&lt;/td>
&lt;td>每个按钮判断（N × 5 行）&lt;/td>
&lt;td>Provider 统一&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>侧边栏菜单&lt;/strong>&lt;/td>
&lt;td>手写配置（50 行）&lt;/td>
&lt;td>自动生成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>路由配置&lt;/strong>&lt;/td>
&lt;td>手写（5 × 4 × 5 行）&lt;/td>
&lt;td>自动生成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>总代码量&lt;/strong>&lt;/td>
&lt;td>&lt;strong>约 3000 行&lt;/strong>&lt;/td>
&lt;td>&lt;strong>约 300 行&lt;/strong>（主要是 Data Provider + UI 美化）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>开发时间对比&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>阶段&lt;/th>
&lt;th>传统开发&lt;/th>
&lt;th>用 Refine&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>搭建框架&lt;/td>
&lt;td>2 天&lt;/td>
&lt;td>0.5 天&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>写 CRUD 页面&lt;/td>
&lt;td>5 天&lt;/td>
&lt;td>0.5 天（Inferencer）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>美化 UI&lt;/td>
&lt;td>2 天&lt;/td>
&lt;td>2 天&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>处理边界情况&lt;/td>
&lt;td>3 天&lt;/td>
&lt;td>1 天&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>总计&lt;/strong>&lt;/td>
&lt;td>&lt;strong>12 天&lt;/strong>&lt;/td>
&lt;td>&lt;strong>4 天&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="refine-的局限">Refine 的局限
&lt;/h2>&lt;h3 id="1-后端-api-要规范">1. 后端 API 要规范
&lt;/h3>&lt;p>Refine 的核心假设：&lt;strong>API 即界面&lt;/strong>。&lt;/p>
&lt;p>如果你的 API 是标准的 RESTful：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GET /api/projects
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GET /api/projects/:id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">POST /api/projects
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PUT /api/projects/:id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DELETE /api/projects/:id
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Refine 可以零成本对接。&lt;/p>
&lt;p>但如果你的 API 比较随意：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GET /api/get_project_list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">POST /api/create_new_project
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GET /api/project_detail?id=123
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你就得写很多自定义 Data Provider，优势减弱。&lt;/p>
&lt;p>&lt;strong>建议&lt;/strong>：用 Refine 之前，先规范后端 API。&lt;/p>
&lt;h3 id="2-学习成本">2. 学习成本
&lt;/h3>&lt;p>Refine 的概念：&lt;/p>
&lt;ul>
&lt;li>Data Provider&lt;/li>
&lt;li>Auth Provider&lt;/li>
&lt;li>Resource&lt;/li>
&lt;li>Access Control&lt;/li>
&lt;li>Live Mode（实时更新）&lt;/li>
&lt;li>Audit Log（操作日志）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>学习曲线&lt;/strong>：中等。比从头写低，比用 Ant Design Pro 高。&lt;/p>
&lt;h3 id="3-复杂场景还是要手写">3. 复杂场景还是要手写
&lt;/h3>&lt;p>如果你的页面：&lt;/p>
&lt;ul>
&lt;li>有复杂的图表&lt;/li>
&lt;li>有拖拽排序&lt;/li>
&lt;li>有复杂的联动逻辑&lt;/li>
&lt;li>有非 CRUD 的业务&lt;/li>
&lt;/ul>
&lt;p>Refine 帮不了你。它只解决 &lt;strong>CRUD 部分&lt;/strong>。&lt;/p>
&lt;p>但好消息是：&lt;strong>Refine 不妨碍你手写&lt;/strong>。你可以在 Refine 项目里混用手写页面。&lt;/p>
&lt;hr>
&lt;h2 id="总结refine-的价值">总结：Refine 的价值
&lt;/h2>&lt;p>Refine 解决了一个真实痛点：&lt;strong>后台管理界面的重复劳动&lt;/strong>。&lt;/p>
&lt;p>它的核心价值：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>API 驱动界面&lt;/strong>：后端 API 结构决定前端页面结构&lt;/li>
&lt;li>&lt;strong>Headless 设计&lt;/strong>：业务逻辑和 UI 完全分离&lt;/li>
&lt;li>&lt;strong>Provider 抽象&lt;/strong>：数据、认证、权限统一管理&lt;/li>
&lt;li>&lt;strong>Inferencer&lt;/strong>：自动生成 CRUD 页面代码&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>适合的场景&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>后台管理系统&lt;/li>
&lt;li>Admin Panel&lt;/li>
&lt;li>Dashboard&lt;/li>
&lt;li>B2B 应用&lt;/li>
&lt;li>内部工具&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>不适合的场景&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>面向 C 端的产品（UI 要求高）&lt;/li>
&lt;li>非 CRUD 为主的业务&lt;/li>
&lt;li>API 非常不规范的后端&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>我的建议&lt;/strong>：&lt;/p>
&lt;p>如果你的项目符合以下条件，强烈推荐尝试 Refine：&lt;/p>
&lt;ol>
&lt;li>后端是 RESTful API（或者可以改成 RESTful）&lt;/li>
&lt;li>大部分页面是 CRUD&lt;/li>
&lt;li>愿意接受新的架构方式&lt;/li>
&lt;li>用 React 技术栈&lt;/li>
&lt;/ol>
&lt;p>Refine + shadcn/ui 是一个很好的组合：&lt;strong>Refine 负责业务逻辑，shadcn/ui 负责美观 UI&lt;/strong>。&lt;/p>
&lt;p>这样既享受了 Refine 的开发效率，又不会变成 Ant Design 那种&amp;quot;一眼模板&amp;quot;的 UI。&lt;/p>
&lt;hr>
&lt;h2 id="参考">参考
&lt;/h2>&lt;ul>
&lt;li>&lt;a class="link" href="https://refine.dev" target="_blank" rel="noopener"
>Refine 官网&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/refinedev/refine" target="_blank" rel="noopener"
>Refine GitHub&lt;/a> - 34,898 ⭐&lt;/li>
&lt;li>&lt;a class="link" href="https://refine.dev/docs/" target="_blank" rel="noopener"
>Refine 文档&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://refine.dev/docs/packages/documentation/inferencer/" target="_blank" rel="noopener"
>Inferencer 文档&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://refine.dev/docs/data/data-provider/" target="_blank" rel="noopener"
>Data Provider 列表&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>React框架使用教程</title><link>https://www.zata.cc/p/react%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/</link><pubDate>Thu, 29 May 2025 13:46:52 +0800</pubDate><guid>https://www.zata.cc/p/react%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/</guid><description>&lt;img src="https://www.zata.cc/p/react%E6%A1%86%E6%9E%B6%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/index.png" alt="Featured image of post React框架使用教程" />&lt;h2 id="目录">目录
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>&lt;a class="link" href="#%e5%9f%ba%e7%a1%80%e7%9f%a5%e8%af%86" >基础知识&lt;/a>&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="#-1-%e5%ad%a6%e4%b9%a0-react-%e5%89%8d%e7%9a%84%e5%87%86%e5%a4%87" >📚 1. 学习 React 前的准备&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%ef%b8%8f-2-%e6%90%ad%e5%bb%ba-react-%e5%bc%80%e5%8f%91%e7%8e%af%e5%a2%83" >🛠️ 2. 搭建 React 开发环境&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#-3-react-%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5" >🧩 3. React 基础概念&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#-4-react-%e6%a0%b8%e5%bf%83%e7%89%b9%e6%80%a7" >🎯 4. React 核心特性&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#-5-%e7%ac%ac%e4%b8%80%e4%b8%aa%e5%8a%a8%e6%89%8b%e5%ae%9e%e8%b7%b5%e5%be%85%e5%8a%9e%e4%ba%8b%e9%a1%b9%e5%88%97%e8%a1%a8-todo-list" >🚀 5. 第一个动手实践：待办事项列表&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#-6-react-hooks-%e7%ae%80%e4%bb%8b" >💡 6. React Hooks 简介&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#-7-%e6%8e%a5%e4%b8%8b%e6%9d%a5%e5%ad%a6%e4%bb%80%e4%b9%88" >🚀 7. 接下来学什么？&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bd%bf%e7%94%a8Vite%e6%9d%a5%e6%9e%84%e5%bb%baReact%e9%a1%b9%e7%9b%ae" >为什么使用Vite来构建React项目&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#-8-%e5%ad%a6%e4%b9%a0%e5%bb%ba%e8%ae%ae" >💖 8. 学习建议&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="#%e5%ae%9e%e6%88%98" >实战&lt;/a>&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="#typescript%e4%b8%ad%e7%9a%84%e4%b8%a4%e7%a7%8d%e7%ac%a6%e5%8f%b7%e5%90%ab%e4%b9%89" >TypeScript中的两种[]符号含义 useState&amp;lt;FileInfo[]&amp;gt;([])&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="基础知识">基础知识
&lt;/h2>&lt;h3 id="-1-学习-react-前的准备">📚 1. 学习 React 前的准备
&lt;/h3>&lt;p>在开始学习 React 之前，你需要对以下技术有一定的了解：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>HTML (超文本标记语言)&lt;/strong>：了解基本的 HTML 结构和标签。&lt;/li>
&lt;li>&lt;strong>CSS (层叠样式表)&lt;/strong>：了解如何使用 CSS 来设置页面样式。&lt;/li>
&lt;li>&lt;strong>JavaScript (JS)&lt;/strong>：这是最重要的。你需要掌握 JavaScript 的基础知识，包括：
&lt;ul>
&lt;li>变量、数据类型、操作符&lt;/li>
&lt;li>函数（特别是箭头函数 &lt;code>=&amp;gt;&lt;/code>）&lt;/li>
&lt;li>数组和对象（包括解构赋值、扩展运算符 &lt;code>...&lt;/code>）&lt;/li>
&lt;li>ES6+ 模块 (&lt;code>import&lt;/code>/&lt;code>export&lt;/code>)&lt;/li>
&lt;li>&lt;code>this&lt;/code> 关键字 (虽然在函数组件中不常用，但理解其基本概念有益)&lt;/li>
&lt;li>异步编程 (Promises, async/await) - 在后续学习中会用到&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="-2-搭建-react-开发环境">🛠️ 2. 搭建 React 开发环境
&lt;/h3>&lt;p>最快上手 React 的方式是使用官方推荐的脚手架工具 &lt;code>Create React App&lt;/code>。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>安装 Node.js 和 npm (或 yarn)&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>React 开发依赖 Node.js 环境。访问 &lt;a class="link" href="https://nodejs.org/" target="_blank" rel="noopener"
>Node.js 官网&lt;/a> 下载并安装最新 LTS (长期支持) 版本。&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://www.zata.cc/Knowledge/temp/images/index/image.png"
loading="lazy"
alt="下载安装"
>&lt;/p>
&lt;ul>
&lt;li>&lt;code>npm&lt;/code> (Node Package Manager) 会随 Node.js 一起安装。你也可以选择使用 &lt;code>yarn&lt;/code> 作为包管理工具。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>创建你的第一个 React 应用&lt;/strong>：
打开你的终端或命令行工具，运行以下命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npx create-react-app my-react-app
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里 &lt;code>my-react-app&lt;/code> 是你的项目名称，你可以替换成任何你喜欢的名字。
&lt;code>npx&lt;/code> 是一个 npm 包运行器，它会下载最新版本的 &lt;code>create-react-app&lt;/code> 并执行它。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>进入项目目录并启动开发服务器&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> my-react-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或者，如果你使用 yarn：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> my-react-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">yarn start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这会启动一个本地开发服务器（通常在 &lt;code>http://localhost:3000&lt;/code>），并在你的浏览器中自动打开应用。当你修改代码并保存时，页面会自动刷新。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="-3-了解项目结构">📂 3. 了解项目结构
&lt;/h3>&lt;p>&lt;code>create-react-app&lt;/code> 会生成一个标准的项目结构：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">my-react-app/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── node_modules/ # 项目依赖的第三方库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── public/ # 存放静态资源
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── index.html # 应用的 HTML 入口文件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── ... # 其他如 favicon.ico, manifest.json 等
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── src/ # 主要的源代码目录
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── App.css # App 组件的样式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── App.js # 主要的 App 组件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── App.test.js # App 组件的测试文件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── index.css # 全局样式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── index.js # JavaScript 的入口文件，将 App 组件渲染到 HTML 中
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── logo.svg # 示例图片
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── ... # 其他辅助文件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── .gitignore # Git 忽略文件配置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── package.json # 项目元数据和依赖项列表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── package-lock.json # (或 yarn.lock) 锁定依赖版本
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── README.md # 项目说明文件
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>&lt;code>public/index.html&lt;/code>&lt;/strong>：这是你的单页面应用的唯一 HTML 文件。React 会将你的组件渲染到这个文件中的某个 DOM 元素（通常是一个 &lt;code>id=&amp;quot;root&amp;quot;&lt;/code> 的 &lt;code>div&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>&lt;code>src/index.js&lt;/code>&lt;/strong>：这是 JavaScript 的入口。它使用 &lt;code>ReactDOM.render()&lt;/code> 方法将你的主组件（通常是 &lt;code>App&lt;/code> 组件）挂载到 &lt;code>public/index.html&lt;/code> 中的 &lt;code>div#root&lt;/code>元素上。&lt;/li>
&lt;li>&lt;strong>&lt;code>src/App.js&lt;/code>&lt;/strong>：这是应用的根组件。你将从这里开始构建你的用户界面。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="-4-react核心概念">✨ 4. React核心概念
&lt;/h3>&lt;h4 id="a-jsx-javascript-xml">a. JSX (JavaScript XML)
&lt;/h4>&lt;p>JSX 是一种 JavaScript 的语法扩展，它允许你在 JavaScript 代码中编写类似 HTML 的结构。浏览器并不直接支持 JSX，它需要通过 Babel 这样的转译器转换为普通的 JavaScript 对象。&lt;/p>
&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;React 学习者&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">element&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">你好&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span> &lt;span class="c1">// JSX 表达式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 上面的 JSX 实际上会被 Babel 转译成类似这样的 JavaScript：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// const element = React.createElement(&amp;#39;h1&amp;#39;, null, &amp;#39;你好, &amp;#39;, name, &amp;#39;!&amp;#39;);
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在 JSX 中：&lt;/p>
&lt;ul>
&lt;li>你可以嵌入 JavaScript 表达式，用花括号 &lt;code>{}&lt;/code> 包裹。&lt;/li>
&lt;li>HTML 属性名需要使用驼峰命名法（例如 &lt;code>className&lt;/code> 而不是 &lt;code>class&lt;/code>，&lt;code>onClick&lt;/code> 而不是 &lt;code>onclick&lt;/code>）。&lt;/li>
&lt;li>所有标签必须闭合（例如 &lt;code>&amp;lt;img /&amp;gt;&lt;/code> 或 &lt;code>&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code>）。&lt;/li>
&lt;/ul>
&lt;h4 id="b-组件-components">b. 组件 (Components)
&lt;/h4>&lt;p>组件是 React 的核心。它们是独立且可复用的代码块，负责渲染 UI 的一部分。React 主要有两种类型的组件：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>函数组件 (Functional Components)&lt;/strong>：这是目前推荐的方式。它们是简单的 JavaScript 函数，接收一个 &lt;code>props&lt;/code> 对象作为参数，并返回一个 React 元素（通常是 JSX）。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/components/Welcome.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">Welcome&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">你好&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Welcome&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用组件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/App.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">Welcome&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;./components/Welcome&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 假设你创建了components目录
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">App&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Welcome&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;张三&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Welcome&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;李四&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">App&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>类组件 (Class Components)&lt;/strong>：这是老版本 React 中常用的方式，基于 ES6 的 &lt;code>class&lt;/code>。它们需要继承 &lt;code>React.Component&lt;/code> 并实现一个 &lt;code>render()&lt;/code> 方法。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/components/Greeting.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">class&lt;/span> &lt;span class="nx">Greeting&lt;/span> &lt;span class="kr">extends&lt;/span> &lt;span class="nx">React&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Component&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">render&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">你好&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">!&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">来自类组件&lt;/span>&lt;span class="p">)&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Greeting&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>建议&lt;/strong>：对于新项目，优先使用函数组件和 Hooks。&lt;/p>
&lt;h4 id="c-props-属性">c. Props (属性)
&lt;/h4>&lt;p>&lt;code>Props&lt;/code> (properties 的缩写) 是组件的配置信息。它们是从父组件传递给子组件的数据。Props 是&lt;strong>只读的&lt;/strong>，子组件不能直接修改接收到的 props。&lt;/p>
&lt;p>示例见上面的 &lt;code>Welcome&lt;/code> 组件，&lt;code>name&lt;/code> 就是一个 prop。&lt;/p>
&lt;h4 id="d-state-状态">d. State (状态)
&lt;/h4>&lt;p>&lt;code>State&lt;/code> 是组件内部私有的数据，它可以随时间变化（例如用户交互、网络响应等）。当组件的 state 改变时，React 会自动重新渲染该组件。&lt;/p>
&lt;p>在函数组件中，我们使用 &lt;code>useState&lt;/code> Hook 来管理 state。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/components/Counter.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useState&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 引入 useState Hook
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">Counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 声明一个名为 count 的 state 变量，初始值为 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// setCount 是一个用来更新 count 的函数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setCount&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">你点击了&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="nx">次&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">setCount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">点我增加&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">setCount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">点我减少&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Counter&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>关键点&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>useState&lt;/code> 返回一个数组：&lt;code>[currentStateValue, functionToUpdateState]&lt;/code>。&lt;/li>
&lt;li>通过调用 &lt;code>setCount(newValue)&lt;/code> 来更新 &lt;code>count&lt;/code> 的值，这会触发组件的重新渲染。&lt;/li>
&lt;li>永远不要直接修改 state (例如 &lt;code>count = count + 1;&lt;/code>)，总是使用 &lt;code>set&lt;/code> 函数。&lt;/li>
&lt;/ul>
&lt;h4 id="e-事件处理-event-handling">e. 事件处理 (Event Handling)
&lt;/h4>&lt;p>React 元素的事件处理和 DOM 元素的事件处理非常相似，但有一些语法差异：&lt;/p>
&lt;ul>
&lt;li>React 事件的命名采用驼峰式，而不是纯小写 (例如 &lt;code>onClick&lt;/code> 而不是 &lt;code>onclick&lt;/code>)。&lt;/li>
&lt;li>通过 JSX，你传递一个函数作为事件处理程序，而不是一个字符串。&lt;/li>
&lt;/ul>
&lt;p>示例见上面的 &lt;code>Counter&lt;/code> 组件中的 &lt;code>onClick&lt;/code> 事件。&lt;/p>
&lt;h4 id="f-条件渲染-conditional-rendering">f. 条件渲染 (Conditional Rendering)
&lt;/h4>&lt;p>你可以根据应用的 state 或 props 来决定渲染哪些组件或元素。常用的方法有：&lt;/p>
&lt;ul>
&lt;li>&lt;code>if&lt;/code> 语句&lt;/li>
&lt;li>逻辑与运算符 &lt;code>&amp;amp;&amp;amp;&lt;/code>&lt;/li>
&lt;li>三元运算符 &lt;code>condition ? trueValue : falseValue&lt;/code>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/components/LoginControl.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useState&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">UserGreeting&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">欢迎回来&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">GuestGreeting&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">请登录&lt;/span>&lt;span class="p">.&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">LoginButton&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">onClick&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>&lt;span class="nx">登录&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">LogoutButton&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">onClick&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>&lt;span class="nx">退出&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">LoginControl&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">isLoggedIn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setIsLoggedIn&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">handleLoginClick&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setIsLoggedIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">handleLogoutClick&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setIsLoggedIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">button&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">greeting&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">isLoggedIn&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">button&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">LogoutButton&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleLogoutClick&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">greeting&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">UserGreeting&lt;/span> &lt;span class="p">/&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">button&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">LoginButton&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleLoginClick&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">greeting&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">GuestGreeting&lt;/span> &lt;span class="p">/&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">greeting&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">button&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* 使用三元运算符的另一种方式 */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="cm">/* {isLoggedIn ? &amp;lt;UserGreeting /&amp;gt; : &amp;lt;GuestGreeting /&amp;gt;}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> {isLoggedIn
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> ? &amp;lt;LogoutButton onClick={handleLogoutClick} /&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> : &amp;lt;LoginButton onClick={handleLoginClick} /&amp;gt;} */&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">LoginControl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="g-列表和-keys-lists-and-keys">g. 列表和 Keys (Lists and Keys)
&lt;/h4>&lt;p>你可以使用 JavaScript 的 &lt;code>map()&lt;/code> 方法来将数组渲染为元素列表。当渲染列表时，React 需要一个特殊的 &lt;code>key&lt;/code> prop 来帮助识别哪些列表项被更改、添加或删除。&lt;code>key&lt;/code> 应该是唯一的字符串，通常使用数据项中的 ID。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// src/components/NumberList.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">ListItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 正确！这里不需要指定 key:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">li&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">li&lt;/span>&lt;span class="p">&amp;gt;;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">NumberList&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">props&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">numbers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">numbers&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">listItems&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 正确！key 应该在数组的上下文中被指定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ListItem&lt;/span> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">()}&lt;/span> &lt;span class="na">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">listItems&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">NumberList&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 在 App.js 中使用:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// import NumberList from &amp;#39;./components/NumberList&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// const numbers = [1, 2, 3, 4, 5];
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// &amp;lt;NumberList numbers={numbers} /&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>重要提示&lt;/strong>：&lt;code>key&lt;/code> 只需要在兄弟节点中唯一，不需要全局唯一。不要使用数组的索引作为 &lt;code>key&lt;/code>，除非列表是静态的且不会重新排序。&lt;/p>
&lt;hr>
&lt;h3 id="-5-第一个动手实践待办事项列表-todo-list">🚀 5. 第一个动手实践：待办事项列表 (Todo List)
&lt;/h3>&lt;p>让我们结合以上概念，尝试构建一个简单的待办事项列表。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>创建 &lt;code>TodoItem.js&lt;/code> 组件 (&lt;code>src/components/TodoItem.js&lt;/code>)&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">TodoItem&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">todo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">toggleTodo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">removeTodo&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">li&lt;/span> &lt;span class="na">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{{&lt;/span> &lt;span class="nx">textDecoration&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">todo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isCompleted&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s1">&amp;#39;line-through&amp;#39;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span> &lt;span class="p">}}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">todo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">toggleTodo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">)}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">todo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isCompleted&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s1">&amp;#39;撤销&amp;#39;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;完成&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">onClick&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">removeTodo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">)}&lt;/span> &lt;span class="na">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{{&lt;/span> &lt;span class="nx">marginLeft&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;10px&amp;#39;&lt;/span> &lt;span class="p">}}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">删除&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">li&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">TodoItem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>创建 &lt;code>TodoList.js&lt;/code> 组件 (&lt;code>src/components/TodoList.js&lt;/code>)&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useState&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">TodoItem&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;./TodoItem&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">TodoList&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">todos&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setTodos&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">text&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;学习 React&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isCompleted&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">text&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;构建一个 Todo 应用&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isCompleted&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">inputValue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setInputValue&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">addTodo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">trim&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 不添加空内容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newTodos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[...&lt;/span>&lt;span class="nx">todos&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isCompleted&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span> &lt;span class="p">}];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setTodos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newTodos&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">toggleTodo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newTodos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[...&lt;/span>&lt;span class="nx">todos&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newTodos&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">isCompleted&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="nx">newTodos&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">isCompleted&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setTodos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newTodos&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">removeTodo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">newTodos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[...&lt;/span>&lt;span class="nx">todos&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newTodos&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">splice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setTodos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">newTodos&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">handleSubmit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">preventDefault&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 阻止表单默认提交行为
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">addTodo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">inputValue&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setInputValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 清空输入框
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;todo-list&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">我的待办事项&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">form&lt;/span> &lt;span class="na">onSubmit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">handleSubmit&lt;/span>&lt;span class="p">}&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;text&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">inputValue&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">onChange&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">setInputValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">target&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="p">)}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">placeholder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;添加新的待办...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;submit&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">添加&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">form&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">todos&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">todo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">index&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TodoItem&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="err">//&lt;/span> &lt;span class="na">注意&lt;/span>&lt;span class="err">：&lt;/span>&lt;span class="na">这里用&lt;/span> &lt;span class="na">index&lt;/span> &lt;span class="na">做&lt;/span> &lt;span class="na">key&lt;/span> &lt;span class="na">仅为演示&lt;/span>&lt;span class="err">，&lt;/span>&lt;span class="na">实际项目中如有唯一&lt;/span> &lt;span class="na">ID&lt;/span> &lt;span class="na">更好&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">index&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">index&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">todo&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">todo&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">toggleTodo&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">toggleTodo&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">removeTodo&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">removeTodo&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">))}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">ul&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">TodoList&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你可能需要一些简单的 CSS 来美化它。在 &lt;code>src/App.css&lt;/code> (或新建一个 &lt;code>TodoList.css&lt;/code> 并导入) 添加：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="c">/* src/App.css 或 src/TodoList.css */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-family&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">sans-serif&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">max-width&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">50&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">auto&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#eee&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">box-shadow&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="nb">rgba&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mf">0.1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">h1&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">text-align&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">center&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#333&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">form&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">display&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">flex&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin-bottom&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">input&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">flex-grow&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#ddd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">button&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="mi">15&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin-left&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#5cb85c&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">white&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">none&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">cursor&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">pointer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">button&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">hover&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#4cae4c&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">ul&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">list-style-type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">none&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">li&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">display&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">flex&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">justify-content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">space-between&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">align-items&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">center&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border-bottom&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#eee&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">li&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">last-child&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border-bottom&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">none&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">li&lt;/span> &lt;span class="nt">button&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#d9534f&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">12&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">todo-list&lt;/span> &lt;span class="nt">li&lt;/span> &lt;span class="nt">button&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="nd">first-of-type&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c">/* 针对 &amp;#34;完成/撤销&amp;#34; 按钮 */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#f0ad4e&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>在 &lt;code>src/App.js&lt;/code> 中使用 &lt;code>TodoList&lt;/code> 组件&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="s1">&amp;#39;./App.css&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 确保引入了样式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="nx">TodoList&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;./components/TodoList&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 确保路径正确
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">App&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;App&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">TodoList&lt;/span> &lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">App&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>现在，你的浏览器应该显示一个可以添加、完成和删除待办事项的列表了！&lt;/p>
&lt;hr>
&lt;h3 id="-6-react-hooks-简介">💡 6. React Hooks 简介
&lt;/h3>&lt;p>Hooks 是 React 16.8 版本引入的新特性，它允许你在不编写 class 的情况下使用 state 以及其他的 React 特性。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;code>useState&lt;/code>&lt;/strong>：如上所述，用于在函数组件中添加 state。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>useEffect&lt;/code>&lt;/strong>：用于处理副作用（side effects），例如数据获取、订阅或手动更改 DOM。它类似于类组件中的 &lt;code>componentDidMount&lt;/code>、&lt;code>componentDidUpdate&lt;/code> 和 &lt;code>componentWillUnmount&lt;/code> 的组合。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">React&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">useEffect&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;react&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">Timer&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">seconds&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setSeconds&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">useEffect&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 这个函数会在组件挂载后以及每次更新后执行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">intervalId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setInterval&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">setSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">prevSeconds&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">prevSeconds&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 清理函数：这个函数会在组件卸载前执行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">clearInterval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">intervalId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="p">[]);&lt;/span> &lt;span class="c1">// 空数组 [] 表示这个 effect 只在组件挂载和卸载时运行一次
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">计时器&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">seconds&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="nx">秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">Timer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>&lt;code>useEffect&lt;/code> 的依赖项数组&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>[]&lt;/code> (空数组)：Effect 只在挂载时运行一次，清理函数在卸载时运行。&lt;/li>
&lt;li>&lt;code>[var1, var2]&lt;/code>：Effect 在挂载时运行，并且在 &lt;code>var1&lt;/code> 或 &lt;code>var2&lt;/code> 改变后的每次渲染时运行。&lt;/li>
&lt;li>不传第二个参数（不推荐）：Effect 在每次渲染后都会运行。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>还有许多其他的 Hooks，如 &lt;code>useContext&lt;/code>, &lt;code>useReducer&lt;/code>, &lt;code>useCallback&lt;/code>, &lt;code>useMemo&lt;/code>, &lt;code>useRef&lt;/code> 等，你可以在后续学习中逐步掌握。&lt;/p>
&lt;hr>
&lt;h3 id="-7-接下来学什么">🚀 7. 接下来学什么？
&lt;/h3>&lt;p>当你掌握了以上基础知识后，可以继续学习：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>表单处理 (Forms)&lt;/strong>：更深入地学习受控组件和非受控组件。&lt;/li>
&lt;li>&lt;strong>组件生命周期 (Lifecycle Methods)&lt;/strong>：虽然 Hooks 简化了许多，但了解类组件的生命周期方法对理解 React 的工作方式仍有帮助。&lt;/li>
&lt;li>&lt;strong>路由 (Routing)&lt;/strong>：使用 &lt;code>React Router&lt;/code> 来构建多页面的单页面应用。&lt;/li>
&lt;li>&lt;strong>状态管理 (State Management)&lt;/strong>：
&lt;ul>
&lt;li>&lt;strong>Context API&lt;/strong>：React 内置的，用于在组件树中共享状态，避免 props drilling (逐层传递 props)。&lt;/li>
&lt;li>&lt;strong>Redux, Zustand, MobX&lt;/strong>：更强大的第三方状态管理库，适用于大型复杂应用。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>数据获取 (Data Fetching)&lt;/strong>：如何从后端 API 获取数据并展示（例如使用 &lt;code>fetch&lt;/code> API 或 &lt;code>axios&lt;/code> 库，并结合 &lt;code>useEffect&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>组件库 (Component Libraries)&lt;/strong>：例如 Material-UI, Ant Design, Chakra UI 等，它们提供了预制的 UI 组件，可以加速开发。&lt;/li>
&lt;li>&lt;strong>测试 (Testing)&lt;/strong>：使用 Jest, React Testing Library 等工具为你的组件编写测试。&lt;/li>
&lt;li>&lt;strong>构建和部署 (Build and Deployment)&lt;/strong>：学习如何将你的 React 应用打包并部署到服务器或静态网站托管平台。&lt;/li>
&lt;li>&lt;strong>TypeScript 与 React&lt;/strong>：为你的 React 项目添加静态类型检查。&lt;/li>
&lt;li>&lt;strong>性能优化 (Performance Optimization)&lt;/strong>：&lt;code>React.memo&lt;/code>, &lt;code>useCallback&lt;/code>, &lt;code>useMemo&lt;/code>, 代码分割等。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="-8-学习建议">💖 8. 学习建议
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>多动手实践&lt;/strong>：理论学习很重要，但通过实际编写代码来巩固知识更重要。&lt;/li>
&lt;li>&lt;strong>阅读官方文档&lt;/strong>：React 的官方文档非常出色，是学习的最佳资源。&lt;/li>
&lt;li>&lt;strong>参与社区&lt;/strong>：在 Stack Overflow, Reddit (r/reactjs), DEV Community 等社区提问和交流。&lt;/li>
&lt;li>&lt;strong>从小项目开始&lt;/strong>：逐步增加项目的复杂度。&lt;/li>
&lt;li>&lt;strong>保持耐心&lt;/strong>：学习曲线可能有些陡峭，但坚持下去就会有收获。&lt;/li>
&lt;/ul>
&lt;h3 id="为什么使用vite来构建react项目">为什么使用Vite来构建React项目
&lt;/h3>&lt;p>“React 和 Vite 项目”是将强大的前端库 React 与现代、极速的构建工具 Vite 结合起来的开发方式。&lt;/p>
&lt;p>这是一种非常流行和高效的组合，Vite 旨在通过提供闪电般的开发服务器启动速度和极快的热模块替换 (HMR) 来显著改善前端开发体验。&lt;/p>
&lt;p>以下是关于 React 和 Vite 项目的详细说明：&lt;/p>
&lt;hr>
&lt;p>🚀 为什么使用 Vite 来构建 React 项目？&lt;/p>
&lt;p>Vite 解决了传统构建工具（如 Create React App 背后的 Webpack）在大型项目中遇到的一些性能瓶颈。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>极速的开发服务器启动：&lt;/strong> Vite 使用浏览器原生的 ES 模块 (ESM) 功能。它不需要在启动前打包整个应用，而是按需提供代码文件，使得开发服务器几乎可以瞬时启动。&lt;/li>
&lt;li>&lt;strong>闪电般的热模块替换 (HMR)：&lt;/strong> 当你修改代码时，Vite 只会更新被修改的模块，而不是重新加载整个页面。这个过程非常快，让你能够几乎实时地看到代码变更的效果，且不会丢失应用状态。&lt;/li>
&lt;li>&lt;strong>优化的构建：&lt;/strong> 在生产环境中，Vite 使用 Rollup 进行打包。Rollup 是一个非常高效的打包器，擅长 tree-shaking（移除未使用的代码）和生成高度优化的代码包。&lt;/li>
&lt;li>&lt;strong>开箱即用的体验：&lt;/strong> Vite 原生支持 TypeScript、JSX、CSS Modules 等，无需繁琐配置。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>🛠️ 如何创建一个 React + Vite 项目？&lt;/p>
&lt;p>创建
一个 React + Vite 项目非常简单。你可以使用 &lt;code>npm&lt;/code>, &lt;code>yarn&lt;/code>, 或 &lt;code>pnpm&lt;/code>。&lt;/p>
&lt;p>打开你的终端，运行以下命令之一：&lt;/p>
&lt;p>&lt;strong>使用 NPM:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npm create vite@latest my-react-app -- --template react
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>使用 Yarn:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">yarn create vite my-react-app --template react
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>使用 PNPM:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pnpm create vite my-react-app --template react
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>🏃‍♂️ 启动项目&lt;/p>
&lt;p>创建完成后，按照终端提示的步骤操作：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>进入项目目录：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> my-react-app
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>安装依赖：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npm install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>启动开发服务器：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npm run dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>执行 &lt;code>npm run dev&lt;/code> 后，你将看到一个本地服务器地址（通常是 &lt;code>http://localhost:5173/&lt;/code>）。在浏览器中打开它，你就能看到你的 React + Vite 应用了。&lt;/p>
&lt;hr>
&lt;p>🆚 与 Create React App (CRA) 的对比&lt;/p>
&lt;p>Vite 通常被视为 Create React App (CRA) 的现代替代品。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">特性&lt;/th>
&lt;th style="text-align: left">Vite&lt;/th>
&lt;th style="text-align: left">Create React App (CRA)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>开发服务器&lt;/strong>&lt;/td>
&lt;td style="text-align: left">原生 ESM，按需编译&lt;/td>
&lt;td style="text-align: left">Webpack 打包，冷启动慢&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>启动速度&lt;/strong>&lt;/td>
&lt;td style="text-align: left">极快（秒级）&lt;/td>
&lt;td style="text-align: left">较慢（随项目增大而变慢）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HMR&lt;/strong>&lt;/td>
&lt;td style="text-align: left">极快（基于 ESM）&lt;/td>
&lt;td style="text-align: left">较慢（通常刷新整个组件）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>构建工具&lt;/strong>&lt;/td>
&lt;td style="text-align: left">Rollup (生产环境)&lt;/td>
&lt;td style="text-align: left">Webpack&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>配置&lt;/strong>&lt;/td>
&lt;td style="text-align: left">简单，&lt;code>vite.config.js&lt;/code>&lt;/td>
&lt;td style="text-align: left">复杂，需 &lt;code>eject&lt;/code> 或 Craco&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>总之，&lt;strong>React + Vite&lt;/strong> 组合提供了当下最顶级的开发体验，特别是对于追求效率和性能的开发者而言。&lt;/p>
&lt;p>你是想了解如何&lt;strong>将旧的 CRA 项目迁移到 Vite&lt;/strong>，还是想知道关于 Vite 配置的&lt;strong>更多细节&lt;/strong>（例如如何配置代理）？&lt;/p>
&lt;hr>
&lt;p>&lt;strong>如何禁止用户在浏览器中查看前端源码&lt;/strong>&lt;/p>
&lt;p>首先，我们必须明确一个核心事实：&lt;strong>你永远无法 100% 阻止用户查看你的前端代码。&lt;/strong>&lt;/p>
&lt;p>Web的根本机制决定了浏览器&lt;strong>必须&lt;/strong>下载你的HTML、CSS和JavaScript文件才能在用户屏幕上渲染出页面。只要代码在浏览器上，技术上总有办法被查看（例如通过开发者工具的“网络”或“来源”面板）。&lt;/p>
&lt;p>但是，你的目标可能不是“完全隐藏”，而是“&lt;strong>防止他人轻易地阅读、理解和复制你的原始源代码&lt;/strong>”。&lt;/p>
&lt;p>对于这个目标，Vite（以及其他现代构建工具）已经为你做了大部分工作。你需要做的主要有以下几件事：&lt;/p>
&lt;p>1. 依赖生产构建（Vite 默认会做）&lt;/p>
&lt;p>你&lt;strong>绝对不能&lt;/strong>将 &lt;code>npm run dev&lt;/code>（开发环境）的代码部署到线上。开发环境为了便于调试，会保留所有原始代码、注释和模块路径。&lt;/p>
&lt;p>你必须使用生产构建命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">npm run build
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个命令会触发 Vite (底层使用 Rollup) 执行以下操作，这已经解决了你 90% 的担忧：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>打包 (Bundling):&lt;/strong> 将你所有的 &lt;code>.js&lt;/code>, &lt;code>.jsx&lt;/code> 模块合并成少数几个文件。&lt;/li>
&lt;li>&lt;strong>转译 (Transpiling):&lt;/strong> 将你的 JSX 语法和 ES6+ 语法转换为浏览器兼容的普通 JavaScript。&lt;/li>
&lt;li>&lt;strong>压缩 (Minification):&lt;/strong> 这是最关键的一步。它会删除所有空格、换行、注释，并将你的变量名（如 &lt;code>const [userInfo, setUserInfo] = useState(null);&lt;/code>）替换成单个字母（如 &lt;code>const [a,b]=useState(null);&lt;/code>）。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>结果：&lt;/strong> 用户在浏览器中看到的将是一堆紧凑、混乱、几乎无法阅读的“乱码”，这已经不是你的“源码”了，而是“编译后的机器码”。&lt;/p>
&lt;p>2. 关键一步：关闭 Source Maps（源映射）&lt;/p>
&lt;p>这是&lt;strong>最重要&lt;/strong>的一步，也是新手最容易忽略的一步。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>什么是 Source Map?&lt;/strong> 它是一个 &lt;code>.map&lt;/code> 文件，作用是&lt;strong>将“压缩后的代码”映射回“你的原始源代码”&lt;/strong>。它的唯一目的是帮助开发者在生产环境（线上）也能轻松地调试错误。&lt;/li>
&lt;li>&lt;strong>你的问题：&lt;/strong> 如果你将 &lt;code>.map&lt;/code> 文件上传到服务器，任何人都可以使用浏览器的开发者工具，像看你本地的 &lt;code>src&lt;/code> 文件夹一样，看到你所有的原始 &lt;code>.jsx&lt;/code> 文件、目录结构和注释。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>如何解决 (在 Vite 中):&lt;/strong>&lt;/p>
&lt;p>Vite 非常智能，&lt;strong>默认情况下在生产构建 (&lt;code>npm run build&lt;/code>) 中是关闭 Source Map 的&lt;/strong>。&lt;/p>
&lt;p>但如果你（或你的同事）之前为了调试方便打开过它，你需要检查你的 &lt;code>vite.config.js&lt;/code> (或 &lt;code>vite.config.ts&lt;/code>) 文件，确保 &lt;code>build.sourcemap&lt;/code> 设置为 &lt;code>false&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// vite.config.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">defineConfig&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;vite&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">react&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;@vitejs/plugin-react&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">defineConfig&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">plugins&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">react&lt;/span>&lt;span class="p">()],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">build&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 确保这一行是 false，或者直接删掉（Vite 默认就是 false）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">sourcemap&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>检查方法：&lt;/strong> 运行 &lt;code>npm run build&lt;/code> 后，检查生成的 &lt;code>dist/assets&lt;/code> 文件夹，确保里面&lt;strong>没有&lt;/strong> &lt;code>.js.map&lt;/code> 或 &lt;code>.css.map&lt;/code> 结尾的文件。&lt;/p>
&lt;p>3. （不推荐）极端方案：代码混淆 (Obfuscation)&lt;/p>
&lt;p>如果你觉得“压缩”还不够，还想让代码逻辑变得更难被逆向工程，下一步就是“代码混淆”。&lt;/p>
&lt;p>混淆器会做一些事情，比如：&lt;/p>
&lt;ul>
&lt;li>将字符串（如 &amp;ldquo;API_KEY_PUBLIC&amp;rdquo;）拆分成 &lt;code>['A','P','I',...].join('')&lt;/code>。&lt;/li>
&lt;li>重写控制流（&lt;code>if/else&lt;/code> 变成复杂的 &lt;code>switch&lt;/code> 或循环）。&lt;/li>
&lt;li>注入无意义的“僵尸代码”。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>为什么不推荐？&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>严重性能问题：&lt;/strong> 混淆后的代码通常体积更大，运行速度更慢。&lt;/li>
&lt;li>&lt;strong>调试噩梦：&lt;/strong> 如果线上出了 bug，你自己也看不懂代码，Source Map 将完全失效。&lt;/li>
&lt;li>&lt;strong>虚假的安全感：&lt;/strong> 专业的逆向工程师依然可以（花更多时间）破解它。&lt;/li>
&lt;/ol>
&lt;p>如果你坚持要用，可以使用 &lt;code>vite-plugin-javascript-obfuscator&lt;/code> 这类插件。&lt;/p>
&lt;hr>
&lt;p>总结：你的最佳策略&lt;/p>
&lt;p>对于 99% 的项目，你只需要做两件事：&lt;/p>
&lt;ol>
&lt;li>始终使用 &lt;code>npm run build&lt;/code> 来打包你的应用。&lt;/li>
&lt;li>确保 &lt;code>vite.config.js&lt;/code> 中 &lt;code>build.sourcemap&lt;/code> 为 &lt;code>false&lt;/code>（或未设置，使用默认值）。&lt;/li>
&lt;/ol>
&lt;p>最后，请记住一个黄金法则：&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>任何敏感信息（如 API 密钥、私有算法、管理员密码）绝对不能放在前端代码中，无论你如何压缩或混淆。&lt;/strong> 这些信息必须始终保留在你的后端服务器上。&lt;/p>
&lt;/blockquote>
&lt;h2 id="实战">实战
&lt;/h2>&lt;h3 id="typescript中的两种符号含义">TypeScript中的两种[]符号含义
&lt;/h3>&lt;h5 id="问题">问题
&lt;/h5>&lt;p>在React的useState中看到这样的代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">files&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setFiles&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">FileInfo&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([]);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为什么&lt;code>&amp;lt;FileInfo[]&amp;gt;&lt;/code>里面也有一个&lt;code>[]&lt;/code>？&lt;/p>
&lt;h5 id="解答">解答
&lt;/h5>&lt;p>这里有两个不同的&lt;code>[]&lt;/code>，它们的含义完全不同：&lt;/p>
&lt;h6 id="1-fileinfo---typescript类型注解中的">1. &lt;code>&amp;lt;FileInfo[]&amp;gt;&lt;/code> - TypeScript类型注解中的&lt;code>[]&lt;/code>
&lt;/h6>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">FileInfo&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里的&lt;code>[]&lt;/code>表示&lt;strong>数组类型&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>FileInfo&lt;/code>是单个文件信息对象的类型&lt;/li>
&lt;li>&lt;code>FileInfo[]&lt;/code>表示&amp;quot;FileInfo对象的数组&amp;quot;&lt;/li>
&lt;li>相当于&lt;code>Array&amp;lt;FileInfo&amp;gt;&lt;/code>的简写形式&lt;/li>
&lt;/ul>
&lt;h6 id="2----初始值中的">2. &lt;code>([])&lt;/code> - 初始值中的&lt;code>[]&lt;/code>
&lt;/h6>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">FileInfo&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里的&lt;code>[]&lt;/code>是&lt;strong>空数组字面量&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>这是JavaScript中创建空数组的语法&lt;/li>
&lt;li>作为useState的初始值传入&lt;/li>
&lt;li>表示状态的起始值是一个空数组&lt;/li>
&lt;/ul>
&lt;h6 id="对比理解">对比理解
&lt;/h6>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 完整写法更清楚地看出区别：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">files&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setFiles&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">Array&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">FileInfo&lt;/span>&lt;span class="p">&amp;gt;&amp;gt;(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nb">Array&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 简化写法：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">files&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setFiles&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">FileInfo&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([]);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h6 id="类比说明">类比说明
&lt;/h6>&lt;p>想象一下：&lt;/p>
&lt;ul>
&lt;li>&lt;code>&amp;lt;FileInfo[]&amp;gt;&lt;/code>像是说&amp;quot;这个盒子专门装FileInfo类型的物品，而且可以装很多个&amp;quot;&lt;/li>
&lt;li>&lt;code>([])&lt;/code>像是说&amp;quot;开始的时候盒子是空的&amp;quot;&lt;/li>
&lt;/ul>
&lt;h6 id="其他例子">其他例子
&lt;/h6>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 数字数组，初始值为空
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">numbers&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setNumbers&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">number&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 字符串数组，初始值有内容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">names&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setNames&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">string&lt;/span>&lt;span class="err">[]&lt;/span>&lt;span class="p">&amp;gt;([&lt;/span>&lt;span class="s2">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Bob&amp;#34;&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 单个对象，不是数组
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">setUser&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">useState&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">FileInfo&lt;/span> &lt;span class="err">|&lt;/span> &lt;span class="na">null&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="总结">总结
&lt;/h5>&lt;p>第一个&lt;code>[]&lt;/code>是&lt;strong>类型定义&lt;/strong>，第二个&lt;code>[]&lt;/code>是&lt;strong>实际的空数组值&lt;/strong>！&lt;/p></description></item></channel></rss>