React.js 查看状态管理教程

已发表: 2022-03-11

前端 Web 开发中最大和最常见的问题之一是状态管理。 像我这样的自由前端开发人员一直专注于保持状态对象与其视图和 DOM 表示同步。 用户可以通过多种方式与应用程序交互,提供从一种状态到另一种状态的干净转换是一项艰巨的任务。

通过本 React.js 教程,了解有关视图状态管理的更多信息。

一点历史

不久前,Web 应用程序的数据流要简单得多。 浏览器将向服务器发送请求,所有应用程序逻辑都将在服务器上执行,完整的 HTML 视图将被发送回浏览器以呈现给用户。 随后的用户操作(如点击、表单提交等)将再次触发相同的流程。 应用程序不必担心用户状态,并且可以通过向服务器发送新请求来重新生成每个视图。

然而,Web 应用程序的复杂性不断增加,用户对 UI/UX 的需求也在不断提高。 重新加载整个页面,当它只有一部分发生变化时,效率低下且速度慢。 我们需要一种对 UI 产生直接影响的快速、快速、响应式的交互。

JavaScript 来救场了。 开发人员开始编写大量代码,这些代码在将请求发送到服务器之前在浏览器中执行。 jQuery 还为前端 Web 开发带来了重大进步,因为它提供了简单有效的开箱即用功能,例如客户端验证、模式窗口、警报消息、动画,甚至基于 Ajax 的部分页面更新。

了解复杂性

让我们看一个评估密码强度的简单示例。 如果密码正确,输入框应该有一个绿色的边框,并且应该显示一个不错的消息。 如果密码较弱,则输入框应有红色边框并显示警告消息。 当密码足够强大时,我们也可能会显示笑脸。

以下代码演示了如何通过 DOM 操作来完成此操作。 这里有很多“ifs”,代码也不是很容易阅读。

 if (hasInputBorder()) { removeInputBorder(); } if (text.length === 0) { if (hasMessage()) { removeMessage(); } if (hasSmiley()) { removeSmiley(); } } else { var strength = getPasswordStrength(text); if (!hasInputBorder()) { addInputBorder(); } var color = (strength == 'weak' ? 'red' : 'green'); setInputBorderColor(color); var message = (strength == 'weak' ? "Password is weak" : "That's what I call a password!"); if (hasMessage()) { setMessageText(message); } else { addMessageWithText(message); } if (strength == 'weak') { if (hasSmiley()) { removeSmiley(); } } else { if (!hasSmiley()) { addSmiley(); } } }

如上所示,首先我们需要检查用户是否提供了任何密码,并处理密码字段为空的情况。 在所有情况下,我们都需要确保所有相关的 DOM 元素都正确更新。 这包括消息、边框和笑脸。

我们的密码字段可以处于以下三种状态之一:空、弱或强。 如前所述,我们有三个不同的 DOM 元素,它们受密码字段状态的影响。 处理所有组合,并确保我们的视图正确显示,即使是这样一段简单的代码也会增加圈复杂度。

DOM 以保留模式工作,这意味着它只记住当前状态。 为了修改我们的视图,我们需要为每个 DOM 元素提供指令并对转换进行编程。

编码转换而不是状态可能很复杂。 我们需要在代码中执行的分支和检查的数量随着要管理的视图状态的数量呈指数增长。

在我们的示例中,我们定义了三个视图状态,这给了我们3 * 2 = 6转换。 一般来说,给定 N 个状态,我们需要建模N * (N - 1) = N^2 - N转换。 想想如果我们在示例中添加第四个状态会增加复杂性。

通常有太多与转换建模相关的代码。 如果我们可以只定义我们的视图状态,而不用担心从一种状态转换到另一种状态的所有细节,那就更好了。

降低复杂性

假设我们可以根据模型状态声明视图状态,而不是显式编码从一种状态到另一种状态的转换,我们可以这样:

 var strength = getPasswordStrength(text); if (text.length == 0) { return div(input({type: 'password', value: text})); } else if (strength == 'weak') { return div( input({type: 'password', value: text, borderColor: 'red'}), span({}, "Weak") ); } else { return div( input({type: 'password', value: text, borderColor: 'green'}), span({}, "That's what I call a password!"), img({class: 'icon-smiley'}) ); }

这里我们有三个简单的代码分支,代表我们应用程序的三种可能状态。 我们只是根据模型状态返回每个分支中视图的规范。 移除所有 DOM 操作代码; 我们只是提供有关我们想要什么的信息,而不是如何到达那里。

