A Discussion on Logical Deletion (Soft Deletion): Whether It’s Necessary and How to Implement It

In the real world, some documents become obsolete and are immediately shredded, while others need to be preserved in archives for future reference.

Consider an order, which references many other pieces of information, such as seller details, buyer details, and product details. When a product is removed from the catalog, we can’t directly delete the product information; doing so would render the associated order information incomplete.

For tables that require logical deletion (referred to as “collections” in MongoDB), there are two methods:

  1. Method 1: Add fields like is_deleted or is_active to indicate whether a row has been logically deleted.
  2. Method 2: Create a corresponding archive table (recycle bin table) for each table that needs logical deletion. First, insert the rows to be deleted into the archive table (including additional fields like deletion time, ID of the person who deleted it, etc.), and then delete the rows from the source table.

Many experts have already pointed out the drawbacks of Method 1.

Method 2 is easier to implement, non-intrusive to the source table, but it comes at the cost of increased disk space usage.

When designing the database, we should analyze the specific application scenario:

1. Based on the Business Requirements

If the business requirement is to back up data, the solution should be database backups, not logical deletions.

If the business requirement is to archive data, the solution should be archiving data, i.e., moving data into an archive table.

If the goal is simply to “freeze” data, an inactive flag should be used. This provides perfect semantic meaning and ensures consistency. It’s important to note that “freezing” is not the same as “deleting.” “Freezing” means that if you need to use the data again, you can simply “unfreeze” it. “Deleting” means the data is gone, and if you need it again, you must recreate it.

2. Based on the Database System

Some database systems already implement archival features for you. For example, SQL Server has a history table feature that automatically logs deleted records into a history table, and updates are stored with their previous values. In such cases, there’s no need to create an archive table or implement archival logic at the code level.

3. Based on the Data Type in the Table

For tables such as menu tables (dictionary tables) or log tables, logical deletion is unnecessary and physical deletion is sufficient.

Only tables that store critical data, such as account tables or balance tables, should implement logical deletion. In such cases, using an archive table may be the better approach.

For further reading:

(Note: Currently, Zhihu requires login to view the original content. Without logging in, it will show random text as part of their anti-scraping measures against AI models.)

关于逻辑删除(假删除)是否要做,该怎么做的探讨

在现实世界中,有些文档用不到了、过期了,我们直接把它放入碎纸机碎掉,而有些文档需要保存到档案室,供以后查询。

一条订单,引用了很多其他信息,例如卖家信息、买家信息、商品信息等,当某种商品被下架时,我们也不能直接物理删除商品信息,否则引用它的订单的信息就不完整了。

对于要做假删除的那些表(在MongoDB中叫作“集合”):

  • 方法一,可以增加一个is_deleted、is_active等字段来标识这行数据是否被假删除了。
  • 方法二,可以专门为要做假删除的每张表建立一张对应的归档表(回收站表),把要删除的数据行先插入到归档表(包含删除时间、删除人ID、删除人姓名等额外字段),再在源表里删除。

方法一的缺点很多大神已经讲清楚了:

方法二更容易实现,对源表没有侵入性破坏,缺点只是增加了磁盘空间的开销。

我们设计数据库时,要视具体应用场景具体分析:

一、视具体业务的需求而定

如果业务问题是要备份数据,那应该做的是数据库备份,不应该是假删除。

如果业务问题是要归档数据,那应该做的是归档数据,也就是把数据移到归档表里。

如果业务问题只是想冻结数据,应该做inactive标记,这样在语义上非常完美,一致性约束同样没有问题。注意,“冻结”不是“删除”,“冻结”的意思是是这条数据如果你要再次用到,解冻它即可;而“删除”的意思是是这条数据已经被删除了,如果你要再次用到它,只能重新创建一条数据。

二、视数据库软件系统的类型而定

有些数据库软件系统已经帮你实现了归档数据的功能,例如SQL Server有历史表的功能,删除的记录自动记录到历史表里,更新操作也会把更新前的记录保存到历史表里。那么我们就无需自己创建归档表并在代码层面实现归档逻辑了。

