最近我一直在学习有关Facebook JavaScript库React.js的功能和使用方式。当谈到它与JavaScript世界中其他部分的不同之处时,通常会提到两种编程风格——声明式
和命令式
。
这两种风格有什么区别呢?
最近我一直在学习有关Facebook JavaScript库React.js的功能和使用方式。当谈到它与JavaScript世界中其他部分的不同之处时,通常会提到两种编程风格——声明式
和命令式
。
这两种风格有什么区别呢?
像React一样的声明式编程风格可以通过描述"它应该长成这个样子"来控制应用程序的流程和状态。而命令式编程则相反,通过描述"这是你应该做的事情"来控制应用程序。
声明式编程的好处在于,您不会陷入表示状态的实现细节中。您将代理保持应用程序视图一致性的组织部分,因此只需担心状态。
想象一下您有一个管家(类比于框架),您想制作晚餐。在命令式编程世界中,您将逐步告诉他们如何制作晚餐。您必须提供这些指令:
Go to the kitchen
Open fridge
Remove chicken from fridge
...
Bring food to the table
在一个声明式的世界里,你只需要简单地描述你想要什么。I want dinner with chicken.
如果您的管家不知道如何烹制鸡肉,那么您无法以声明式风格操作。就像如果 Backbone 不知道如何突变自身以完成某个任务,您不能仅仅告诉它执行该任务。React 能够以声明式方式工作,因为它“知道如何烹制鸡肉”,例如。相比之下,Backbone 只知道如何与厨房进行交互。
能够描述状态可以大大减少错误的表面积,这是一个好处。另一方面,您可能在实现状态时委托或抽象了掉一些实现方式,因此在 如何 发生事情方面可能会有较小的灵活性。
想象一个简单的 UI 组件,比如一个“喜欢”按钮。当你点击它时,如果它之前是灰色的,那么它就会变成蓝色;如果它之前是蓝色的,那么它就会变成灰色。
这样做的命令式方式如下:
if( user.likes() ) {
if( hasBlue() ) {
removeBlue();
addGrey();
} else {
removeGrey();
addBlue();
}
}
基本上,您需要检查屏幕上当前的内容,并处理所有必要的更改以使用当前状态重新绘制它,包括撤消先前状态的更改。您可以想象在现实世界情况下这可能会有多么复杂。
相反,声明式方法则是:
return this.state.liked ? <blueLike /> : <greyLike />;
由于声明性方法将不同的关注点分离开来,因此它只需要处理UI在特定状态下的外观应该是什么样子,因此更加简单易懂。
最好比较React(声明式)和JQuery(命令式)以展示它们的差异。
在React中,你只需要在render()
方法中描述你的UI的最终状态,而不必担心如何过渡从先前的UI状态到新的UI状态。例如,
render() {
const { price, volume } = this.state;
const totalPrice = price * volume;
return (
<div>
<Label value={price} className={price > 100 ? 'expensive' : 'cheap'} ... />
<Label value={volume} className={volume > 1000 ? 'high' : 'low'} ... />
<Label value={totalPrice} ... />
...
</div>
)
}
另一方面,JQuery要求您以命令方式转换UI状态,例如,选择标签元素并更新它们的文本和CSS:
updatePrice(price) {
$("#price-label").val(price);
$("#price-label").toggleClass('expansive', price > 100);
$("#price-label").toggleClass('cheap', price < 100);
// also remember to update UI depending on price
updateTotalPrice();
...
}
updateVolume(volume) {
$("#volume-label").val(volume);
$("#volume-label").toggleClass('high', volume > 1000);
$("#volume-label").toggleClass('low', volume < 1000);
// also remember to update UI depending on volume
updateTotalPrice();
...
}
updateTotalPrice() {
const totalPrice = price * volume;
$("#total-price-label").val(totalPrice);
...
}
在现实世界的情境下,需要更新的UI元素会更多,还包括它们的属性(例如CSS样式和事件监听器)等。如果使用JQuery来进行命令式编程,那么这将变得复杂而繁琐。容易忘记更新某些UI部分或忘记删除旧的事件处理程序(导致内存泄漏或处理程序多次触发),等等。这就是错误发生的地方,即UI状态和模型状态不同步。
React声明式的方法不会出现状态不同步的问题,因为我们只需要更新模型状态,React负责保持UI和模型状态同步。
您还可以阅读我对编程中声明式和命令式范例的区别是什么?的回答。
PS:从上述jQuery示例,您可能会想到,如果我们将所有DOM操作放入updateAll()
方法中,并在模型状态更改时每次调用该方法,UI将永远不会失步。你是正确的,这实际上就是React所做的,唯一的区别在于jQuery的updateAll()
会导致许多不必要的DOM操作,但React只会使用其虚拟DOM差异算法来更新已更改的DOM元素。
这是一个很好的类比:
*命令式响应:从停车场北出口出去,向左转。上I-15南行,直到到达Bangerter高速公路出口。像前往宜家一样右转离开出口。直走,在第一个红绿灯处右转。通过下一个红绿灯后,再左转。我的房子是#298。
声明式响应:我的地址是Utah州Draper市Immutable Alley西298号,邮编84020*
来源:https://tylermcginnis.com/imperative-vs-declarative-programming/
JavaScript的命令式代码会指示JavaScript执行每个步骤的方式。而声明式代码则是告诉JavaScript我们想要完成的任务,然后让JavaScript处理执行步骤。
React是声明式的,因为我们编写我们想要的代码,React负责使用我们声明的代码执行所有的JavaScript / DOM步骤,以达到我们想要的结果。
声明式编程是一种编程风格,应用程序的结构优先描述应该发生什么,而不是定义它如何发生。
为了理解声明式编程,让我们将其与命令式编程进行比较(一种只关注如何使用代码实现结果的编程风格)。
例如:使字符串符合URL规范。通常,这可以通过将字符串中的所有空格替换为连字符来完成,因为空格不符合URL规范。首先,对于这个任务来说,一种命令式的方法是:
const string = "difference between declarative and imperative in react.js";
const urlFriendly = "";
for (var i = 0; i < string.length; i++) {
if (string[i] === " ") {
urlFriendly += "-";
} else {
urlFriendly += string[i];
}
}
console.log(urlFriendly); // "difference-between-declarative-and-imperative-in-react-js"
const string = "Difference between declarative and imperative in React.js?";
const urlFriendly = string.replace(/ /g, "-");
console.log(urlFriendly);
在这里,我们使用string.replace
和正则表达式一起替换所有空格为连字符。使用string.replace
是描述应该发生什么的方法:应该替换字符串中的空格。如何处理空格的详细信息都在replace函数中抽象化了。
在声明式程序中,语法本身描述了应该发生什么,而事情如何发生的细节被抽象化了。
基本上,声明式编程产生的应用程序更容易推理,当应用程序更容易推理时,它就更容易扩展。有关声明式编程范例的其他详细信息可以在Declarative Programming wiki找到。
现在,让我们考虑构建文档对象模型的任务。一种命令式方法会关注DOM是如何构建的:
const target = document.getElementById("target");
const wrapper = document.createElement("div");
const headline = document.createElement("h1");
wrapper.id = "welcome";
headline.innerText = "Hello World";
wrapper.appendChild(headline);
target.appendChild(wrapper);
这段代码涉及到创建元素、设置元素并将它们添加到文档中。如果使用命令式方式构建DOM,那么要对一万行代码进行更改、添加功能或者扩展是非常困难的。
现在让我们看一下如何使用React组件以声明方式构建DOM:
const { render } = ReactDOM;
const Welcome = () => (
<div id="welcome">
<h1>Hello World</h1>
</div>
);
render(<Welcome />, document.getElementById("target"));
React是一种声明式编程方式。在这里,Welcome组件描述了应该呈现的DOM。渲染函数使用组件中声明的指令来构建DOM,抽象掉了如何呈现DOM的细节。我们可以清楚地看到,我们希望将Welcome组件呈现到具有ID为target的元素中。
声明式编程是一种编程范式...它表达了计算的逻辑,而不描述其控制流程。
命令式编程是一种编程范式,使用改变程序状态的语句。
参考链接:-https://codeburst.io/declarative-vs-imperative-programming-a8a7c93d9ad2
//Imperative
const para = document.createElement('p');
para.innerText = 'Hello World !';
document.querySelector('#root').appendChild(para);
//Declarative
import React from "react";
import ReactDOM from "react-dom";
const App = () =>{
return(<p>Hello World !</p>);
}
ReactDOM.render(<App />, document.getElementById("root"));
声明式编程是指更有经验的专业人员以一种方式编写代码,使其行为可以通过使用外部配置来改变,这些配置代表对象的导向图。例如,Jetpack Compose或Flutter Widgets。
传统的解决方案使用了类似于XML的标记语言来表示UI对象树。例如,WPF中的XAML(请参见FuncUI)或Qt中的*.ui。它们仍然具有内部对象组合语法(如DOM API),但强烈建议使用标记语言以声明方式实现UI。
在Facebook引入JSX之后,整个企业都迁移到Web上,因为它为开发人员提供了更多的设计功能和定制用户体验的可访问性,通过使用函数式编程
(代码显着便宜)。 在这种情况下,JSX
是声明性的到HTML
(更正确的是DOM API
-> React
-> JSX
)
如果你学过计算机科学,你一定知道软件中的所有内容都是建立在抽象之上的。例如,在macOS中,硬件
被mach内核
使用。Mach内核
被Core OS
使用。Core OS
被QuickTime
使用。QuickTime
被GUI
使用。正如你所看到的,关系的主语对关系的客体是陈述性的。
这个想法很简单。如果C/C++语言
是声明式的,那么JavaScript
就是对C++
的声明,而React
则是对JavaScript
的声明。通常情况下,开发人员正在设计一些东西,以减少使用React(React
-> Plain JS objects
-> JSX
)时的应用程序开发成本。
import { Scaffold2, IScaffold2Group } from "react-declarative";
const options: IScaffold2Group[] = [
{
id: 'build',
label: 'Build',
children: [
{
id: 'authentication',
label: 'Authentication',
isVisible: async () => await ioc.authService.hasRole('unauthorized'),
icon: PeopleIcon,
tabs: [
{ id: 'tab1', label: 'Tab1 in header', },
{ id: 'tab2', label: 'Tab2 in header', },
],
options: [
{ id: 'tab1', label: 'Tab1 in side menu' },
{ id: 'tab2', label: 'Tab2 in side menu' },
],
},
{ id: 'Database', label: 'Label is optional (can be generated automatically from ID in snake case)', icon: DnsRoundedIcon, },
{ id: 'Storage', isDisabled: async () => await myAmazingGuard(), icon: PermMediaOutlinedIcon, },
{ id: 'Hosting', icon: PublicIcon, },
...
<Scaffold2
options={options}
...