React.js的Rails开发者指南

React.js的Rails开发者指南

原作者:Fernando Villalobos

初稿链接:https://www.airpair.com/reactjs/posts/reactjs-a-guide-for-rails-developers

译者:Sail Lee

目录

React.js简介

React.js是1个近似“JavaScript框架”的风行类库,因其简洁而非凡。相对于任何完整兑现了MVC结构的框架,我们说React仅达成了V(其实有点人用React来替代它们框架的V部分)。React应用程序通过四个重大标准来组织:Components和States。Components能够用任何更小的零部件来整合,内置或定制;State驱动了推文(Tweet)称之为单向响应式数据流的东西,那象征大家的UI将会对每一遍状态的改观作出反应。

React的三个亮点之一正是它无需任何额外的依赖,那让它差不离能和别的别的的JS库插接到一起。利用那么些特点,大家将其包含到我们Rails的技能栈中,来构建1个前端强大的利用,恐怕你会说它是个Rails视图层的高兴剂。

三个仿照的资费跟踪应用

在本指南中,我们正要从零做起,营造2个笔录普通消费的小应用。每种记录将席卷二个日子、标题和金额。假诺1个记下的金额超越零,它将被认为是买方(译者注:会计术语),相反则计入借方(译者注:会计术语)。这是系列的模型:

CoffeeScript 1

类型的模型

总结下,该使用表现如下:

  • 当用户通过横向的表单创制二个新记录时,它将被添加到记录表格中去。
  • 用户能够对别的部存款和储蓄器在的笔录举办行内编辑。
  • 点击任何删除按钮会把有关的笔录从表格中去除。
  • 追加、编辑或移除多少个存在的笔录都将更新位于页面顶部的各种协议项。

在Rails项目中初叶化React.js

率先,大家要起来一个崭新的Rails项目,大家叫它Accounts

rails new accounts

我们将利用推文(Tweet)的Bootstrap做此项指标UI。安装流程非本文研讨范围,你能够依照官方github仓库的指点来安装bootstrap-sass官方gem。

只重要项目目初步化后,大家接下去要把React涵盖进来。本文中,因为大家打算利用react-rails这么些官方gem里面的一部分很酷的成效,所以要将其包括进项目。其实也有任何格局来成功那项任务,如利用Rails
assets、甚至从官方页面下载源码包并把它们复制到项指标javascripts目录。

只要您曾经开发过Rails应用,你会清楚安装二个gem有多不难:把react-rails添加到您的Gemfile文本中去。

gem 'react-rails', '~> 1.0'

下一场,(友好地)让Rails来安装新的gem包:

bundle install

react-rails带有一个本子,会在大家存放React组件的app/assets/javascripts目录下创设components.js文件和components目录。

rails g react:install

在跑完安装之后,即使您看看application.js文本中会发现以下三行:

//= require react
//= require react_ujs
//= require components

大多,它富含了实际的react库、components零件清单文件和一种以ujs说到底的常见文件。由文件名你能够已经猜到,react-rails饱含了一种帮忙我们载入React组件并且同时也处理Turbolinks事件的非侵略式JavaScript驱动。

创建Resource

咱俩即将构建二个富含datetitleamount字段的Record财富(resource)。大家要用resource生成器(generator)来代替scaffold,那样大家就不会用到由scaffold开创的装有文件和格局。另1个抉择是先运营scaffold生成器,接着删除无用的公文或情势,可是这么会另大家的系列有点乱。进入项目目录后,运营以下命令:

rails g resource Record title date:date amount:float

运作完后,大家末了将赢得三个新的Record
model、controller和routes。我们明天只必要创立我们的数据库并运营之后的数额迁移。

rake db:create db:migrate

用作附加,你能够透过rails console制造八个记录:

Record.create title: 'Record 1', date: Date.today, amount: 500
Record.create title: 'Record 2', date: Date.today, amount: -100

别忘了用rails s来运营你的服务器。
好了!大家要预备写点代码了。

嵌套式组件:记录列表

我们的首先个职分急需在几个表格中显得任何已某个记录。首先,我们须要在RecordController内部创设二个index动作(action)。

  # app/controllers/records_controller.rb

  class RecordsController < ApplicationController
    def index
      @records = Record.all
    end
  end

