rails .try_如何构建与Rails 5.1 API兼容的React App

rails .try

React + Ruby on Rails =🔥

React已经席卷了前端开发领域。 这是用于构建用户界面的优秀JavaScript库。 与Ruby on Rails结合使用非常好。 您可以通过多种方式在后端使用Rails,在前端使用React

在本动手教程中,我们将构建一个可与Rails 5.1 API一起使用的React应用。

您可以在此处观看本教程的视频版本

观看本教程的视频版本

要遵循本教程,您需要熟悉Rails并了解React的基础知识。

如果您不使用Rails,也可以使用您选择的语言或框架来构建API,并将本教程用于React部分。

该教程涵盖了无状态功能组件,基于类的组件,使用Create React App,使用axios进行API调用,不可变性帮助器等等。

我们将要建立的

我们将构建一个单页应用程序(SPA)的想法板 ,以方形图块的形式显示想法。

您可以添加,编辑和删除新的想法。 当用户将焦点移出编辑表单时,便会自动保存想法。

创意板应用程序的演示

在本教程的最后,我们将提供一个功能强大的CRUD应用程序,在以后的教程中,我们可以向其添加一些增强功能,例如动画,排序和搜索。

您可以在GitHub上查看该应用程序的完整代码:

Ideaboard Rails API

Ideaboard React前端

设置Rails API

让我们开始构建Rails API。 我们将使用Rails的内置功能来构建仅API的应用程序。

确保已安装5.1或更高版本的Rails gem。

gem install rails -v 5.1.3

在撰写本教程时,5.1.3是最新的稳定版本,因此我们将使用它。

然后使用--api标志生成一个新的Rails API应用。

rails new --api ideaboard-api
cd ideaboard-api

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

接下来,让我们创建数据模型。 对于带有两个字段的想法,我们只需要一个数据模型-标题和正文,都为string类型。

让我们生成并运行迁移:

rails generate model Idea title:string body:string

rails db:migrate

现在,我们已经在数据库中创建了一个想法表,让我们为它添加一些记录,以便我们可以显示一些想法。

db/seeds.rb文件中,添加以下代码:

ideas = Idea.create(
  [
    {
      title: "A new cake recipe",
      body: "Made of chocolate"
    },
    {
      title: "A twitter client idea",
      body: "Only for replying to mentions and DMs"
    },
    {
      title: "A novel set in Italy",
      body: "A mafia crime drama starring Berlusconi"
    },
    {
      title: "Card game design",
      body: "Like Uno but involves drinking"
    }
  ])

随时添加您自己的想法。

然后运行:

rails db:seed

接下来,让我们在app/controllers/api/v1/ideas_controller.rb创建一个带有索引动作的app/controllers/api/v1/ideas_controller.rb

module Api::V1
  class IdeasController < ApplicationController
    def index
      @ideas = Idea.all
      render json: @ideas
    end
  end
end

请注意,该控制器位于app/controllers/api/v1因为我们正在对API进行版本控制。 这是一个很好的做法,可以避免破坏更改并提供与我们的API的向后兼容性。

然后在config/routes.rb添加想法作为资源:

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :ideas  
    end
  end
end

好了,现在让我们测试我们的第一个API端点!

首先,让我们在端口3001上启动Rails API服务器:

rails s -p 3001

然后,让我们测试端点以获取所有带有curl的想法:

curl -G http://localhost:3001/api/v1/ideas

并以JSON格式打印出我们所有的想法:

[{"id":18,"title":"Card game design","body":"Like Uno but involves drinking","created_at":"2017-09-05T15:42:36.217Z","updated_at":"2017-09-05T15:42:36.217Z"},{"id":17,"title":"A novel set in Italy","body":"A mafia crime drama starring Berlusconi","created_at":"2017-09-05T15:42:36.213Z","updated_at":"2017-09-05T15:42:36.213Z"},{"id":16,"title":"A twitter client idea","body":"Only for replying to mentions and DMs","created_at":"2017-09-05T15:42:36.209Z","updated_at":"2017-09-05T15:42:36.209Z"},{"id":15,"title":"A new cake recipe","body":"Made of chocolate","created_at":"2017-09-05T15:42:36.205Z","updated_at":"2017-09-05T15:42:36.205Z"}]

