react node服务器

In this article we are going to learn how to build a simple "Universal JavaScript" application (a.k.a. "Isomorphic") using React, React Router and Express.

在本文中,我们将学习如何使用ReactReact RouterExpress构建一个简单的“ Universal JavaScript ”应用程序(又名“ Isomorphic”)。

Warm up your fingers and get your keyboard ready... it's going to be fun!

加热手指并准备好键盘……这将很有趣!

关于作者 ( About the Author )

Hi, I am Luciano and I am the co-author of Node.js Design Patterns Second Edition (Packt), a book that will take you on a journey across various ideas and components, and the challenges you would commonly encounter while designing and developing software using the Node.js platform. In this book you will discover the "Node.js way" of dealing with design and coding decisions. It also features an entire chapter dedicated to Universal JavaScript. If this is the first time you read this term, keep reading, you are going to love this article!

嗨,我是Luciano,并且是Node.js Design Patterns Second Edition(Packt)的合著者,这本书将带您探索各种概念和组件,以及在设计和开发过程中通常会遇到的挑战使用Node.js平台的软件。 在本书中,您将发现处理设计和编码决策的“ Node.js方法”。 它还包含一整章专门介绍Universal JavaScript的章节。 如果这是您第一次阅读本术语,请继续阅读,您将爱上这篇文章!

关于通用JavaScript ( About Universal JavaScript )

One of the advantages of having Node.js as runtime for the backend of a web application is that we have to deal only with JavaScript as a single language across the web stack. With this capability it is totally legit to be willing to share some code between the frontend and the backend to reduce the code duplication between the browser and the server to the bare minimun. The art of creating JavaScript code that is "environment agnostic" is today being recognized as "Universal JavaScript", term that — after a very long debate — seems to have won a war against the original name "Isomorphic JavaScript".

将Node.js作为Web应用程序后端的运行时的优点之一是,我们只需要在整个Web堆栈中将JavaScript作为一种语言来处理。 有了此功能,愿意在前端和后端之间共享一些代码以将浏览器和服务器之间的代码重复减少到最低限度是完全合法的。 创建JavaScript代码的艺术是“环境无关的”今天是被认定为“通用JavaScript”,术语, -经过一个很 长的 辩论 -似乎已经赢得了对原来的名字“同构JavaScript”的战争。

The main concerns that we generally have to face when building an Universal JavaScript application are:

在构建通用JavaScript应用程序时,我们通常要面对的主要问题是:

  • Module sharing: how to use Node.js modules also in the browser.
    模块共享 :如何在浏览器中也使用Node.js模块。
  • Universal rendering: how to render the views of the application from the server (during the initialization of the app) and then keep rendering the other views directly in the browser (avoiding a full page refresh) while the user keep navigating across the different sections.
    通用渲染 :如何从服务器渲染应用程序的视图(在应用程序初始化期间),然后继续在浏览器中直接渲染其他视图(避免刷新整个页面),同时用户继续在不同部分之间导航。
  • Universal routing: how to recognize the view associated to the current route from both the server and the browser.
    通用路由 :如何从服务器和浏览器识别与当前路由关联的视图。
  • Universal data retrival: how to access data (typically through APIs) from both the server and the browser.
    通用数据检索 :如何从服务器和浏览器访问数据(通常通过API)。

Universal JavaScript is still a pretty fresh field and there is no framework or approach that emerged as a "de-facto" standard with ready-made solutions for all these problems yet. Although, there are already a miriad of stable and well known libraries and tools that can be combined to successfully build a Universal JavaScript web application.

通用JavaScript仍然是一个非常新鲜的领域,尚无框架或方法作为“事实上的”标准出现,并且没有针对所有这些问题的现成解决方案。 虽然,已经有大量稳定且众所周知的库和工具可以组合使用,以成功构建Universal JavaScript Web应用程序。

In this article we are going to use React (with its companion library React Router) and Express to build a simple application focused on showcasing universal rendering and routing. We will also use Babel to take advantage of the lovely EcmaScript 2015 syntax and Webpack to build our code for the browser.

在本文中,我们将使用React(及其配套库React Router)和Express来构建一个专注于展示通用渲染和路由的简单应用程序。 我们还将使用Babel来利用可爱的EcmaScript 2015语法和Webpack来为浏览器构建代码。

我们将要建立的 ( What we are going to build )

I am a Judo fan and so the app we are going to build today is "Judo Heroes", a web app that showcases some the most famous Judo athletes and their collection of medals from the Olympic Games and other prestigious international tournaments.

我是柔道迷 ,所以我们今天要构建的应用程序是“柔道英雄”,这是一个网络应用程序,其中展示了一些最著名的柔道运动员及其从奥运会和其他享有盛誉的国际比赛中获得的奖牌。

This app has essentially two views:

此应用实质上具有两个视图:

An index page where you can select the athletes:

Universal JavaScript demo app "Judo Heroes" - Athlete selection view

一个索引页面,您可以在其中选择运动员:

And an athlete page that showcases their medals and some other details:

Universal JavaScript demo app "Judo Heroes" - Athlete details view

还有一个运动员页面,展示了他们的奖牌和其他一些细节:

To understand better how it works you can have a look at the demo app and navigate across the views.

为了更好地了解其工作原理,您可以查看演示应用程序并在视图之间导航。

What's the matter with it anyway, you are probably asking yourself! Yes, it looks like a very simple app, with some data and a couple of views...

无论如何,你可能在问自己! 是的,它看起来像一个非常简单的应用,带有一些数据和一些视图...

Well there's something very peculiar that happens behind the scenes that will be hardly noticed by a regular user but it makes developement super interesting: this app is using universal rendering and routing!

好吧,在幕后发生了一些非常特殊的事情,普通用户几乎不会注意到,但是这使开发变得非常有趣:此应用程序正在使用通用渲染和路由!

We can prove this using the developers tools of the browser. When we initially load a page in the browser (any page, not necessarily the home page, try for example this one) the server provides the full HTML code of the view and the browser only needs to download linked resources (images, stylesheets and scripts):

我们可以使用浏览器的开发人员工具来证明这一点。 当我们最初在浏览器中加载页面(任何页面,不一定是首页,例如尝试此页面)时,服务器将提供视图的完整HTML代码,浏览器仅需要下载链接的资源(图像,样式表和脚本) ):

Universal JavaScript demo app "Judo Heroes" - Server side rendering resources

Then, from there, when we switch to another view, everything happens only on the browser: no HTML code is loaded from the server and only the new resources (3 new images in the following example) are loaded by the browser:

然后,从那里,当我们切换到另一个视图时,一切都只发生在浏览器上:服务器未加载HTML代码,浏览器仅加载了新资源(以下示例中为3个新图像):

