voting_如何构建freeCodeCamp Voting App项目-深入教程

voting

by Daniel Deutsch

由Daniel Deutsch

如何构建freeCodeCamp Voting App项目-深入教程 (How to build the freeCodeCamp Voting App project — an in-depth tutorial)

The voting app challenge on freeCodeCamp was the first freeCodeCamp project in the curriculum that struck me as really hard. I just couldn’t do it as easily as all the other challenges. So much knowledge in of so many concepts is necessary to build it.

freeCodeCamp上的投票应用程序挑战是课程中的第一个freeCodeCamp项目,这让我感到非常震惊。 我无法像其他所有挑战一样轻松地做到这一点。 要构建它,必须包含许多概念的大量知识。

I didn’t find any tutorials or examples that broke this challenge down with up-to-date tools. So I decided to document my process of building it.

我找不到任何可以使用最新工具来解决这一难题的教程或示例。 因此,我决定记录我的构建过程。

In this tutorial, will use:

在本教程中,将使用:

  • MongoDB

    MongoDB
  • Express

    表达
  • React + Redux

    React + Redux
  • Node.js

    Node.js

also known as the “MERN-Stack”.

也称为“ MERN堆栈”。

“I fear not the man who has practiced 10,000 kicks once, but I fear the man who has practiced one kick 10,000 times.”

“我不怕练过一万次的人,但是我怕练过一万次的人。”

— Bruce Lee

- 李小龙

本文是关于什么的 (What this article is about)

I will describe the process of building the voting app for the freeCodeCamp challenge.

我将描述为freeCodeCamp挑战构建投票应用程序的过程。

This is not an optimized example for building the application. I am open for feedback of any kind. I am still a beginner and also left some things open.

这不是用于构建应用程序的优化示例。 我愿意接受任何形式的反馈。 我仍然是一个初学者,还保留了一些东西。

This is not designed as a tutorial! It’s simply a documentation I wrote while building the app.

这不是设计为教程! 这只是我在构建应用程序时编写的文档。

结构体 (Structure)

I will divide this article into sections of back-end, front-end, data visualization and the deployment process. The project will be available as open source code on GitHub. That is where you can follow up with commits and the end result.

我将把本文分为后端,前端,数据可视化和部署过程。 该项目将作为开源代码在GitHub上提供。 在这里,您可以跟进提交和最终结果。

开发环境 (Development Environment)

软件包/功能/依赖项 (Packages / Features / Dependencies)

一般 (General)
  • (ES 6 (JS scripting-language specification))

    ( ES 6 (JS脚本语言规范))

  • eslint with Airbnb extension (for writing higher quality code)

    带有Airbnb扩展名的eslint (用于编写更高质量的代码)

  • nodemon (restarting server when changes occur)

    nodemon (发生更改时重新启动服务器)

  • Babel (javascript compiler)

    Babel (JavaScript编译器)

  • Webpack (module bundler/builder)

    Webpack (模块捆绑器/构建器)

  • dotenv (for configuring environment variables)

    dotenv (用于配置环境变量)

  • shortid (random ID generator)

    shortid (随机ID生成器)

后端 (Back-end)

  • Node.js (JS runtime environment for server-side)

    Node.js (服务器端的JS运行时环境)

  • MongoDB (document based database)

    MongoDB (基于文档的数据库)

  • connect-mongo (for storing sessions in MongoDB)

    connect-mongo (用于在MongoDB中存储会话)

  • body-parser (for parsing incoming requests)

    正文解析器 (用于解析传入的请求)

  • express (to make the application run)

    表示 (使应用程序运行)

  • mongoose (object data modeling to simplify interactions with MongoDB)

    猫鼬 (用于简化与MongoDB交互的对象数据建模)

  • morgan (HTTP request logger middleware)

    摩根 (HTTP请求记录器中间件)

  • passport (authentication middleware for Node.js)

    护照 (Node.js的身份验证中间件)

前端 (Front-end)

可视化 (Visualization)

