九、演示 JavaScript 函数式反应式编程——真实示例第二部分:待办事项列表

在本章中,我们将浏览一个待办事项列表。这个待办事项列表将说明一种稍微模糊的双向数据绑定。ReactJS 的强项是通过单向数据绑定,大多数以惯用的 ReactJS 方式解决的问题通常会遵循带有单向数据绑定的 von Neumann 模型(据称通常不需要双向数据绑定,而 AngularJS: The Bad Parts 等文章则建议双向绑定默认带有很重的价格标签,尤其是在伸缩方面)。

如果我们以一种显而易见的方式构建我们的待办事项列表,复选框将没有响应。我们可以随心所欲地点击它们,但它们永远不会被检查,因为单向数据绑定使用道具——或者在我们的情况下,使用状态——来确定它们是否会被检查。我们将尝试双向数据绑定,这意味着不仅复选框是活动的,而且点击复选框将更新状态。这意味着在用户界面中显示的内容和作为状态的幕后内容保持彼此同步。

待办事项列表作为一个显著的特征,提供了比完成未完成更多的状态。它有重要进行中问题等复选框。

本章将演练以下内容:

  • 在双向数据绑定中使用附加组件的具体细节
  • 设置适当的初始状态
  • 使TEXTAREA内的文本可编辑
  • 一个render()功能,做一些繁重的工作
  • render()使用的内部函数
  • 构建要显示的表格
  • 呈现我们的结果
  • 直观区分列

向我们的应用添加待办事项列表

在前一章中,您为您自己要创建的应用实现了一个YouPick占位符。在这里,我们将对它进行注释,以便只显示我们的待办应用(我们可以,而且我们将在屏幕的不同部分安排事情,但是现在我们一次只显示一件事情)。在 JSX,注释掉代码的方法是将其包装在一个 C 风格的多行注释中,然后将其包装在大括号中,因此<YouPick /> becomes {/* <YouPick /> */}:

  var Pragmatometer = React.createClass(
    {
    render: function()
      {
      return (
        <div className="Pragmatometer">
        <Calendar />
        <Todo />
        <Scratch />
        {/* <YouPick /> */}
        </div>
      );
    }
  }
);

包括我们项目中的 ReactJS 加载项

我们将打开 Todo类并包含React.addons.LinkedStateMixin功能。请注意,我们在这里使用的是一个加载项,这意味着当我们在页面中包含 ReactJS 时,我们需要包含一个包含加载项的构建。所以,考虑一下这条线:

//cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.min.js

相反,我们包括以下内容:

//cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react-with-addons.min.js

