学习编程已经成为近些年的热门话题之一。无论是 Apple、Google、AWS 等科技巨头积极响应 编程一小时 活动,还是 Z 世代的孩子早早地被父母送进少儿编程培训机构,都给人带来一种再不学习编程就会被时代抛弃的感觉。

我们都需要或者都想要成为程序员吗?答案应该是否定的。无论是自学编程的朋友,还是被送进培训机构的孩子们,他们的父母大概也不是希望孩子长大后就一定成为程序员,而是训练逻辑思维。程序员作为一份职业,依旧是少数人的选择,而编程作为一项技能,每个人都可以拥有。这就像是很多人都会自己炒菜做饭,但不全都是厨师一样。

我们可以运用逻辑思维,将重复繁杂的工作梳理得更清晰,而编程可以创造工具让流程变得更高效。但由于每个人的工作是个性化的,所以很难有完全通用工具。困难出现了,很多人都经历过学习编程从入门到放弃的过程。纵使某些编程语言入门相对简单(例如:Python),但从了解到最终实现能用的工具,中间涉及到的技术点纷繁复杂,小白还是很难短时间掌握。

什么是低代码

于是市面上出现了许多低代码平台,让普通人也能较快地开发出自己想要的工具。Pietro Invernizzi 给出了他收集到的市面上常见的 低代码平台列表,将常用的低代码工具分为了 12 类,涉及网页开发、应用开发、自动化流程、表格工具等。当然国内做低代码产品的公司也很多,例如:腾讯云微搭阿里云宜搭明道云飞书多维表格 等。

市面上常见的低代码平台

当然,低代码并不意味着完全零代码。根据低代码工具的属性和应用场景不同,对代码能力的要求也有区别。例如以 Airtable 和飞书多维表格这类以表格为主体,可以用来存放、统计、分析数据的平台,基本属于零代码。而 Bubble、Retool 等网页应用开发工具,则需要具备基本的编程思维和代码开发能力。

Retool 主界面

我试用过市面上大部分知名的低代码平台,个人最喜欢的是 Retool,也一直在工作中使用。这是一家位于旧金山,成立于 2017 年的创业公司,去年年底刚拿到 C 轮融资。相比之下,Retool 的组件和交互设计更加现代化,易用性也会更好一些。所以本文将使用 Retool 作为示例,手把手教会小白用户在一小时内开发一个和少数派相关的网页应用。

Retool 开发入门

本文中,我们将尝试使用 Retool 开发一个能随机推荐少数派作者以及作者文章的 网页应用。通过实例练习,大家可以详细了解到 Retool 的常用功能并对低代码平台有更加深入的认识。

最终实现页面预览效果

如上所示的网页应用具备两个功能:

  1. 能够随机呈现一位少数派作者的个人信息,与少数派网页上的信息完全一致。
  2. 随机展示作者的一篇文章,并且能够手动刷新。

注册 Retool

首先我们需要 注册 Retool,过程就不再赘述了。登录之后,你需要设置一个自定义子域名。免费版的 Retool 可以永久使用全部开发功能,但过了 14 天试用期之后,将无法发布新的应用。已经发布的应用不受影响,且后续可以继续编辑并更新。

新建工程

点击控制台右上角的新增按钮,创建一个空白的应用,页面会自动跳转到应用编辑器。

新建工程

界面的正中是整个网页的预览区域,后续将在此区域完成应用界面设计和预览。左侧是应用数据结构区域,你可以在这里看到应用开发过程中的所有结构化数据,方便后续快速定位所需数据和字段。所有的数据都是 JSON 格式,这是最常用的数据交换格式之一。

Retool 主界面功能分区

界面下方是数据的获取和加工区域。你可以通过一些预设组件加载来自互联网的各类数据,例如:连接数据库、访问在线表格、请求 API 等。更重要的是,你可以在这里对获取到的数据进行再加工(Transformer),组合成自己所需的数据结构,并最终与功能组件完成数据交互。这一点将会在后续的开发过程中实际体验到。

界面右侧则是功能组件区域,Retool 提供了数十种常见的通用功能组件,你可以像搭积木一样,通过组合不同的组件去实现自己想要的功能。一款低代码工具提供的组件类型和功能越多,意味着其支持开发更加复杂多样的应用。

让我们直接开始吧。

配置 Header

一款网页应用界面上呈现的数据分为静态和动态两类。静态资源不会频繁更新,例如网站的 LOGO,菜单栏等。你可以直接拖拽相应的组件,并配置好静态资源即可。接下来,我们从界面右侧选中 Image 组件拖动到 Header 顶部菜单栏区域,放置一个少数派 LOGO。

