查看原文
其他

我的第一个全栈 Web 应用程序

Anne-Laure CSDN 2020-02-12

在这篇文章,作者将使用 React.js 和 Redux.js 前端技术,并通过调用 Ruby on Rails API,手把手教会你如何创建一个完美的全栈 Web 应用程序。

作者 | Anne-Laure Chadeyas
译者 | 弯月,责编 | 屠敏
出品 | CSDN(ID:CSDNnews)

以下为译文:

在学习了600多节教学课程,并实践4个项目之后,现如今我终于可以谈一谈我的最后一个项目了。在参加 Flatiron School 的这个在线软件开发自学课程时,我知道我必须完成五个项目才能毕业。最后一个项目似乎一直遥不可及,因为这个项目需要大量我还没有掌握的技能。但现在,我终于可以提交这个项目了。

最后这个项目的目标是要构建一个漂亮的单页应用,前端使用 React.js 和 Redux.js,调用 Ruby on Rails API。


单页应用


首先,什么是单页应用?单页应用就是一个网站或Web应用程序,根据用户的动作或行为,动态地改写当前页的内容,而不是从服务器加载全新的页面。实现途径有两种:

  • 在一次页面加载中读取所有的页面内容。但考虑到应用程序的复杂性,这样做可能需要很长时间,因此会影响用户体验。

  • 在某个用户事件后,向服务器请求相应的内容。常见的用户事件包括点击按钮、页面向下滚动、鼠标悬停在某个元素上、按下键盘上的某个键等。

对于复杂的应用程序,第二种方式更常见。毕竟,单页应用存在的原因就是它能提供更为平滑的用户体验,不会被全页重新加载打断。

从代码的角度来讲,单页应用意味着整个应用程序中只有一个HTML页面,通常这个页面名为index.html。


构建应用程序结构


应用程序分为两部分:前端和后端。前端是用户交互的部分,即用户界面。后端负责服务器与用户界面之间的连接。构建应用程序有两个选择:

  • 第一个选择就是把前端和后端都放在同一个代码仓库中(比如GitHub上的代码仓库)。

  • 第二个选择是建立两个代码仓库,一个用于后端,一个用于前端。这样做有几个好处,其中之一就是后端(比如本文中的API)可以被多个前端复用,另一个好处就是编辑器中管理的目录更小。

上述两种方法并没有对错之分。基于上面给出的两个理由。我在构建应用程序时选择了使用两个独立的代码仓库、

第一个代码仓库是后端的。我在终端中使用下述命令创建了一个Rails应用程序作为API,不过没有任何视图。这跟创建普通的Rails应用程序是一样的,只不过多了一个参数。

rails new my_app_backend --api

至于前端,我采用了create-react-app生成器。

npx create-react-app my_app_frontend

这两个命令可以帮我建好所需的一切文件。


关于组件的类型


该项目的技术要求是,至少需要写两个容器组件,以及5个无状态组件。React中的组件是界面的基本构成元素。它可以从父组件接受输入(通过props访问),还可以重用。

下面详细介绍一下容器组件和无状态组件。首先需要解释一下什么是状态(state)。状态就是可能会改变的数据。状态改变可能有多种原因,其中之一就是数据库更新导致状态变化,另一个原因就是用户修改了数据。

容器组件也称为有状态组件,而无状态组件也称为表现组件。容器组件和表现组件并没有严格的区分,每个开发者都可以按照自己的意愿来组织各个组件。但一般而言,容器组件是有状态的,可以通过其状态改变来跟踪,而表现组件没有状态,它可以显示传递过来的props,也可以永远显示固定的内容。

至于本文讨论的应用程序,我决定采用最基本的分割方法。我给API中的每个模型都建立了一个容器组件。随着项目的进行,我删掉了一些不再需要的组件,同时还添加了一些其他组件。有状态组件基本上都是表单。最好的例子就是注册表单和登录表单。在React的表单中,每次用户输入都会导致状态的变化,可能是局部状态变化,也可能是Redux存储状态变化(我们稍后讨论Redux)。无状态组件的例子就是 BicyclesList.js 中的自行车列表。这个组件通过 props 接受一个来自 CitiesContainer 组件的城市列表,它本身与状态没有任何关系。