我们还可以通过访问http:// localhost:3001 / api / v1 / ideas在浏览器中测试端点。

在浏览器中测试我们的API端点

使用Create React App设置我们的前端应用程序

现在我们有了基本的API,让我们使用Create React App来设置前端React应用程序 。 创建React App是Facebook的一个项目,可帮助您快速进行React应用入门,而无需进行任何配置。

首先,请确保已安装Node.js和npm。 您可以从Node.js网站下载安装程序。 然后运行以下命令安装Create React App:

npm install -g create-react-app

然后,确保您不在Rails目录中并运行以下命令:

create-react-app ideaboard

这将生成一个名为ideaboard的React应用程序,我们现在将使用该应用程序与Rails API进行对话。

让我们运行React应用程序:

cd ideaboard
npm start

这将在http:// localhost:3000上打开它。

Create React App生成的新应用程序的主页

该应用程序具有一个默认页面,该页面包含一个名为App的React组件,该组件显示React徽标和欢迎消息。

页面上的内容通过src/App.js文件中的React组件呈现:

import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App

我们的第一个React组件

下一步是编辑此文件以使用我们刚刚创建的API,并在页面上列出所有想法。

首先,用我们的应用程序“ Idea Board”的标题将h1标签替换为欢迎消息。

我们还添加一个名为IdeasContainer的新组件。 我们需要导入它并将其添加到render函数中:

import React, { Component } from 'react'
import './App.css'
import IdeasContainer from './components/IdeasContainer'

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <h1>Idea Board</h1>
        </div>
        <IdeasContainer />
      </div>
    );
  }
}

export default App

让我们在src/components目录下src/IdeasContainer.js中的新文件中创建这个IdeasContainer组件。

import React, { Component } from 'react'

class IdeasContainer extends Component {
  render() {
    return (
      <div>
        Ideas
      </div>
    )
  }
}

export default IdeasContainer

我们还要在App.css中将样式App.css为具有白色标题和黑色文本,并删除我们不需要的样式:

.App-header {
  text-align: center;
  height: 150px;
  padding: 20px;
}

.App-intro {
  font-size: large;
}

骨架创意板应用程序

该组件需要与我们的Rails API端点进行对话,以获取所有想法并显示它们。

使用axios提取API数据

我们将在IdeasContainer组件的componentDidMount()生命周期方法中对API进行Ajax调用,并将想法存储在组件状态中。

让我们首先将构造函数中的状态初始化为空数组:

constructor(props) {
  super(props)
  this.state = {
    ideas: []
  }
}

然后,我们将在componentDidMount()更新状态。

让我们使用axios库进行API调用。 如果喜欢,也可以使用fetch或jQuery。

使用npm安装axios:

npm install axios --save

然后将其导入IdeasContainer

import axios from 'axios'

并在componentDidMount()使用它:

componentDidMount() {
  axios.get('http://localhost:3001/api/v1/ideas.json')
  .then(response => {
    console.log(response)
    this.setState({ideas: response.data})
  })
  .catch(error => console.log(error))
}

现在,如果我们刷新页面……它将无法正常工作!

不存在Access-Control-Allow-Origin标头

我们将收到“不存在Access-Control-Allow-Origin标头”错误,因为我们的API位于其他端口上,并且尚未启用跨源资源共享(CORS)。

启用跨源资源共享(CORS)

因此,首先让我们使用Rails应用程序中的rack-cors gem启用CORS。

将gem添加到Gemfile中:

gem 'rack-cors', :require => 'rack/cors'

安装它:

bundle install

然后将中间件配置添加到config/application.rb文件:

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3000'
    resource '*', :headers => :any, :methods => [:get, :post, :put, :delete, :options]
  end
end

我们将源限制为位于http://localhost:3000的前端应用程序,并允许访问所有资源的标准REST API端点方法。

现在我们需要重新启动Rails服务器,并且如果刷新浏览器,我们将不再收到CORS错误。

该页面将加载正常,我们可以看到控制台中记录的响应数据。

来自API的Ideas JSON响应已记录到控制台

因此,既然我们知道我们能够从API中获取想法,就让我们在React组件中使用它们。

我们可以更改render函数,以从状态中迭代列表提示并显示每个提示:

render() {
  return (
    <div>
      {this.state.ideas.map((idea) => {
        return(
          <div className="tile" key={idea.id} >
            <h4>{idea.title}</h4>
            <p>{idea.body}</p>
          </div>
        )       
      })}
    </div>
  );
}

现在将在页面上显示所有想法。

按组件显示的提示列表

注意tile div上的key属性

创建元素列表时,我们需要包括它。 按键可帮助React识别哪些项目已更改,添加或删除。

现在让我们在App.css添加一些样式,以使每个想​​法看起来像一个图块:

.tile {
  height: 150px;
  width: 150px;
  margin: 10px;
  background: lightyellow;
  float: left;
  font-size: 11px;
  text-align: left;
}

我们设置高度,宽度,背景色,并使瓷砖向左浮动。

风格的主意图块

无状态功能组件

在继续之前,让我们重构代码到目前为止,然后将想法块的JSX移动到名为Idea的单独组件中。

import React from 'react'

const Idea = ({idea}) =>
  <div className="tile" key={idea.id}>
    <h4>{idea.title}</h4>
    <p>{idea.body}</p>
  </div>

export default Idea

这是一个无状态的功能组件(或称“哑”组件),这意味着它不处理任何状态。 这是一个纯函数,它接受一些数据并返回JSX。

然后在IdeasContainer的map函数中,我们可以返回新的Idea组件:

{this.state.ideas.map((idea) => {
  return (<Idea idea={idea} key={idea.id} />)
})}

别忘了也导入Idea

import Idea from './Idea'

太好了,这就是我们应用程序完整的第一部分。 我们有一个带有端点的API,用于获取想法,还有一个React应用,用于将它们显示为板上的图块!

添加新记录

接下来,我们将添加一种创建新想法的方法。

让我们首先添加一个按钮来添加一个新的想法。

IdeasContainer的render函数中,添加:

<button className="newIdeaButton">
  New Idea
</button>

让我们在App.cssApp.css添加一些样式:

.newIdeaButton {
  background: darkblue;
  color: white;
  border: none;
  font-size: 18px;
  cursor: pointer;
  margin-right: 10px;
  margin-left: 10px;
  padding:10px;
}

新想法按钮

现在,当我们单击按钮时,我们希望另一个图块出现并带有一个表单以编辑该想法。

编辑表单后,我们希望将其提交给我们的API以创建新的想法。

用于创建新想法的API端点

因此,让我们从首先创建一个API端点开始,以便在IdeasController创建新想法:

def create
  @idea = Idea.create(idea_params)
  render json: @idea
end

private

  def idea_params
    params.require(:idea).permit(:title, :body)
  end

由于Rails使用强大的参数 ,我们定义的私有方法idea_params到白名单中PARAMS我们需要- titlebody

现在,我们有了一个API端点,可以向其发布想法数据并创建新想法。

回到我们的React应用程序中,现在让我们将名为addNewIdea的点击处理程序添加到新提示按钮:

<button className="newIdeaButton"
  onClick={this.addNewIdea} >
  New Idea
</button>

让我们将addNewIdea定义为一个函数,该函数使用axios对带有空白提示的新提示端点进行POST调用。 现在让我们将响应记录到控制台:

addNewIdea = () => {
  axios.post(
    'http://localhost:3001/api/v1/ideas',
    { idea:
      {
        title: '',
        body: ''
      }
    }
  )
  .then(response => {
    console.log(response)
  })
  .catch(error => console.log(error))
}

现在,如果尝试单击浏览器中的“新提示”按钮,我们将在控制台中看到响应包含一个带有新标题和正文的带有新提示的数据对象。

空白的想法数据记录到控制台

刷新页面时,我们可以看到一个代表我们新想法的空白磁贴。

空白的想法瓷砖

我们真正想发生的事情是,当我们单击“新提示”按钮时,会立即创建一个提示,并在页面上显示用于编辑该提示的表单。

这样,我们可以使用相同的形式和逻辑来编辑本教程后面的任何想法。

在执行此操作之前,让我们首先按相反的时间顺序在页面上排列想法,以便最新的想法出现在顶部。

因此,让我们在IdeasController更改@ideas的定义,以按其created_at降序对创意进行排序:

module Api::V1
  class IdeasController < ApplicationController
    def index
      @ideas = Idea.order("created_at DESC")
      render json: @ideas
    end
  end
end

好了,现在首先显示最新的想法。

首先显示最新的想法

现在,让我们继续定义addNewIdea

首先,让我们使用来自POST调用的响应来更新状态中的创意数组,以便在我们添加新创意时,它立即出现在页面上。

因为这只是一个示例应用程序,所以我们可以push新想法push送到数组中,但是使用不可变数据是个好习惯。

因此,让我们使用immutability-helper ,这是一个不错的软件包,用于更新数据而无需直接对其进行更改。

用npm安装它:

npm install immutability-helper --save

然后将update函数导入IdeasContainer

import update from 'immutability-helper'

现在让我们在addNewIdea使用它,将我们的新想法插入到想法数组的开头:

addNewIdea = () => {
  axios.post(
    'http://localhost:3001/api/v1/ideas',
    { idea:
      {
        title: '',
        body: ''
      }
    }
  )
  .then(response => {
    console.log(response)
    const ideas = update(this.state.ideas, {
      $splice: [[0, 0, response.data]]
    })
    this.setState({ideas: ideas})
  })
  .catch(error => console.log(error))
}

我们制作了this.state.ideas的新副本,并使用$splice命令在该数组的第0个索引处插入了新的想法(在response.data )。

然后,我们使用这个新的想法数组通过setState更新状态。

现在,如果我们在浏览器中尝试该应用并单击“新提示”按钮,则会立即出现一个新的空白图块。

添加新想法

现在,我们可以继续编辑这个想法。

首先,我们需要一个新的状态属性editingIdeaId ,它可以跟踪当前正在编辑哪个构想。

默认情况下,我们不编辑任何想法,因此让我们使用空值将state初始化为editingIdeaId

this.state = {
  ideas: [],
  editingIdeaId: null
}

现在,当我们添加一个新想法时,除了将其添加到state.ideas ,我们还希望将其ID设置为state.editingIdeaId的值。 因此,让我们修改addNewIdeasetState调用, addNewIdea还包含set editingIdeaId

this.setState({
  ideas: ideas,
  editingIdeaId: response.data.id
})

因此,这表明我们刚刚添加了一个新的想法,我们想立即对其进行编辑。

现在,完整的addNewIdea函数如下所示:

addNewIdea = () => {
  axios.post(
    'http://localhost:3001/api/v1/ideas',
    { idea:
      {
        title: '',
        body: ''
      }
    }
  )
  .then(response => {
    const ideas = update(this.state.ideas, {
      $splice: [[0, 0, response.data]]
    })
    this.setState({
      ideas: ideas,
      editingIdeaId: response.data.id
    })
  })
  .catch(error => console.log(error))
}
表单组件

现在,我们可以在render函数中使用state.editingIdeaId ,这样我们就可以显示表单,而不是仅显示普通的提示图块。

map函数内部,让我们将返回值更改为一个条件语句,如果一个主意的ID与state.editingIdeaId匹配, state.editingIdeaId返回一个IdeaForm组件,否则将呈现一个Idea组件:

{this.state.ideas.map((idea) => {
  if(this.state.editingIdeaId === idea.id) {
    return(<IdeaForm idea={idea} key={idea.id} />)
  } else {
    return (<Idea idea={idea} key={idea.id} />)
  }
})}

让我们在IdeasContainer导入IdeaForm组件:

import IdeaForm from './IdeaForm'

让我们在IdeaForm.js定义它。 我们将从一个简单的类组件开始,该组件将呈现一个带有两个输入字段的表单,这些输入字段用于提示标题和正文:

import React, { Component } from 'react'
import axios from 'axios'

class IdeaForm extends Component {
  constructor(props) {
    super(props)
    this.state = {
    }
  }

  render() {
    return (
      <div className="tile">
        <form>
          <input className='input' type="text"
            name="title" placeholder='Enter a Title' />
          <textarea className='input' name="body"
            placeholder='Describe your idea'></textarea>
        </form>
      </div>
    );
  }
}

export default IdeaForm

让我们在App.css添加一些CSS来样式化表单:

.input {
  border: 0;
  background: none;
  outline: none;
  margin-top:10px;
  width: 140px;
  font-size: 11px;
}