Universal JavaScript demo app "Judo Heroes" - Client side rendering resources

We can do another quick test (if you are still not convinced) from the command line using curl:

我们可以使用curl从命令行进行另一个快速测试(如果您仍然不满意):

curl -sS "https://judo-heroes.herokuapp.com/athlete/teddy-riner"

You will see the full HTML page (including the code rendered by React) being generated directly from the server:

Universal JavaScript demo app "Judo Heroes" - Server side rendering with curl

您将看到完整HTML页面(包括React渲染的代码)直接从服务器生成:

I bet you are now convinced enough and eager to get your hands dirty, so let's start coding!

我敢打赌,您现在已经足够确信并渴望弄脏您的手,所以让我们开始编码!

资料夹结构 ( Folder structure )

At the end of this tutorial our project structure will look like in the following tree:

在本教程的最后,我们的项目结构如下图所示:

├── package.json
├── webpack.config.js
├── src
│   ├── app-client.js
│   ├── routes.js
│   ├── server.js
│   ├── components
│   │   ├── AppRoutes.js
│   │   ├── AthletePage.js
│   │   ├── AthletePreview.js
│   │   ├── AthletesMenu.js
│   │   ├── Flag.js
│   │   ├── IndexPage.js
│   │   ├── Layout.js
│   │   ├── Medal.js
│   │   └── NotFoundPage.js
│   ├── data
│   │   └── athletes.js
│   ├── static
│   │   ├── index.html
│   │   ├── css
│   │   ├── favicon.ico
│   │   ├── img
│   │   └── js
│   └── views`       └──  index.ejs

In the main level we have our package.json (to describe the project and define the dependencies) and webpack.config.js (Webpack configuration file).

在主级别,我们有package.json (用于描述项目并定义依赖项)和webpack.config.js (Webpack配置文件)。

All the rest of the code will be stored inside the folder src, which contains the main files needed for routing (routes.js) and rendering (app-client.js and server.js). It also contains 4 subfolders:

所有其余代码将存储在src文件夹中,该文件夹包含路由( routes.js )和渲染( app-client.jsserver.js )所需的主文件。 它还包含4个子文件夹:

  • components: contains all the React components
    components :包含所有React组件
  • data: contains our data "module"
    data :包含我们的数据“模块”
  • static: contains all the static files needed for our application (css, js, images, etc.) and an index.html that we will use initially to test our app.
    static :包含应用程序所需的所有静态文件(css,js,图像等)和一个index.html ,我们将首先使用它们来测试我们的应用程序。
  • views: contains the template that we will use from the server to render the HTML content from the server.
    views :包含我们将从服务器使用的模板,以从服务器呈现HTML内容。

项目初始化 ( Project initialization )

The only requisite here is to have Node.js (version 6 is preferred) and NPM installed in your machine.

这里唯一的要求是在计算机上安装Node.js (首选6版)和NPM

Let's create a new folder called judo-heroes somewhere in the disk and point the terminal there, then launch:

让我们在磁盘上的某个地方创建一个名为judo-heroes的新文件夹,并将终端指向该文件夹,然后启动:

npm init

This will bootstrap our Node.js project allowing us to add all the needed dependencies.

这将引导我们的Node.js项目,允许我们添加所有需要的依赖项。

We will need to have babel, ejs, express, react and react-router installed. To do so you can run the following command:

我们将需要安装babelejsexpressreactreact-router 。 为此,您可以运行以下命令:

npm install --save babel-cli@6.11.x babel-core@6.13.x  \babel-preset-es2015@6.13.x babel-preset-react@6.11.x ejs@2.5.x \express@4.14.x react@15.3.x react-dom@15.3.x react-router@2.6.x

We will also need to install Webpack(with its Babel loader extension) and http-server as a development dependencies:

我们还需要安装Webpack (带有其Babel loader扩展名)和http-server作为开发依赖项:

npm install --save-dev webpack@1.13.x babel-loader@6.2.x http-server@0.9.x

HTML样板 ( The HTML boilerplate )

From now on, I am assuming you have a basic knowledge of React and JSX and its component based approach. If not you can read an excellent article on React components or have a look at all the other React related articles on Scotch.io.

从现在开始,我假设您具有React和JSX及其基于组件的方法的基本知识。 如果不是这样,您可以阅读有关React组件的出色文章,或者在Scotch.io上查看所有其他与React相关的文章 。

Initially we will focus only on creating a functional "Single Page Application" (with only client side rendering). Later we will see how to improve it by adding universal rendering and routing.

最初,我们将只专注于创建功能强大的“单页应用程序”(仅客户端渲染)。 稍后,我们将看到如何通过添加通用渲染和路由来对其进行改进。

So the first thing we need is an HTML boilerplate to "host" our app that we will store in src/static/index.html:

因此,我们需要做的第一件事是HTML样板来“托管”我们将存储在src/static/index.html应用程序:

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Judo Heroes - A Universal JavaScript demo application with React</title><link rel="stylesheet" href="/css/style.css"></head><body><div id="main"></div><script src="/js/bundle.js"></script></body>
</html>

Nothing special here. Only two main things to underline:

这里没什么特别的。 要强调的只有两件事:

  • We are using a simple "hand-made" stylesheet that you might want to download and save it under src/static/css/.
    我们正在使用一个简单的“手工制作”样式表,您可能需要下载并将其保存在src/static/css/
  • We also reference a /js/bundle.js file that contains all our JavaScript frontend code. We will see later in the article how to generate it using Webpack and Babel, so you don't need to worry about it now.
    我们还引用了/js/bundle.js文件,其中包含我们所有JavaScript前端代码。 我们将在本文后面看到如何使用Webpack和Babel生成它,因此您现在不必担心它。

数据模块 ( The data module )

In a real world application we would probably use an API to obtain the data necessary for our application.

在实际的应用程序中,我们可能会使用API​​来获取应用程序所需的数据。

In this case we have a very small dataset with only 5 athletes and some related information, so we can keep things simple and embed the data into a JavaScript module. This way we can easily import the data into any other component or module synchronously, avoiding the added complexity and the pitfalls of managing asynchronous APIs in an Universal JavaScript project, which is not the goal of the article.

在这种情况下,我们有一个非常小的数据集,仅包含5名运动员和一些相关信息,因此我们可以简化事情,并将数据嵌入到JavaScript模块中。 这样,我们可以轻松地将数据同步导入任何其他组件或模块,从而避免了通用JavaScript项目中增加的复杂性和管理异步API的陷阱,这不是本文的目标。

Let's see how the module looks like:

让我们看一下模块的外观:

// src/data/athletes.js
const athletes = [{'id': 'driulis-gonzalez','name': 'Driulis González','country': 'cu','birth': '1973','image': 'driulis-gonzalez.jpg','cover': 'driulis-gonzalez-cover.jpg','link': 'https://en.wikipedia.org/wiki/Driulis_González','medals': [{ 'year': '1992', 'type': 'B', 'city': 'Barcelona', 'event': 'Olympic Games', 'category': '-57kg' },{ 'year': '1993', 'type': 'B', 'city': 'Hamilton', 'event': 'World Championships', 'category': '-57kg' },{ 'year': '1995', 'type': 'G', 'city': 'Chiba', 'event': 'World Championships', 'category': '-57kg' },{ 'year': '1995', 'type': 'G', 'city': 'Mar del Plata', 'event': 'Pan American Games', 'category': '-57kg' },{ 'year': '1996', 'type': 'G', 'city': 'Atlanta', 'event': 'Olympic Games', 'category': '-57kg' },{ 'year': '1997', 'type': 'S', 'city': 'Osaka', 'event': 'World Championships', 'category': '-57kg' },{ 'year': '1999', 'type': 'G', 'city': 'Birmingham', 'event': 'World Championships', 'category': '-57kg' },{ 'year': '2000', 'type': 'S', 'city': 'Sydney', 'event': 'Olympic Games', 'category': '-57kg' },{ 'year': '2003', 'type': 'G', 'city': 'S Domingo', 'event': 'Pan American Games', 'category': '-63kg' },{ 'year': '2003', 'type': 'S', 'city': 'Osaka', 'event': 'World Championships', 'category': '-63kg' },{ 'year': '2004', 'type': 'B', 'city': 'Athens', 'event': 'Olympic Games', 'category': '-63kg' },{ 'year': '2005', 'type': 'B', 'city': 'Cairo', 'event': 'World Championships', 'category': '-63kg' },{ 'year': '2006', 'type': 'G', 'city': 'Cartagena', 'event': 'Central American and Caribbean Games', 'category': '-63kg' },{ 'year': '2006', 'type': 'G', 'city': 'Cartagena', 'event': 'Central American and Caribbean Games', 'category': 'Tema' },{ 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'Pan American Games', 'category': '-63kg' },{ 'year': '2007', 'type': 'G', 'city': 'Rio de Janeiro', 'event': 'World Championships', 'category': '-63kg' },],},{// ...}
];export default athletes;

For brevity the file here has been truncated, and we are displaying just the data of one of the five athletes. If you want to see the full code check it out on the official repository. You can download the file into src/data/athletes.js.

为简洁起见,此处的文件已被截断,我们仅显示五名运动员之一的数据。 如果您想查看完整的代码, 请在官方存储库中查看 。 您可以将文件下载到src/data/athletes.js

Also notice that I am not reporting 'use strict'; here though it should be present in every JavaScript file we are going to create through the course of this tutorial.

另请注意,我没有报告'use strict'; 尽管在本教程中我们将要创建的每个JavaScript文件中都应该包含它。

As you can see, the file contains an array of objects where every object represents an athlete containing some generic information like id, name and country and another array of objects representing the medals won by that athlete.

如您所见,该文件包含一个对象数组,每个对象代表一个运动员,其中包含一些通用信息(例如idnamecountry ,另一个对象数组代表该medals获得的medals

You might also want to grab all the image files from the repository and copy them under: src/static/img/.

您可能还想从存储库中获取所有图像文件 ,并将它们复制到src/static/img/

React组件 ( React components )

We are going to organize the views of our application into several components:

我们将把应用程序的视图组织成几个组件:

  • A set of small UI components used to build the views: AthletePreview, Flag, Medal and AthletesMenu.
    一组用于构建视图的小型UI组件: AthletePreviewFlagMedalAthletesMenu
  • A Layout component that is used as master component to define the generic appearence of the application (header, content and footer blocks).
    用作主组件的Layout组件,用于定义应用程序的一般外观(页眉,内容和页脚块)。
  • Two main components that represent the main sections: IndexPage and AthletePage.
    代表主要部分两个主要组件: IndexPageAthletePage
  • An extra "page" component that we will use as 404 page: NotFoundPage
    我们将用作404页面的额外“页面”组件: NotFoundPage
  • The AppRoutes component that uses React Router to manage the routing between views.
    使用React Router管理视图之间的路由的AppRoutes组件。

标记组件 (Flag component)

The first component that we are going to build allows us to display a nice flag and, optionally, the name of the country that it represents:

我们将要构建的第一个组件使我们可以显示一个漂亮的标志,并可以选择显示它代表的国家的名称:

// src/components/Flag.js
import React from 'react';const data = {'cu': {'name': 'Cuba','icon': 'flag-cu.png',},'fr': {'name': 'France','icon': 'flag-fr.png',},'jp': {'name': 'Japan','icon': 'flag-jp.png',},'nl': {'name': 'Netherlands','icon': 'flag-nl.png',},'uz': {'name': 'Uzbekistan','icon': 'flag-uz.png',}
};export default class Flag extends React.Component {render() {const name = data[this.props.code].name;const icon = data[this.props.code].icon;return (<span className="flag"><img className="icon" title={name} src={`/img/${icon}`}/>{this.props.showName && <span className="name">{name}</span>}</span>);}
}

As you might have noticed this component uses a small array of countries as data source. Again this makes sense only because we need a very small data set which, for the sake of this demo app, is not going to change. In a real application with a larger and more complex data set you might want to use an API or a different mechanism to connect the data to the component.

您可能已经注意到,该组件使用一小部分国家/地区作为数据源。 同样,这仅是有意义的,因为我们需要一个非常小的数据集,就此演示应用而言,该数据集将不会改变。 在具有更大和更复杂数据集的真实应用程序中,您可能需要使用API​​或其他机制将数据连接到组件。

In this component it's also important to notice that we are using two different props, code and showName. The first one is mandatory and must be passed to the component to select which flag will be shown among the ones supported. The showName props is instead optional and if set to a truthy value the component will also display the name of the country just after the flag.

在此组件中,还要注意我们正在使用两个不同的道具codeshowName ,这一点也很重要。 第一个是强制性的,必须传递给组件以选择将在支持的标志中显示哪个标志。 相反, showName道具是可选的,如果设置为真实值,该组件还将在标志后显示国家名称。

If you want to build a more refined reusable component for a real world app you might also want to add to it props validation and defaults, but we are going to skip this step here as this is not the goal for the app we want to build.

如果您想为现实世界的应用程序构建更精致的可重用组件,则可能还需要向其添加道具验证和默认值 ,但是我们将在此处跳过此步骤,因为这不是我们要构建的应用程序的目标。

勋章组成部分 (Medal component)

The Medal component is similar to the Flag component. It receives some props that represent the data related to a medal: the type (G for gold, S for silver and B for bronze), the year when it was won, the name of the event and the city where the tournament was hosted and the category where the athlete who won the medal competed.

Medal组件类似于Flag组件。 它会收到一些代表奖牌数据的道具: typeG代表金牌, S代表银牌, B代表铜牌),获胜yearevent名称以及eventcity以及赢得奖牌的运动员参加比赛的category

// src/components/Medal.js
import React from 'react';const typeMap = {'G': 'Gold','S': 'Silver','B': 'Bronze'
};export default class Medal extends React.Component {render() {return (<li className="medal"><span className={`symbol symbol-${this.props.type}`} title={typeMap[this.props.type]}>{this.props.type}</span><span className="year">{this.props.year}</span><span className="city">{this.props.city}</span><span className="event">({this.props.event})</span><span className="category">{this.props.category}</span></li>);}
}

As for the previous component here we also use a small object to map the codes of the medal types to descriptive names.

至于前面的组件,我们还使用一个小对象将奖牌类型的代码映射到描述性名称。

运动员菜单组件 (Athletes Menu component)

In this section we are going to build the menu that is displayed on top of every athlete page to allow the user to easily switch to another athlete without going back to the index:

在本部分中,我们将构建显示在每个运动员页面顶部的菜单,以使用户可以轻松切换到其他运动员,而无需返回索引:

// src/components/AthletesMenu.js
import React from 'react';
import { Link } from 'react-router';export default class AthletesMenu extends React.Component {render() {return (<nav className="athletes-menu">{this.props.athletes.map(menuAthlete => {return <Link key={menuAthlete.id} to={`/athlete/${menuAthlete.id}`} activeClassName="active">{menuAthlete.name}</Link>;})}</nav>);}
}

The component is very simple, but there are some key points to underline:

该组件非常简单,但是需要强调一些要点:

  • We are expecting the data to be passed in the component through a athletes prop. So from the outside, when we use the component in our layout, we will need to propagate the list of athletes available in the app directly into the component.
    我们希望数据通过athletes道具传递到组件中。 因此,从外部来看,当我们在布局中使用组件时,我们需要将应用中可用的运动员列表直接传播到组件中。
  • We use the map method to iterate over all the athletes and generate for every one of them a Link.
    我们使用map方法遍历所有运动员,并为每个运动员生成一个Link
  • Link is a special component provided by React Router to create links between views.
    Link是React Router提供的一个特殊组件,用于在视图之间创建链接。
  • Finally, we use the prop activeClassName to use the class active when the current route matches the path of the link.
    最后,当当前路由与链接的路径匹配时,我们使用activeClassName来使用active类。

运动员预览组件 (Athlete Preview component)

The AthletePreview component is used in the index to display the pictures and the names of the athletes. Let's see its code:

索引中使用了AthletePreview组件来显示图片和运动员的姓名。 让我们看一下它的代码:

// src/components/AthletePreview.js
import React from 'react';
import { Link } from 'react-router';export default class AthletePreview extends React.Component {render() {return (<Link to={`/athlete/${this.props.id}`}><div className="athlete-preview"><img src={`img/${this.props.image}`}/><h2 className="name">{this.props.name}</h2><span className="medals-count"><img src="/img/medal.png"/>{this.props.medals.length}</span></div></Link>);}
}

The code is quite simple. We expect to receive a number of props that describe the attributes of the athlete we want to display like id, image, name and medals. Note that again we are using the Link component to create a link to the athlete page.

代码很简单。 我们期望收到许多道具,这些道具描述了我们想要显示的运动员的属性,例如idimagenamemedals 。 请注意,我们再次使用Link组件来创建指向运动员页面的链接。

布局组件 (Layout component)

Now that we built all our basic components let's move to creating those that give the visual structure to the application. The first one is the Layout component, which has the only purpose of providing a display template to the whole application defining an header, a space for the main content and a footer:

现在,我们已经构建了所有基本组件,让我们开始创建那些为应用程序提供视觉结构的组件。 第一个是Layout组件,它的唯一目的是为整个应用程序提供一个显示模板,该模板定义了页眉,主要内容的空间和页脚:

// src/components/Layout.js
import React from 'react';
import { Link } from 'react-router';export default class Layout extends React.Component {render() {return (<div className="app-container"><header><Link to="/"><img className="logo" src="/img/logo-judo-heroes.png"/></Link></header><div className="app-content">{this.props.children}</div><footer><p>This is a demo app to showcase universal rendering and routing with<strong>React</strong>and<strong>Express</strong>.</p></footer></div>);}
}

The component is pretty simple and we should understand how it works just by looking at the code. There's though a very interesting prop that we are using here, the children prop. This is a special property that React provides to every component and allows to nest components one inside another.

该组件非常简单,我们仅需看一下代码就应该了解它是如何工作的。 尽管我们在这里使用了一个非常有趣的道具,即children道具。 这是React提供给每个组件的一种特殊属性,并允许将组件嵌套在另一个组件中。

We are going to see in the routing section how the React Router will make sure to nest the components into the Layout component.

我们将在路由部分中看到React Router如何确保将组件嵌套到Layout组件中。

索引页组件 (Index Page component)

This component constitutes the full index page and it contains some of the components we previously defined:

此组件构成完整的索引页,并且包含我们先前定义的一些组件:

// src/components/IndexPage.js
import React from 'react';
import AthletePreview from './AthletePreview';
import athletes from '../data/athletes';export default class IndexPage extends React.Component {render() {return (<div className="home"><div className="athletes-selector">{athletes.map(athleteData => <AthletePreview key={athleteData.id} {...athleteData} />)}</div></div>);}
}

Note that in this component we are using the AthletePreview component we created previously. Basically we are iterating over all the available athletes from our data module and creating an AthletePreview component for each of them. The AthletePreview component is data agnostic, so we need to pass all the information about the current athlete as props using the JSX spread operator ({...object}).

请注意,在此组件中,我们使用的是先前创建的AthletePreview组件。 基本上,我们从数据模块中迭代所有可用的运动员,并为每个运动员创建一个AthletePreview组件。 AthletePreview组件与数据无关,因此我们需要使用JSX传播运算符 ( {...object} )将有关当前运动员的所有信息作为道具传递。

运动员页面组件 (Athlete Page component)

In a similar fashion we can define the AthletePage component:

我们可以类似的方式定义AthletePage组件:

// src/components/AthletePage.js
import React from 'react';
import { Link } from 'react-router';
import NotFoundPage from './NotFoundPage';
import AthletesMenu from './AthletesMenu';
import Medal from './Medal';
import Flag from './Flag';
import athletes from '../data/athletes';export default class AthletePage extends React.Component {render() {const id = this.props.params.id;const athlete = athletes.filter((athlete) => athlete.id === id)[0];if (!athlete) {return <NotFoundPage/>;}const headerStyle = { backgroundImage: `url(/img/${athlete.cover})` };return (<div className="athlete-full"><AthletesMenu athletes={athletes}/><div className="athlete"><header style={headerStyle}/><div className="picture-container"><img src={`/img/${athlete.image}`}/><h2 className="name">{athlete.name}</h2></div><section className="description">Olympic medalist from<strong><Flag code={athlete.country} showName="true"/></strong>,born in{athlete.birth}(Find out more on<a href={athlete.link} target="_blank">Wikipedia</a>).</section><section className="medals"><p>Winner of<strong>{athlete.medals.length}</strong>medals:</p><ul>{athlete.medals.map((medal, i) => <Medal key={i} {...medal}/>)}</ul></section></div><div className="navigateBack"><Link to="/">« Back to the index</Link></div></div>);}
}

By now, you must be able to understand most of the code shown here and how the other components are used to build this view. What might be important to underline is that this page component accepts from the outside only the id of the athlete, so we include the data module to be able to retrieve the related information. We do this at the beginning of the render method using the function filter on the data set. We are also considering the case where the received id does not exist in our data module, in this case we render NotFoundPage, a component that we are going to create in the next section.

到目前为止,您必须能够理解此处显示的大多数代码,以及如何使用其他组件来构建此视图。 要强调的重要一点是,此页面组件仅从外部接受运动员的ID,因此我们包括数据模块以能够检索相关信息。 我们在render方法的开始使用数据集上的函数filter进行此操作。 我们还考虑了数据模块中不存在接收到的ID的情况,在这种情况下,我们渲染NotFoundPage ,这是我们将在下一部分中创建的组件。

One last important detail is that here we are accessing the id with this.props.params.id (instead of simply this.props.id): params is a special object created by React Router when using a component from a Route and it allows to propagate routing parameters into components. It will be easier to understand this concept when we will see how to setup the routing part of the application.

最后一个重要的细节是,这里我们使用this.props.params.id (而不是this.props.id )访问id: params是React Router在使用Route的组件时创建的特殊对象,它允许将路由参数传播到组件中。 当我们看到如何设置应用程序的路由部分时,更容易理解这个概念。

找不到页面组件 (Not Found Page component)

Now let's see the NotFoundPage component, which acts as a template to generate the code of our 404 pages:

现在,让我们看一下NotFoundPage组件,该组件充当生成404页面代码的模板:

// src/components/NotFoundPage.js
import React from 'react';
import { Link } from 'react-router';export default class NotFoundPage extends React.Component {render() {return (<div className="not-found"><h1>404</h1><h2>Page not found!</h2><p><Link to="/">Go back to the main page</Link></p></div>);}
}

应用程序路线组件 (App Routes component)

The last component we need to create is the AppRoutes component which is the master component that renders all the other views using internally the React Router. This component will use the routes module, so let's have a quick look at it first:

我们需要创建的最后一个组件是AppRoutes组件,该组件是使用React Router在内部渲染所有其他视图的主组件。 该组件将使用routes模块,因此让我们先快速浏览一下:

// src/routes.js
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import Layout from './components/Layout';
import IndexPage from './components/IndexPage';
import AthletePage from './components/AthletePage';
import NotFoundPage from './components/NotFoundPage';const routes = (<Route path="/" component={Layout}><IndexRoute component={IndexPage}/><Route path="athlete/:id" component={AthletePage}/><Route path="*" component={NotFoundPage}/></Route>
);export default routes;

In this file we are basically using the React Router Route component to map a set of routes to the page components we defined before. Note how the routes are nested inside a main Route component. Let's explain how this works:

在这个文件中,我们基本上是使用React Router Route组件将一组路由映射到我们之前定义的页面组件。 注意路线如何嵌套在主要Route组件内。 让我们解释一下它是如何工作的:

  • The root route maps the path / to the Layout component. This allows us to use our custom layout in every section of our application. The components defined into the nested routes will be rendered inside the Layout component in place of the this.props.children property that we discussed before.
    根路径将路径/映射到Layout组件。 这使我们可以在应用程序的每个部分中使用自定义布局。 定义到嵌套路由中的组件this.props.children现在Layout组件中,以代替我们之前讨论的this.props.children属性。
  • The first child route is an IndexRoute which is a special route used to define the component that will be rendered when we are viewing the index page of the parent route (/ in this case). We use our IndexPage component as index route.
    第一个子路径是IndexRoute ,它是一种特殊的路径,用于定义当我们查看父路径(在本例中为/ )的索引页面时将呈现的组件。 我们使用IndexPage组件作为索引路由。
  • The path athlete/:id is mapped to the AthletePage. Note here that we are using a named parameter :id. So this route will match all the paths with the prefix /athlete/, the remaining part will be associated to the params id and will be available inside the component in this.props.params.id.
    athlete/:id路径映射到AthletePage 。 请注意,这里我们使用的是命名参数:id 。 因此,此路由将使所有路径都带有前缀/athlete/匹配,其余部分将与params id关联,并将在this.props.params.id的组件内部可用。
  • Finally the match-all route * maps every other path to the NotFoundPage component. This route must be defined as the last one.
    最终, 所有匹配路由*将所有其他路径映射到NotFoundPage组件。 此路由必须定义为最后一条。

Let's see now how to use these routes with the React Router inside our AppRoutes component:

现在让我们看看如何在AppRoutes组件内的React Router中使用这些路由:

// src/components/AppRoutes.js
import React from 'react';
import { Router, browserHistory } from 'react-router';
import routes from '../routes';export default class AppRoutes extends React.Component {render() {return (<Router history={browserHistory} routes={routes} onUpdate={() => window.scrollTo(0, 0)}/>);}
}

Basically we only need to import the Router component and add it inside our render function. The router receives our routes mapping in the router prop. We also configure the history prop to specify that we want to use the HTML5 browser history for the routing (as an alternative you could also use hashHistory).

基本上,我们只需要导入Router组件并将其添加到我们的render函数中。 路由器在router属性中接收我们的路由映射。 我们还配置了history道具,以指定我们要使用HTML5浏览器历史记录进行路由(或者,您也可以使用hashHistory )。

Finally we also added an onUpdate callback to reset the scrolling of the window to the top everytime a link is clicked.

最后,我们还添加了onUpdate回调,以在每次单击链接时将窗口的滚动重置为顶部。

应用程序入口点 ( The application entry point )

The last bit of code to complete our first version of the application is to define the JavaScript logic that initializes the whole app in the browser:

完成我们的第一个应用程序版本的最后代码是定义JavaScript逻辑,该逻辑在浏览器中初始化整个应用程序:

// src/app-client.js
import React from 'react';
import ReactDOM from 'react-dom';
import AppRoutes from './components/AppRoutes';window.onload = () => {ReactDOM.render(<AppRoutes/>, document.getElementById('main'));
};

The only thing we do here is to import our master AppRoutes component and render it using the ReactDOM.render method. The React app will be living inside our #main DOM element.

我们在这里所做的唯一一件事就是导入我们的主AppRoutes组件,并使用ReactDOM.render方法对其进行呈现。 React应用程序将存在于我们的#main DOM元素中。

设置Webpack和Babel ( Setting up Webpack and Babel )

Before we are able to run our application we need to generate the bundle.js file containing all our React components with Webpack. This file will be executed by the browser so Webpack will make sure to convert all the modules into code that can be executed in the most common browser environments. Webpack will convert ES2015 and React JSX syntax to equivalent ES5 syntax (using Babel), which can be executed practically by every browser. Furthermore we can use Webpack to apply a number of optimizations to the resulting code like combining all the scripts files into one file and minifying the resulting bundle.

在我们能够运行我们的应用程序之前,我们需要生成bundle.js文件,其中包含bundle.js的所有React组件。 该文件将由浏览器执行,因此Webpack将确保将所有模块转换为可以在最常见的浏览器环境中执行的代码。 Webpack会将ES2015和React JSX语法转换为等效的ES5语法 (使用Babel),每个浏览器实际上都可以执行。 此外,我们可以使用Webpack对生成的代码进行多种优化,例如将所有脚本文件组合到一个文件中并最小化生成的包。

Let's write our webpack configuration file:

让我们编写我们的webpack配置文件:

// webpack.config.js
const webpack = require('webpack');
const path = require('path');module.exports = {entry: path.join(__dirname, 'src', 'app-client.js'),output: {path: path.join(__dirname, 'src', 'static', 'js'),filename: 'bundle.js'},module: {loaders: [{test: path.join(__dirname, 'src'),loader: ['babel-loader'],query: {cacheDirectory: 'babel_cache',presets: ['react', 'es2015']}}]},plugins: [new webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)}),new webpack.optimize.DedupePlugin(),new webpack.optimize.OccurenceOrderPlugin(),new webpack.optimize.UglifyJsPlugin({compress: { warnings: false },mangle: true,sourcemap: false,beautify: false,dead_code: true})]
};

In the first part of the configuration file we define what is the entry point and the output file. The entry point is the main JavaScript file that initializes the application. Webpack will recursively resolve all the included/imported resources to determine which files will go into the final bundle.

在配置文件的第一部分,我们定义什么是入口点和输出文件。 入口点是用于初始化应用程序的主JavaScript文件。 Webpack将递归解析所有包含/导入的资源,以确定哪些文件将进入最终捆绑包。

The module.loaders section allows to specify transformations on specific files. Here we want to use Babel with the react and es2015 presets to convert all the included JavaScript files to ES5 code.

module.loaders部分允许在特定文件上指定转换。 在这里,我们想将Babel与reactes2015预设一起使用,以将所有包含JavaScript文件转换为ES5代码。

In the final section we use plugins to declare and configure all the optimizations plugins we want to use:

在最后一节中,我们使用plugins来声明和配置我们要使用的所有优化插件:

  • DefinePlugin allows us to define the NODE_ENV variable as a global variable in the bundling process as if it was defined in one of the scripts. Some modules (e.g. React) relies on it to enable or disable specific features for the current environment (production or development).
    DefinePlugin允许我们在捆绑过程中将NODE_ENV变量定义为全局变量,就像在脚本之一中定义它一样。 一些模块(例如React)依靠它来启用或禁用当前环境( 生产开发 )的特定功能。
  • DedupePlugin removes all the duplicated files (modules imported in more than one module).
    DedupePlugin删除所有重复的文件(在多个模块中导入的模块)。
  • OccurenceOrderPlugin helps in reducing the file size of the resulting bundle.
    OccurenceOrderPlugin有助于减小结果包的文件大小。
  • UglifyJsPlugin minifies and obfuscates the resulting bundle using UglifyJs.
    UglifyJsPlugin使用UglifyJs UglifyJsPlugin化和混淆生成的包。

Now we are ready to generate our bundle file, you just need to run:

现在我们准备生成我们的捆绑文件,您只需要运行:

NODE_ENV=production node_modules/.bin/webpack -p

(if you are on Windows you can use PowerShell and run set NODE_ENV=production | node_modules/.bin/webpack -p. Thanks Miles Rausch for the suggestion)

(如果您在Windows上,则可以使用PowerShell并运行set NODE_ENV=production | node_modules/.bin/webpack -p 。感谢Miles Rausch的建议)

The NODE_ENV environment variable and the -p option are used to generate the bundle in production mode, which will apply a number of additional optimizations, for example removing all the debug code from the React library.

NODE_ENV环境变量和-p选项用于在生产模式下生成捆绑包,这将应用许多其他优化,例如从React库中删除所有调试代码。

If everything went fine you will now have your bundle file in src/static/js/bundle.js.

如果一切顺利,您现在将在src/static/js/bundle.js拥有捆绑文件。

玩单页应用 ( Playing with the single page app )

We are finally ready to play with the first version of our app!

我们终于可以使用我们的应用程序的第一个版本了!

We don't have a Node.js web server yet, so for now we can just use the module http-server (previously installed as development dependency) to run a simple static file web server:

我们还没有Node.js Web服务器,所以现在我们可以只使用模块http-server (以前安装为开发依赖项)来运行一个简单的静态文件Web服务器:

node_modules/.bin/http-server src/static

And your app will be magically available on http://localhost:8080.

您的应用程序将在http:// localhost:8080上神奇地可用。

Ok, now take some time to play with it, click on all the links and explore all the sections.

好的,现在花一些时间来玩它,单击所有链接并浏览所有部分。

Does everything seem to work allright? Well, almost! There's just a little caveat... If you refresh the page in a section different from the index you will get a 404 error from the server.

一切似乎都正常吗? 好吧,差不多! 只是有一点警告...如果在不同于索引的部分刷新页面,则会从服务器收到404错误。

There are a number of ways to address this problem. In our case it will be solved as soon as we implement our universal routing and rendering solution, so let's move on to the next section.

有很多方法可以解决此问题。 在我们的情况下,将在实现通用的路由和渲染解决方案后立即解决该问题,因此让我们继续下一部分。

使用Express在服务器上进行路由和渲染 ( Routing and rendering on the server with Express )

Ok, we are now ready to evolve our application to the next level and build the missing server side part.

好的,我们现在可以将应用程序升级到一个新的级别,并构建缺少的服务器端部分。

In order to have server side routing and rendering we will use Express with a relatively small server script that we will see in a moment.

为了进行服务器端路由和渲染,我们将Express与相对较小的服务器脚本结合使用,稍后我们将看到该脚本。

The rendering part will use an ejs template as replacement for our index.html file that we will save in src/views/index.ejs:

呈现部分将使用ejs模板替代我们将保存在src/views/index.ejs index.html文件:

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Judo Heroes - A Universal JavaScript demo application with React</title><link rel="stylesheet" href="/css/style.css"></head><body><div id="main"><%- markup -%></div><script src="/js/bundle.js"></script></body>
</html>

The only difference with the original HTML file is that we are using the template variable <%- markup -%> inside the #main div in order to include the React markup into the server-generated HTML code.

与原始HTML文件的唯一区别是,我们在#main div中使用模板变量<%- markup -%> ,以便将React标记包含在服务器生成HTML代码中。

Now we are ready to write our server application:

现在我们准备编写服务器应用程序:

// src/server.jsimport path from 'path';
import { Server } from 'http';
import Express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import routes from './routes';
import NotFoundPage from './components/NotFoundPage';// initialize the server and configure support for ejs templates
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));// define the folder that will be used for static assets
app.use(Express.static(path.join(__dirname, 'static')));// universal routing and rendering
app.get('*', (req, res) => {match({ routes, location: req.url },(err, redirectLocation, renderProps) => {// in case of error display the error messageif (err) {return res.status(500).send(err.message);}// in case of redirect propagate the redirect to the browserif (redirectLocation) {return res.redirect(302, redirectLocation.pathname + redirectLocation.search);}// generate the React markup for the current routelet markup;if (renderProps) {// if the current route matched we have renderPropsmarkup = renderToString(<RouterContext {...renderProps}/>);} else {// otherwise we can render a 404 pagemarkup = renderToString(<NotFoundPage/>);res.status(404);}// render the index template with the embedded React markupreturn res.render('index', { markup });});
});// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
server.listen(port, err => {if (err) {return console.error(err);}console.info(`Server running on http://localhost:${port}[${env}]`);
});