搜索 Image 组件

请注意如图所示的 2 处配置,其中:

  1. 少数派 LOGO 来自于官网,你可以下载上传到某个图床(示例链接)。
  2. 图片高度选择固定 Fixed(拖动组件自定义高度),宽度选择包含 Contain,然后居中对齐。
少数派 LOGO 配置示意图

除了 LOGO 一类的静态资源,动态的部分往往会频繁更新,或者和部分功能一起完成数据交互。例如本次开发过程中的作者信息、作者文章等,这些数据大多直接来自于数据库,并通过功能组件实现对数据的新增、删除、修改和查询(简称:增删改查)。

作者信息模块

回顾你平日里见过的大多数网页和应用,实际上都是这一套逻辑。例如少数派首页的样式是用 HTML、CSS、Javascript 等前端代码开发,而文章数据是通过 API 从后端数据库中实时获取。

少数派首页文章的数据来自于后端 API

低代码实际就是高度提炼这一套逻辑,通过提供大量的预设组件,减少自定义代码编写的工作量,最终实现前后端数据交互。

获取用户信息数据

回到本次开发的网页应用,页面中的作者信息和文章数据都来自于少数派的数据库,我们没有权限直接访问。而少数派并也不提供开发者平台,所以我们无法使用其 API 数据接口(不建议大家通过技术方法使用少数派非公开 API)。

为了方便大家了解 Retool,文章提供了和少数派 API 数据结构一致的用户信息接口,示例地址为:https://jsonbase.huhuhang.workers.dev/sspai/api/v1/user/{slug}/info/get,其中 {slug} 是用户名,可以使用 huhuhang

配置 Retool 数据

我们就可以将这个数据接口配置到 Retool,然后使用 Retool 里面的组件呈现数据即可。打开底部区域,新增一个数据查询:

  1. 点击 + 新建 Resource query,选择 RESTQuery,即通过 API 请求数据。双击请求名称,将其重命名为 userInfo 方便后续区分。
  2. Action type 处选择 GET(查询),然后粘贴 URL:https://jsonbase.huhuhang.workers.dev/sspai/api/v1/user/huhuhang/info/get
  3. 点击右上角菜单保存并执行,就可以看到下方区域成功获取到数据。这里拿到的数据应该和上一步通过浏览器控制台看到的数据一致。
将 API 配置到 Retool

添加布局所需组件

接下来,我们就可以在界面上呈现这些数据了,步骤如下:

  1. 从右侧组件栏拖动 1 个 Container 容器组件到界面上,居中布局。Container 起到分组的作用,后续我们在其中加入的其他组件会被视为一个整体,方便整体调整和拖动。
  2. 从右侧组件栏拖动 1 个 Circular Image 圆形图片组件用于放置头像。
  3. 从右侧组件栏拖动 1 个 Text 组件用于展示作者名称。
  4. 从右侧组件栏拖动 1 个 Tags 组件用于展示作者粉丝和关注数。
  5. 从右侧组件栏拖动 1 个 Avatar Group 组件用于展示作者的徽章。
  6. 最后从右侧组件栏拖动 2 个 Tags 组件用于展示作者其他数据信息。

上述组件的最终的摆放效果大致如下:

作者信息的布局效果

此时,你会发现拖动到界面上的组件并不会像上图一样展示正确的数据。我们需要从上一步的 userInfo 数据查询结果中读取相应的数据,然后配置到组件中。在这里需要介绍一下 Retool 中解析动态数据的语法,你可以使用 {{ }} 左右两个花括号来解析动态数据。

配置头像和作者姓名

我们以头像为例,你可以在 URL 配置项中输入 {{userInfo.data.data.avatar}},就可以从刚刚 userInfo 的数据请求中,逐层解析拿到头像 avatar 的链接。Retool 支持自动补全,帮助快速选择到所需的数据,例如当你输入 {{userInfo.}} 后会自动预览下一级数据,这对不熟悉 JSON 数据结构的用户很有帮助。

作者头像配置项

但很快你就会发现少数派 API 中只存储了相对链接 2022/04/01/avatar/7f0214727c4c666a32b6a7a7ed9ab19a.png,对比网站上的头像链接可以看出缺少了 https://cdn.sspai.com/ 域名,于是最终的 URL 配置为 https://cdn.sspai.com/{{userInfo.data.data.avatar}},成功解析并组合出数据。

同理,你可以解析出用户名数据为 {{userInfo.data.data.nickname}}。Retool 的 Text 组件支持 Markdown 语法,这里可以直接配置如下:

作者昵称配置项

配置粉丝和关注数

除了文本字段,Retool 中的 Tags 字段是通过列表 [ ] 来配置数据的。例如粉丝和关注数据,我们可以配置成:

[{{"粉丝 "+userInfo.data.data.followed_count.toLocaleString('en-US')}}, {{"关注 "+userInfo.data.data.following_count.toLocaleString('en-US')}}]

解释一下,Retool 中的 {{ }} 中支持直接使用 Javascript 语法处理数据。例如上方我们将解析得到的粉丝整数,加上 .toLocaleString('en-US') 处理成有逗号分隔符的英文数字书写方式。同时使用 + 将两个字符串合并在一起。

粉丝和关注数配置项

这一步相对于上一步,我们需要使用到简单的 Javascript 代码,这也就是为什么低代码并非零代码。当然,小白也无需担心和畏惧代码。你可以在搜索引擎中输入自己的需求 + Javascript 关键字来查找代码片段,从而实现自己想要的功能。

配置成为少数派时间

同理,如果你注意观察原始数据,少数派只提供了用户的创建时间字段 {{userInfo.data.data.created_at}}。这里返回的并非我们想要的天数,而是 Unix 时间戳(秒)。我们同样需要通过 Javascript 代码将其处理成我们需要的天数。

[{{"成为少数派 "+Math.round((new Date().getTime()/1000-userInfo.data.data.created_at)/(3600*24)).toLocaleString('en-US')+" 天"}}]
成为少数派天数配置项

解释一下这段代码:

  1. new Date().getTime()/1000 获取当前时刻的 Unix 时间戳(秒)。
  2. 减去 userInfo.data.data.created_at 之后得到当前时刻距离创建时刻的秒数。
  3. 除以一天 24 小时的秒数 3600*24,得到当前时刻距离创建时刻的天数,为浮点数。
  4. 使用 Math.round 将浮点数转换为整数。
  5. 最终使用 .toLocaleString('en-US') 转换整数为逗号分隔展示。

配置作者勋章

作者信息中,比较复杂的是作者勋章的展示。观察 API 返回的原始数据,可以看到勋章的数据主要集中在 user_badgesuser_plan_flagsuser_flagsuser_reward_badgesuser_old_badges 等字段中。

观察原始 API 中勋章的相关数据

而反观用于展示勋章图片的 Avatar Group 组件配置项时,你发现其 Image URLs,也就是图片链接是需要用类似 Tags 的列表 [] 来存储数据。

Avatar Group 组件默认配置项

为了达到目的,我们这里引入一个 Retool 中的新概念 Transformer,即数据转换器。简单来讲,你可以在编辑器下方数据处理区域创建自定义数据转换器,通过编写 Javascript 代码,它可以将某个输入的原始数据按照你想要的样式处理之后返回。

Transformer 让你对数据有了主动权,例如在本次开发过程中,API 返回的数据格式是由少数派控制的,你不可能要求少数派的后端工程师将 API 数据结构改成你需要的样子。但是通过 Transformer 的二次加工,你可以将 API 返回的原始数据处理成任何结构。

接下来,我们创建一个 JavaScript transformer,并重命名为 badges

新增 Transformer 数据转换器

然后编写 Javascript 代码,将原始数据 {{ userInfo.data.data }} 遍历解析其中不同字段中的勋章图片链接,最后组合成列表返回。完整代码如下:

const data = {{ userInfo.data.data }}

badgeIcons = []
badgeNames = []

if (data.user_badges) {
    data.user_badges.forEach(badge => {
        badgeIcons.push(badge.icon)
        badgeNames.push(badge.name)
    })
}
if (data.user_plan_flags) {
    data.user_plan_flags.forEach(flag => {
        badgeIcons.push(flag.icon)
        badgeNames.push(flag.name)
    })
}
if (data.user_flags) {
    data.user_flags.forEach(flag => {
        badgeIcons.push(flag.icon)
        badgeNames.push(flag.name)
    })
}
if (data.user_reward_badges) {
    data.user_reward_badges.forEach(badge => {
        badgeIcons.push(badge.icon)
        badgeNames.push(badge.name)
    })
}
if (data.user_old_badges) {
    data.user_old_badges.forEach(badge => {
        badgeIcons.push(badge.icon)
        badgeNames.push(badge.name)
    })
}

return { badgeIcons, badgeNames }

我自己也并非前端工程师,仅略懂 Javascript 代码,上述代码可能会比较粗糙。😂

配置和预览 Transformer