.input:focus {
  border: solid 1px lightgrey;
}

textarea {
  resize: none;
  height: 90px;
  font-size: 11px;
}

现在,当我们单击“新提示”按钮时,将出现一个新的磁贴,其中包含一个表单:

用于编辑新想法的样式表

现在,让此表单起作用!

我们需要将表单输入字段连接到状态。

首先,让我们根据从IdeasContainer接收到的idea prop初始化IdeaForm组件状态值:

class IdeaForm extends Component {
  constructor(props) {
    super(props)
    this.state = {
      title: this.props.idea.title,
      body: this.props.idea.body
    }
  }

然后将表单字段值设置为其相应的状态值,并设置一个onChange处理程序:

<form>
  <input className='input' type="text"
    name="title" placeholder='Enter a Title'
    value={this.state.title} onChange={this.handleInput} />
  <textarea className='input' name="body"
    placeholder='Describe your idea'
    value={this.state.body} onChange={this.handleInput}>
  </textarea>
</form>

我们将定义handleInput ,以便在我们输入任何一个输入字段时,都将更新相应的状态值,然后更新该字段的值:

handleInput = (e) => {
  this.setState({[e.target.name]: e.target.value})
}
在React Developer Tools中跟踪状态更改

让我们看看这些状态变化在React开发工具浏览器扩展中的作用。 您可以在这里为Chrome浏览器Firefox获取它。

安装完成后,刷新应用程序页面并打开开发人员控制台。 您应该看到一个新的React选项卡。

单击它时,您会在左侧看到我们的应用程序组件树,在右侧看到与每个组件关联的所有道具和状态。

React开发人员工具显示状态更新

现在,我们正在更新表单字段,但仍未保存已编辑的提示。 因此,接下来需要做的是,当我们模糊表单字段时,我们想要提交表单并更新想法。

API端点,用于更新想法

首先,我们需要定义一个API端点来更新思想。 因此,让我们在IdeasController添加一个update操作:

def update
  @idea = Idea.find(params[:id])
  @idea.update_attributes(idea_params)
  render json: @idea
end

回到IdeaForm.js ,我们将一个名为handleBluronBlur处理程序设置为以下形式:

<form onBlur={this.handleBlur} >

我们将定义handleBlur以对我们的API端点进行PUT调用,以使用状态中的想法数据更新想法。 现在,让我们将响应记录到控制台,看看我们的调用是否有效:

handleBlur = () => {
  const idea = {
    title: this.state.title,
    body: this.state.body
  }

  axios.put(
    `http://localhost:3001/api/v1/ideas/${this.props.idea.id}`,
    {
      idea: idea
    })
  .then(response => {
    console.log(response)
  })
  .catch(error => console.log(error))
}

我们还需要在此文件中导入axios才能使用它:

import axios from 'axios'

现在,如果我们单击“新提示”按钮,编辑其标题并模糊显示该字段,我们将看到控制台中记录了API响应以及新编辑的提示数据。

如果我们编辑实体并模糊该字段,则会发生相同的情况。

在控制台中检查编辑的想法数据

因此我们的onBlur处理程序可以工作,我们可以编辑新的想法,但是我们还需要将编辑后的想法数据发送回IdeasContainer以便它也可以更新自己的状态。

否则, state.ideas将不会具有我们刚刚编辑过的想法的更新值。

我们将使用一个名为updateIdea的方法,该方法将作为从IdeasContainerIdeaForm的道具IdeaForm 。 我们将使用来自API调用的响应数据调用updateIdea

handleBlur = () => {
  const idea = {
    title: this.state.title,
    body: this.state.body
  }

  axios.put(
    `http://localhost:3001/api/v1/ideas/${this.props.idea.id}`,
    {
      idea: idea
    })
  .then(response => {
    console.log(response)
    this.props.updateIdea(response.data)
  })
  .catch(error => console.log(error))
}

现在在IdeasContainer ,让我们发送一个updateIdea函数作为对IdeaForm的支持:

<IdeaForm idea={idea} key={idea.id}
 updateIdea={this.updateIdea} />

让我们定义一个函数来对state.ideas中的思想进行不可变的更新:

updateIdea = (idea) => {
  const ideaIndex = this.state.ideas.findIndex(x => x.id === idea.id)
  const ideas = update(this.state.ideas, {
    [ideaIndex]: { $set: idea }
  })
  this.setState({ideas: ideas})
}

首先,我们在数组中找到已编辑构思的索引,然后使用$set命令将旧值替换为新值。 最后,我们调用setState更新state.ideas

我们可以在打开React开发者工具标签的浏览器中看到这一点。

容器状态更新

显示成功通知

现在,我们可以添加一个新的想法并对其进行编辑,但是保存该想法时,用户不会收到任何视觉反馈或确认。 因此,让我们添加一条通知消息,以告诉用户何时成功保存构思。

让我们在新提示按钮旁边添加一个跨度,以显示来自状态值的通知:

<span className="notification">
  {this.state.notification}
</span>

让我们将state.notification初始化为一个空字符串:

constructor(props) {
  super(props)
  this.state = {
    ideas: [],
    editingIdeaId: null,
    notification: ''
  }
}

现在,每次更新想法时,我们都会使用要显示给用户的成功通知来更新state.notification

因此,在updateIdea中的setState调用中,除了更新ideas ,我们还更新notification

this.setState({
  ideas: ideas,
  notification: 'All changes saved'
})

现在,当我们编辑构思并模糊输入字段时,该构思即被保存,并且看到成功通知。

成功更新构思的通知消息

我们还希望在用户进行尚未保存的更改后立即重置通知。

因此,在IdeaForm组件的handleInput函数中,让我们调用一个名为resetNotification的函数来重置通知消息:

handleInput = (e) => {
  this.props.resetNotification()
  this.setState({[e.target.name]: e.target.value})
}

现在,在IdeasContainerrender函数中,我们还将resetNotification传递给IdeaForm

<IdeaForm idea={idea} key={idea.id}
  updateIdea={this.updateIdea}
  resetNotification={this.resetNotification} />

让我们将resetNotification定义为:

resetNotification = () => {
  this.setState({notification: ''})
}

现在,出现成功通知后,如果我们再次编辑构思,通知将消失。

在未保存的修改上重置通知消息

编辑现有想法

接下来,让我们添加编辑现有想法的功能。 当单击一个主意图块时,我们想要更改该图块,以使其用IdeaForm组件替换Idea组件以编辑该主意。

然后,我们可以编辑该想法,然后将其保存为模糊效果。

为了添加此功能,我们需要在想法图块上添加一个点击处理程序。

因此,首先我们需要将Idea组件从功能组件转换为类组件,然后可以为标题和正文设置定义点击处理程序函数handleClick

import React, { Component } from 'react'

class Idea extends Component {