三、视表中的数据类型而定

对于菜单表(字典表)、流水表(例如日志表),就无需做假删除了,因为没有必要,直接物理删除即可。

只有一些存储了重要数据的表,例如账户表、余额表,才要做假删除,也许使用回收站表的做法更好。

参考

https://www.zhihu.com/question/39967106/answer/121674339 注意,知乎现在需要登录才能查看到原文,否则显示的是一段随机文本,这是知乎应对AI大模型的爬虫的反爬措施。

Vue的渲染机制

Vue的视图模板被编译为一个render函数,render函数返回一棵虚拟DOM树,虚拟DOM树挂载(mount)为实际的DOM树。render函数会跟踪Vue的视图模板的依赖(即响应式状态),当响应式状态发生变化时,会执行其副作用,重新编译出一个render函数,返回一棵新的虚拟DOM树,然后与旧的虚拟DOM树进行节点之间的比对,找出更新的节点,然后只需把这些更新的节点更新(patch)到实际的DOM树的对应节点即可。这与替换整棵实际DOM树这种更新方法比起来,无疑性能更高,并且Vue还使用了静态提升、更新类型标记、树结构打平等优化手段来使这一过程更加高效!

参考

https://cn.vuejs.org/guide/extras/rendering-mechanism.html

Flexbox交互式指南

本文翻译自《An Interactive Guide to Flexbox》。

Flexbox(弹性框)是一种非常强大的布局模式。当我们真正理解它的工作原理时,我们可以构建自动响应的动态布局,并根据需要重新排列。

例如,看看这个:

(译者注:案例请看原文)

此案例很大程度上受到Adam Argyle的出色的“4种布局,1种价格”的启发。它不使用媒体/容器查询。它没有设置任意断点,而是使用流畅的原则来创建无缝流动的布局。 以下是此案例的CSS:

form {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 16px;
}
.name {
  flex-grow: 1;
  flex-basis: 160px;
}
.email {
  flex-grow: 3;
  flex-basis: 200px;
}
button {
  flex-grow: 1;
  flex-basis: 80px;
}

我记得我曾经遇到过这样的演示,当时完全不知所措。我知道 Flexbox 的基础知识,但这似乎是绝对的魔法!

在这篇博文中,我想完善您对 Flexbox 的心理模型。我们将通过了解每个属性来建立对 Flexbox 算法如何工作的直觉。无论您是 CSS 初学者,还是已经使用 Flexbox 多年,我敢打赌您都会学到很多东西!

让我们开始吧!

内容警告

我在本教程后面会做一个与食物相关的比喻。

Flexbox简介

CSS由许多不同的布局算法组成,布局算法的正式名称是“布局模式(layout mode)”。每种布局模式都是CSS中的一种小语言。默认布局模式是流式布局(Flow layout),但我们可以通过更改父容器上的display属性来选择使用Flexbox布局模式:

(译者注:案例请看原文)

当我们将display属性的值设置为flex时,我们会创建一个“flex格式化上下文”。这意味着,所有子元素都将根据Flexbox布局算法进行定位。

每种布局算法都旨在解决特定问题。默认的“流式”布局旨在创建数字文档,它本质上是Microsoft Word的布局算法。标题和段落以块的形式垂直堆叠,而文本、链接和图像等内容则不显眼地位于这些块内。

那么,Flexbox解决了什么问题?Flexbox就是将一组条目(元素)排列成一行或一列,并赋予我们对这些项目的分布和对齐方式的极大控制权。顾名思义,Flexbox就是“灵活性”。我们可以控制条目是增大还是缩小,如何分配额外的空间等等。

它仍然有意义吗?

你可能想知道:既然CSS Grid在现代浏览器中得到了很好的支持,那么Flexbox不是过时了吗?

CSS Grid是一种很棒的布局模式,但它解决的问题与Flexbox不同。我们应该学习这两种布局模式,并使用正确的工具来完成工作。