如上图所示的预览界面,最终返回的勋章图片列表 badgeIcons,其中中包含了作者全部勋章的图片链接。此时回到 Avatar Group 组件配置项,我们读取 Transformer 中返回的数据 {{badges.value.badgeIcons}} 即可。

作者勋章配置项

至此,作者信息的部分就全部处理完毕了。对比一下少数派官方页面的信息,基本一致。✌️

Retool 实现效果和原效果对比

实现随机作者

至此,我们实现了可以针对固定作者的信息展示。然而,我们希望能够更换展示指定作者的信息,或者随机展示作者。

获取指定作者信息

我们先尝试达成第一个目标,即对指定用户名(slug)的作者信息进行展示,而非固定展示某一位作者的信息。这里需要使用的 Retool 的链接参数功能,让我们可以通过 URL 参数传入不同作者的用户名,从而更换作者信息。

首先,从左侧栏顶部创建一个 TEMPORARY STATE 临时变量,选中后在右侧栏重命名其为 slug,并设置一个默认值。完整操作步骤如下:

配置 URL Parameters

如上所示,可以从界面右上角的设置项中找到 URL Parameters 选项,新增参数 slug,然后配置其值为 {{slug.value}},即使用上一步 TEMPORARY STATE 的默认值。

然后,我们重新配置之前的 userInfo 获取用户数据的接口,将其后面固定的用户名改为 {{urlparams.hash.slug}},即使用 URL 传入的参数。这样,就可以实现默认情况下使用 URL Parameters 传入 TEMPORARY STATE 的默认值,而又可以手动修改指定的 slug 参数。

刷新页面之后,你就可以看到编辑器 URL 和之前的区别,最末端出现了 slug 参数。尝试手动设置其为其他作者用户名,例如少数派编辑部的 slug=ee0vj778,回车之后就可以看到指定作者的数据获取成功。

通过参数手动指定任意作者

获取随机作者信息

实现随机获取一个少数派作者的信息,理想情况是能够随机从数据库中选择一位作者,但是我们并没有权限操作。所以,你可以尝试从网页中找出一些和作者相关的数据源,例如我们找到 Matrix 社区 右侧推荐作者的板块。

Matrix 社区推荐作者板块

同样,我们提供了此区域的示例 API 链接:https://jsonbase.huhuhang.workers.dev/sspai/api/v1/user/worth/follow/page/get

然后,创建一个新的数据请求,命名为 randomAuthor

接下来,我们需要进行一些配置。第一步是在 randomAuthor 执行成功时,触发 Set temporary state,随机从返回数据中抽出一位作者的 slug,并赋值给之前创建的 TEMPORARY STATE。

{{randomAuthor.data.data[Math.floor(Math.random() * randomAuthor.data.data.length)].slug}}

解释一下这部分代码,randomAuthor.data.data.length 是返回作者数组的长度,然后 Math.floor(Math.random() * randomAuthor.data.data.length) 可以从长度上限中随机抽取一个整数。例如,当数据源返回了 5 位作者,则会从 [0, 4] 区间内随机返回一个整数。我们可以利用随机整数作为索引(索引从 0 开始),从原数组 randomAuthor.data.data 抽取一个作者值,并拿到其 slug。详细配置如下:

配置获取推荐作者执行成功后的下一步动作

接下来,我们在界面右上方拖动一个 Button 按钮组件。选择点击按钮时的触发动作为执行 randomAuthor,配置如下图所示。这样,我们就可以实现在点击刷新按钮时,重新获取一个作者信息并展示出来。

配置手动刷新按钮

至此,我们就成功通过 Retool 构建了一个能够展示默认指定作者,并支持随机获取少数派的推荐作者,并将其信息展示在页面上。同时,还支持通过 URL 参数指定展示某位作者的信息。

文章信息

我们来实现第二个功能,随机展示作者的一篇文章。本文提供了和少数派作者文章 API 数据结构完全一致的示例 API 地址:https://jsonbase.huhuhang.workers.dev/api/v1/article/user/{slug}/page/get。其中 slug 为作者的用户名。

实现随机获取文章的逻辑和上方随机获取作者差不多。我们可以从作者信息 API 中找到 released_article_count 字段,代表作者累计发布的文章数量。只要生成一个随机数,即可通过数组索引获取到指定位置的 1 篇文章就能达到目的。这样,我们就可以将文章的信息展示在页面上了。

文章布局

