ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Delphi 12 HTMX与WebStencils:零基础实现网页局部交互

Delphi 12 HTMX与WebStencils:零基础实现网页局部交互

简介:这份PDF面向Delphi开发者,尤其适合使用RAD Studio 12、希望以传统HTML思维快速构建动态Web应用的中高级程序员;它系统讲解HTMX与WebStencils两项技术的配合使用,帮助读者摆脱繁琐的JavaScript与Ajax手写调用,用简洁的HTML属性完成异步请求与局部刷新,并借助WebBroker、WebStencils模板快速搭建页面原型。资源包仅1个PDF文件,约919KB,内容涵盖HTMX基本概念、与JavaScript/AJAX的对比、hx-get与hx-post请求写法、hx-swap内容交换控制,以及WebBroker架构、请求响应处理等章节,结构按由浅入深编排,便于对照查阅。目前已有479人学习。读者可从中获得一套可落地的服务端驱动交互思路:把页面逻辑上移到服务器端,减少客户端脚本依赖,使交互更直观可预测,并掌握用预设HTML片段快速拼装界面的方法,适合需要快速开发与迭代的Delphi Web项目参考。

1. 先弄明白这份资源是什么:在 HTML 标签上写几行属性,网页就能动起来

很多人写惯了桌面程序,一想到做网页就犯怵:HTML、CSS、JavaScript、前端框架,一层叠一层,光让一个按钮点下去换块内容,就要写一串脚本、拼一堆字符串。这份 Delphi 12 的 HTMX & WebStencils 中文版资料,讲的正是绕开这套复杂流程的做法。HTMX 是一套前端写法,你在现有的 HTML 标签上加上hx-get、hx-target这样的属性,页面就能向服务器发起请求,并把返回的一小段 HTML 替换到指定位置,全程不写 JavaScript;WebStencils 是配套的模板引擎,用@开头的语法把数据和页面结构拼起来,让服务器端生成 HTML 变得干净可维护。两者加上 RAD Studio 里的 WebBroker,构成一条从零到能跑通网页的完整路径。适合会一点 Delphi、想往 Web 走但被前端吓退的人,也适合完全零基础、想看清网页交互到底怎么发生的读者。

2. 为什么零基础也值得学:HTMX 把前端复杂度简化了很多

2.1 前端那套麻烦事,究竟麻烦在哪

先看一个再普通不过的需求:列表下面有个“加载更多”按钮,点一下追加十条数据。如果用传统 JavaScript 加法 AJAX 的方式写,代码大致要经历这些环节:拿到按钮元素、绑定点击事件、点击瞬间把按钮置灰防止连点、发起请求、等待返回、判断状态码是不是 200、把返回的 JSON 解析成对象、遍历对象拼出一段 HTML 字符串、找到列表容器、把字符串塞进去、恢复按钮、出错了还要弹个提示。这还没算 loading 动画和重复点击的边界情况。

这段代码本身不难,难的是它散落在各处。一个页面上有五个这样的交互,就有五份长得差不多的脚本;页面结构一改,脚本里的选择器跟着失效;接口字段名调整,前端要同步改。很多人第一次做 Web 项目,时间不是花在业务逻辑上,而是花在“为什么这个按钮点了两次就发出两个请求”这种问题上。这些经验只能自己摸索积累,别人讲一遍你也记不住。

更麻烦的是排查问题时的视角切换。数据不对,你要先判断是前端没发对、后端没收到,还是收到了但返回错了。前端脚本和后端代码分属两套语言、两个调试器,出问题时经常要在两边来回跳。

2.2 一句话记住 HTMX:操作写在标签上,拿回来的是 HTML

HTMX 的思路非常直接:把“发请求”和“换内容”这两件事,变成 HTML 标签上的属性。你不用写document.querySelector,不用写fetch,不用拼字符串,浏览器解析到这个属性,就知道该在什么时候、向哪个地址、发什么请求,以及拿到结果之后放到哪。

关键差别在于返回的内容。传统接口返回 JSON,前端拿到数据后自己负责渲染;HTMX 期望服务器直接返回一段现成的 HTML 片段,浏览器把这段片段塞进目标元素里就完事。渲染这件事回到了服务器端,而服务器端正好是你熟悉的地方——Delphi 代码加上模板。