随着,大家要在app/views/records/目录下创办1个新文件index.html.erb,该公文在大家的Rails应用和React组件之间扮演着桥梁的效力。要完结该职责,我们将动用helper方法react_component,通过它来获得大家要显示的React组件的称谓及其大家要传送给它的数额。

  <%# app/views/records/index.html.erb %>

  <%= react_component 'Records', { data: @records } %>

亟需提议的是,该helper是由react-railsgem包提供的,如果你说了算选择任何的集成React的法子,就不可能用到那几个helper。

你未来能到localhost:3000/records这一个途径看看了。显著,因为Records本条React组件的缺乏,那还不能够工作。然则,如若大家看看浏览器中的HTML源文件,我们就能发现接近以下的代码:

  <div data-react-class="Records" data-react-props="{...}">
  </div>

有了那么些标记,react_ujs就会检查和测试到我们尝试显示1个React组件并实例化它,包罗大家通过react_component出殡的质量,在该案例中,就是@records的内容。

创设大家率先个React组件的时刻到了,进入javascripts/components目录,创设3个叫records.js.coffee的新文件来放置大家的Records组件。

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    render: ->
      React.DOM.div
        className: 'records'
        React.DOM.h2
          className: 'title'
          'Records'

种种组件都需求一个render形式,它将负担渲染组件本人。render方法会重返3个ReactComponent的实例,那样,当React执行重新渲染时,它将以最优的点子展开(当React检查和测试新节点存在时,会在内存中营造一个虚拟的DOM)。在上头代码中,我们创造了3个h2实例,内置于ReactComponent中。

瞩目:实例化ReactComponent的另3个格局是在render方法中动用JSX语法,以下代码段与前段代码功效一样:

  render: ->
    `<div className="records">
      <h2 className="title"> Records </h2>
    </div>`

对自个儿个人而言,当自身使用CoffeeScript时,笔者更爱好使用React.DOM语法而不是JSX,因为代码能够排列成三个层次结构,类似于HAML。不过,如若您正尝试集成React到三个用erb文件建立的共处应用中,你能够选择重用现有erb代码并将其更换来JSX。

您未来得以刷新浏览器了。

CoffeeScript 2

records_1

好极了!大家早已画出第3个React组件了。现在,是时候显得大家的笔录了。

除了render格局以外,React组件还借助properties的使用来和其余零件沟通,并且用states来检查和测试是不是须求展开再一次渲染。大家须要用期望的值来开头化大家的零件状态和属性值:

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    getInitialState: ->
      records: @props.data
    getDefaultProps: ->
      records: []
    render: ->
      ...

getDefaultProps艺术将早先化我们组件的习性,避防在开头化时大家忘了发送任何数据。而getInitialState主意则会变卦大家组件的起先状态。未来我们还要来得由Rails提供的记录。

看起来大家还必要2个格式化amount字符串的helper方法,咱们得以兑现3个简练的字符串格式化学工业具并使其能让具备别的的coffee文本访问。用下列内容,在javascripts/目录下开创多个新的utils.js.coffee文件:

  # app/assets/javascripts/utils.js.coffee

  @amountFormat = (amount) ->
    '$ ' + Number(amount).toLocaleString()

咱俩供给创设1个新的Record组件来呈现每种独立的笔录,在javascripts/components目录下开创1个record.js.coffee的新文件,并插入以下内容:

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    render: ->
      React.DOM.tr null,
        React.DOM.td null, @props.record.date
        React.DOM.td null, @props.record.title
        React.DOM.td null, amountFormat(@props.record.amount)

Record组件将显得三个涵盖记录各种属性值单元格的表格行。不用顾虑那多少个在React.DOM.*调用中的那几个null,那表示大家不要传送属性值给组件。未来用于下代码更新下Record组件中的render方法:

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    render: ->
      React.DOM.div
        className: 'records'
        React.DOM.h2
          className: 'title'
          'Records'
        React.DOM.table
          className: 'table table-bordered'
          React.DOM.thead null,
            React.DOM.tr null,
              React.DOM.th null, 'Date'
              React.DOM.th null, 'Title'
              React.DOM.th null, 'Amount'
          React.DOM.tbody null,
            for record in @state.records
              React.createElement Record, key: record.id, record: record