部署/ DevOps (Deployment / DevOps)

  • Heroku (PaaS to run applications in the cloud)

    Heroku (用于在云中运行应用程序的PaaS)

  • (Unit)Testing: Not implemented in this app (but normally it should be)

    (单元)测试:未在此应用中实现(但通常应如此)

第一件事 (First things first)

First I will set up my environment:

首先,我将设置我的环境:

  • add Git for version control

    添加Git进行版本控制

  • create your package management with yarn init

    使用yarn init创建您的包裹管理

  • add express for a fast web development

    添加Express以进行快速的Web开发

  • add the nodemon package for restarting your server on changes

    添加nodemon软件包以在更改时重新启动服务器

  • add eslint.rc for your eslint configuration

    为您的eslint配置添加eslint.rc

  • add babel and corresponding plugins for compiling JS

    添加babel和相应的插件来编译JS

As additional integration I’ll use:

作为附加集成,我将使用:

Here is my commit on GitHub after the setup.

这是设置后我在GitHub上的提交。

后端 (Back-end)

For me, the back-end is most difficult. So that’s where I’ll start.

对我来说,后端是最困难的。 这就是我要开始的地方。

设置软件包,中间件和猫鼬 (Set up Packages, Middleware and Mongoose)

I will use:

我将使用:

  • body-parser for parsing request bodies

    正文解析器,用于解析请求正文

  • morgan for logging out HTTP requests

    morgan用于注销HTTP请求

  • compression for compressing response bodies

    压缩,用于压缩响应体

  • helmet for setting basic security with HTTP headers

    用于通过HTTP标头设置基本安全性的头盔

  • mongoose object modeling tool for asynchronous database connection

    用于异步数据库连接的猫鼬对象建模工具

下一步: (Next steps:)
  • create a constants file to set your different environment variables and corresponding settings

    创建一个常量文件来设置您的不同环境变量和相应的设置

  • create a middleware file to pass in middleware to your app and differentiate for environments. Use bodyparser and morgan packages here.

    创建一个Middlewar e文件,以将中间件传递给您的应用并针对环境进行区分。 在此处使用bodyparsermorgan软件包。

  • create a database file to set up the mongoDB connection

    创建一个数据库文件以建立mongoDB连接

  • modularize your code and outsource your constants, middleware and database connection. This is for keeping smaller files.

    模块化您的代码,并将常量,中间件和数据库连接外包。 这是为了保留较小的文件。
  • import everything in your app.js file, pass in the middleware function and test your setup with a simple http request

    将所有内容导入到app.js文件中,传递middleware功能并通过简单的http请求测试设置

Here’s my commit on GitHub after this setup.

设置完成后,这是我在GitHub上的提交。

设定路线 (Set up your routes)

Revisit the User stories and lay out your routes accordingly.

重新查看用户故事并相应地布置路线。

Following the CRUD approach:

遵循CRUD方法

As an unauthenticated user I want to:

作为未经身份验证的用户,我想:

  • see all polls (R)

    查看所有民意调查(R)
  • see individual polls (R)

    查看个人意见调查(R)
  • vote on available polls (C)

    对现有民意调查(C)投票

As an authenticated user I want to

作为经过身份验证的用户,我想

  • see and read all polls (R)

    查看和阅读所有民意调查(R)
  • see individual polls (R)

    查看个人意见调查(R)
  • vote on available polls (C)

    对现有民意调查(C)投票
  • create new polls (C)

    创建新的民意调查(C)
  • create new options and votes (C)

    创建新的选项和投票(C)
  • delete polls (D)

    删除民意调查(D)

Therefore:

因此:

设置猫鼬和您的模式并将所有内容连接到您的路线 (Set up Mongoose and your Schemas and connect everything to your routes)

When setting up Schemas think about how you want to structure the documents that you will store in the database. In this example we need to store the user for the authentication process and polls with answers.

设置模式时,请考虑如何构造将存储在数据库中的文档。 在此示例中,我们需要存储用户进行身份验证过程并使用答案进行轮询。

