本文共 15577 字,大约阅读时间需要 51 分钟。
This is a continuation of the where we discussed Webtask. We shared few Webtask concepts and built a RESTful API using Express and Node. Our data is persisted in a MongoDB database provisioned by Mongolab.
这是我们讨论Webtask的的延续。 我们共享了一些Webtask概念,并使用Express和Node构建了RESTful API。 我们的数据保存在Mongolab提供的MongoDB数据库中。
In this part of the article, we will consume the REST API in a React-based UI app. At the end, we will deploy the app to Github Pages so as to have both our API and Frontend available remotely.
在本文的这一部分中,我们将在基于React的UI应用程序中使用REST API。 最后,我们将应用程序部署到Github Pages,以便可以远程使用我们的API和Frontend。
For maintenance purposes and task distribution among teams, it's always preferable to split the entire application into different projects. We have our API ready in a project directory, it's not much of good practice to build our React app right in the same directory. Rather, we will create an independent React project that will communicate with the API via endpoints.
为了维护目的和团队之间的任务分配,始终最好将整个应用程序拆分为不同的项目。 我们已经在项目目录中准备好了我们的API,在同一个目录中构建React应用程序并不是一个好习惯。 相反,我们将创建一个独立的React项目,该项目将通过端点与API进行通信。
Facebook makes creating a React project easy by providing a CLI tool for that:
通过提供以下方面的CLI工具,Facebook使创建React项目变得容易:
# 1. Install CLI toolnpm install -g create-react-app# 2. Create a React app, "wt-mern-ux"create-react-app wt-mern-ux# 3. cd into appcd wt-mern-ux# 4. Launch appnpm start
React is a component-based tool, therefore, it is easier to visualize what is expected from an app when the app's components hierarchical structure is analyzed. Let's have a look:
React是基于组件的工具,因此,在分析应用程序的组件层次结构时,更容易可视化应用程序的预期。 我们来看一下:
The App
component is the as well as the 1st in the hierarchy. This makes it the entry point of our app thereby serving as the control unit for all other .
App
组件是 ,也是层次结构中的第一个 。 这使其成为我们应用程序的切入点,从而充当所有其他的控制单元。
The obvious components are the presentation components because they paint the browser with contents and visuals. In that regards, we will build the app starting with presentation components and when a logic in App
is needed, we will discuss that as well.
显而易见的组件是表示组件,因为它们用内容和视觉效果绘制浏览器。 在这方面,我们将从演示组件开始构建应用程序,并且当需要App
的逻辑时,我们也会对此进行讨论。
We need to read a list of stories from the API endpoint and display them on the webpage. This should be some pretty basic stuff:
我们需要从API端点读取故事列表,并将其显示在网页上。 这应该是一些非常基本的东西:
// ./src/Components/StoryList/StoryList.jsimport React from 'react'// FlipMove for list animationsimport FlipMove from 'react-flip-move';import StoryItem from '../StoryItem/StoryItem'import './StoryList.css'export default ({ stories, handleEdit, handleDelete}) => (){ stories.map(story => )}
And there it is; A functional component that receives a list of stories, and some event handlers from the App
component. It iterates over this stories
and passes each of the items down to a child StoryItem
component. The event handlers, handleEdit
and handleDelete
are also passed down to StoryItem
.
在那里; 一个功能组件,可从App
组件接收故事列表以及一些事件处理App
。 它遍历此stories
并将每个项目向下传递到子StoryItem
组件。 事件处理程序handleEdit
和handleDelete
也将传递给StoryItem
。
Let's see how App
fetches stories
:
让我们看看App
如何获取stories
:
import React, { Component } from 'react';import Axios from 'axios';import StoryList from './Components/StoryList/StoryList';import './App.css';class App extends Component { constructor() { super(); this.state = { stories: [], }; this.apiUrl = 'https://wt--0.run.webtask.io/wt-mern-api/stories' this.handleEdit = this.handleEdit.bind(this); this.handleDelete = this.handleDelete.bind(this) } componentDidMount() { // Fetch stories from API and // and update `stories` state Axios.get(this.apiUrl).then(({ data}) => { this.setState({ stories: data}); }) } handleEdit(id) { // Open a modal to update a story // Uncomment this line later // this.openModal(this.state.stories.find(x => x._id === id)) } handleDelete(id) { // Delete story from API Axios.delete(`${ this.apiUrl}?id=${ id}`).then(() => { // Remove story from stories list const updatedStories = this.state.stories.findIndex(x => x._id === id); this.setState({ states: [...this.state.stories.splice(updatedStories, 1)]}) }) } render() { return ( ); }}export default App;{ /* pass stories and event handlers down to StoryList*/}Stories
Thank you!
componentDidMount
is a lifecycle method. It is called when your component is ready. This feature makes it a great place to fetch bootstrap data. In that case, we are requesting a list of stories from our server and setting the stories
state to whatever is returned. componentDidMount
是生命周期方法。 当组件准备就绪时,将调用它。 此功能使其成为获取引导程序数据的好地方。 在这种情况下,我们需要从服务器中获取故事列表,并将stories
状态设置为返回的内容。 handleEdit
method is meant to pop up a modal with a form and an existing story to be updated. /*(Don’t be scared :), we’ll take a look at that soon) */ We will see about that soon. handleEdit
方法旨在弹出一个带有表单和要更新的现有故事的模式。 / *(不要害怕:),我们很快会看的)* /我们很快就会看到。 handleDelete
makes a DELETE
request for a single resource. If that was successful, rather than re-fetch the whole list, we just remove the item from the stories
list. handleDelete
对单个资源发出DELETE
请求。 如果成功,则无需重新获取整个列表,我们只需从stories
列表中删除该项目即可。 <StoryList />
receives the stories and event handlers as props. Functions are first class objects so it's possible to pass them around. 丢失的<StoryList />
故事和事件处理程序作为道具。 函数是一流的对象,因此可以传递它们。 <FlipMove />
is an animation component that helps us apply different animation effects to the list when adding and removing items from the list. <FlipMove />
是一个动画组件,可帮助我们在列表中添加和删除项目时将不同的动画效果应用于列表。 StoryItem
is yet another presentation component. It takes the iteration values passed down from StoryList
and displays each of them. It also receives the event handlers and binds them to some buttons.
StoryItem
是另一个演示组件。 它采用从StoryList
传递的迭代值并显示每个值。 它还接收事件处理程序并将它们绑定到某些按钮。
// ./src/Components/StoryItem/StoryItem.jsimport React from 'react'import './StoryItem.css'export default class StoryItem extends React.Component { render() { const { story, handleEdit, handleDelete } = this.props; return () }}{ story.author}
{
story.content}
This component doesn't have any direct relationship with App
container component, so we don't have to worry about that. It's also a class component rather than functional component because FlipMove
uses React refs
for list items which functional components do not support.
该组件与App
容器组件没有任何直接关系,因此我们不必为此担心。 它也是类组件而不是功能组件,因为FlipMove
使用React refs
来列出功能组件不支持的列表项。
We need to add a button which when clicked, launches a Modal to create a new story. /Nothing strange here!/ Just a stateless functional component that returns a HTML button:
我们需要添加一个按钮,单击该按钮将启动模态以创建新故事。 / 这里没什么奇怪的! /只是一个返回HTML按钮的无状态功能组件:
// ./src/Components/StoryButton/StoryButton.jsimport React from 'react';import './StoryButton.css'export default ({ handleClick}) =>
It's housed by the App
components:
它位于App
组件中:
import React, { Component } from 'react';import StoryButton from './Components/StoryButton/StoryButton';...class App extends Component { constructor() { super(); this.state = { ... }; ... this.openModal = this.openModal.bind(this); } ... openModal(story) { // Launches Modal. We will un-comment later /* this.setState({modalIsOpen: true}); if(story) { this.setState({story}); } */ } render() { return (); }}export default App;...
The handleClick
property holds an event handler to open a modal. Now this Modal is not a mystery, let's have a look at its component:
handleClick
属性持有一个事件处理程序以打开模式。 现在这个模态已经不是什么谜,让我们看一下它的组成部分:
import React from 'react';import Modal from 'react-modal';import './StoryModal.css'// Modal custom styles// Basically centering stuffconst customStyles = { content : { top : '50%', left : '50%', right : 'auto', bottom : 'auto', marginRight : '-50%', transform : 'translate(-50%, -50%)' }};export default class ModalComponent extends React.Component { constructor(props) { super(props) // Internal state this.state = { author: '', content: '', _id: '' } this.handleInputChange = this.handleInputChange.bind(this); } handleInputChange(e) { // Re-binding author and content values if(e.target.id === 'author') { this.setState({ author: e.target.value}) } if(e.target.id === 'content') { this.setState({ content: e.target.value}) } } componentWillReceiveProps({ story}) { // Update story state anytime // a new props is passed to the Modal // This is handy because the component // is never destroyed but it's props might change this.setState(story) } render() { const { modalIsOpen, closeModal } = this.props; // Use React's Modal component return () }} Story Form
Modal
component from the react-modal
library. 我们正在使用react-modal
库中的Modal
组件。 StoryModal
possesses an internal state. For this reason, it’s not entirely a presentation component./ has a form to keep track of, for that reason, it's not entirely a presentation component because of it's internal state. / 因为StoryModal
具有跟踪的形式,所以它具有内部状态。 因此,它并不完全是表示组件。 /具有某种形式的跟踪记录,因此,由于其内部状态,它并不完全是表示组件。 StoryModal
component can be shown or hidden but not created/mounted /n/or destroyed. Therefore, if its props changes, we update the story
state with the new story
props. This is why instead of using componentDidMount
, we are using componentWillReceiveProps
. /A/ Possible occurrence of such /a/ situation is when story
state changes from empty property values to values that need to be updated. 所述StoryModal
部件可以显示或隐藏的,但不创建/安装/ N /或破坏。 因此,如果其道具发生变化,我们将使用新的story
道具更新story
状态。 这就是为什么我们不使用componentWillReceiveProps
而是使用componentDidMount
原因。 这样的/ 一个 /情形/ A /可能发生的是,当story
的状态,从空的属性值更改为值,需要进行更新。 Next, we need to uncomment openModal
and handleEdit
logics in App
:
接下来,我们需要在App
取消注释openModal
和handleEdit
逻辑:
// ./src/App.js...constructor() { super(); this.state = { modalIsOpen: false, } };openModal(story) { this.setState({ modalIsOpen: true}); if(story) { this.setState({ story}); } }handleEdit(id) { this.openModal(this.state.stories.find(x => x._id === id)) } ...
If openModal
is passed a story, we will set the state's story
object to its content. This is passed down to the Modal for us to edit. If no story is passed, we just create a new story via the form.
如果openModal
传递了一个故事,则将状态的story
对象设置为其内容。 这被传递给模态供我们编辑。 如果未传递任何故事,则仅通过表单创建一个新故事。
Let's now complete the Modal wire by writing logic for what happens when the Modal is closed:
现在,通过编写关闭模态时发生的逻辑来完成模态连线:
import React, { Component } from 'react';import Axios from 'axios';import StoryModal from './Components/StoryModal/StoryModal';import './App.css';class App extends Component { constructor() { super(); this.state = { modalIsOpen: false, stories: [], story: { author: '', content: '', _id: undefined } }; ... } ... closeModal(model) { this.setState({ modalIsOpen: false}); if(model) { if(!model._id) { Axios.post(this.apiUrl, model).then(({ data}) => { this.setState({ stories: [data, ...this.state.stories]}); this.setState({ isLoading: false}) }) } else { Axios.put(`${ this.apiUrl}?id=${ model._id}`, model).then(({ data}) => { const storyToUpdate = this.state.stories.find(x => x._id === model._id); const updatedStory = Object.assign({ }, storyToUpdate, data) const newStories = this.state.stories.map(story => { if(data._id === story._id) return updatedStory; return story; }) this.setState({ stories: newStories}); }) } } this.setState({ story: { author: '', content: '', _id: undefined }}) } ... render() { return (); }}export default App;
Three possible outcomes:
三种可能的结果:
A story model/data from the form does NOT exist. This means no argument was sent to closeModal
when calling it. If that's the case, nothing should happen. A typical example is the Modal's Cancel button.
表单中的故事模型/数据不存在。 这意味着在调用它时没有将参数发送给closeModal
。 如果是这样,则什么也不会发生。 一个典型的例子是“模态”的“取消”按钮。
If model._id
is NOT undefined, it means the model existed before, so we just need to make an update write rather than creating a new entry entirely. We do this by using axios's put
method to send a PUT
request with the payload to a single resource. The response contains the updated record which we can shove into the array.
如果没有定义model._id
,则表示该模型以前存在过,因此我们只需要进行更新写入即可,而不必完全创建一个新条目。 为此,我们使用axios的put
方法将带有有效负载的PUT
请求发送到单个资源。 响应包含更新的记录,我们可以将其推送到数组中。
In a situation where model._id
is undefined, then we create a new story using a POST
request and adding the new story to the top of our array.
在未定义model._id
的情况下,我们使用POST
请求创建一个新故事,并将新故事添加到数组的顶部。
Extend the app a little bit to show a loading spinner for every HTTP request that's fired.
稍微扩展一下应用程序,以显示每个触发的HTTP请求的加载微调器。
To deploy the React app to GitHub pages, we need to carefully follow the steps below:
要将React应用程序部署到GitHub页面,我们需要仔细遵循以下步骤:
npm run build
This will generate a production bundle in the build
directory.
这将在build
目录中生成生产捆绑包。
package.json
: 为应用程序创建一个Github存储库,并在package.json
添加以下行: "homepage": "https://.github.io/ ",
gh-pages
: 安装gh-pages
: npm install --save gh-pages
"scripts": { ... "deploy": "gh-pages -d build" }
npm run deploy
Hopefully, I have proven to you that you do not need to be a backend expert before you can make your UI come to life. Tools like Webtask and even Node with a little bit of digging docs will provide a server for you while you focus on writing the ever awesome JavaScript. The frontend can be anything; not necessarily React.
希望我已经向您证明,您无需成为后端专家,即可使UI栩栩如生。 诸如Webtask甚至带有少量挖掘文档的Node之类的工具将为您提供服务器,而您将专注于编写超赞JavaScript。 前端可以是任何东西; 不一定是React。
翻译自:
转载地址:http://meuwd.baihongyu.com/