当涉及到以垂直或水平列表排列项目的动态、流畅的UI时,Flexbox仍然占据主导地位。我们将在本指南中看到一个示例,即解构的煎饼,这无法通过CSS Grid轻松实现。

老实说,作为一个熟悉CSS Grid和Flexbox的人,我仍然发现自己经常使用Flexbox!

Flex方向

如上所述,Flexbox主要是控制行或列中元素的分布。默认情况下,条目将并排堆叠在一行中,但我们可以使用flex-direction属性翻转到一列:

(译者注:案例请看原文)

使用flex-direction: row时,主轴水平运行,从左到右。当我们翻转为flex-direction: column时,主轴垂直运行,从上到下。

在Flexbox中,一切都基于主轴。算法不关心垂直或水平,甚至行或列。所有规则都围绕此主轴和垂直延伸的横轴构建。

这非常酷。当我们学习Flexbox的规则时,我们可以无缝地从水平布局切换到垂直布局。所有规则都会自动适应。此功能是Flexbox布局模式所独有的。

默认情况下,子元素将根据以下2条规则进行定位:

  • 主轴(primary axis):子元素将聚集在容器的起始处。
  • 交叉轴(cross axis):子元素将伸展以填满整个容器。

以下是这些规则的快速可视化:

在Flexbox中,我们决定主轴是水平还是垂直。这是所有Flexbox计算的根源。

“主轴”和“交叉轴”也可以称它们为“主轴”和“副轴”。

对齐

我们可以使用justify-content属性来改变子元素沿主轴的分布方式:

(译者注:案例请看原文)

当谈到主轴时,我们通常不会考虑对齐单个子项元素。相反,我们关心的是整个(子项元素)组的分布。

我们可以将所有项目集中在一个特定位置(使用flex-start、center和flex-end),也可以将它们分开(使用space-between、space-around和space-evenly)。

对于副轴,情况略有不同。我们使用align-items属性:

(译者注:案例请看原文)

有趣的是…使用align-items,我们有一些与justify-content相同的选项,但并没有完美的重叠。

为什么它们不共享相同的选项?我们很快就会解开这个谜团,但首先,我需要分享另一个对齐属性:align-self。

与justify-content和align-items不同,align-self应用于子元素,而不是容器。它允许我们沿副轴更改特定子元素的对齐方式:

(译者注:案例请看原文)

align-self具有与align-items相同的所有值。事实上,它们改变的是完全相同的东西。align-items是一种语法糖,是一种方便的简写,可以自动同时设置所有子元素的在副轴上的对齐方式。

没有justify-self属性。要理解为什么没有,我们需要深入研究Flexbox算法。

内容 vs 条目(子元素)

因此,根据我们目前所学的知识,Flexbox可能看起来相当随意。为什么名字是justify-content和align-items,而不是justify-items或align-content?

还有上文那个问题,为什么有align-items属性,而没有justify-self属性?

这些问题涉及到Flexbox最重要的和最容易被误解的事情之一。为了帮助解释,我想用一个比喻来说明。

在Flexbox中,条目(子元素)沿主轴分布。默认情况下,它们并排整齐排列。我可以画一条水平直线,将所有子元素串起来,就像烤肉串一样:

但是,交叉轴(副轴)有所不同。垂直直线只会与其中一个子项相交。它不像烤肉串,而更像一组鸡尾酒香肠:

这里有一个显著的区别。对于鸡尾酒香肠,每条都可以沿着签子移动,而不会干扰任何其他香肠:

相比之下,由于我们的主轴将每个子元素串在一起,因此单个元素无法沿着轴移动,而不会碰到其兄弟元素!尝试将中间部分左右拖动:

这是主轴和副轴之间的根本区别。当我们谈论副轴上的对齐时,每个条目都可以做任何它想做的事情。然而,在主轴上,我们只能考虑如何整组分配。

这就是为什么没有justify-self属性。中间部分设置justify-self: flex-start意味着什么?那个位置已经有另一个条目了!