For polls we need:

对于民意调查,我们需要:

  1. the question

    问题
  2. answers and votes

    答案和投票
  • create your mongoose schemas and models

    创建您的猫鼬模式和模型

  • connect to mlab to monitor your DB actions better

    连接到mlab以更好地监视您的数据库操作

Be aware that MLab creates “System Collections.” They throw “duplicate key error index dup key: { : null }” error in postman, when creating new polls. Until now I haven’t found a solution but deleting all collections allows us to start again.

请注意,MLab会创建“系统集合”。 创建新民意调查时,他们在邮递员中抛出“重复键错误索引重复键:{:null}”错误。 到目前为止,我还没有找到解决方案,但是删除所有集合可让我们重新开始。

  • use the dotenv package to store your credentials in the environment and add the .env file to .gitignore (if you make your project open source)

    使用dotenv软件包将您的凭据存储在环境中,并将.env文件添加到.gitignore(如果您使项目开源)

  • connect you routes with your mongoose model to handle the documents in MongoDB

    将您的路线与您的猫鼬模型联系起来,以处理MongoDB中的文档

Be Sure To Read the Docs if you are stuck. This part is pretty hard when you haven’t done a lot with mongoose and MongoDB!

如果您遇到问题, 请务必阅读文档 。 如果您还没有对Mongoose和MongoDB做很多工作,那么这部分就很难了​​!

Here’s what my commits looked like on Github after these steps.

经过这些步骤后,我在Github上的提交看起来像这样

通过Twitter建立身份验证和授权 (Establish authentication and authorization with Twitter)

I want to use the twitter sign-on as an OAuth provider to authenticate. It provides better user experience and I also got to explore OAuth.

我想使用twitter登录作为OAuth提供程序进行身份验证。 它提供了更好的用户体验,我还必须探索OAuth。

OAuth is a standard protocol that allows users to authorize API access to web and desktop or mobile applications. Once access has been granted, the authorized application can utilize the API on behalf of the user.

OAuth是一种标准协议,允许用户授权对Web和桌面或移动应用程序的API访问。 授予访问权限后,授权的应用程序可以代表用户使用API​​。

Of course I found the great article on how to set up the authentication process in Nodejs. After I failed implement it properly in my app and it took me a whole day, I decided to dive straight into the documentation of passport!

当然,我找到了一篇很棒的文章 ,介绍如何在Nodejs中设置身份验证过程。 在我无法在自己的应用程序中正确实施它并花了整整一天的时间后,我决定直接研究护照文件

I love the quote they put up there:

我喜欢他们在那里的报价:

“Despite the complexities involved in authentication, code does not have to be complicated.”

“尽管认证涉及复杂性,但代码不必复杂。”

⭐ Again, as a reminder: Read the Documentation!

再次提醒您:请阅读文档!

  • register your app on twitter apps and get your settings right. Determine the Access Level and the Callback URL

    Twitter应用程序上注册您的应用程序并正确设置您的设置。 确定访问级别和回调URL

  • add passport, passport-twitter and express-session packages to your application

    在您的应用程序中添加通行证通行证快速会议套餐

  • create a file defining a passport strategy for Twitter

    创建定义Twitter护照策略的文件
  • to support login session passport has to serialize and deserialize user

    支持登录会话护照必须对用户进行序列化和反序列化
  • pass passport to your passport configuration and connect passport.initialize and passport.session to your app as middleware. Use express-session before this!

    将通行证传递到您的通行证配置,然后将passport.initialize和password.session作为中间件连接到您的应用程序。 在此之前使用快速会话!
  • set up routes for authenticating and the callback

    设置身份验证和回调的路由

Check out my commit on Github after these steps.

完成这些步骤后,请查看我对Github的提交。

After that, connect the authentication process to your database

之后,将身份验证过程连接到您的数据库

⭐ Tip: Use for your callback and testing always http://127.0.0.1:3000/ instead of http://localhost:3000/, since it solves a lot of problems, that might occur using passport-twitter. ?