这个转变带来的直接好处是:页面上任何一个局部的变化,从数据到 HTML 的整条链路都在同一门语言、同一个项目里。哪里显示不对,你去改那个返回片段的处理程序就行,不用两头对。

2.3 四个核心动作:触发、目标、交换、附带数据

把这套写法拆开,其实只有四个概念需要记住,下面这张表建议先抄下来贴在显示器边上。

属性作用常见写法
hx-get用 GET 方式向服务器要内容hx-get="/todo/list"
hx-post用 POST 方式提交数据hx-post="/todo/add"
hx-target返回的内容放到哪个元素里hx-target="#todo-list"
hx-swap用哪种方式替换目标元素innerHTML、outerHTML、beforeend
hx-trigger什么时候触发这次请求click、change、every 2s
hx-vals请求里额外带上的数据hx-vals='{"id":3}'

hx-target的取值是 CSS 选择器,#todo-list表示 id 为todo-list的那个元素,.card表示所有 class 为card的元素。hx-swap的默认值是innerHTML,意思是把目标元素内部的内容整体换掉,元素本身保留;改成outerHTML就把元素本身也换掉;改成beforeend就是追加到末尾,做“加载更多”时最常用。

这四个概念组合起来,就能覆盖绝大多数页面交互:按钮点击取数据、表单提交存数据、输入框变化时实时过滤、下拉框切换时刷新列表。资源里还提到hx-put、hx-delete这类语义化的请求方式,以及hx-select从返回的整页里挑一段出来用,属于进阶内容,第一遍读的时候可以先跳过。

2.4 为什么这套路子对 Delphi 开发者格外友好

Delphi 开发者长期待在一个“事件驱动 + 可视化设计”的环境里,思考方式天然贴合这套模型:用户点了按钮,触发一段代码,代码处理完给出结果。HTMX 把这个模型搬到了浏览器上,只不过“事件”变成了 HTML 属性,“结果”变成了一段 HTML 文本。

服务器端那边,资源里用的是 WebBroker。它是 RAD Studio 自带的 Web 框架,核心就三个东西:请求对象、响应对象、处理程序。你在 WebModule 上放一个动作项,写一个事件处理函数,往Response.Content里写内容,一个接口就有了。不需要额外装框架,不需要配一堆中间件,也不需要理解路由注册那一套。

我第一次用这种方式做页面时,最意外的是调试体验。以前查一个前端问题,要开浏览器开发者工具、看网络请求、打断点看变量;现在大部分问题直接在 Delphi 的调试器里就能定位,因为生成 HTML 的那段代码就在手边,断点打上去,Response.Content里到底是什么一目了然,不用再靠猜。

3. 第一个例子怎么跑:在 RAD Studio 里做出一个会回话的按钮

3.1 准备:从零建一个 WebBroker 项目

整个过程在 RAD Studio 里完成,不需要命令行。打开 IDE 之后按下面的顺序走:

  1. 菜单里选File→New→Other,打开新建对话框。
  2. 左侧分类里找到Web相关的一项,选中Web Server Application。
  3. 向导里选择Stand-alone console application,也就是独立控制台程序,这种方式不需要额外配置 Web 服务器,最省事。
  4. 端口填空闲的,比如8080,别和系统里其他服务撞上。
  5. 完成后 IDE 会生成一个WebModule单元,里面默认有一个处理所有请求的动作项。

建好之后先别急着写业务,直接运行一次。控制台窗口里会出现一行类似“正在监听 8080 端口”的提示,打开浏览器输入http://localhost:8080,能看到默认页面就说明环境通了。这一步看着不起眼,但它把后面最容易卡住的环境问题提前排掉了。踩坑经验告诉我,新手第一次往往会把代码写得没问题、却卡在端口被占用或者防火墙弹窗上,先跑通空的比什么都重要。

3.2 第一个处理程序:让浏览器收到一段自己写的 HTML

在WebModule上双击默认的那个动作项,IDE 会跳到一个空白的事件处理函数里。在这里写下面这段:

// 处理 /hello 路径的请求,返回一小段 HTML procedure TWebModule1.WebModule1HelloAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin // 一定要带 charset,否则浏览器可能按 GBK 解析,中文变乱码 Response.ContentType := 'text/html; charset=utf-8'; Response.Content := '<p>你好,我是服务器返回的一小段 HTML</p>'; Handled := True; // 告诉框架:这个请求我处理完了,别再往下传 end;