The code is commented, so it shouldn't be hard to get a general understanding of what is going on here.

该代码带有注释,因此对这里发生的事情有一个大致的了解并不难。

The important part of the code here is the Express route defined with app.get('*', (req, res) => {...}). This is an Express catch-all route that will intercept all the GET requests to every URL in the server. Inside this route, we take care of delegating the routing logic to the React Router match function.

此处代码的重要部分是使用app.get('*', (req, res) => {...})定义的Express路由。 这是Express 包罗万象的路由,它将拦截对服务器中每个URL的所有GET请求。 在此路由中,我们负责将路由逻辑委托给React Router match功能。

ReactRouter.match accepts two parameters: the first one is a configuration object and the second is a callback function. The configuration object must have two keys:

ReactRouter.match接受两个参数:第一个是配置对象 ,第二个是回调函数 。 配置对象必须具有两个键:

  • routes: used to pass the React Router routes configuration. Here, we are passing the exact same configuration that we used for the client-side rendering.
    routes :用于传递React Router路由配置。 在这里,我们传递的是与客户端渲染所使用的配置完全相同的配置。
  • location: This is used to specify the currently requested URL.
    location :用于指定当前请求的URL。

The callback function is called at the end of the matching. It will receive three arguments, error, redirectLocation, and renderProps, that we can use to determine what exactly the result of the match operation was.