⭐提示:始终使用http://127.0.0.1:3000/而不是http://localhost:3000/进行回调和测试,因为它解决了很多问题,这可能是使用通行证发送程序发生的。 ?

  • create a mongoose Schema for your users to track them in your database

    为您的用户创建一个猫鼬模式,以在您的数据库中跟踪他们
  • fill the callback function of your passport.js file when implementing the twitter strategy. Filter your database for the user and create a new one if a user is not existing

    实施推特策略时,填写您的password.js文件的回调函数。 为用户过滤数据库,如果不存在用户,则创建一个新数据库
  • use the connect-mongo package to create a mongoStore and store your sessions in MongoDB

    使用connect-mongo包创建mongoStore并将您的会话存储在MongoDB中

  • create a function to test if a user is authenticated. Implement it in your desired routes when providing sufficient authorization

    创建一个函数来测试用户是否已通过身份验证。 提供足够的授权时,按照您希望的路线实施它

The implementation can look like this:

该实现可以如下所示:

passport.use(		new Strategy(constants.TWITTER_STRATEGY, (req, token, tokenSecret, profile, cb) => {  process.nextTick(() => {    if (!req.user) {      User.findOne({ 'twitter.id': profile.id }, (err, user) => {        if (err) return cb(err);        if (user) {          if (!user.twitter.token) {            user.twitter.token = token;            user.twitter.username = profile.username;            user.twitter.displayName = profile.displayName;            user.save(() => {              if (err) return cb(err);              return cb(null, user);            });          }          return cb(null, user);        }
// if no user is found create one        const newUser = new User();
newUser.twitter.id = profile.id;        newUser.twitter.token = token;        newUser.twitter.username = profile.username;        newUser.twitter.displayName = profile.displayName;
newUser.save(() => {          if (err) return cb(err);          return cb(null, newUser);        });      });    } else {					// when user already exists and is logged in      const user = req.user;
user.twitter.id = profile.id;      user.twitter.token = token;      user.twitter.username = profile.username;      user.twitter.displayName = profile.displayName;
user.save((err) => {        if (err) return cb(err);        return cb(null, user);      });    }  });}),	);

After that your authentication and authorization with Twitter is done.

之后,您对Twitter的身份验证和授权就完成了。

Here’s what my commits looked like on Github after these steps.

完成这些步骤后,我在Github上所做的事情就是这样

建立本地身份验证和授权 (Establish local authentication and authorization)

The next step is to authenticate locally. There is actually not much to it, since we have already set up the environment.

下一步是在本地进行身份验证。 实际上,由于我们已经设置了环境,因此实际所需的内容并不多。

  • update your user schema for local by defining email and password

    通过定义电子邮件和密码来更新本地用户模式
  • add the bcrypt-nodejs package for securing passwords

    添加用于保护密码的bcrypt-nodejs软件包

  • add hashing and validating password methods to your Schema

    向您的架构添加哈希和验证密码方法
  • define the routes. This process always clarifies what I actually want to implement

    定义路线。 这个过程总是澄清我实际想要实现的

I had a main issue which I was only able to resolve after many hours of searching. Here is the example from the docs:

我遇到了一个主要问题,经过数小时的搜索,我才能够解决。 这是docs中的示例:

app.get('/login', function(req, res, next) {  passport.authenticate('local', function(err, user, info) {    if (err) { return next(err); }    if (!user) { return res.redirect('/login'); }    req.logIn(user, function(err) {      if (err) { return next(err); }      return res.redirect('/users/' + user.username);    });  })(req, res, next);});

Passing in the authentication in the callback function provided enough flexibility for displaying errors. But it’s very important to create the session explicitly with logIn()!

在回调函数中传递身份验证为显示错误提供了足够的灵活性。 但是使用logIn()显式创建会话非常重要!

  • make sure to differentiate in the routes between signup and login!

    确保区分注册和登录之间的路由!
  • I installed EJS as view engine to actually being able to test my signup and login properly and efficient

    我安装了EJS作为查看引擎,实际上可以测试我的注册和正确有效地登录
  • create a logout route, that destroys your session

    创建注销路线,这会破坏您的会话