考虑到所有这些知识背景,让我们对讨论的所有4个术语进行适当的定义:

  • justify — 沿主轴定位某物。
  • align — 沿交叉轴(副轴)定位某物。
  • content — 一组可以排列的“东西”。
  • items — 可以单独设置位置的单个条目。

因此:我们有justify-content属性来控制组沿主轴的排列,我们有align-items属性来沿副轴单独定位每个条目。这是我们用来管理Flexbox布局的两个主要属性。

没有justify-items属性的原因与没有justify-self属性的原因相同;当谈到主轴时,我们必须将所有条目(子元素)视为一个组。

那么align-content属性呢?实际上,它确实存在于Flexbox中!稍后我们将在讨论flex-wrap属性时介绍它。

假尺寸(hypothetical size)

让我们来谈谈我对Flexbox最令人大开眼界的认识之一。假设我有以下 CSS:

.item {
  width: 2000px;
}

一个理性的人看到这个可能会说:“好吧,所以我们会得到一个宽度为2000像素的项目”。但这总是正确的吗?让我们测试一下:

(译者注:案例请看原文)

这很有趣,不是吗?两个条目应用了完全相同的CSS。它们各自的宽度都是:2000px。然而,第一个条目比第二个条目宽得多!

不同之处在于布局模式。第一个条目使用流式布局进行渲染,在流式布局中,宽度是一个硬性约束。当我们设置宽度:2000px时,我们将得到一个2000像素宽的元素,即使它必须冲破视口的一侧。

然而,在Flexbox中,width属性的实现方式不同。它更像是一种建议,而不是硬性约束。规范对此有一个名称:假设尺寸。它是元素在完美的乌托邦世界中的尺寸,当没有任何阻碍时。

唉,事情很少这么简单。在这种情况下,限制因素是父元素没有空间容纳2000px宽的子元素。因此,子元素的尺寸会缩小以适应它。

这是Flexbox理念的核心部分。事物是流动的、弹性的,可以适应世界的限制。

算法的输入

我们倾向于将CSS语言视为属性的集合,但我认为这是错误的思维模型(mental model)。正如我们所见,width属性的行为会根据所使用的布局模式而有所不同!

相反,我喜欢将CSS视为布局模式的集合。每种布局模式都是一个可以实现或重新定义每个CSS属性的算法。我们通过CSS声明(键/值对)提供一个算法,然后该算法决定如何使用它们。

换句话说,我们编写的CSS是这些算法的输入,就像传递给函数的参数一样。如果我们想真正熟悉CSS,仅仅学习CSS属性是不够的;我们必须了解算法如何使用这些属性。(译者注:这就是很多CSS新手说“CSS很难学”、“CSS属性的功能不正交”的原因了,因为学习和使用CSS的思维模式不正确)

增长与收缩

因此,我们已经看到Flexbox算法具有一些内建的弹性,具有假尺寸。但要真正了解Flexbox的弹性,我们需要讨论3个属性:flex-grow、flex-shrink和flex-basis。 让我们看看这些属性。

flex-basis

我承认:很长一段时间以来,我都不太明白flex-basis到底是什么。

简单来说:在Flex行中,flex-basis的作用与width相同。在Flex列中,flex-basis的作用与height相同。

我们已经知道,Flexbox中的所有内容都与主轴和副轴挂钩。例如,justify-content将沿主轴分布子元素,无论主轴是水平还是垂直,其工作方式都完全相同。

但是 width和height不遵循这条规则!width总是会影响水平尺寸。当我们将flex-direction从row翻转为column时,它不会突然变成height。

因此,Flexbox的作者创建了一个通用的“尺寸”属性,称为flex-basis。它类似于width或height,但与其他所有东西一样与主轴挂钩。它允许我们在主轴方向上设置元素的假尺寸,无论主轴方向是水平还是垂直。

在这里尝试一下。每个子元素都被赋予了flex-basis: 50px,但你可以调整第一个子元素:

(译者注:案例请看原文)

就像我们在width方面看到的一样,flex-basis更多的是一种建议尺寸,而不是硬性约束。在某个时候,空间不足以让所有元素都按照指定的大小放置,因此它们必须做出妥协,以避免溢出。

