我一直在阅读一些非常聪明的人的出色帖子,涉及关于固体和角度的反应。
我基本上都喜欢他们(框架和人们!)。我的意思是,我不是一个钩爱好者...但是谁在乎。
您已经听到了大新闻:信号即将到来!我喜欢这个原因很多,但是我想在这里专注于一件重要的事情:声明代码。
本文的灵感来自Dan Abramov的React组件的示例:
// React
function VideoList({ videos, emptyHeading }) {
const count = videos.length;
let heading = emptyHeading;
if (count > 0) {
const noun = count > 1 ? 'Videos' : 'Video';
heading = count + ' ' + noun;
}
return <h1>{heading}</h1>
}
非常简单!它接受数组和一个占位符价值,进行一些计算,并返回一些文本。
No videos here
1 Video
2 Videos
就是这样,那是组合。
现在:我喜欢功能性编程,并且反应有点遵循功能性思维方式。
f(state) = UI
我不会在这里谈论钩子:这不是重点。
关键是功能编程是声明性编程的子集。我喜欢声明的代码。这个特定组件的示例是我不太喜欢的。
在这种情况下,该组件是证明概念的琐碎例子。但实际上,我在许多项目中发现了这种代码,所以让我们从那里开始谈论声明代码,以及为什么我为Angular中的信号感到兴奋。
。这篇文章假定信号的基本知识(以及对比较的一些反应)!
功能是声明性的
让我们回到组件:
// React
function VideoList({ videos, emptyHeading }) {
const count = videos.length;
let heading = emptyHeading;
if (count > 0) {
const noun = count > 1 ? 'Videos' : 'Video';
heading = count + ' ' + noun;
}
return <h1>{heading}</h1>
}
我为什么不喜欢它?因为该组件的逻辑是命令。这不是一个小的组件的问题,但这不是复杂代码库中的大多数组件的样子。
为了了解heading
可能是什么,我必须深入研究 所有组件的其余部分。
在纯函数编程中,没有变量,只有常数。在JavaScript中,我们 can 变量变量,我们不必严格遵循FP的指南即可编写好代码,但是在许多情况下,这仍然是一个很好的建议。
这将是声明的方法:
// React (declarative)
function VideoList({ videos, emptyHeading }) {
const count = videos.length;
const heading = count > 0
? count + ' ' + (count > 1 ? 'Videos' : 'Video')
: emptyHeading;
return <h1>{heading}</h1>
}
每个声明包含该特定常数的所有逻辑。当我阅读const heading = this_thing
时,在我看来,我读了“ 这就是heading
的内容”。我不在乎其余的组件。
这里的缺点是,三元运算符可能更难阅读,并且写一些尴尬。我们也不能真正使用中间变量(例如上一个示例中的noun
),而没有像iife”这样的奇怪语法。
,这对我来说是一个很小的劣势。现在,乍一看,我知道heading
的全部内容。声明包含有关如何计算该值的完整配方。我不再需要阅读其余部分来理解它。
这对我作为顾问尤其重要:我需要能够查看一些代码并立即理解其背后的逻辑。为什么? 因为我无法理解整个项目,客户也不能!时间是金钱。
也对团队也是如此!如果我能立即理解一些东西,那意味着该代码背后的人将能够保持良好的维护。
课程
与纯函数相比,类是一种不同的心理模型。这不是潜在的指令流,而是一组属性和方法。
这意味着,如果我们使用类,则不能以相同的方式编写上一个反应组件(命令率)。我们必须将逻辑放置在方法中。这既丑陋又奇怪。
// Angular (kinda, you get the point)
class VideoListComponent {
@Input() videos;
@Input() emptyHeading;
count = 0;
heading = '';
ngOnChanges(changes) {
this.count = changes.videos?.currentValue.length;
this.heading = this.count > 0
? this.count + ' ' + (this.count > 1 ? 'Videos' : 'Video')
: this.emptyHeading;
}
}
老实说?这很糟糕。将其与以前的反应示例的清洁程度进行比较。我不想触摸生命周期方法。我不在乎。我想声明派生值:我想编写食谱。
在课堂上,我们可以利用getters
来声明派生的状态。
class VideoListComponent {
@Input() videos = [];
@Input() emptyHeading = '';
get count() {
return this.videos.length;
}
get heading() {
if (this.count > 0) {
const noun = this.count > 1 ? 'Videos' : 'Video';
return this.count + ' ' + noun;
}
return this.emptyHeading;
}
}
阅读和推理要简单得多。这就是我在如此简单的用例中的建议。
请注意,由于getter是一个函数,因此易于使用中间变量(noun
)和命令式代码的一些闪闪发光(它们是独立的,并且不散布在整个组件周围)。
当然,写有点冗长。我们必须问自己:框架会弥补更改吗?我们依靠变更检测来使这些机器人工作,并且可能会被解雇很多次,因为没有真正的方法知道可变的财产已经发生变化。
一旦我们获得信号即将结束。
信号
class VideoListComponent {
// I think this is what we'll end up with, reactive
// inputs as signals with default values, or something like that!
videos = input([]);
emptyHeading = input('');
count = computed(() => this.videos().length);
heading = computed(() => {
if (this.count() > 0) {
const noun = this.count() > 1 ? 'Videos' : 'Video';
return this.count() + ' ' + noun;
}
return this.emptyHeading();
});
}
这仍然比基于功能的替代方案更多的代码。真的没有什么。但是:
- 非常明确
- 这是声明
也:
- extraint 默认情况下(实际上是任何其他信号实现),我认为这是任何框架的未来。我不喜欢“以后进行优化,如果需要的话”的思维方式,因为在大型应用程序中,我可能没有意识到从现在的3个月内使用更多代码的特定计算将在3个月内的昂贵。而且很难回到罪名代码。
- 我们将不再需要
zone.js
(phew)
这些都是我的好处。
信号具有多种口味,每个框架都使用它们。将其与Solid的方法进行比较:
// Solid
function VideoList(props) {
const count = () => props.videos.length;
const heading = () => {
if (count() > 0) {
const noun = count() > 1 ? "Videos" : "Video";
return count() + " " + noun;
}
return props.emptyHeading;
}
return <h1>{heading()}</h1>
}
我的看法是,这种方法也很棒。但是它有一些小警告。
- 为什么到处都有箭头功能?我们必须知道,要使可变的反应性做出固体,必须是一个函数。但这很难一见钟情。
- 当我看到功能时,我不知道它是反应性派生状态还是事件处理程序。它们看起来相同,所以我必须阅读代码才能知道它的作用(或者,希望该变量有一个好名字!)
重要的是要注意,这只是一种意见,您可能比其他任何东西都更喜欢Solid的代码!这是一个很棒的工具。
那么为什么我喜欢带信号的角度?为什么我喜欢这样上课?
因为有了Angular的信号,我们强迫传达值是衍生状态。我们将被强迫说应该将输入视为信号(如果我们愿意的话!)。并且编写命令式代码,例如本文的第一个示例,在反应性上下文中的班级性质几乎劝阻。如果您的代码看起来很糟糕,那么您可能会讨厌它,也许明天,也许会在3个月内。
使用React,我们有可能(我不想)编写命令代码的可能性。我们有一个很好的简短语法。但是我们没有细粒度的反应性,钩子有规则(我认为可以说React的心理模型很棒 您添加钩子,并且它们使用了很多)。 P>
使用固体,我们通常会处理很多匿名功能,而处理道具可能很奇怪(它们是反应性的,但是我们将它们视为正常价值,破坏是奇怪的)。
使用Angular,我们有一个清晰的语法,我们将其推向宣言代码。要支付的税款比替代方案更多。
我的看法已经相同多年了:较少的代码并不总是客观上好。任何学习OOP的高中生都可以轻松理解课程。他们很容易理解我们是否正确。所以做正确的事情:)
许多人批评课程。他们有一些充分的理由这样做。那么,即使我喜欢功能性编程,我怎么会感谢他们呢?
如果我们使用具有一些FP心态的类(不变性,属性是常数),那么它们的许多缺点就消失了。
我很高兴我永远不再需要ngOnChanges
。或一些输入设置来设置一些BehaviorSubject
s值!
ps。请,不要将其视为对反应或固体的批评。