虽然这种方法确实显着降低了代码复杂性,但它也假设有人或其他人代表我们处理实际的 DOM 操作。

这就是 React 的用武之地。React 将确保根据底层数据模型的状态立即管理和更新视图状态。

反应

React 是 Facebook 创建的 JavaScript 库。 它旨在处理 Web 应用程序的 UI 部分。 您可以将其视为 MVC 架构中的 V。 它非常专注。 它不对您的技术堆栈的其余部分做任何假设,并且除了渲染组件之外不处理任何事情。 它不提供通常捆绑在较大框架中的任何路由机制、模型或其他功能。 因此,您可以混合使用它并将其与您想要的任何其他库或框架一起使用。

React 允许我们将 UI 定义为复合组件的树。 给定输入状态,React 开发人员通过指定描述组件的渲染函数来定义这些组件。 该函数应该是函数(即,它不应有任何副作用或依赖于其显式输入以外的任何内容)。

render 函数返回一个视图描述,React 将其称为Virtual DOM 。 将其视为与渲染的 DOM 元素相对应的 JavaScript 对象。

当您更改组件的状态时,它只会重新渲染自身及其所有子元素,并返回一个新的Virtual DOM

此外,当从一种状态转换到另一种状态时,React 不会进行简单的 HTML 替换。 它将找到前一个状态和新状态之间的差异,并计算最有效的 DOM 操作集以执行转换。

即使不考虑性能,代码复杂性的降低本身也很重要,它使我们能够将精力集中在应用程序中更独特和更复杂的部分。

为了更具体一点,这就是我们的教程示例如何使用 React 来管理视图状态。