React中的路由


由于单页应用中不会重新加载完整的页面,因此产生的问题之一就是路由如何进行。Web应用程序中路由的作用是,在用户访问特定网页时确定需要执行什么。我们的单页应用中只有一个视图,因此没办法像Rails应用程序那样在用户点击链接时跳转到另一个视图。

凡事都有解决的办法,对于这个问题,我们可以使用react-router库。它有许多功能,其中包括:

  • URL显示用户的当前位置,而不仅仅是显示根页面的URL

  • 用户可以使用浏览器的前进和后退按钮

  • 用户可以在地址栏中输入URL,跳转到指定页面

下面以 CitiesContainer 为例来介绍路由的工作方式:

App.jsimport React from 'react';
import { Route } from 'react-router-dom';
import CitiesContainer from './containers/CitiesContainer';class CitiesContainer extends React.Component {
  render() {
    return (
      <Route path='/cities' component={CitiesContainer} />
    )
  }
}export default App;

这里使用了 path 而不是 exact path,因此所有包含 /cities 的路径都会被处理。CitiesContainer 容器的内部如下:

containers/CitiesContainer.js import React from 'react';
import { connect } from 'react-redux';
import { Route } from 'react-router-dom';
import { fetchCities } from '../actions/fetchCities';
import CitiesList from '../components/CitiesList';
import CityPage from '../components/CityPage';
import BicyclesList from '../components/BicyclesList';class CitiesContainer extends React.Component {
  componentDidMount() {
    this.props.fetchCities()
  } 

  render() {
    return (
      <div>
        <Route exact path='/cities' render={() => <CitiesList 
         cities={this.props.cities} />} />
        <Route exact path='/cities/:id' render={(routerProps) => 
         <CityPage {...routerProps} cities={this.props.cities} 
         />}/>
        <Route path='/cities/:id/bicycles' render={(routerProps) => 
         <BicyclesList {...routerProps} cities={this.props.cities} 
         />}/>
      </div>
    )
  }
}const mapStateToProps = state => {
  return {
    cities: state.cities
  }
}export default connect(mapStateToProps, { fetchCities })(CitiesContainer)

React-router-dom 包为开发者提供了 routerProps。该属性可以将URL的内容作为参数传递给props。在这里,我们可以通过 props 访问城市的id,进而可以对 cities(通过props访问)进行过滤,找到我们需要的那个城市。


使用Redux 