不完全一样

通常,我们可以在Flex行(Flex row)中互换使用width和flex-basis,但也有一些例外。例如,width属性对图像等替换元素的影响与flex-basis不同。此外,width可以将元素缩小到其最小尺寸以下,而flex-basis则不能。

这远远超出了这篇博文的范围,但我想提一下,因为你可能偶尔会遇到两个属性具有不同效果的边界情况。

flex-grow

默认情况下,Flex上下文中的元素将沿主轴缩小到其最小舒适尺寸。这通常会产生容器元素的额外的空间。

我们可以使用flex-grow属性来指定如何使用该空间:

(译者注:案例请看原文)

flex-grow的默认值是0,这意味着flex-grow是可选的。如果我们想让某个子元素占用容器中的所有额外空间,我们需要明确地告诉它。

如果多个子元素设置了flex-grow会怎么样?在这种情况下,额外空间会根据子元素的flex-grow的值按比例分配给它们。

我认为用可视化方式解释会更容易。尝试增加/减少每个子元素的flex-grow的值:

(译者注:案例请看原文)

例如,第一个子元素想要4个单位的额外空间,而第二个子元素想要1个单位。这意味着单位总数是5 (=4 + 1)。每个子元素都会按比例获得该额外空间的份额(第一个子元素获得4/5的额外空间,第二个子元素获得1/5的额外空间)。

flex-shrink

在目前我们看到的大多数示例中,我们都有额外的空间可供使用。但如果我们的子元素对于容器来说太大了怎么办?

让我们测试一下。尝试缩小容器的宽度以查看会发生什么:

(译者注:案例请看原文)

很有趣,对吧?两个子元素都会缩小,但它们会按比例缩小。第一个子元素的宽度始终是第二个子元素的2倍。

温馨提醒,flex-basis的作用与width相同。我们将使用flex-basis,因为它是常规用法,但如果使用width,我们也会得到完全相同的结果!

flex-basis和width设定元素的假设尺寸。Flexbox算法可能会将元素缩小到低于它所需尺寸,但默认情况下,它们将始终一起缩放,从而保持两个元素之间的比例。

现在,如果我们不想让元素按比例缩小怎么办?这就是flex-shrink属性的作用所在。

花几分钟看看这个演示。看看你是否能弄清楚这里发生了什么。我们将在下面进行探索。

(译者注:案例请看原文)

我们有两个子元素,每个元素的假设尺寸为250px。容器的宽度至少需要500px,才能容纳假设尺寸的这些子元素。

假设我们将容器缩小到400px。好吧,我们无法将价值500px的内容塞进400px的袋子里!我们缺口是100px。我们的元素将需要放弃总共100px,才能适合。

flex-shrink属性让我们决定如何支付这笔差额。

和flex-grow一样,它是一个比例。默认情况下,两个子元素都有flex-shrink: 1,因此每个子元素都支付缺口的1/2。它们各自放弃50px,其实际尺寸从250px缩小到200px。

现在,假设我们将第一个子元素的flex-shrink设置为3:

我们总共缺口100px。通常情况下,每个子元素都会支付1/2,但是由于我们对flex-shrink进行了调整,第一个元素最终支付3/4(75px),第二个元素支付1/4(25px)。

请注意,绝对值并不重要,重要的是比率。如果两个子元素都具有flex-shrink: 1,则每个子元素将支付总缺口的1/2。如果两个子元素都设置为flex-shrink: 1000,则每个子元素将支付总缺口的1000/2000。无论哪种方式,结果都是一样的。

收缩和比例

在我们查看的示例中,两个Flex子元素具有相同的假设尺寸(250px)。在确定如何收缩它们时,我们可以使用flex-shrink进行专门计算。

不过,正如我们之前所看到的,收缩算法也会尝试保持兄弟元素之间的比例。如果第一个子元素的大小是第二个的2倍,那么第一个子元素的收缩幅度会更大。