你是否探望刚刚产生了哪些?大家成立了三个带标题行的表格,并且在表格体内为种种已有的记录创建了二个Record要素。换句话说,大家正嵌套了安置或定制的React组件。10分酷,是不?

当大家处理动态子组件(本案例中为记录)时,我们要求提供三个key质量来动态变化的成分,这样React就不会很难刷新UI,那正是怎么大家要在制造Record元素时会同实际的记录一起发送key: record.id。假如不是那般做,大家将会在浏览器的JS控制台收到一条警告消息(并且在不远的以往产生一些憎恶的难点)。

CoffeeScript 3

records_2

您能够到这里去探访本章的结果代码,或然仅仅到这里探访由本章引入的更改。

父子组件间通讯:创制记录

明天我们来得了有着的已有记录,最好能包括四个用来制造记录的表单,让大家扩张三个新成效给大家的React/Rails应用。

第二,大家吸引加入二个create方式到Rails控制器(不要忘了利用_strongparams):

  # app/controllers/records_controller.rb

  class RecordsController < ApplicationController
    ...

    def create
      @record = Record.new(record_params)

      if @record.save
        render json: @record
      else
        render json: @record.errors, status: :unprocessable_entity
      end
    end

    private

      def record_params
        params.require(:record).permit(:title, :amount, :date)
      end
  end

继而,我们要求构建多少个用以拍卖创造新记录的React组件。该零件将具备和谐的state来存放datetitleamount。用下列代码,在目录javascripts/components下创制四个record_form.js.coffee的新文件:

  # app/assets/javascripts/components/record_form.js.coffee

  @RecordForm = React.createClass
    getInitialState: ->
      title: ''
      date: ''
      amount: ''
    render: ->
      React.DOM.form
        className: 'form-inline'
        React.DOM.div
          className: 'form-group'
          React.DOM.input
            type: 'text'
            className: 'form-control'
            placeholder: 'Date'
            name: 'date'
            value: @state.date
            onChange: @handleChange
        React.DOM.div
          className: 'form-group'
          React.DOM.input
            type: 'text'
            className: 'form-control'
            placeholder: 'Title'
            name: 'title'
            value: @state.title
            onChange: @handleChange
        React.DOM.div
          className: 'form-group'
          React.DOM.input
            type: 'number'
            className: 'form-control'
            placeholder: 'Amount'
            name: 'amount'
            value: @state.amount
            onChange: @handleChange
        React.DOM.button
          type: 'submit'
          className: 'btn btn-primary'
          disabled: !@valid()
          'Create record'

不是太花俏,仅仅是个日常的Bootstrap内嵌表单。注意,大家定义了value本性来设置输入的值,并且定义了onChange脾性来绑定二个总计机方法,它将会在历次按键时都会被调用。handleChange处理器方法将用name质量来检查和测试那二遍输入触发了事件并更新相关的state值:

  # app/assets/javascripts/components/record_form.js.coffee

  @RecordForm = React.createClass
    ...
    handleChange: (e) ->
      name = e.target.name
      @setState "#{ name }": e.target.value
    ...

大家刚用了字符串插值来动态地定义对象的键值,当name等于title时,与@setState title: e.target.value等值。但为啥我们必须选择@setState?为啥我们不能够象对待普通的JS对象一样,仅对@state设置期望的值吗?因为@setState会时有爆发多少个动作:

  1. 更新组件的state
  2. 听大人讲新景色,布署四个UI的认证或刷新

当我们每一趟在大家的组件中利用state时,明白那几个文化是可怜首要的。

让大家看看submit按钮,就在render主意最终的地点:

  # app/assets/javascripts/components/record_form.js.coffee

  @RecordForm = React.createClass
    ...
    render: ->
      ...
      React.DOM.form
        ...
        React.DOM.button
          type: 'submit'
          className: 'btn btn-primary'
          disabled: !@valid()
          'Create record'