Todo课的开场内容如下:

  var Todo = React.createClass(
    {
      mixins: [React.addons.LinkedStateMixin],

设置合适的初始状态

初始状态为空;列表中没有待办事项,任何新的待办事项都有空文本:

      getInitialState: function()
      {
        return {
          'items': [],
          'text': ''
        };
      },

请注意,一些初始状态设置可能会涉及重载;这里,它相当简单,但不一定总是这样。

使文本可编辑

作为一个次要的看家细节,当有人在盒子里打字时,我们想要明显的行为。因此,我们为 TEXTAREA 定义了一个双向数据绑定,这样,如果有人在 TEXTAREA 中键入内容,更改将被添加到状态中,并溢出回 TEXTAREA:

      onChange: function(event)
      {
        this.setState({text: event.target.value});
      },

如果有人在输入一些文本后点击提交按钮提交新的待办事项,我们会将该事项添加到列表中:

      handleSubmit: function(event)
      {
        event.preventDefault();
        var new_item = get_todo_item();
        new_item.description = this.state.text;
        this.state.items.push(new_item);
        var next_text = '';
        this.setState({text: next_text});
      },

带渲染的重物提升()

render()功能比稍微复杂一些,包含内部功能和基于双向数据绑定的响应用户界面的大部分繁重工作。就在它里面,我们使用var that=this;模式,这是大多数 ReactJS 代码所没有的。在大多数 ReactJS 中,我们可以使用它,它可以自动工作;这里我们定义的内部函数不像其他 ReactJS 函数那样直接构建,我们保留了对 this 对象的引用:

      render: function()
      {
        var that = this;
        var table_rows = [];

用于渲染的内部函数

table_rows数组将保存待办事项。定义了这些之后,我们定义了第一个内部匿名函数handle_change()。如果用户点击某个待办事项的复选框,我们会提取 HTML 标识,它会告诉用户待办事项的标识,以及哪个字段(即复选框标识符)已经被切换:

        var handle_change = function(event)
        {
          var address = event.target.id.split('.', 2);
          (that.state.items[parseInt(address[0])][address[1]] = !that.state.items[parseInt(address[0])][address[1]]);
        };

display_item_details()功能是用于建立显示的几个功能中最低的一个。它构建了一个包含复选框的单个 TD:

      var display_item_details = function(label, item)
          {
          var html_id = item.id + '.' + label;
        return ( <td className={label} title={label}>
            <input onChange={handle_change} 
              id={html_id} className={label} type="checkbox" checked={item[label]} />
          </td>
        );
      };

接下来,display_item()使用这些构建块来构建待办事项的显示。除了包括渲染的 Node,即复选框,它还对框中的文本应用标记格式:

      var display_item = function(item)
      {
        var rendered_nodes = [];
        for(var index = 0; index < todo_item_names.length;
        index += 1) {
          rendered_nodes.push(
            display_item_details(todo_item_names[index], item)
          );
        }
        return ( <tr>
          {rendered_nodes}
          <td dangerouslySetInnerHTML={{__html:
          converter.makeHtml(item.description)}} />
        </tr>
        );
      };

建立结果表

对于每个项目,我们添加一个表格行:

      table_rows.push(
      <tr>{this.state.items.map(display_item)}</tr>);

最后,返回一个 JSX 表达式,其中包含到目前为止计算的各种值,并将它们包装在一个全功能表中:

      return (
        <form onSubmit={this.handleSubmit}>
          <table>
            <thead>
              <tr>
                <th>To do</th>
              </tr>
            </thead>
            <tbody>
              {table_rows}
            </tbody>
            <tfoot>
              <textarea onChange={this.onChange}
              value={this.state.text}></textarea><br />
              <button>{'Add activity'}</button>
            </tfoot>
          </table>
        </form>
      );
    }
  }
);

当应该隐藏和显示数据行的复选框被选中时,您可以隐藏和显示数据行。

类型

这里有一个关于表格使用的简短评论:已经从主要使用表格转变为主要使用 CSS 进行格式化。然而,关于表的使用的确切规则并不完全是“根本不使用表”或“只有在真正需要时才使用表”,而是“将表用于表格数据”这意味着网格,比如我们在这里显示的。带有复选框网格的表格是一个很好的例子,说明了表格在当前标记中是完全合适的。

渲染我们的结果

我们的结果只有在我们告诉它的时候才会呈现;这可以被看作是一件苦差事,或者是我们这边增加的自由度。在结束之前,我们写下以下内容:

var update = function()
{
  React.render(<Pragmatometer />,
  document.getElementById('main'));
};
update();
var update_interval = setInterval(update, 100);

目视区分色谱柱

目前,我们无差别的复选框网格一起运行。我们可以做几件事来区分它们。index.html CSS 中的一系列颜色区分了它们:

      td.Completed {
        border-left: 3px solid black;
        background-color: white;
      }
      td.Delete {
        background-color: gray;
      }
      td.Invisible
      {
        background-color: black;
      }
      td.Background
      {
        background-color: #604000;
      }
      td.You.Decide
      {
        background-color: blue;
      }
      td.In.Progress
      {
        background-color: #00ff00;
      }
      td.Important
      {
        background-color: yellow;
      }
      td.In.Question
      {
        background-color: darkorange;
      }
      td.Problems
      {
        background-color: red;
      }

总结

在本章中,我们对略多于最小的待办事项列表进行了演练。就功能而言,我们看到了以包含双向数据绑定的方式构建的交互式表单。ReactJS 通常的建议是,大多数时候,您认为您需要双向数据绑定,但是如果只使用单向数据绑定,您会感觉更好。然而,该框架似乎并不打算作为一个直板,在 ReactJS 说“你通常不应该做 X”的地方,有一种方法可以做 X。对于 dangerouslySetInnerHTML 和双向数据绑定,你可以在特定的点上选择它,但是已经尽了一切努力来选择更好的工程。dangerouslySetInnerHTML函数是一种非常有力的命名方式,ReactJS 团队明确表达的观点是,冯·诺依曼模型要求单向数据绑定,至少在大多数情况下是这样。然而,ReactJS 哲学明确允许开发人员使用他们认为通常最好避免的特性;最终的裁决在你身上。

加入我们的下一章,我们将创建一个日历应用,优雅地处理定期约会。