这段代码只有一个真正的逻辑:往响应的内容属性里写一段字符串。ContentType是告诉浏览器“我给你的是一段 HTML,编码是 UTF-8”,后半句很重要,中文乱码十有八九是这里没写。

Handled这个参数容易被忽略。WebBroker 会把请求依次交给动作项处理,Handled := True表示已经处理完毕,后面的动作项不用再管;不写的话可能出现同一请求被处理两次、返回内容拼接在一起的情况,现象是页面重复出现两遍内容。

回到 IDE 里选中这个动作项,把它的PathInfo属性设成/hello。再运行时访问http://localhost:8080/hello,浏览器上就会显示你写的那句话。这是整个学习路径上的第一个里程碑——服务器端返回的 HTML 真的出现在浏览器里了。

3.3 加上 HTMX:一个按钮点下去,只换页面里的一小块

接下来让页面“动”起来。先准备 htmx 的脚本文件:从 HTMX 官方渠道下载htmx.min.js,放到程序可执行文件所在目录下一个名叫wwwroot的文件夹里。然后在项目里加一个动作项,路径设成/static,用来把静态文件送出去:

// 把 wwwroot 目录下的文件按请求路径读出来返回 procedure TWebModule1.WebModule1StaticAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var LocalPath: string; begin // 请求路径里的 / 换成系统目录分隔符,拼成磁盘上的真实路径 LocalPath := ExtractFilePath(ParamStr(0)) + 'wwwroot' + StringReplace(Request.PathInfo, '/', PathDelim, [rfReplaceAll]); if FileExists(LocalPath) then Response.ContentStream := TFileStream.Create(LocalPath, fmOpenRead or fmShareDenyWrite) else begin Response.StatusCode := 404; Response.Content := 'not found'; end; Handled := True; end;

ParamStr(0)是当前可执行文件的完整路径,ExtractFilePath取出它所在的目录,后面拼上wwwroot和请求路径,就得到了文件在磁盘上的位置。fmShareDenyWrite表示允许别人同时读、但不允许写,避免多个请求同时打开同一个文件出问题。这段代码没有做路径穿越的防护,入门阶段够用,等你要放到公网上时一定要补上,这一点在避坑那一章会再说。

现在写页面。新增一个动作项,路径/demo,返回下面这段 HTML:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <!-- 引用刚放好的本地脚本,不依赖外网 --> <script src="/static/htmx.min.js"></script> </head> <body> <h2>第一个 HTMX 例子</h2> <button hx-get="/hello" hx-target="#result" hx-swap="innerHTML"> 点我一下 </button> <!-- 服务器返回的内容会进到这个容器里 --> <div id="result">这里会显示服务器返回的内容</div> </body> </html>

三个属性的分工很明确:hx-get="/hello"说明点下去要往/hello要内容;hx-target="#result"说明拿回来的东西放进 id 为result的容器;hx-swap="innerHTML"说明替换容器内部的内容,容器本身保留。访问http://localhost:8080/demo,点一下按钮,下面那行占位文字就会被替换成 3.2 里写的那句“你好,我是服务器返回的一小段 HTML”,而页面其他部分纹丝不动。

3.4 逐行解释:一次点击背后发生了什么

点下去的那一瞬间,浏览器做了这几件事:

  1. 脚本监听到按钮的点击事件,读这个元素上的hx-get属性,知道要发一个 GET 请求到/hello。
  2. 请求发出去,走的是浏览器原生的网络通道,和你在地址栏敲回车没有本质区别。
  3. WebBroker 收到请求,按路径匹配到/hello这个动作项,调用 3.2 里那个函数。
  4. 函数把一段 HTML 字符串写进响应,Handled := True结束处理。
  5. 浏览器拿到响应内容,按hx-target找到#result元素,按hx-swap的方式把内容替换进去。

整个过程没有任何一行 JavaScript 是你写的。这解释了为什么这套方式对新手友好:你要关心的事情只有两件——服务器返回什么、返回的东西放到哪。至于“什么时候发”“怎么发”“怎么塞”,属性已经替你说清楚了。