我们用!@valid()概念了3个disabled个性,那表示大家即将达成3个valid措施来判定由用户提供的多少是还是不是是正确的。

  # app/assets/javascripts/components/record_form.js.coffee

  @RecordForm = React.createClass
    ...
    valid: ->
      @state.title && @state.date && @state.amount
    ...

为了简化,大家仅仅校验@state质量是还是不是为空。那样,每一次状态更新后,Create
record
按钮都基于数据的有效性来控制可用或不可用。

CoffeeScript 4

creating_record_1

CoffeeScript 5

creating_record_2

当今控制器和表单都已就位,是时候提交新记录给服务器了。咱们要求处理表单的submit事件。要完毕那项职务,我们需求给表单添加三个onSubmit天性和3个新的handleSubmit形式(仿佛在此以前大家处理onChange事件相同):

  # app/assets/javascripts/components/record_form.js.coffee

  @RecordForm = React.createClass
    ...
    handleSubmit: (e) ->
      e.preventDefault()
      $.post '', { record: @state }, (data) =>
        @props.handleNewRecord data
        @setState @getInitialState()
      , 'JSON'

    render: ->
      React.DOM.form
        className: 'form-inline'
        onSubmit: @handleSubmit
      ...

让我们逐行检阅下这一个新措施:

  1. 阻止表单的HTTP提交
  2. POST新的record音信到当下U奇骏L
  3. 付出成功后进行回调函数

success回调函数是这一个进程的显要,在成功地创立新记录后,关于这么些动作和state平复到起始值的新闻会被打招呼。还记得在此以前作者曾涉及的组件通过质量(或@props)与其余零件举办关联吗?对,就是它。当前大家这些组件正是经过@props.handleNewRecord发回数据给父组件,来打招呼它存在一个新记录。

莫不你已经猜到,无论在哪儿创制RecordForm要素,大家要传送二个handleNewRecord质量,并用贰个措施引用到它,就像是React.createElement RecordForm, handleNewRecord: @addRecord。好,父组件Records便是以此“无论在哪里”,由于它拥有3个附带了具备现存记录的state,全体须求大家用新建记录来更新它的state。

records.js.coffee中添加新的addRecord主意并创造那个新的RecordForm元素,就在h2标题之后(在render情势之中)。

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    addRecord: (record) ->
      records = @state.records.slice()
      records.push record
      @setState records: records
    render: ->
      React.DOM.div
        className: 'records'
        React.DOM.h2
          className: 'title'
          'Records'
        React.createElement RecordForm, handleNewRecord: @addRecord
        React.DOM.hr null
      ...

刷新浏览器,在表单中填入三个新记录,点击Create
record
按钮…此次没有悬念,记录大概立即被加上,而且在付出后表单被清理了,刷新仅仅是为了确认新数据现已被存入了后端服务器。

CoffeeScript 6

creating_record_and_records

只要连同Rails一起,使用别的的JS框架(例如AngularJS)来塑造类似的效果,你恐怕会遭遇难题,因为您的POST请求不包蕴Rails所需的CSRFtoken。那么为何我们并未赶上同样的标题?很简短,因为机关运用jQuery与后端交互,而且Rails的jquery_ujs非侵略式驱动器为大家各类AJAX呼吁都含有了CSRFtoken。酷!

你能够到这里去看望本章的结果代码,只怕仅仅到CoffeeScript,这里探访由本章引入的改观。

可选拔组件:合计指标

二个应用程序怎能没有一些优质的目的呢?让我们拿些有用的音信在窗口顶部添加一些目标框。大家的指标是为着在本章中展示三个值:贷方合计、借方合计和余额。这看起来像是八个零件,或单独是一个带属性组件的工作量?

我们能营造二个新的AmountBox组件,它赢得多少个属性:amounttexttype。在javascripts/components目录下开创一个叫作amount_box.js.coffee的文本,并粘贴以下代码:

  # app/assets/javascripts/components/amount_box.js.coffee

  @AmountBox = React.createClass
    render: ->
      React.DOM.div
        className: 'col-md-4'
        React.DOM.div
          className: "panel panel-#{ @props.type }"
          React.DOM.div
            className: 'panel-heading'
            @props.text
          React.DOM.div
            className: 'panel-body'
            amountFormat(@props.amount)