匹配结束时将调用回调函数。 它将接收三个参数errorredirectLocationrenderProps ,我们可以使用它们来确定匹配操作的结果是什么。

We can have four different cases that we need to handle:

我们可以处理四种不同的情况:

  • The first case is when we have an error during the routing resolution. To handle this case, we simply return a 500 internal server error response to the browser.
    第一种情况是在路由解析期间出现错误时。 为了处理这种情况,我们只需向浏览器返回500个内部服务器错误响应即可。
  • The second case is when we match a route that is a redirect route. In this case, we need to create a server redirect message (302 redirect) to tell the browser to go to the new destination (this is not really happening in our application because we are not using redirect routes in our React Router configuration, but it's good to have it ready in case we decide to keep evolving our application).
    第二种情况是当我们匹配作为重定向路由的路由时。 在这种情况下,我们需要创建服务器重定向消息(302重定向)以告诉浏览器转到新目的地(这在我们的应用程序中实际上没有发生,因为我们没有在React Router配置中使用重定向路由,但是做好准备,以防万一我们决定继续发展我们的应用程序。
  • The third case is when we match a route and we have to render the associated component. In this case, the argument renderProps is an object that contains the data we need to use to render the component. The component we are rendering is RouterContext (contained in the React Router module), which is responsible for rendering the full component tree using the values in renderProps.
    第三种情况是当我们匹配路线并且必须渲染关联的组件时。 在这种情况下,参数renderProps是一个对象,其中包含我们需要用来渲染组件的数据。 我们正在渲染的组件是RouterContext (包含在React Router模块中),它负责使用renderProps的值渲染完整的组件树。
  • The last case is when the route is not matched, and here we can simply return a 404 not found error to the browser.
    最后一种情况是路由不匹配,在这里我们可以简单地向浏览器返回404 not found错误。

This is the core of our server- side routing mechanism and we use the ReactDOM.renderToString function to be able to render the HTML code that represents the component associated to the currently matched route.

这是我们服务器端路由机制的核心,我们使用ReactDOM.renderToString函数能够呈现表示与当前匹配的路由关联的组件HTML代码。

Finally, we inject the resulting HTML into the index.ejs template we defined before to obtain the full HTML page that we send to the browser.

最后,我们将结果HTML注入到我们之前定义的index.ejs模板中,以获得发送到浏览器的完整HTML页面。

Now we are ready to run our server.js script, but because it's using the JSX syntax we cannot simply run it with the node interpreter. We need to use babel-node and the full command (from the root folder of our project) looks like this:

现在我们可以运行server.js脚本了,但是因为它使用的是JSX语法,所以我们不能简单地使用node解释器来运行它。 我们需要使用babel-node ,完整的命令(来自项目的根文件夹)如下所示:

NODE_ENV=production node_modules/.bin/babel-node --presets react,es2015 src/server.js

运行完整的应用程序 ( Running the complete app )

At this stage your app is available at http://localhost:3000 and, for the sake of this tutorial, it can be considered complete.

在此阶段,您的应用程序可在http:// localhost:3000上获得,就本教程而言,可以认为它是完整的。

Again feel free to check it out and try all the sections and links. You will notice that this time we can refresh every page and the server will be able to identify the current route and render the right page.

再次随意检查并尝试所有部分和链接。 您会注意到,这次我们可以刷新每个页面,服务器将能够识别当前路线并呈现正确的页面。

Small advice: don't forget to check out the 404 page by entering a random non-existing URL!

小建议:不要忘记输入随机不存在的URL来签出404页面!

结语 ( Wrapping up )

HOORAY! This completes our tutorial! I'm really happy to know you got to the end, but you can make me even more happier if you post here in the comments some example Universal JavaScript apps that you built using this (or a similar) approach.

哇! 这样就完成了我们的教程! 我很高兴知道您已经走到了尽头,但是如果您在评论中发布一些使用此(或类似方法)构建的示例Universal JavaScript应用示例,则可以让我更加开心。

If you want to know more about Universal Javascript and improve your application even more (e.g. by adding Universal Data Retrival using REST APIs) I definitely recommend to read the chapter Universal JavaScript for Web Applications on my book Node.js Design Patterns:

如果您想进一步了解Universal Javascript并进一步改善您的应用程序(例如,通过使用REST API添加Universal Data Retrival ),我绝对建议您阅读Node.js Design Patterns一书中的Web应用程序通用JavaScript一章:

Until next time!

直到下一次!

PS: Huge thanks to Mario Casciaro for reviewing this article and to Marwen Trabelsi for the support on improving the code! Also thanks to @CriticalMAS for finding a typo :P

PS: 非常感谢 Mario Casciaro审阅了本文,并感谢 Marwen Trabelsi对改进代码的支持! 也感谢@CriticalMAS查找拼写错误:P

翻译自: https://scotch.io/tutorials/react-on-the-server-for-beginners-build-a-universal-react-and-node-app

react node服务器

react node服务器_适用于初学者的服务器上的React:构建通用的React和Node应用程序...相关推荐

  1. kubectl查看node状态_适用于初学者的基本 kubectl 和 Helm 命令 | Linux 中国

    去杂货店"采购"这些命令,你需要用这些 Kubernetes 工具来入门.-- Jessica Cherry 去杂货店"采购"这些命令,你需要用这些 Kuber ...

  2. centos 计算器_计算初学者进行服务器centos 7.6系统及orca、xtb、gaussian 16软件的安装乱谈...

    本帖最后由 欢乐多 于 2020-8-18 10:37 编辑 自己动手丰衣足食--计算初学者进行服务器centos 7.6系统及orca.xtb.gaussian 16软件的安装乱谈 经过一夜苦战通宵 ...

  3. 链接不到服务器_新手搭建云服务器详细过程

    最近突然想使用下服务器,为后续工作准备,作为之前Linux都没有玩过的我感觉很费劲,所以就是无数次的百度,最终将服务器搭建好了:期间整理了一些资料,和过程上也做了些笔记,这里和大家分享下,希望对和我一 ...

  4. linux 企业邮件服务器_什么是Linux服务器,为什么您的企业需要一个?

    linux 企业邮件服务器 IT组织努力通过提高生产率和提供服务的速度来交付业务价值,同时保持足够的灵活性以结合云,容器和配置自动化等创新技术. 无论是在裸机,虚拟机,容器,私有云还是公共云上运行的现 ...

  5. 请求头是针对服务器_什么是无头服务器?

    请求头是针对服务器 Arjuna Kodisinghe/Shutterstock.comArjuna Kodisinghe / Shutterstock.com A headless server i ...

  6. 饥荒联机云服务器_饥荒联机独立服务器搭建教程(三):配置篇

    早在 2015 年底,Klei 就将整个饥荒的配置系统都换了,所以之前写的配置方法算是老古董没法用了,于是这不,博主更新了最新的配置方法. 说起来真是残念,在 2016 年 5 月初,我就差不多把整个 ...

  7. 最优惠租云服务器_租一个云服务器一个月需要多少钱?

    ++++++++ 12.8日更新「2020双十二活动」 来不及解释了,该上车了!阿里云 双十二拼团会场​www.aliyun.com腾讯云 双十二限时秒杀​cloud.tencent.com 云服务器 ...

  8. CryptoTab 服务器_如何架设FTP服务器,如何架设FTP服务器,具体架设方法

    FTP服务器,则是在互联网上提供存储空间的计算机,它们依照FTP协议提供服务. FTP的全称是File Transfer Protocol(文件传输协议).顾名思义,就是专门用来传输文件的协议.简单地 ...

  9. 配置iscsi服务器_在Windows Server 2016上安装和配置iSCSI目标服务器

    配置iscsi服务器 In this article, I am going to explain how we can install and configure the iSCSI Target ...

最新文章

  1. Nat. Commun. 速递:虚拟颅内脑电与神经动力学模型
  2. 当产品上线前出了 Bug | 每日趣闻
  3. leetcode 101 Symmetric Tree
  4. SOPC第二课 新手易犯错误和小灯闪烁SOPC代码以及固化讲解
  5. #地形剖面图_高考地理笔记:经纬网、等值线、地形剖面图知识汇总
  6. 【thymeleaf】【SpringBoot】Thymeleaf 获取.properties中的配置项变量
  7. 前端读取文件图片信息流;js读取图片不同信息流;js读取图片;前端就js读取二进制数据;前端js读取文件流使用FileReader对象的readAsDataURL方法来读取图像文件;
  8. c#多线程操作界面控件的简单实现
  9. meethigher-腾讯课堂自动签到
  10. 浅谈“互联网+”浪潮下传统行业的战略转型
  11. 微信h5页面不用服务器吗,你知道微信h5页面到底是个什么意思吗?
  12. android云测如何使用教程,iTestin使用教程-Testin云测.PDF
  13. idea 双击打不开了咋办
  14. 交换机和路由器技术-32-命名ACL
  15. windows使用命令行创建文件echo >test.txt(可以是.gp .js .ts..)
  16. 彻底解密C++宽字符
  17. Shell | 查询IP
  18. 数字集成电路设计-5-pipelining(流水线)
  19. 开源发票识别_自由职业者和小型企业的前4种开源发票工具
  20. 微信小程序(游戏)----拖拽拼图(图片分块和打乱顺序)

热门文章

  1. Error 1292: Incorrect datetime value: ‘1661309974‘ for column
  2. linux驱动---ioctl函数解析
  3. android 计算gps距离,Android:如何使用GPS测量距离
  4. 笔试:求数组左边减去右边的最大值 / 右边减去左边的最大值
  5. 大数据(7z)Scala手写中文分词
  6. 主站外置的终端服务器,故障分析与处理方法
  7. 五个故事,告诉你为什么要做目标管理
  8. python模拟行星运动_Matlab动画模拟太阳系行星运动
  9. 【LeetCode·位运算.67】二进制求和,详解分析+两种思路+知识点总结
  10. 拼团小程序源码_如何做好小程序拼团?