这里有个思维习惯需要调整。以前你可能觉得“局部刷新”是很高级的技术,其实本质就是“服务器给我一段 HTML,我把它放进某个盒子”。盒子在哪、放进去还是替换掉,由属性决定。想明白这一点,后面所有交互都是这个模式的变体。

3.5 再进一步:写一个能添加的待办

光有按钮还不够,试一个带表单的。再加一个动作项/todo/add,返回一小段列表项:

// 接收表单提交的标题,返回一个列表项片段 procedure TWebModule1.WebModule1TodoAddAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var Title: string; begin // 表单字段名是 title,取不到就给个默认值防止空着 Title := Request.ContentFields.Values['title']; if Title = '' then Title := '(没有填写内容)'; Response.ContentType := 'text/html; charset=utf-8'; // 拼接时对用户输入做最基本的转义,防止标签被当成 HTML 执行 Response.Content := '<li>' + StringReplace(Title, '<', '&lt;', [rfReplaceAll]) + '</li>'; Handled := True; end;

Request.ContentFields是表单提交过来的字段集合,Values['title']按字段名取值,取不到返回空字符串。这个变量里的内容来自用户,直接拼进 HTML 是有风险的,所以代码里做了一次<的替换,这是最小化的处理方式,正式项目里更稳妥的做法是交给模板引擎自动转义。

页面上这么写:

<form hx-post="/todo/add" hx-target="#todo-list" hx-swap="beforeend"> <input type="text" name="title" placeholder="今天要做什么"> <button type="submit">添加</button> </form> <ul id="todo-list"> <li>这是原本就有的第一条</li> </ul>

注意hx-swap换成了beforeend,意思是把返回的<li>追加到列表末尾,而不是覆盖整个列表。这就是前面说的“追加型交互”,做消息流、评论列表、日志输出全都用这一招。

跑到这一步,你已经把“GET 取内容”“POST 提交数据”“替换”“追加”四种最常见的动作都试过一遍了。剩下的复杂交互,基本都是在这几个动作上换参数。

4. 模板怎么写:WebStencils 的 @ 符号、条件和循环

4.1 为什么不再用字符串拼接:@ 符号的角色

3.5 里那段'<li>' + Title + '</li>',短的时候没什么感觉,一旦页面长起来就是灾难:标签和引号混在一起、条件判断要靠 if 拼字符串、改一个样式要翻半天、少写一个闭合标签浏览器还照样显示,导致错误非常隐蔽。

WebStencils 解决的就是这个问题。它让你把页面写成一个普通的 HTML 文件,需要插入数据的地方用@开头标记,运行时由引擎把值填进去。页面结构和代码逻辑因此分开:.html文件管长什么样,.pas文件管取什么数据。

这个概念用一句话概括:模板是一个带空缺的 HTML,@就是那个空缺的标记。你不需要记很多语法,日常用到的就是“取值”“判断”“循环”三类。

4.2 变量、点表示法和注释:最常用的三件事

取值最基础的写法是@加变量名。如果数据是一个对象,可以用点表示法往里取,比如@Model.Title表示取Model这个对象的Title属性,@Model.Items表示取它的Items集合。这种写法和 Delphi 里访问对象属性的习惯一致,不需要额外记一套语法。

有一点新手常犯:模板里的@后面跟的名字,必须和你在代码里注册进去的名字一致,大小写敏感。资源里的示例用的是@Model.xxx这种形式,你自己写的时候如果注册的是TodoList,模板里就得写@TodoList,写成@Todolist会取不到值,页面上直接显示空白,还不报错——这种问题排查起来最容易浪费时间。

注释的写法是@*开头、*@结尾,中间的整段内容不会输出到最终页面。调试模板时我习惯把暂时不用的块用注释包起来,比反复删改安全,改坏了还能还原。

4.3 条件与循环:让列表自己长出来

条件判断的写法接近 Delphi 的语法结构,大致是这样的形式:

@if (Model.Count > 0) begin <p>当前共有 @Model.Count 条记录</p> end else begin <p>还没有任何记录,先添加一条吧</p> end;

条件为真时输出上面那段,为假时输出下面那段。资源里还提到@ifnot这样的反向写法,适合“条件不成立时提示”的场景,比如登录状态检查。

循环用来渲染列表,这是模板最常处理的场景:

<ul> @ForEach (var item in Model.Items) begin <li>@item.Name</li> end; </ul>