我们只用Bootstrap的panel要素以“块状”的法门来呈现音信,并且通过type品质来设定颜色。大家也含有了四个称作amountFormatter的一对一简单的协议格式化方法,它读取amount品质并以货币格式来突显它.

为了有个整体的消除方案,咱们需求在主组件中创立这一个成分(二次),依赖我们要出示的数据,传送给所需的性质。让大家首先构建计算器方法,打开Records组件并添加以下代码:

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    credits: ->
      credits = @state.records.filter (val) -> val.amount >= 0
      credits.reduce ((prev, curr) ->
        prev + parseFloat(curr.amount)
      ), 0
    debits: ->
      debits = @state.records.filter (val) -> val.amount < 0
      debits.reduce ((prev, curr) ->
        prev + parseFloat(curr.amount)
      ), 0
    balance: ->
      @debits() + @credits()
    ...

credits合计拥有金额大于0的记录,debits商业事务持有金额小于0的记录,而余额就无需多解释了。未来总括器方法已经就位了,大家仅需在render办法中创制AmountBox要素(就像是上面的RecordForm组件一样):

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    render: ->
      React.DOM.div
        className: 'records'
        React.DOM.h2
          className: 'title'
          'Records'
        React.DOM.div
          className: 'row'
          React.createElement AmountBox, type: 'success', amount: @credits(), text: 'Credit'
          React.createElement AmountBox, type: 'danger', amount: @debits(), text: 'Debit'
          React.createElement AmountBox, type: 'info', amount: @balance(), text: 'Balance'
        React.createElement RecordForm, handleNewRecord: @addRecord
    ...

作者们早就到位这些职能了!刷新浏览器,你会看出四个框里面展现计算好的金额。可是!那还没完!成立个新记录看看有何神奇的东西…

CoffeeScript 7

amount_indicators

您能够到这里去看望本章的结果代码,或然唯有到这里看望由本章引入的转移。

setState/replaceState:删除记录

大家清单中的下3个功力是删除记录,大家须要在笔录表格中追加一个新的Actions列,对于每一个记录的该列中都会有三个Delete按钮,卓殊语专科学校业的UI。和事先的例证一样,大家要在Rails控制器中创立3个destroy方法:

  # app/controllers/records_controller.rb

  class RecordsController < ApplicationController
    ...

    def destroy
      @record = Record.find(params[:id])
      @record.destroy
      head :no_content
    end

    ...
  end

那正是大家为此作用所需的整套服务器端代码。今后,打开Records组件并在表头最左侧的岗位添加Actions列:

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    render: ->
      ...
      # almost at the bottom of the render method
      React.DOM.table
        React.DOM.thead null,
          React.DOM.tr null,
            React.DOM.th null, 'Date'
            React.DOM.th null, 'Title'
            React.DOM.th null, 'Amount'
            React.DOM.th null, 'Actions'
        React.DOM.tbody null,
          for record in @state.records
            React.createElement Record, key: record.id, record: record

最后,打开Record零件并用Delete链接添加叁个附加的列:

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    render: ->
      React.DOM.tr null,
        React.DOM.td null, @props.record.date
        React.DOM.td null, @props.record.title
        React.DOM.td null, amountFormat(@props.record.amount)
        React.DOM.td null,
          React.DOM.a
            className: 'btn btn-danger'
            'Delete'

保留你的文书,刷新浏览器并…我们有个别只是没有的按钮,还没把事件附上!

CoffeeScript 8

deleting_record_1

让大家添加一些成效给它。和我们从RecordForm零件里学到的同样,方法如下:

  1. 检查和测试在子组件Record中的事件(onClick)
  2. 施行3个动作(在该案例中,发送二个DELETE请求到服务器)
  3. 针对该动作,通知父组件Records(通过props来发送或收取叁个电脑方法)
  4. 更新Record零件的处境

要落到实处步骤1,大家能够为onClick加上三个电脑到Record,就像我们为onSubmit增进一个总结机到RecordForm来创制新记录一样。幸运的是,React以绳墨方式落成了多数周边浏览器事件,这样我们就无需担心跨浏览器的兼容性(你能够在这里查看到全部的轩然大波清单)。