注意:以下代码示例是用 JSX 预处理器编写的,这是编写基于 React 的 UI 的常用方法。

 function getPasswordStrength(text) { // Some code that calculates the strength given the password text. } var PasswordWithStrength = React.createClass({ getInitialState: function() { return {value: ''}; }, render: function() { var strength = getPasswordStrength(this.state.value); if (this.state.value.length == 0) { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} /> </div>; } else if (strength == 'weak') { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid red'} } /> <span style={{color: 'red'}}>Weak!</span> </div>; } else { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid green'} } /> <span style={{color: 'green'}}>That's what I call a password!</span> <Emoji value="smiley" /> </div>; } }, handleInputChange: function(ev) { this.setState({value: ev.target.value}); } }); React.render(<PasswordWithStrength />, document.body);

<Emoji value="smiley" />密码强度正常时呈现的Emoji组件只是另一个自定义组件(就像PasswordWithStrength一样)。 它是这样定义的:

 var Emoji = React.createClass({ render: function() { var emojiSrc = this.props.value + '.png'; return <img src={emojiSrc}></img>; } });

React.js 与其他

不过,公平地说,还有其他客户端 JavaScript 框架(例如 Ember、Angular、Knockout 等)也解决了视图状态管理问题,甚至为其添加了更多功能。 那么,为什么要使用 React 而不是任何其他框架呢?

与大多数其他库相比,React 有两个关键优势。

无数据绑定

其他一些替代框架使用数据绑定将 DOM 元素映射到状态属性,并通过观察属性更改使它们保持同步。 这种方法允许渲染一次视图,每次更改都只触发受影响的 DOM 元素的修改。 其他替代方案使用脏检查; 也就是说,它们不是观察单个属性的变化,而是在前一个状态和新状态之间进行差异。 React 更类似于后一种方法,但它不是比较状态,而是比较视图表示。

React 没有数据绑定。 当状态改变时,开发人员应该调用setState方法,或重新渲染顶部组件。 它包含从状态到视图的单向流。

这个概念很容易采用,因为开发人员通常不会考虑数据绑定。 重点是数据的可视化表示。 因此,您无需考虑依赖属性、格式、绑定特殊 HTML 标记等。使用 React,您只需在模型更改时重新渲染组件。

要了解这里的视图状态管理的区别,让我们比较一下 Ember 和 React 。 我们将创建一个以大写形式输出全名的对象person 。 两秒钟后,我们将模拟更改并更新视图。

 // EXAMPLE USING EMBER App = Ember.Application.create(); App.Person = Ember.Object.extend({ firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); var person = App.Person.create({ firstName: "John", lastName: "Doe" }); Ember.Handlebars.helper('upcase', function(value) { return value.toUpperCase(); }); App.IndexRoute = Ember.Route.extend({ model: function () { return person; } }); setTimeout(function() { person.set('firstName', 'Harry'); }, 2000); // Templates: <script type="text/x-handlebars"> <h2>Welcome to Ember.js</h2> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="index"> The current user is: {{upcase model.fullName}} </script>

我们创建了一个具有firstNamelastNamefullName属性的对象。 由于 Ember 正在观察属性变化,我们必须指定fullName依赖于firstNamelastName 。 为此,我们在定义fullName时添加了.property('firstName', 'lastName')

两秒后, person.set('firstName', 'Harry'); 被执行。 这触发了视图及其绑定的更新。

现在让我们在 React 中做同样的事情。

 // EXAMPLE USING REACT var CurrentUser = React.createClass({ render: function() { return <div>The current user is: {this.props.user.fullName().toUpperCase()}</div>; } }); var person = { firstName: 'John', lastName: 'Doe', fullName: function() { return this.firstName + ' ' + this.lastName; } }; var currentUser = React.render(<CurrentUser user={person}/>, document.body); setTimeout(function() { person.firstName = 'Harry'; currentUser.setProps({user: person}); }, 2000);

尽管 Ember 代码简单易读,但很明显 React 在简单性方面胜出。 person是一个普通的 JavaScript 对象, fullName只是一个函数。

没有模板

每个替代框架都有不同的处理模板的方式。 其中一些使用编译成 JavaScript 的字符串,而另一些则直接使用 DOM 元素。 他们中的大多数使用自定义的 HTML 属性和标签,然后将其“编译”成 HTML。

模板不是 JavaScript 代码的一部分。 因此,每个替代方案都需要一种自定义方式来表示常见操作、条件、迭代、调用函数等。它们最终都会创建一种开发人员必须学习的新伪语言。

React 中没有模板,一切都只是简单的旧 JavaScript。

React 使用 JavaScript 的全部功能来生成视图。 组件的 render 方法是一个 JavaScript 函数。

JSX 可用作将“类 HTML 语法”转换为普通 JavaScript 的预处理器,但 JSX 是可选的,您可以在没有任何预处理器的情况下自由使用标准 JavaScript。 您还可以利用现有的 JavaScript 工具。 短绒、预处理器、类型注释、缩小、死代码消除等。

让我们再次使用一个具体的例子来比较 React 和视图状态管理的替代框架之一。

以下教程是使用AngularJS列出主题标签和每个标签的推文计数的示例。 该列表按计数排序,如果没有主题标签,则会显示一条消息。

 <!-- EXAMPLE USING ANGULAR --> <div ng-controller="MyCtrl"> <ul ng-show="hashTags.length > 0"> <li ng-repeat="hashTag in hashTags | orderBy:'tweetCount':true"> {{hashTag.name}} - {{hashTag.tweetCount}} tweets </li> </ul> <span ng-show="hashTags.length == 0">No hashtags found!</span> </div>

为了能够列出这个列表,开发人员必须了解AngularJS directivesng-showng-repeat 。 然后他需要学习AngularJS filters来理解orderBy 。 像输出列表这样简单的事情需要做很多工作!

现在让我们考虑做同样事情的 React 示例:

 // EXAMPLE USING REACT function byTweetCountDesc(h1, h2) { return h2.tweetCount - h1.tweetCount; } //... render: function() { if (this.state.hashTags.length > 0) { var comps = this.state.hashTags.sort(byTweetCountDesc).map(function(hashTag, index) { return <li key={index}> {hashTag.name} - {hashTag.tweetCount} tweets </li>; }); return <ul>{comps}</ul>; } else { return <span>No hashtags found!</span> } }

即使我们使用“更高级”的方法和 JSX,每个对 JavaScript 有基本了解的 Web 开发人员都可以轻松阅读上面的代码并理解它的作用。 使用if的标准条件检查、使用map()的迭代和标准的 'sort()' 对任何开发人员来说都很自然,因此无需学习额外的语法或其他概念。

结论

这个 React.js 教程的主要内容是 React 使您能够专注于实际的视图状态管理而不是转换,从而简化您的工作和应用程序。

采用 React 的学习曲线相当简单。 无需掌握自定义模板语言,无需担心数据绑定,一切都归结为描述 UI 元素的 JavaScript 函数。

要了解有关使用 React 简化应用程序代码的更多信息,请查看 Steven Luscher 的演讲,Decomplexifying Code with React。

对于想要迈出下一步并开始使用 React 的人来说,这里有一些额外的阅读材料:

  • http://jlong​​ster.com/Removing-User-Interface-Complexity,-or-Why-React-is-Awesome