  handleClick = () => {
    this.props.onClick(this.props.idea.id)
  }

  render () {
    return(
      <div className="tile">
        <h4 onClick={this.handleClick}>
          {this.props.idea.title}
        </h4>
        <p onClick={this.handleClick}>
          {this.props.idea.body}
        </p>
      </div>
    )
  }
}

export default Idea

请注意,我们必须添加this.props. 使用props值,因为与功能组件不同,我们不再破坏props对象。

handleClick使用提示ID调用this.props.onClick

现在,在IdeasContainerrender功能中,我们还将onClick传递给Idea

return (<Idea idea={idea} key={idea.id} onClick={this.enableEditing} />)

我们将定义enableEditing到的值设置state.editingIdeaId所点击的想法的id:

enableEditing = (id) => {
  this.setState({editingIdeaId: id})
}

现在,当我们单击图块时,它立即变为可编辑的!

单击一个提示图块进行编辑

单击磁贴时,一旦出现表单,我们还将光标焦点设置在标题输入字段上。

我们可以通过在IdeaForm的title输入字段上添加一个ref来做到这IdeaForm

<input className='input' type="text"
  name="title" placeholder='Enter a Title'
  value={this.state.title} onChange={this.handleInput}
  ref={this.props.titleRef} />

我们需要将ref作为道具传递,因为我们想在父组件IdeasContainer使用它,在这里我们可以将ref定义为回调函数:

<IdeaForm idea={idea} key={idea.id}
  updateIdea={this.updateIdea}
  titleRef= {input => this.title = input}
  resetNotification={this.resetNotification} />

现在,我们可以在enableEditing使用此引用在标题输入字段中设置焦点:

enableEditing = (id) => {
  this.setState({editingIdeaId: id},
    () => { this.title.focus() })
}

注意,在调用setState之后,我们没有将this.title.focus()作为单独的函数调用。 相反,我们将其作为第二个参数传递给了回调中的setState

我们这样做是因为setState并不总是立即更新组件。 通过在回调中传递焦点调用,我们确保仅在组件更新后才调用它。

现在,如果我们在浏览器中尝试该应用程序,则当我们单击一个想法图块时,它便可以通过表单进行编辑,并且光标将集中在其标题输入字段内。

单击以编辑想法并将焦点设置到输入字段

现在,我们可以添加和编辑想法。

删除想法

最后,我们希望能够删除想法。

当我们将鼠标悬停在某个提示图块上时,我们希望删除按钮(以红色叉号的形式)出现在右上角。 单击该十字应删除该主意,并从板上移除磁贴。

因此,让我们开始添加一些标记和CSS以在悬停时显示删除按钮。

在“ Idea组件中,添加带有类deleteButton和文本“ x”的范围:

<div className="tile">
  <span className="deleteButton">
    x
  </span>

然后让我们在App.css添加一些CSS,以默认隐藏此范围,并将其悬停在图块上时可见:

.deleteButton {
  visibility: hidden;
  float: right;
  margin: 5px;
  font-size: 14px;
  cursor: pointer;
  color: red;
}

.tile:hover .deleteButton {
  visibility: visible;
}

将鼠标悬停在图块上时会出现“删除”按钮

接下来,让我们向此删除按钮添加一个点击处理程序handleDelete ,然后删除该想法:

<span className="deleteButton" onClick={this.handleDelete}>
  x
</span>

handleClick相似,我们将handleDelete定义为一个箭头函数,该函数使用图块的想法ID调用另一个函数this.props.onDelete

handleDelete = () => {
  this.props.onDelete(this.props.idea.id)
}

让我们通过onDelete从道具IdeasContainer

<Idea idea={idea} key={idea.id}
  onClick={this.enableEditing}
  onDelete={this.deleteIdea} />

稍后我们将定义deleteIdea ,但是首先让我们添加一个API端点,用于删除IdeasController想法:

def destroy
  @idea = Idea.find(params[:id])
  if @idea.destroy
    head :no_content, status: :ok
  else
    render json: @idea.errors, status: :unprocessable_entity
  end
end

现在,让我们定义deleteIdeaIdeasContainer的功能,使一个DELETE调用我们的想法ID API和操作成功,更新state.ideas

deleteIdea = (id) => {
  axios.delete(`http://localhost:3001/api/v1/ideas/${id}`)
  .then(response => {
    const ideaIndex = this.state.ideas.findIndex(x => x.id === id)
    const ideas = update(this.state.ideas, { $splice: [[ideaIndex, 1]]})
    this.setState({ideas: ideas})
  })
  .catch(error => console.log(error))
}

再次,我们查找已删除的提示的索引,使用$splice命令的update创建一个新的提示数组,然后使用它更新state.ideas

现在我们可以在浏览器中尝试它。 当我们将鼠标悬停在一个主意图块上时,将显示红色的删除按钮。 单击它会删除该主意,并从板上移除磁贴。

点击删除按钮以删除提示

欢呼,我们现在有一个具有所有基本CRUD功能的实用应用程序!

结语

在本教程中,我们使用Rails 5.1 API和前端React应用程序构建了完整的CRUD应用程序。

我们的API有三个端点,每个端点用于创建,更新和删除想法。

我们使用Create React App来制作我们的React应用。 这使安装过程完全轻松而又轻松。 我们可以直接投入到构建应用程序中,而无需进行任何配置。

我们使用axios对API进行Ajax调用,并使用不可变性帮助程序进行数据更新。

在以后的教程中,我们将研究如何将此应用程序部署到生产服务器,并添加一些动画和过渡效果,以使UI更加有趣。 例如,我们可以淡入新的想法图块,淡出已删除的图块,淡入和淡出通知消息。

您可以在此处观看本教程的视频版本

您可以在GitHub上查看该应用程序的完整代码:

Ideaboard Rails API

Ideaboard React前端

翻译自: https://www.sitepoint.com/react-rails-5-1/

rails .try

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值