Model.Items是一个集合,循环体每次取一个元素,元素内部的属性用点表示法取。这段模板不管你传进来三条还是三百条,都会自动展开成对应数量的<li>,你不需要在代码里写循环拼字符串,也不需要处理末尾多出来的逗号或标签。

提示:这类循环语法在不同小版本的模板引擎里可能略有差异,比如花括号和begin...end两种写法都见过。资源里对语法有逐条说明,动手前先照着那份语法写,跑通之后再改成自己顺手的风格,别一开始就凭印象硬写。

4.4 布局复用:@LayoutPage、@RenderBody 和 @Import

页面一多,重复的问题就来了:每个页面都要写<head>、导航栏、页脚。这些结构如果每个文件各写一遍,改一次站名要改二十个文件。

WebStencils 提供的解决方式是布局页。你写一个_Layout.html作为外层骨架,在要放具体页面内容的位置写@RenderBody,然后在子页面顶部用@LayoutPage _Layout.html声明“我套用这个骨架”。运行时引擎会把子页面的内容填进@RenderBody的位置,拼成完整页面。

@Import用来引入可复用的片段,比如把导航栏、侧边栏、卡片这些反复出现的块单独存成文件,在需要的地方引入。@ExtraHeader配合@RenderTitle可以处理“父布局里有默认标题,个别页面想换一个”的情况。

这套组合的价值在你写到第五六个页面时才会明显感觉到。我个人的做法是:第一个页面老老实实写完整 HTML,等第二个页面出现大量重复时再抽布局,抽早了容易过度设计,抽晚了改动成本高。资源里给了标准布局、标题正文分页、可复用组件几种模板模式,第一遍可以先看标准布局,其余等有需要再翻。

4.5 把模板接进 WebBroker:绑定与数据传递

模板写好了,得让 WebBroker 知道去哪找它、数据从哪来。资源里列出的组件包括模板引擎、模板处理器、Web 服务器引擎这几类,作用是把模板文件读进来、把变量注册进去、把生成的 HTML 交给响应。典型的流程是:创建模板引擎实例、设置模板所在目录、注册要用的变量、执行渲染、把结果写进Response.Content。

顺序上有一点值得提醒:变量要在渲染之前注册好,渲染之后再注册就没用了,页面上该显示的地方全是空白。这类问题现象很统一——页面结构出来了,但所有@位置都是空的。遇到这种情况,先回头检查注册代码在不在渲染语句前面,其次检查名字拼写,最后再怀疑模板文件路径。

5. 避坑清单:新手最常出错的五个地方

5.1 点了没反应,页面一动不动

现象:按钮上属性都写了,点下去什么都不发生,浏览器地址栏也没变,页面看上去完全没动。

原因:最常见的是 htmx 脚本根本没加载成功。路径写错、文件没放进wwwroot、静态文件的动作项路径没对上,都会导致脚本 404。脚本不在,属性就只是普通字符串,没人理会它。其次是属性名拼错,比如写成hx-tartget,脚本扫不到就跳过。

解决:打开浏览器开发者工具的网络面板,刷新页面,看htmx.min.js那条请求的状态码是不是 200。不是 200 就先修静态文件路径,先别怀疑业务代码。是 200 还不动,就在元素上右键检查,看属性名有没有拼错、有没有被其他脚本覆盖掉。

5.2 只想换一小块,结果整页都塞进来了

现象:点按钮之后,目标区域里出现了一整个页面,<html>、<head>全塞进去了,样式也乱了。

原因:服务器返回的是完整页面,而hx-target指向的是页面里的一个小容器。htmx 默认会把返回内容的全部塞进去,它并不知道你只想要中间那一段。

解决:两个方向。要么让服务器端根据请求返回片段而不是整页,可以在请求头里判断是不是 htmx 发来的,是就只输出局部;要么在元素上加hx-select="#todo-list",让脚本从返回的整页里挑出指定元素再用。前者更干净,后者改动更小,看你的项目处在什么阶段。

5.3 模板找不到,或者中文全变成乱码

现象:一种是启动就报找不到模板文件;另一种是页面出来了,中文全是问号或者方块。

原因:路径问题多半是模板目录用了相对路径,程序的工作目录和你以为的不一样,调试时对、部署后就错。乱码基本是响应头里的编码没写,或者模板文件本身的保存编码与声明不一致。