I spent so many hours on an Error that I want to display it here: MongooseError: Cast to ObjectId failed for value “favicon.ico” at path “_id”

我花了很多时间处理一个错误,想在这里显示该错误:MongooseError:由于路径“ _id”中的值“ favicon.ico”而强制转换为ObjectId

I solved it through checking all middleware which had a major error, and routes. It turned out that setting a route to (‘/:pID’) is not good when working in development.

我通过检查所有存在重大错误的中间件和路由来解决此问题。 事实证明,在开发中工作时,将路由设置为('/:pID')是不好的。

Check out my commit on GitHub after the back-end setup.

后端设置后,检查我在GitHub上的提交。

Of course at this point the back-end is not perfect. But it’s stable enough to go to the next step, the front-end.

当然,此时后端并不完美。 但是它足够稳定,可以进行下一步,即前端。

Things to do:

要做的事情:

  • use validation with joi

    joi一起使用验证

  • write unit tests

    编写单元测试

前端 (Frontend)

事前想想! (Think before you do!)

First of all think about what you want to create. Draw out some sketches to visualize what you want to build.

首先考虑一下您要创建的内容。 绘制一些草图以可视化要构建的内容。

Then consider appropriate frameworks. I will choose React.js and the state management library Redux. The size of this application does not necessarily require the use of Redux.

然后考虑适当的框架。 我将选择React.js和状态管理库Redux 。 此应用程序的大小并不一定需要使用终极版。

I want to build it as a single page experience. I want to have scalability and I like to practice the use of Redux. So, it’s a good fit.

我想将其构建为单页体验。 我希望具有可伸缩性,并且喜欢练习使用Redux。 所以,这很合适。

Start planning everything out thinking in React.

开始在React中计划所有事情。

使用Babel和Webpack进行必要的设置 (Necessary setup with Babel and Webpack)

It’s important to realize that Babel and Webpack are not too complicated to set it up yourself. There are so many tutorials for both that you can do it easily yourself.

重要的是要意识到BabelWebpack并不会太复杂以至于无法自行设置。 两者都有太多教程,您可以自己轻松完成。

  • add Babel for React and ES2015:

    为React和ES2015添加Babel:

    Add babel-preset-react babel-preset-es2015 to your dev dependencies to compile JSX into JS and have all ES6 features.

    将babel-preset-react babel-preset-es2015添加到您的开发依赖项中,以将JSX编译为JS并具有所有ES6功能。

  • update your .babelrc file

    更新您的.babelrc文件

  • update your webpack config and add the react-hot-loader package

    更新您的webpack配置并添加react-hot-loader软件包

First I want to structure my front-end without the back-end to connect the whole front-end with the back-end at the end. This is because right now I don’t know how my Redux implementation will look. So progressively connecting to the back-end wouldn’t be efficient.

首先,我要构建没有后端的前端,以将整个前端与后端连接起来。 这是因为现在我不知道我的Redux实现会如何。 因此,逐步连接到后端效率不高。

  • restructure your current app.js into an own folder

    将当前的app.js重组到自己的文件夹中

  • create a new app.js as entry point and provide the basic setup code for rendering a simple page

    创建一个新的app.js作为入口点,并提供用于呈现简单页面的基本设置代码

  • get the setup working. Install the react-router, webpack-dev-server and react and react-dom packages

    使设置工作。 安装react-router,webpack-dev-server以及react和react-dom软件包
  • opening a page on the dev-server port should display your react component

    在开发服务器端口上打开页面应显示您的react组件

Here’s what my commits looked like on Github after these steps.

经过这些步骤后,我在Github上的提交看起来像这样

结构组件 (Structure components)

I sketched everything out on a paper and came to the conclusion that I need to build 14 components:

我在纸上画出了所有草图,得出的结论是我需要构建14个组件:

  • the app component, that hosts everything

    应用程序组件,可托管所有内容
  • a header

    标头
  • a footer

    页脚
  • a sidebar

    侧边栏
  • a signup, login and social media component

    注册,登录和社交媒体组件
  • a home screen

    主屏幕
  • a list of all polls

    所有民意测验清单
  • the display of a single poll

    一次民意测验的显示
  • a component for the poll and it’s answers

    投票的组成部分及其答案
  • the answers as a list

    答案列表
  • the chart

    图表
  • a 404 page

    404页

That layout was for the start and should provide an overview. It is very natural to adapt the component structure when the application is evolving.

该布局仅供参考,应提供概述。 当应用程序不断发展时,适应组件结构是很自然的。

设计和构建组件 (Design and build components)
  • I lay out all the components and styled them with Materialize. Materialize is a responsive design framework.

    我布置了所有组件,并使用Materialize设置了样式。 Materialize是一个响应式设计框架。

  • remember that styling with React is more complicated than styling normal HTML elements. For simplicity reasons I fixed everything with inline styling on the component itself.

    请记住,使用React进行样式比样式普通HTML元素更为复杂。 为简单起见,我使用内联样式将所有内容固定在组件本身上。

Tip: For 100vh on your main content use this inline style on a div. It fits perfectly into the Materialize flexbox:

提示:对于主要内容上的100vh,请在div上使用此内联样式。 它非常适合Materialize弹性框:

style={{  display: 'flex',  minHeight: '100vh',  flexDirection: 'column',}}
  • As you build components you will get a feeling on how you need to structure your state management with React and Redux

    在构建组件时,您将了解如何使用React和Redux构建状态管理

Check out my commit on GitHub after the components are built and styled

在构建和设置组件样式后,检查我在GitHub上的提交

  • Now we have to set up React Router to get a basic functionality and feeling for the app

    现在我们必须设置React Router来获得应用程序的基本功能和感觉
  • enable historyApiFallback: true on your webpack dev server to allow proper routing with react router

    启用historyApiFallback historyApiFallback: true在webpack开发服务器上为historyApiFallback: true ,以允许使用React Router进行正确的路由

  • add state and it’s management to the components

    向组件添加状态及其管理
  • realize that Redux might be a good next step

    意识到Redux可能是一个不错的下一步

Here is a list of painful learnings I had to undergo throughout this process:

这是我在整个过程中必须经历的痛苦学习的清单:

  • to access object properties, use bracket instead of dot notation. For example: JavaScript answers = answers.concat(this.refs[temp].value)

    要访问对象属性,请使用方括号而不是点表示法。 例如:JavaScript answers = answers.concat(this.refs[temp].value)

  • import everything as * (import * as Polls from ‘./ducks/polls’;) from ducks. Otherwise it will not work

    从鸭子* (import * as Polls from './ducks/polls';)所有内容导入为* (import * as Polls from './ducks/polls';)中将* (import * as Polls from './ducks/polls';) 。 否则它将无法正常工作

  • I have often read to not use the index of a map function as a key value for a component. However, when rendering with onChange and generating a unique key, the input loses focus and does not work properly. For example:(const answerList = this.state.answers.map((answer, ind) => { return (<div className=”input-field col s10" key={ind}>)

    我经常读到不要将map函数的索引用作组件的键值。 但是,当使用onChange渲染并生成唯一键时,输入将失去焦点并且无法正常工作。 例如: (const answerList = this.state.answers.map((answer, ind) => { return (<div className=”input-field col s10" key={ ind}>)

  • when you iterate over an array of objects and want to change properties on an object you have to return an object. For example: return{ answer: answ.answer, votes: 0};

    当您遍历对象数组并想更改对象的属性时,必须返回一个对象。 例如: return{ answer: answ.answer, votes: 0};

    It took me 4 hours to understand ?

    我花了四个小时才了解?

The Principles of Redux are:

Redux的原则是:

  • Single source of truth

    真理的单一来源
  • State is read-only

    状态为只读
  • Changes are made with pure functions

    使用纯函数进行更改

Keep in mind, that local state doesn’t need to take part in Redux when it’s state isn’t used by other components.

请记住,当本地状态未被其他组件使用时,它不需要参与Redux。

  • add the react-redux and redux packages

    添加react-reduxredux软件包

  • make use of the ducks structure to manage the redux files better

    利用鸭子结构更好地管理redux文件

  • create a store in Redux and wrap your rendering app in a Provider tag from react-redux

    在Redux中创建商店,并将您的渲染应用包装在react-redux的Provider标签中

  • connect state to your application with connect

    连接状态与应用程序connect

  • add the Redux DevTool to debug faster

    添加Redux DevTool以更快地调试

Now that State is available through Redux, it’s time to create the event handlers and render everything properly. Now you should validate your your propTypes as well.

现在可以通过Redux使用State了,是时候创建事件处理程序并正确呈现所有内容了。 现在,您还应该验证您的propTypes

可视化 (Visualization)

For displaying the results I chose between:

为了显示结果,我选择了:

After skimming all docs and trying a few things out I ended up choosing React-Google-Charts. Google provides many options and the React wrapper makes it easy to implement in a React application.

浏览了所有文档并尝试了一些方法之后,我最终选择了React-Google-Charts。 Google提供了许多选项,React包装器使它易于在React应用程序中实现。

With the React Wrapper this step was super easy and fast.

使用React Wrapper,此步骤超级简单,快速。

const resultChart = (props) => {  basic = [['Answer', 'Votes']];  (() => props.poll.answers.map(ans => basic.push([ans.answer, ans.votes])))();  return (    <Chart      chartType="PieChart"      data={basic}      options={{        title: `${props.poll.question}`,        pieSliceText: 'label',        slices: {          1: { offset: 0.1 },          2: { offset: 0.1 },          3: { offset: 0.1 },          4: { offset: 0.1 },        },        is3D: true,        backgroundColor: '#616161',      }}      graph_id="PieChart"      width="100%"      height="400px"      legend_toggle    />  );};

使用React Router将前端连接到Express后端 (Connect Front-end to the Express Back-end with React Router)

呈现客户端和服务器端 (Rendering client- and server-side)

As this was my first real full-stack app, connecting the front-end and back-end was a mystery to me. I found a good answer to my question on Stack Overflow.

因为这是我第一个真正的全栈应用程序,所以连接前端和后端对我来说还是一个谜。 我在堆栈溢出问题上找到了一个很好的答案。

To summarize and quote the answer of Stijn:

总结并引用Stijn的答案:

“With client-side routing, which is what React-Router provides, things are less simple. At first, the client does not have any JS code loaded yet. So the very first request will always be to the server. That will then return a page that contains the needed script tags to load React and React Router etc. Only when those scripts have loaded does phase 2 start. In phase 2, when the user clicks on the ‘About us’ navigation link for example, the URL is changed locally only to http://example.com/about (made possible by the History API), but no request to the server is made. Instead, React Router does it’s thing on the client side, determines which React view to render and renders it.”

“使用React-Router提供的客户端路由,事情就变得不那么简单了。 首先,客户端尚未加载任何JS代码。 因此,第一个请求将始终是服务器。 然后将返回一个页面,其中包含用于加载React和React Router等所需的脚本标记。仅在加载了这些脚本后,阶段2才会启动。 在第2阶段中,例如,当用户单击“关于我们”导航链接时,URL仅在本地更改为http://example.com/about (由History API设置),但没有对服务器的请求制造。 相反,React Router在客户端进行操作,确定要渲染的React视图并进行渲染。”

To read more of his comments click here.

要阅读他的更多评论,请单击此处

In the end I went with the catch-all solution: See in my routes.js file.

最后,我采用了全部解决方案:请参阅我的route.js文件

//routes.jsrouter.get('/*', (req, res) => {  const options = {    root: `${__dirname}/../../public/`,    dotfiles: 'deny',  };  res.sendFile('index.html', options);});

It was easy and fast to implement and covered the basic problems.

实施起来很容易且快捷,涵盖了基本问题。

一起服务一切 (Serving everything all together)

To understand that, the best way is to take a look at my package.json file.

要了解这一点,最好的方法是查看我的package.json文件

The scripts say:

脚本说:

"scripts": {		"start": "node src/serverSide/server.js",		"serve": "babel-node src/serverSideES6/server.js",		"dev": "npm-run-all --parallel dev:*",		"dev:client": "webpack-dev-server --hot",		"dev:server": "nodemon src/serverSide/server.js",		"build": "npm-run-all --parallel build:*",		"build:client": "webpack --progress",		"build:server": "babel src/serverSideES6 --out-dir src/serverSide"	},

The build script builds the files on the client and server side.

build脚本在客户端和服务器端构建文件。

  • It compiles all my ES6 node.js code into ES5 so Heroku can read it as well

    它将我所有的ES6 node.js代码编译成ES5,因此Heroku也可以读取它
  • Webpack starts the bundling and transpiling of the client side such as from ES6 to ES5, and JSX to JavaScript.

    Webpack开始捆绑和转换客户端,例如从ES6到ES5,以及从JSX到JavaScript。

The dev script serves everything in a development environment and (hot) reloading. Everything is as fast and smooth as possible, when changing the codebase.

dev脚本为开发环境和(热)重载中的所有内容提供服务。 更改代码库时,一切都应尽可能快速,流畅。

The start script actually starts the back-end server, which also consumes the built and bundled front-end HTML, CSS, JavaScript, presenting the whole application.

start脚本实际上启动了后端服务器,该服务器还使用构建并捆绑的前端HTML,CSS,JavaScript来呈现整个应用程序。

部署方式 (Deployment)

For deploying the app, Heroku once again has proven to be the way to go.

对于部署该应用程序,Heroku再次被证明是必经之路。

Using the Heroku CLI, the Heroku logs command helps a lot. I always had trouble setting up my app on the platform. But after solving all the errors the logs show, it becomes very easy.

使用Heroku CLIHeroku logs命令有很大帮助。 我总是在平台上设置应用程序时遇到麻烦。 但是在解决了日志显示的所有错误之后,这变得非常容易。

Always important:

始终很重要:

  • Be aware that devDependencies are not installed

    请注意,未安装devDependencies
  • Use the adequate build-pack. In this case it is for Node.js

    使用足够的构建包。 在这种情况下,它适用于Node.js
  • Have start script or define one in your Procfile

    在您的Procfile中具有start脚本或定义一个脚本

  • Be sure to push the right branch from the right repository

    确保从正确的存储库中推送正确的分支

结论 (Conclusion)

As you can see my documentation for this article gets worse and worse with the progress of the app. This is due to the fact that I got completely overwhelmed with Redux. I did other projects on the side and wasn’t able to keep track.

如您所见,随着应用程序的进展,本文的文档变得越来越糟。 这是由于我对Redux完全不知所措。 我在旁边做过其他项目,无法追踪。

But don’t worry! I tried to name my commits as clear as possible. So you can traverse all commits for details in my Repository. See Commits here.

但是不用担心! 我试图尽可能明确地命名我的提交。 因此,您可以遍历所有提交,以获取我的存储库中的详细信息。 请参阅此处提交

If you have questions feel free to ask :)

如果您有任何疑问,请随时提问:)

  • Repository on Github is available here.

    Github上的仓库可以在这里找到

  • Live version of the result is available here.

    在此处获得结果的实时版本。

  • Learnings and numbers are available here.

    此处提供学习和数字。

Many, many thanks to Edo Rivai, who gave very valuable tips along the way. :)

非常感谢Edo Rivai ,他一路提供了非常宝贵的建议。 :)

Thanks for reading my article! Feel free to leave any feedback!

感谢您阅读我的文章! 随时留下任何反馈!

翻译自: https://www.freecodecamp.org/news/building-the-free-codecamp-voting-app-1a6fdce1f4a8/

voting

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值