因此,完整的计算涉及每个子元素的flex-shrink值和相对大小。

不久前,我对flex-shrink有了新的认识:我们可以将其视为flex-grow的“反面”。它们是同一枚硬币的两面:

  • flex-grow控制当子元素小于其容器时如何分配额外空间
  • flex-shrink控制当子元素大于其容器时如何移除空间

这意味着同一时刻,这两个属性中只有一个可以处于激活的状态。如果有多余的空间,flex-shrink不会产生任何效果,因为子元素不需要缩小。如果子元素对于其容器来说太大,flex-grow不会产生任何效果,因为没有多余的空间可以分配。

我喜欢把它想象成两个不同的世界。你要么在地球上,要么在相反的世界。每个世界都有自己的规则。

防止收缩

有时,我们不希望某些Flex子元素收缩。

我经常在SVG图标和形状中发现这种情况。让我们看一个简化的示例:

(译者注:案例请看原文)

Initial Data in Laravel Projects

Initial data for a project, such as roles (e.g., super admin role), permissions, and which users are granted admin privileges, is typically designed during the requirements analysis phase.

This initial data is a part of the project’s operation and will be used in the production environment. However, data seeding is generally used during the development phase.

Although Laravel doesn’t offer a built-in solution for this, we can leverage the database migration feature to achieve it. In terms of functionality, data migration is also part of the project, with the execution timing aligning perfectly with the installation of the project. The execution order is critical, ensuring that the initialization data is applied after the database table structure is created.

We can generate migration files for initializing data using the following command:

php artisan make:migration seed_categories_data

We define the naming convention for such migration files as seed_(table_name)_data.

For project initialization data, using a Seeder is less convenient than using a database migration file, especially in collaborative development. With multiple migration files from different developers, you only need to execute a single migration command. If issues arise, you can easily roll back, whereas Seeders lack rollback functionality. Rollback is crucial, both in development and production environments. If the initial data can be fully determined before the project is deployed (which is almost impossible), then using a Seeder is acceptable and recommended. However, after the project goes live, it’s likely that the initial data will require modifications (such as additions, deletions, or updates). In such cases, using a Seeder is inappropriate, and database migration files should be used instead. Seeders are primarily used for generating test data, and they should not be used for altering or deleting production data.

Laravel项目的初始化数据

项目的初始化数据,例如一个应用程序有哪些角色(例如超级管理员角色)、哪些权限、授予哪些用户管理员权限是在需求分析阶段就设计好的。

项目的初始化数据是项目运行的一部分,在生产环境下也会使用到,而数据填充(Seeder)一般在开发时使用。

虽然Laravel没有自带此类解决方案,不过我们可以借助数据迁移功能来实现。在功能定位上,数据迁移也是项目的一部分,执行的时机刚好是在项目安装时。并且区分执行先后顺序,这确保了初始化数据发生在数据表结构创建完成后。

我们可以使用命令生成数据迁移文件,作为初始化数据的迁移文件:

php artisan make:migration seed_categories_data

我们定义这种迁移文件的命名规范为seed_(数据库表名称)_data

对于项目的初始化数据,使用Seeder没有使用数据库迁移文件来得方便,特别是在多人协作开发时,对于别的开发者提供的多个数据库迁移文件,你只需执行一条迁移命令即可,如果有问题还能回滚,Seeder没有回滚功能。回滚功能无论是对于开发环境还是生产环境都是很重要的。 如果项目的初始化数据在项目上线(部署到生产环境)之前,就能全部确定下来(这几乎不可能),那么使用Seeder是可以的,也是应该的。但是在项目上线之后,很可能还要对初始化数据进行增删改操作,此时再使用Seeder是不合适的,应该使用数据库迁移文件。Seeder主要用来生成测试数据,很难、也不要用Seeder来删改数据库数据。

Web应用的类型以及它们各自的理想实现/交付方式

本文翻译自Jason Miller的博客文章Application Holotypes

