React.js查看状态管理教程

本文概述

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

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

一点历史

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

但是, Web应用程序的复杂性不断增长, UI / UX的用户需求也在不断提高。当其中一部分更改时, 重新加载整个页面是无效且缓慢的。我们需要一种快速, 灵活且响应迅速的交互方式, 并对UI产生直接影响。

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

了解复杂性

让我们看一个评估密码强度的简单示例。如果密码确定, 则输入框应带有绿色边框, 并应显示一条漂亮的消息。如果密码不正确, 则输入框应带有红色边框, 并应显示警告消息。密码足够牢固时, 我们可能还会露出笑脸。

以下代码演示了如何通过DOM操作来做到这一点。这里有很多” if”, 并且代码不是很容易阅读。

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

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

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

渲染函数返回视图描述, React调用该视图描述虚拟DOM。将其视为与呈现的DOM元素相对应的JavaScript对象。

当你更改组件的状态时, 它只会重新呈现其自身及其所有子元素, 并返回一个新的虚拟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。我们将创建一个对象人, 将大写输出全名。两秒钟后, 我们将模拟更改并更新视图。

// 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>

我们创建了一个具有firstName, lastName和fullName属性的对象。由于Ember正在观察属性更改, 因此我们必须指定fullName取决于firstName和lastName。为此, 我们在定义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以简单的方式取胜。这个人是一个普通的JavaScript对象, 其中fullName只是一个函数。

没有模板

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

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

React中没有模板, 所有东西都只是普通的旧JavaScript。

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

JSX可以用作将”类似HTML的语法”转换为普通JavaScript的预处理器, 但是JSX是可选的, 你可以自由使用没有任何预处理器的标准JavaScript。你还可以利用现有的JavaScript工具。短绒, 预处理器, 类型注释, 最小化, 无效代码消除等。

让我们再次使用一个具体的示例, 将React与其他用于视图状态管理的框架进行比较。

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

<!-- 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指令, ng-show和ng-repeat。然后, 他需要学习AngularJS过滤器以了解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的演讲, 《使用React分解代码》。

这是一些想要进一步并开始使用React的人的额外阅读材料:

  • http://jlongster.com/Removing-User-Interface-Complexity, -or-Why-React-is-Awesome
微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?