解决:模板目录统一用ExtractFilePath(ParamStr(0))拼绝对路径,别依赖工作目录。编码上两头对齐:响应头写text/html; charset=utf-8,模板文件本身也存成 UTF-8。这两处只要有一处是别的编码,中文就会出问题,而且报错信息往往误导你去查别的地方。

5.4 表单提交被拦下,或者用户输入把页面搞坏

现象:表单提交后没有预期结果,或者某条数据一显示出来,整个页面布局就崩了,甚至弹出奇怪的提示。

原因:前者是跨站请求伪造防护在起作用。资源里专门讲了 CSRF 保护,服务器会校验请求来源的合法性,缺少令牌或者来源不对的请求会被拒绝。后者是跨站脚本问题,用户输入里带了 HTML 标签,被原样输出到了页面上,浏览器把它当成结构执行了。

解决:CSRF 这块,把模板里生成的令牌字段原样带上,别手动拼请求;如果用的是表单提交,确认表单里有那个隐藏字段。XSS 这块,输出用户内容时一定要走转义,模板引擎通常默认转义,但你手动拼字符串的地方是绕过转义的,凡是拼字符串的地方都要过一遍。资源里对数据校验和安全注意事项有专门说明,第一遍读的时候容易跳过,建议至少把这一节看完。

5.5 本机跑得好好的,换个环境就崩

现象:开发机上一切正常,换台机器或者换个端口部署,页面打不开或者静态资源全丢。

原因:端口被占用、防火墙拦截、路径写死、静态文件没跟着一起拷过去,这几种情况占了绝大多数。特别是wwwroot目录,很多人只拷了可执行文件,忘了拷资源目录。

解决:部署前对一遍清单——端口是否可用、可执行文件同目录下的资源目录是否完整、路径是否都是运行时拼出来的而不是写死的绝对路径。养成这个习惯之后,这类问题基本不会再找上门。

6. 下一步学什么:从待办清单走到一个能交付的小系统

跑通按钮和表单之后,很多人会卡在“接下来做什么”。这里给一条我自己走过、也觉得最省力的路径:不要急着看高级特性,先把一个待办清单从头到尾做完整,再回头补知识点。

具体顺序建议是这样。第一步,把 3.5 那个只能追加的列表改成能存能删:加一个删除按钮,用hx-delete或者hx-post到删除接口,返回更新后的整个列表片段,hx-swap用outerHTML整个换掉。这一步会让你真正理解“返回片段”的粒度怎么选。第二步,把列表换成模板渲染,不再拼字符串,数据用一个对象集合传进去,用@ForEach展开。第三步,抽出布局页,把导航和页脚放进_Layout.html,体会@RenderBody的作用。第四步,加上分类和筛选,用hx-trigger监听下拉框的change事件,体验“不点按钮也能发请求”。

阶段目标对应资源里的部分
第一阶段增删改查全通WebBroker 基础、HTMX 请求方式
第二阶段页面由模板生成模板语法、变量注册
第三阶段多页面不重复布局、@RenderBody、@Import
第四阶段条件渲染与筛选@if、hx-trigger、hx-vals
第五阶段能给别人用会话管理、CSRF、数据校验

每个阶段做完都停下来,把代码从头读一遍,问自己两个问题:这个请求是谁触发的、返回的内容去了哪里。这两个问题能答上来,说明你真的理解了,而不是照着抄。答不上来就回去看那一节,不要往下走,因为后面的东西都建立在这个模型上。

资源里后半部分讲了@Scaffolding、@LoginRequired、OnValue事件处理、模板模式这些进阶内容,第一遍读的时候完全可以跳过,等你把上面五个阶段走完再回来翻,会发现每一节都能对上你遇到的问题。这种“先做再读”的顺序,比从头到尾读一遍效率高得多,因为你有具体的问题要解决,读的时候注意力会自动落在答案上。

有个习惯我从第一次做这类项目一直保持到现在:每加一个新交互,先不看效果,先把“请求发到哪、返回什么、放到哪”这三句话写下来,再动手写代码。写不出来说明我还没想清楚,那就先想清楚再写。这么做之后,我因为“忘了设 target 导致整页乱塞”这类问题返工的次数少了很多。希望这些能帮到你,从跑通第一个按钮开始,一步一步来。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进