分析现实世界中的应用程序的特征很困难。我们经常看到有人对应用程序做出概括,既有漫不经心的,也有经过数据统计的:“单页应用程序比多页应用程序慢”或“TTI 低的应用程序加载速度更快”。然而,这些概括对于我们关心的应用程序的性能和架构特征都不太准确。我认为主要决定因素之一是产品的功能和设计约束,根据应用程序的功能和约束对其进行分类可以为每个应用程序面临的问题提供更有针对性和更有效的解决方案。

构建一组类别,以便有效地将应用程序分组是一种挑战:很难预测所有可能的组,每个组设定的边界都是主观的,可能会随着时间的推移而改变。此外,像这样的抽象分组可能很难推理或可视化。例如,我们可以向“胖客户端、以页面为中心的富媒体应用程序,具有离线浏览和用户生成内容的功能”的开发人员推荐哪些性能优化技术?如果转而问的是我们可以向“类似Instagram的应用程序”的开发人员推荐什么,这种讨论就更具体化、更容易进行了。

为了建立这种讨论框架,我们可以构建一个典型应用程序列表。这些应用程序既可以代表当今的网络,也可以基于我们预见到的开发人员为响应未来趋势和平台计划而做出的改变。为了方便起见,代表Web的长尾历史和遗留内容的部分典型应用程序更加通用,而代表当前和即将推出的部分典型应用程序可以更狭窄地限定讨论范围,以便提供更具体的建议。

每个典型应用程序都附有粗略的类别名称、额外的真实案例以及定义其架构的特征和约束。我还根据其架构提供了理想的部署实施和交付技术。

社交网络应用

  • 典型:Facebook
  • 其他案例:LinkedIn、Reddit、Google+
  • 特点:多面性、子应用程序、无限滚动内容、用户生成内容、实时更新、通知
  • 限制:过深的会话深度、大规模、实时更新、嵌入式内容的资源争用、嵌套应用程序、SEO
  • 理想实现方式:具有外壳和登录页面预渲染的单页应用程序。
  • 理想交付方式:独立显示模式下的 PWA。或TWA。

社交媒体应用

  • 典型:Instagram
  • 其他案例:Youtube、Twitter
  • 特点:富媒体、无限滚动内容、用户生成内容、实时更新、通知、可嵌入性、嵌入式内容
  • 限制:扩展会话深度、实时更新、嵌入式内容的资源争用、不间断媒体播放、SEO
  • 理想实现方式:具有应用程序外壳预渲染和缓存的单页应用程序。
  • 理想交付方式:独立显示模式下的 PWA。

店面应用

  • 典型:亚马逊
  • 其他案例:百思买、新蛋、Shopify(基于商店)
  • 特点:搜索、支付、可发现性、过滤和排序 限制:浅到中等会话深度、小交互、高购物车/结帐流失率、SEO
  • 理想实现方式:具有 CSR/SPA 接管或 turbolinks 样式转换的服务器呈现站点。
  • 理想交付方式:默认显示模式下的 PWA。

内容网站

  • 典型:CNN
  • 其他案例:FT、BBC、BuzzFeed、Engadget、Salon、Smashing Magazine、The Onion
  • 特点:可发现性、富媒体、嵌入式内容 限制:浅会话深度(~1)、广告和多变量测试的资源争用、SEO
  • 理想实现方式:具有轮播图风格过渡效果的服务器端渲染网站。
  • 理想交付方式:默认显示模式下的 PWA。

PIM应用

  • 典型:Gmail
  • 其他案例:Google 日历、Outlook.com、Fastmail
  • 特点:胖客户端、无限列表、嵌入式内容、富文本编辑、清理、MDI、存储、离线和同步、通知
  • 限制:延长会话长度、敏感且大量不可缓存的数据、高安全风险、经常离线
  • 理想实现方式:具有应用程序外壳缓存的单页应用程序。
  • 理想交付方式:独立显示模式下的 PWA。