重新打开Record组件,添加贰个新的handleDelete格局和多个onClick属性到“无用”的删减按钮,代码如下:

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    handleDelete: (e) ->
      e.preventDefault()
      # yeah... jQuery doesn't have a $.delete shortcut method
      $.ajax
        method: 'DELETE'
        url: "/records/#{ @props.record.id }"
        dataType: 'JSON'
        success: () =>
          @props.handleDeleteRecord @props.record
    render: ->
      React.DOM.tr null,
        React.DOM.td null, @props.record.date
        React.DOM.td null, @props.record.title
        React.DOM.td null, amountFormat(@props.record.amount)
        React.DOM.td null,
          React.DOM.a
            className: 'btn btn-danger'
            onClick: @handleDelete
            'Delete'

当删除按钮被点击时,handleDelete出殡二个AJAX请求到服务器来删除后端的笔录,之后,针对本次动作,它通过handleDeleteRecord电脑可用的props来打招呼父组件。那象征大家供给在父组件中调整Record要素的创导来含有额外的性质handleDeleteRecord,而且还要在父组件中落到实处实际的微处理器方法:

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    deleteRecord: (record) ->
      records = @state.records.slice()
      index = records.indexOf record
      records.splice index, 1
      @replaceState records: records
    render: ->
      ...
      # almost at the bottom of the render method
      React.DOM.table
        React.DOM.thead null,
          React.DOM.tr null,
            React.DOM.th null, 'Date'
            React.DOM.th null, 'Title'
            React.DOM.th null, 'Amount'
            React.DOM.th null, 'Actions'
        React.DOM.tbody null,
          for record in @state.records
            React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord

基本上,我们的deleteRecord主意拷贝了当前组建的recordsstate,执行了二个要被删去记录的目录查找,从该数组中拼接好并更新组件的state,万分专业的JavaScript操作。

大家介绍二个和state相互的新点子,replaceStatesetStatereplaceState的重中之重不一致在于前者仅更新state对象的一个键值,而后人会用任何大家发送的新目的来完全覆盖组件的当下state。

在立异完上边那一点代码后,刷新浏览器并尝试删除三个笔录,会三个业务时有爆发:

  1. 该记录会从表格中流失
  2. 目的的金额会立刻更新,不须要万分的代码了

CoffeeScript 9

deleting_record_2

大家差不离实现整个应用程序了,但在贯彻末了2个效率从前,大家能执行2个小重构,并同时介绍三个新的React成效。

您能够到这里去看看本章的结果代码,只怕仅仅到这里看望由本章引入的改观。

重构:State Helpers

明日结束,大家早已有三种情势让state作为大家的数量获得更新,没有其余不便,并不像你所说的那么“复杂”。但考虑下一个带有多层次JSON
state的更扑朔迷离的应用程序,你能协调想象下举办深度复制和更换你的state数据。React包蕴了一部分花俏的state
helpers
来增派你应对这几个重担。无论你的state有多少深度,那个 helper
都会让您就像是使用 MongoDB
的查询语言同样,更轻易地决定它(至少React的文档是这么说的)。

在接纳那一个helper此前,首先大家必要配置下大家的Rails应用程序来含有它们。打开你项目标config/application.rb文件并在Application代码块的底部添加一行config.react.addons = ture

  # config/application.rb

  ...
  module Accounts
    class Application < Rails::Application
      ...
      config.react.addons = true
    end
  end

为了另其收效,你要重启Rails服务器你要重启Rails服务器您要重启Rails服务器,主要的事务说3次!未来我们得以经过React.addons.update来访问state
helpers,它们会处理我们的 state
对象(或其它大家发送给它的靶子),并且能利用提供的指令。大家将会动用的五个指令是$push$splice(对那一个命令,我借用官方React文书档案的表明):

  • {$push: array}array里的有所数据项push()到目的去
  • {$splice: array of arrays}对于在arrays中的各样数组项array,在目标数组中用数据项提供的参数调用splice()

