这不是您使用Hooks洗脑的典型方法,其目的是使用基于类的组件并赞美Ever-So-Holy的Hooks美德而感到羞耻。甚至还没有深入了解Hook是什么以及为什么(或不应该)使用它们。取而代之的是,这是一个案例研究,说明了JavaScript的圣洁教条如何导致抛弃一个完全有用的范式以支持新的“今日风味”的扩展演习-仅因为某些“思想领袖”决定退出与JavaScript的class
关键字进行一场神圣的战争。
背景
我从事专业的React开发已经有大约5年的时间了(以及其他20多种开发风格)。我不容易采用新技术。我有太多的“真正的东西”要完成,无法追踪NPM上弹出的每个“每日包装”。所以我不是React的最新采用者。但是,当我没终于“见光”,它肯定点击与我在一个显著的方式。
作为一个长期的JavaScript开发人员,我(颇有意思地)看着该语言的“思想领袖”的某些干部开始针对不可思议,难以理解,难以想象的恐怖,即JavaScript class
关键字。我读过许多您可能做过的同样的思考。我阅读了所有“合理性”以了解为什么它被认为是邪恶的-尽管它只不过是语法糖,但它绝对无法为JavaScript提供您无法做到的一切。
我不能说我真的很关心圆形辩论。我以为我是相当“中立的”。我看到class
了它的意思-只是一个关键字。我知道关键字既不是“好”也不是“坏”。他们只是…是。如果您想使用该特定关键字,那就太好了!如果您不喜欢该关键字,那也很好! 你做你!
同时,作为一个React开发人员,我无法逃脱class
。在2018年之前,如果您要编写React代码,则主要是使用基于类的组件来完成的。
当然,在概念上始终将重点放在纯函数上。但是纯函数没有生命周期。他们没有状态。它们是…功能。而且,如果您要构建任何规模可观的应用程序,则在某个时候,您将必须获得这些生命周期方法和状态管理功能。
教条的代价
每种技术都有其独裁者。如果您犯了使用过时的function()
声明而不是很酷的箭头函数的错误,那么卑鄙的类型将告诉您您的代码很烂。他们试图使您感到羞耻,因为您没有在return
语句上方留空行。或者因为您没有将开口{
放在自己的线上。
在大多数情况下…我只是忽略了这些独裁者。我有最后期限。我有付费客户。我不必为重构100k + LoC应用程序而烦恼,因为一些热门的新博客文章说不应使用内联样式。撰写您的傲慢博客文章。前五个独裁者朋友。我有工作要做。
但这在2018年发生了变化。那一年的10月,我们受到了… Hooks的好评。
和狂热的人高兴。
在介绍Hooks时,我和一个非常有才华的React开发人员一起工作,他几乎就在他身边。他很高兴。我为他感到高兴。但是他一直向我展示这些胡克斯的例子,并称赞它们如此明显。我一直看这些示例,然后思考:“是的……这是在课堂上做我们已经可以做的所有事情的另一种方式。”
您会发现,最好告诉您的所有好友,制表符比空白符更高级。但是,当您具有将新软件包包含在React的核心版本旁边的影响力–并且您的新软件包尝试执行制表符,或者您的“社区”尝试将可耻的人编码为使用制表符时,那么…只是个混蛋。
市场营销学
并不是所有这些都让我当时感到非常困扰。我仍然有数千行代码需要处理-在基于类的组件中完美地运转着的代码。没有人愿意付钱给我重写他们所有完全没有错误的基于类的组件。
如果您曾经参加过市场营销101课程,那么您会发现人们需要有说服力的理由来更改产品。只是告诉我您有一种新的牙膏不会强迫我改用它。甚至尝试一下。我已经有了喜欢的牙膏品牌。效果很好。很好吃
如果您想让我改用新品牌的牙膏,则必须提供比“新产品”更好的产品。或“不一样!” 您必须给我一个令人信服的改变理由。
可悲的是,这些“市场力量”往往在编程界变得很变态。Joe提出了一种编写JavaScript的新方法。他对所有伙伴大喊大叫,他们应该以新方式进行编码。而且…每个人都耸了耸肩。
但是,如果乔被视为“思想领袖”怎么办?如果他已经被歌迷们誉为编程的“传奇”怎么办?好吧…在那种情况下,狂热的男孩们开始在他身后排成一列。狂热者不仅开始转移他们所有的代码以反映思想领袖乔,而且如果您不内联,他们也会开始对您进行代码欺骗。
不相信我吗?考虑一下我发表的其他一篇与class-vs-function辩论无关的评论:
我不敢相信你还在写课。
而已。没有关于帖子内容的明智论述。根本没有有意义的反馈。我的示例代码使用了…类,这只是一堆乱七八糟的垃圾,肆虐于我的笨拙代码。
狂热者对简单的拖钓不满意。他们也很高兴在A级#FakeNews上兜售。在网络上的无数示例中,我已经看到React博客文章几乎留下了完全相同的评论:
基于类的组件正在逐步淘汰。
嗯…不 他们不是。如果您不相信我,请花几分钟阅读核心React网站上的Hooks文档。很明显。这是明确的。它说:“没有计划从React中删除类。” 显然,狂热者不愿意(或无法)直接从React团队阅读这一基本明确的声明。
比较
在过去的几年中,我对Hooks几乎保持沉默。我不讨厌他们 我不爱他们 我只是将它们视为…一件事情。一个工具,可以在某些情况下证明是有用的-和别人不那么有用。像几乎所有 React开发人员一样,我在本地环境中对它们进行了修改。但是在大多数情况下,它们只是一个旁注。发生这种情况是因为我的雇主(实际上,您知道付我钱来写代码的人)仍然拥有庞大的旧代码库,里面充斥着各种类。开始将所有内容转换为Hooks并非完全简单。
过去几个月对我来说是一个大开眼界。我加入了一家新公司,我们有幸进行了一些“绿色领域”开发。在我们写任何东西之前,我们都对新项目的工具,技术和最佳实践进行了讨论。我们共同决定,所有这些新代码都将使用纯函数和基于函数的组件(即,使用Hooks)完成。因此,我终于有机会对Hooks进行一次真正的“深潜”。
我们不仅在使用Hooks开发全新的代码,而且我真的很想快速了解它们。我有一个大型的副项目,目前位于30k LoC以上,我自己承担了将所有这些转换为Hooks的工作。在花了数百个小时沉浸在万物钩中之后,我可以自信地说我的评估是…
h …
在开始转动眼睛之前,请了解我对钩子没有什么特别的看法。他们很好。他们真棒。但是,当您将数百个基于类的组件转换为Hooks之后,过了一会儿,令人惊讶的是,新的,非常酷的,基于函数的组件看起来像…基于类的组件。
首先,让我们看一个简单的示例:
// the old, evil, class-based component
export default class CancelButton extends React.Component {
render() {
return (
<Button
onClick={this.props.onClick}
style={{
backgroundColor : the.color.cancel,
color : the.color.white.text,
...this.props.buttonStyle,
}}
variant={the.variant.raised}
>
<FontAwesome
name={the.icon.x}
style={{marginRight : 10}}
/>
<TranslatedTextSpan english={'Cancel'}/>
</Button>
);
}
}
现在是这个:
// the amazing, fantabulous, function-based component
export default function CancelButton(props) {
return (
<Button
onClick={props.onClick}
style={{
backgroundColor : val.colors.lightGrey,
color : val.colors.nearWhite,
...props.buttonStyle,
}}
variant={'contained'}
>
<FontAwesome
name={val.icons.x}
style={{marginRight : 10}}
/>
<TranslatedTextSpan english={'Cancel'}/>
</Button>
);
};
哇…有什么不同。基于功能的组件就是SOOOOO好得多,右 ???
嗯…
好吧,公平地说,也许这个例子太简单了,无法说明基于功能的组件的许多好处。毕竟,它甚至没有任何挂钩。因此,让我们看一些有趣的东西:
// the old, evil, class-based component
export default class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
fields : {emailAddress : I.getDefaultFieldProperties()},
okButtonIsDisabled : true,
};
}
checkForEnter(event) {
if (!this.state.okButtonIsDisabled && event.keyCode === the.keyCode.enter) { this.callCreateLogIn(); }
}
componentDidUpdate(prevProps, prevState, snapshot) {
this.updateOkButtonState();
}
dismissAlertAndGoHome() {
app.DisplayLayer.dismissAlert();
app.DisplayLayer.updateModule(<HomeModule />);
}
goToRegister() {
app.DisplayLayer.updateModule(<RegisterModule />);
}
render() {
const {fields, okButtonIsDisabled} = this.state;
return (
<FullHeightPaper>
{/* render ALL THE THINGS */}
</FullHeightPaper>
);
}
updateFieldState(event) {
const updatedFieldState = I.getUpdatedFieldState(event.target, this.state);
this.setState(updatedFieldState);
}
updateOkButtonState() {
const {fields, okButtonIsDisabled} = this.state;
if (this.logInFormIsInFlight) { return; }
const someFieldsAreInvalid = Object.keys(fields).some(fieldName => fields[fieldName].isValid === false);
if (someFieldsAreInvalid !== okButtonIsDisabled) { this.setState({okButtonIsDisabled : someFieldsAreInvalid}); }
}
}
现在是这个:
// the amazing, fantabulous, function-based component
export default function LoginForm() {
const displayLayer = useContext(DisplayLayerContext);
const model = useContext(ModelsContext);
const sessionApi = useContext(SessionApiContext);
const [emailAddressField, setEmailAddressField] = useState(model.textField());
const [okButtonIsDisabled, setOkButtonIsDisabled] = useState(true);
const checkForEnter = (event = {}) => {
if (!is.aPopulatedObject(event))
return;
if (!okButtonIsDisabled && event.keyCode === val.keyCodes.enter)
sendLogIn();
};
const goToHome = () => {
displayLayer.updateModule('home');
};
const goToRegister = () => displayLayer.updateModule('register');
const handleErrors = (errors = []) => {
if (!is.aPopulatedArray(errors))
return;
if (errors.find(responseError => responseError === 'email does not exist')) {
let alert = model.alert();
alert.icon = 'warning';
alert.text = translate('The email address supplied could not be found in our records.');
alert.title = translate('Oops!');
createAlert(alert);
} else {
displayLayer.createGenericErrorAlert();
}
setEmailAddressField(model.textField());
};
const updateFieldState = (event = {}) => {
if (!is.aPopulatedObject(event))
return;
let clonedEmailAddressField = cloneObject(emailAddressField);
clonedEmailAddressField.value = event.currentTarget.value.trim();
clonedEmailAddressField.isValid = isEmailAddressValid(event.currentTarget);
setEmailAddressField(clonedEmailAddressField);
setOkButtonIsDisabled(!clonedEmailAddressField.isValid);
};
return (
<FullHeightPaper>
{/* render ALL THE THINGS*/}
</FullHeightPaper>
);
};
OK,这是一个很大更多的“参与”的例子。我们正在使用useState()
!而我们正在使用useContext()
!而且基于功能的组件是明显优于基于类的组件的“胜利者”…… 对吧?
嗯…
如果您不是立即意识到基于功能的组件明显优于旧的,丑陋的,讨厌的,基于类的组件,那么…祝贺您。您不是一个无意识的狂热粉丝,只是因为React的主要贡献者之一告诉您,而歌颂了The Hooks 。
真实代码
我在网上看到很多seen脚的例子,有人将一个旧的,丑陋的,基于类的组件转换为(据说)漂亮的基于函数的组件,然后用它来赞美Hooks。这些示例的问题在于,它们很少反映真实的,实时的,荒谬的代码。
完全清楚地说,我绝对可以找到一些示例,这些示例中,基于函数的组件最终比原始的基于类的示例小一些,并且名义上“干净”些。不幸的是,我发现这些例子相对较少。
当您真正开始涉足Hooks时,几乎一对一转换的原因就很清楚了:
钩子只是做您在基于类的组件中已经可以做的另一种方式。
国家一团糟。但是您几乎无法完全避免进行状态管理。因此,当您开始将所有状态管理从基于类的组件移植到Hooks时,它看起来惊人地相似。
生命周期混乱。但是您几乎无法完全避免生命周期管理。因此,当您开始将所有生命周期管理从基于类的组件移植到Hooks时,它看起来非常相似。
而且我什至都没有显示任何使用useEffect()
和的转换useCallback()
。当您开始进入这一详细级别时,基于类的组件看起来就更加简单了。
教条的最终结果
让我确切地告诉您我们如何到达胡克斯。大约5年前,JavaScript Illuminati的某个部分决定:
课不好… mmmkay ???
当他们这样做时,这为React社区带来了困惑。React已经步入正轨class
。即使React社区开始大声地抱怨那个不合情理的class
关键字的可怕,难看,丑陋,但始终存在一个中心问题:您不能在纯函数中做很多“反应”。具体来说,您无法执行某些关键功能,例如状态和生命周期管理。
整个class
仇恨可能就在那里死了,除了 … Redux团队完全接受了“必须上课”的口头禅。因此,他们创建了挂钩。然后他们在社区中发挥了相当大的影响力,从而明确了Hooks绝对是下一个大事。
然后…迷迷们无意识地陷入了困境。
因此,现在,如果您要撰写React博客文章或在访谈中展示与class-vs-functions辩论无关的某些概念,则必须警惕潜在的潜在仇恨者。因为如果您class
在白板上扔那些邪恶的关键字之一,那么从字面上看,这可能是对它们的讨论的结尾。
放下仇恨
您可能会认为我是Hooks讨厌的人。但是,事实离真相还很远。一个简单的事实是,钩子是一个工具,在工具带。您的锤子不是“好”或“坏”的。在某些情况下是“好的”。彻头彻尾的别人毫无意义。关于胡克斯也可以这样说。 或类。
因为某人使用类而对他们进行代码欺骗与使工匠因为使用锤子而受到侮辱一样有意义。
实际上,我最近对Hooks的开发非常满意。他们有一些明显的优势(即我还是要强调在未来的岗位)。我还发现他们肯定有一些挑战。我不必在基于类的组件中处理的挑战。
关键不是要确定Hooks是“坏”还是类是“好”(反之亦然)。关键是要了解什么是Hook和类:语法。