终于讲到了Redux。Redux是什么呢?这个问题我也问过自己,也问过Google。我在Youtube上找到了这个视频(https://www.youtube.com/watch?v=np8A_aW7Pew),介绍得非常清楚。我希望对你也有帮助。Redux的文档告诉我们,Redux是一个供 JavaScript 应用程序使用的、“可预测的”状态容器。文档中强调了Redux的4个方面:

  • 可预测:它有助于编写在所有环境中行为都很一致的应用程序,更容易测试。

  • 中心化:应用程序的状态和逻辑中心化,可以实现强大的功能,如状态持久化等。

  • 可调式:Redux DevTools 可以非常方便地跟踪应用程序状态变化发生的时间、位置、原因以及方式等。

  • 灵活:Redux可以与任何UI层结合使用。

在我们的应用程序中,Redux有许多便利之处,但最重要的一点是你可以从任何地方访问当前用户的信息(如果存在当前登录用户的话)。我们将用户信息保存到Redux存储中,就可以从应用程序的任意位置访问,而不仅限于将当前用户通过props传递的那些组件。实际上,任何子组件都可以通过以下两种方式之一连接到Redux存储:

  • 使用mapsStateToProps(),无需从组件中访问Redux存储,就能将状态内容放到props中

  • 使用mapDispatchToProps(),无需从组件中访问Redux存储,就能分发actions。

这样就可以分离状态管理和状态显示。但这并非Redux的全部功能。我们在构建异步action creator的时候还是用了thunk中间件。

什么是中间件?维基百科的解释是“提供系统软件和应用软件之间连接的软件,以便于软件各部件之间的沟通”。你可以认为中间件就像胶水一样。因此 thunk 可以让我们做一些原本做不到的事情。thunk 函数的参数是 dispatch,因此可以在函数中使用,在本例中就是在action creator中使用。由于dispatch可以在函数内使用,我们可以利用这一点,仅在fetch请求结束时进行分发。如下例所示,我们仅在获取所有城市的fetch请求结束后进行分发:

actions/fetchCities.jsexport const fetchCities = () => {
  return (dispatch) => {
    fetch('http://localhost:3000/api/v1/cities')
    .then(response => response.json())
    .then(cities => {
      dispatch({
        type'FETCH_CITIES',
        payload: cities
      })
    })
  }
}



利用fetch实现数据持久化


我们可以通过fetch,用GET方式从服务器上获得数据,也可以用POST方式将数据发送到服务器。用GET获取数据的方式可以参照前面讨论路由的时候提到的 CitiesContainer 示例。我们在下面的action控制器中,利用fetch以GET方式获取所有城市:

actions/fetchCities.jsexport const fetchCities = () => {
  return (dispatch) => {
    fetch('http://localhost:3000/api/v1/cities')
    .then(response => response.json())
    .then(cities => {
      dispatch({
        type'FETCH_CITIES',
        payload: cities
      })
    })
  }
}

在认证部分,登录action creator是一个以POST方式向服务器发送数据的例子:

actions/auth.jsexport const login = credentials => {
  return (dispatch) => {
    fetch('http://localhost:3000/api/v1/login', {
      credentials'include',
      method'POST',
      headers: {
        'Content-Type''application/json'
      },
      bodyJSON.stringify(credentials)
    })
      .then(response => response.json())
      .then(user => {
        if (user.error) {
          alert(user.error)
        } else {
          dispatch(setCurrentUser(user))
          dispatch(resetLoginForm())
        }
      })
      .catch(console.log)
  }
}export const setCurrentUser = user => {
  return {
    type'SET_CURRENT_USER',
    payload: user
  }
}export const resetLoginForm = () => {
  return {
    type'RESET_LOGIN_FORM'
  }
}

至于样式,我最初的计划是使用React版的Bootstrap,因为这是唯一一个我听说过的库。在样式方面我还是新手,而且我没找到导航条,因此搜索了一下React中有什么可以使用的框架。然后在这篇文章(https://medium.com/@zeolearn/6-best-reactjs-based-ui-frameworks-9c780b96236c)中看到了Semantic UI React的Menu组件,这正是我所需要的东西。我从来没有用过其他样式框架,因此没办法比较Semantic UI和其他组件哪个更容易使用,但我必须要说,Semantic UI非常容易上手。 

样式并不是这个项目的重点,但我计划在这个应用程序中实现这个样式。我已经建立了基础结构和基本的功能,现在添加新功能已经比较容易了。我计划继续改进应用程序的功能。

原文:https://medium.com/@annelaure.developer/my-first-full-stack-web-application-8ac82db61b10

本文为 CSDN 翻译,转载请注明来源出处。

【End】

热 文 推 荐 

☞AI 没让人类失业,搞 AI 的人先失业了
☞2020 年,Android 还有哪些新期待?
☞Java 14 有哪些新特性?
铁打的春晚,流水的互联网公司
达摩院 2020 预测:模块化降低芯片设计门槛 | 问底中国 IT 技术演进

千万不要和程序员一起合租!

在调查过基于模型的强化学习方法后,我们得到这些结论

漫话:如何给女朋友解释为什么一到年底,部分网站就会出现日期混乱的现象?

你点的每个“在看”,我都认真当成了喜欢


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存