咱俩打算用那个helper来简化Record组件的addRecorddeleteRecord,代码如下:

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    addRecord: (record) ->
      records = React.addons.update(@state.records, { $push: [record] })
      @setState records: records
    deleteRecord: (record) ->
      index = @state.records.indexOf record
      records = React.addons.update(@state.records, { $splice: [[index, 1]] })
      @replaceState records: records

一律的结果,更短更优雅的代码,现在您能够不管重载下浏览器并认同有没怎么不妥。

你能够到这里去探访本章的结果代码,恐怕只有到这里看望由本章引入的变更。

响应式数据流:编辑记录

为了达成最后一个效应,我们未来加上三个额外的Edit按钮,放在大家记录表格中的每个Delete按钮的一侧。当那个Edit按钮被点击时,它将整个数据行从只读状态切换来可编写制定状态,展示贰个行内表单以便用户能够创新记录的始末。在提交被更新内容或打消该操作后,该记录行将会到它原先的只读状态。

正如您从上文描述中猜到的那样,大家须求处理可变(mutable)数据来切换在Record组件中各种记录的境况。那是1个React调用响应式数据流(reactive data flow)的用例。让大家添加四个edit标明和2个handleToggle方法到record.js.coffee

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    getInitialState: ->
      edit: false
    handleToggle: (e) ->
      e.preventDefault()
      @setState edit: !@state.edit
    ...

这个edit标志暗中同意为false,而handleToggleedit由false变为true,亦可反向操作,大家仅须求从一个用户onClick事件中触发handleToggle

近年来,大家需求处理八个行记录版本(只读和表单)并且有规范地依据edit证明来展现它们。幸运的是,只要render措施再次回到三个React成分,我们就足以在它在那之中随意执行其余操作。大家得以定义recordRowrecordForm两个helper方法,并在render里面,依赖于@state.edit的剧情有规范地调用它们。

咱俩已经有了2个recordRow的伊始化版本,就是大家前几天的render主意。让我们把render的情节移到新的recordRow方法里并加上一些外加的代码给它:

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    ...
    recordRow: ->
      React.DOM.tr null,
        React.DOM.td null, @props.record.date
        React.DOM.td null, @props.record.title
        React.DOM.td null, amountFormat(@props.record.amount)
        React.DOM.td null,
          React.DOM.a
            className: 'btn btn-default'
            onClick: @handleToggle
            'Edit'
          React.DOM.a
            className: 'btn btn-danger'
            onClick: @handleDelete
            'Delete'
    ...

我们只参与了二个附加的React.DOM.a要素,用来监听到onClick事件后调用handleToggle

接着,recordForm的兑现利用类似结构,只是每一个单元格用input来顶替。大家打算为那么些input用贰个新的ref性情来使其变得可存取。和那几个组件不出去state一样,这几个新的质量会让大家的组件通过@refs读出由用户提供的数额。

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    ...
    recordForm: ->
      React.DOM.tr null,
        React.DOM.td null,
          React.DOM.input
            className: 'form-control'
            type: 'text'
            defaultValue: @props.record.date
            ref: 'date'
        React.DOM.td null,
          React.DOM.input
            className: 'form-control'
            type: 'text'
            defaultValue: @props.record.title
            ref: 'title'
        React.DOM.td null,
          React.DOM.input
            className: 'form-control'
            type: 'number'
            defaultValue: @props.record.amount
            ref: 'amount'
        React.DOM.td null,
          React.DOM.a
            className: 'btn btn-default'
            onClick: @handleEdit
            'Update'
          React.DOM.a
            className: 'btn btn-danger'
            onClick: @handleToggle
            'Cancel'
    ...

别害怕,那些方式看起来有点大,仅仅是因为大家用了类似HAML的语法。注意,当用户点击
Update
按钮时大家调用@handleEdit,我们打算选取与实现删除记录成效类似的流水生产线。

你有否注意到这几个React.DOM.input的创始有怎么着两样呢?大家接纳defaultValue代替value来设置初步化
input 的值,那是因为:仅使用value而没有onChange会告一段落创造只读的
input

末尾,render方法浓缩成下列代码:

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    ...
    render: ->
      if @state.edit
        @recordForm()
      else
        @recordRow()