生产力工具

  • 典型:Google Docs
  • 其他案例:Office.com、Zoho、Dropbox、Box
  • 特点:胖客户端、富文本编辑、离线和同步、文件系统、剪贴板、存储、图像处理、嵌入式内容
  • 限制:延长会话长度和多个并发会话有利于客户端实现。
  • 理想实现方式:单页应用程序。考虑应用程序前端缓存。
  • 理想交付方式:独立显示模式下的 PWA。

媒体播放器

  • 典型:Spotify
  • 其他案例:Youtube Music、Google Play Music、Tidal、Soundcloud、Pandora、Deezer
  • 特点:富媒体、胖客户端、无限滚动内容、过滤和排序、通知、操作系统集成、离线、可嵌入性
  • 限制:延长会话长度,用户浏览其他页面时必须继续播放。
  • 理想实现方式:具有应用程序前端预渲染和缓存的单页应用程序。服务器渲染 <head> 以SEO友好。
  • 理想交付方式:独立显示模式下的 PWA。

图形编辑器

  • 典型:Figma
  • 其他案例:AutoCAD、Tinkercad、Photopea、Polarr
  • 特点:3D 渲染和 GPU、图像处理、全屏和指针捕获、MDI、存储、离线、文件系统、线程、wasm
  • 限制:会话长度长、对输入和渲染延迟的敏感性、大对象/文件
  • 理想实现方式:单页应用。将更轻量的浏览 UI 与编辑器分开。
  • 理想交付方式:独立显示模式下的 PWA。

媒体编辑器

  • 典型:Soundtrap
  • 其他案例:Looplabs
  • 特点:音频处理、设备集成(midi、usb)、存储、离线、文件系统、线程、wasm
  • 限制:长会话长度、低延迟 DSP、低延迟媒体录制和播放、大文件大小/IO
  • 理想实现方式:单页应用。将更轻量的浏览 UI 与编辑器分开。
  • 理想交付方式:独立显示模式下的 PWA。

工程工具

  • 典型:Codesandbox
  • 其他案例:Codepen、Jupyter Notebook、RStudio、StackBlitz
  • 特点:胖客户端、MDI、存储、离线、文件系统、线程、嵌入式内容
  • 限制:极长的会话长度、低延迟文本输入、大内存占用、自定义输入处理和文本渲染、预览内容的安全性
  • 理想实现方式:单页应用。考虑将浏览 UI 与编辑器分开。
  • 理想交付方式:独立显示模式下的 PWA。

沉浸式/AAA游戏

  • 典型:Stadia
  • 其他案例:Heraclos、Duelyst、OUIGO 特点:3D 渲染和 GPU、P2P、音频处理、全屏和指针捕获、存储、离线、文件系统、线程、设备集成(游戏手柄)、wasm
  • 限制:会话长度长(高度交互)、沉浸感、对输入和渲染延迟极其敏感、需要一致或分步的 FPS、极端的资产大小
  • 理想实现方式:单页应用
  • 理想交付:全屏显示模式下的 PWA。

休闲游戏

  • 典型:Robostorm
  • 其他案例:Tank Off、War Brokers、GoreScript、Air Wars、”.io games”
  • 特点:2D 和 3D 渲染和 GPU、P2P、音频处理、存储、离线、可嵌入性
  • 限制:会话长度长、对输入和渲染延迟敏感、需要一致/分步 FPS
  • 理想实现方式:单页应用
  • 理想交付方式:嵌入另一个站点,或全屏显示模式下的 PWA。

托管钱包和非托管钱包

钱包按是否被托管可以分为:

  • 非托管钱包(non-custodial wallets)(也称为自托管钱包),意味着用户始终可以控制自己的私钥和资金。非托管钱包有trust、MetaMask、Coinbase等。
  • 托管钱包(custodial wallets),意味着用户的私钥和资金都放在钱包软件公司的服务器里。托管钱包有Binance Wallet等。

托管钱包就像将你的贵重物品存放在仓库中,而非托管钱包就像将它们存放在家中的保险箱中。因此,托管钱包需要较少的个人责任,但受第三方支配,而非托管钱包由你自己完全控制,但也意味着你承担全部责任,确保你的物品安全并保管你的密码和密钥。