接下来,拖动 1 个新的 Container 容器组件到编辑器中用作文章信息呈现区域,分别放置:

  • 1 个 Image 组件用来展示文章的题图。
  • 2 个 Text 组件,分别展示文章的标题和简介。
  • 1 个 Avatar 组件展示带头像的作者信息。
  • 1 个 Tags 组件展示文章的充电和评论数量。

手动调整他们的大小和相对关系大致如下:

然后,我们添加一个新的数据请求 article,地址为:https://jsonbase.huhuhang.workers.dev/api/v1/article/user/{{urlparams.hash.slug}}/page/get,其中 {{urlparams.hash.slug}} 是使用 URL 传入的作者用户名。

该数据接口会返回作者的全部文章,我们通过随机索引获取其中的一篇文章即可。这里采用和上面随机作者不同的方式,直接在请求中开启 Transformer 设置,对数据进行转换,代码如下:

return {{article.rawData.data[Math.floor(Math.random() * (userInfo.data.data.released_article_count))]}}

其中,Math.floor(Math.random() * (userInfo.data.data.released_article_count)) 是从作者发布的文章总数中获取随机值,得到相应位置的文章。

最后,设置右上角刷新按钮的触发器为执行 article 即可,就能实现手动随机切换文章了。至于文章布局中其他字段的解析这里就不再赘述了,大家可以结合后续完整示例应用配置查看。

应用发布和分享

至此本次示例的所有功能均已开发完成,我们可以点击界面右上角的 Share 按钮生成一个公开链接,供所有人访问和使用了。

生成应用公开访问链接

此外,Retool 提供了版本管理的功能,方便控制版本和避免正在开发状态的应用被访问和使用。你可以从右上角的设置项中找到 Releases and history 选项,然后发布版本。

应用版本控制和发布

开发小结

回顾本次案例,我们只涉及到对于现有数据 API 的查询、处理和呈现,并增加了随机作者和随机文章的小功能。其中的难点集中在找到 API 源,对数据的二次处理,以及 Retool 组件的选择和配置。

功能实现的过程实际上是对编程思维和编程逻辑的考验,你需要将功能进行提炼和简化成可以实现的方案,这就是低代码中最难的部分。这些是可以通过不断训练来完成的,当你制作的应用越来越多,对组件的应用更加熟练,就可以开发更多复杂的功能。

这里建议大家模仿本文的案例逻辑,先拿有现成数据源的站点练手,后续再尝试设计数据新增、修改、删除的功能。那么你可能会需要用到数据库,这里推荐大家同样使用低代码工具,如:飞书多维表格,Airtable 等。使用可视化界面快速创建所需的表结构之后,通过工具提供的现有 API 作为后端接口(不需要自己封装接口),前端则使用 Retool 等完成功能和界面的开发,从而快速构建一套基于低代码的前后端分离系统。

最后给出本次示例应用的完整的 JSON 配置,你可以通过 Retool 新建工程时选择导入 JSON,从而看到完整的配置项。

通过 JSON 导入本次示例应用

再谈低代码

当你真正尝试使用低代码开发一个简单的案例之后,我相信你对低代码有了更深刻的理解。日常的工作中,往往都会存在一些高频且重复的事情。很多时候,我们都可以通过开发适合自己的工具来提升效率并减少重复工作带来的倦怠感。

而当我们下定决心学习一门编程语言之后,往往很难在短时间内构建一款真正可用的工具,这会让人非常失望。此时,低代码可以算作捷径,其不需要大量的基础代码编写、不需要考虑到代码的部署和服务器的维护、不需要耗费大量的金钱和时间。大部分情况下,我们甚至无需掌握丰富的前置知识,只需要秉着实用主义的精神,在搜索引擎的帮助下就可以快速实现自己的想法。

当然,目前低代码的普及还存在很多困难。首先是低代码并非零代码,对于业务人员而言,掌握基础的编程语言和具备基本的编程思维仍然存在门槛,而熟悉编程语言的人往往更喜欢直接写代码而非使用低代码。此外,低代码产品本身也很存在诸多问题亟待解决,部分工具复杂的配置让人感觉到还不如直接编写代码。

但终究我还是看好低代码未来的发展,一定存在更好的解决方案等待突破,真正从低代码到零代码,让业务人员快速上手。回顾编程发展史,从汇编语言到高级语言,从面向对象的出现,到如今提倡模块化和组件化,软件开发行业不也在向低代码过渡吗?我相信让更多普通人掌握简单的编程方法,解放专业程序员承担更具挑战性的工作一定是大势所趋。

> 下载 少数派 2.0 客户端、关注 少数派公众号,解锁全新阅读体验 📰

> 实用、好用的 正版软件,少数派为你呈现 🚀