你能够刷新你的浏览器来看看新的切换效果,但绝不提交任何改变,因为咱们还没兑现实际的
update 功能。

CoffeeScript 10

edit_record_1

CoffeeScript 11

edit_record_2

要拍卖记录的更新,我们须要丰裕update格局到大家的Rails控制器:

  # app/controllers/records_controller.rb

  class RecordsController < ApplicationController
    ...
    def update
      @record = Record.find(params[:id])
      if @record.update(record_params)
        render json: @record
      else
        render json: @record.errors, status: :unprocessable_entity
      end
    end
    ...
  end

回来大家的Record零件,大家要求贯彻handleEdit艺术,它将会有意无意要创新的record音信发送三个
AJAX
请求到服务器,然后由发送更新后版本的记录数据通过handleEditRecord方法文告父组件,这么些方法会通过@props被接收到,大家在促成删除记录时用过相同的章程:

  # app/assets/javascripts/components/record.js.coffee

  @Record = React.createClass
    ...
    handleEdit: (e) ->
      e.preventDefault()
      data =
        title: this.refs.title.value
        date: this.refs.date.value
        amount: this.refs.amount.value
      # jQuery doesn't have a $.put shortcut method either
      $.ajax
        method: 'PUT'
        url: "/records/#{ @props.record.id }"
        dataType: 'JSON'
        data:
          record: data
        success: (data) =>
          @setState edit: false
          @props.handleEditRecord @props.record, data
    ...

为不难起见,我们不校验用户数据,我们无非通过React.findDOMNode(@refs.fieldName).value读取它,并且一字不差的把它发送给后端。在success时更新情形来切换
edit 方式不是强制性的,但用户会因而而深入人心地多谢大家。

最终但毫无最不首要,我们仅须求革新Records组件上的state,用子组件的新本子记录来掩盖以前的旧记录并让React发挥它的魅力。实现的代码如下:

  # app/assets/javascripts/components/records.js.coffee

  @Records = React.createClass
    ...
    updateRecord: (record, data) ->
      index = @state.records.indexOf record
      records = React.addons.update(@state.records, { $splice: [[index, 1, data]] })
      @replaceState records: records
    ...
    render: ->
      ...
      # almost at the bottom of the render method
      React.DOM.table
        React.DOM.thead null,
          React.DOM.tr null,
            React.DOM.th null, 'Date'
            React.DOM.th null, 'Title'
            React.DOM.th null, 'Amount'
            React.DOM.th null, 'Actions'
        React.DOM.tbody null,
          for record in @state.records
            React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord, handleEditRecord: @updateRecord

和大家从上一章学到的一样,使用React.addons.update来改变大家的 state
会产生更深厚的主意。RecordsRecord时期最后的过渡是经过handleEditRecord质量来揭橥办法@updateRecord。

最后一回刷新浏览器并尝试更新一些已有些记录,注意页面顶端的金额框怎么着与您转移的每一种记录关联。

CoffeeScript 12

edit_record_3

消除了!大家正好一步步地构建了二个袖珍的 Rails + React 的应用程序!

你可以到这里去探访本章的结果代码,或然唯有到这里看看由本章引入的更改。

末段的构思:React.js,简洁又利落

咱俩早就认证了部分React的作用,而且大家还学到了大约全数它引入的新定义。小编听见人们评价这一个或尤其的JavaScript框架因引入新定义而使其深造曲线变得陡峭,但React不是这么的。它达成了诸如事件处理和绑定等大旨JavaScript概念,使其便于使用和读书。再度证实,其优势之一正是容易。

透超过实际例,大家也学到了怎样使其集成到Rails的assets
pipeline,而且也能很好的与CoffeeScript、jQuery、Turbolinks及Rails的其他部分协同工作。可是,那毫不是想要获取结果的绝无仅有方法。例如,你不想行使Turbolinks(因此你不须要react_ujs),你能用Rails Assets来代替react_rails以此gem,你能够选取Jbuilder来布局更扑朔迷离的JSON一呼百应来替代提供的JSON指标,等等。你照样会获取平等不错的功用。

React将肯定地升高你的前端能力,让它变成你Rails工具箱中3个无敌的库吧!