基于React.js的Web应用程序实现,完整示例

基于React.js的Web应用程序实现,完整示例
尖端技术的完美React应用程序

介绍

每个开发人员都可以在网上搜索并找到基于React与Web应用程序相关的内容,并查看一些实现示例。 一些概念,例如:

  • 服务器端渲染
  • 使用React Router
  • 使用CSS模块
  • 使用PostCSS
  • Webpack配置
  • 表达
  • 用于分隔开发和生产环境的配置
  • 适用于大规模应用程序部署的强大工具
  • 应用扩展
  • 渐进式Web应用程序

它们太多且非常复杂,以至于有时,大量的这些技术导致无法正确实施它们。
有时,我们会看到PinterestInstagramFacebook之类的大公司的网站 ,这个问题向我们提出, 他们如何行动以及如何使用所有技术而不与大量开发人员发生冲突

在本文中,我想向您展示一个良好平台,完整且能够大规模转换的直观构想或示例。 我会逐步解释。

最后,您将拥有一个完整的存储库,可以构建一个包含趋势技术和技术的良好react应用程序。 当然,您的导出在该领域看起来像是很好的例子,但这并不意味着您不能使其比现在更好。

TL; DR! 如果您无聊阅读所有文章,并且想立即查看结果,请参阅并使用此存储库

我希望,如果您有想法或愿景可以改善它,请在Github上进行分叉并在PR上进行。 我和安德鲁对此表示完全欢迎。

我们想要建立的

如果您在互联网上寻找React App,您会发现许多实现都具有SSRReact Helmet和许多其他东西。 但其中许多是根据开发目标而非生产模式编写的。

在本文中,我想实现一个很棒的开发区域,您可以尽快完成您的工作,并且还可以拥有一个采用高效标准技术的生产区域。

本文中的工具和技术

  • 反应
  • 反应路由器4
  • 反应头盔
  • CSS模块
  • PostCSS
  • Webpack 3
  • 巴别塔
  • 表达
  • 下午2

基本要求

实际上,您应该安装Node ,如果没有的话,我更喜欢长期支持版本(LTS),因此,请从Node Official Website下载并安装它。

怎么办?

下一步是使文件夹成为我们想要创建的应用程序。 通过折叠项目文件,您可以调节应用程序并使其可扩展。 折叠的最佳做法是如此不同,在本文中,我使用的是最好的,但是您可以使用折叠样式。

现在,我将项目文件夹命名为react-example ,然后根据您的操作系统打开命令提示符区域或终端。 我假设你打开它。 然后使用终端进入react-example文件夹,然后输入以下命令:

npm init

这个命令和运行一起会问您一些问题。 回答他们,但不要担心。 这个问题和答案是关于制作package.json文件的,您可以轻松地对其进行编辑。

现在在您的终端中输入以下命令:

实际上,使用以上命令,您安装了本文的两个重要dependencies项。 package.json文件旁边创建一个文件夹,名称为node_modules ,这些依赖项和其他依赖项保存在该文件夹中。 现在不在乎它们。 这些数字是本文软件包的版本。 认真使用这些版本,因为它们彼此兼容,也许当您阅读本文时,可能会发布较早的版本。

现在下一步,键入以下命令:

这些是开发区域的依赖项,-- --save-dev安装它们,但将它们与--save依赖项分开。 为了清楚起见,您可以打开并查看package.json文件。

现在,该安装babel ,问题是什么?

有了一个简单的解释,您将基于最先进的技术来编写非常出色的代码,但这并不意味着当您导出项目并准备进行部署时,所有浏览器都像您一样出色。 也许用户使用的是古老的浏览器,所以,您应该照顾好它们。
Babel让您像英雄一样,但是最后,当Webpack构建您的项目时,它会导出各种浏览器都能理解的代码,无论老少皆宜,因此,现在将以下命令粘贴到您的终端中:

这些是babel的插件和依赖项,现在不要关注它们,但是稍后,当您需要某些东西时,肯定会找到并找到这些插件。 要使用这些插件,必须创建一个名称为.babelrc的文件,并在其中写入以下代码:

{   
"presets": [
"env",
"es2015",
"react",
"stage-0"
]
}

以上代码的所有行都有其自身的含义。 例如es2015意味着,让我们使用ES6语法,在构建项目后,您将仅获得ES5.1代码。 react项目系统react以了解JSX语法,并且在构建之后,您只会看到createElement 我不做更多解释,您可以搜索这些预设和插件,或者在评论中询问它们。

Webpack配置

到目前为止,所有文件和配置都是基本的,我们将所有文件和配置都放置在项目文件夹的根目录下,但是现在我们将配置放置在某些文件夹中以管理项目。 这种折叠导致项目将定期且灵活地进行更改并具有可伸缩性。

就像我说的,我们有两种环境,即Development和Production,为这个目标做一个文件夹并命名为webpack 在其中创建文件并将其命名为webpack.development.config.js并按如下所示编写其内容:

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const distDir = path.join(__dirname, '../dist');
const srcDir = path.join(__dirname, '../src');
module.exports = [
{
name: 'client',
target: 'web',
entry: `${srcDir}/client.jsx`,
output: {
path: path.join(__dirname, 'dist'),
filename: 'client.js',
publicPath: '/dist/',
},
resolve: {
extensions: ['.js', '.jsx']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[local]',
sourceMap: true,
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
})
},
],
},
plugins: [
new ExtractTextPlugin({
filename: 'styles.css',
allChunks: true
})
]
},
{
name: 'server',
target: 'node',
entry: `${srcDir}/server.jsx`,
output: {
path: path.join(__dirname, 'dist'),
filename: 'server.js',
libraryTarget: 'commonjs2',
publicPath: '/dist/',
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[local]',
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
}
],
},
}
];

如您所见,在此文件中,我们区分了客户端服务器,并为每个方面设置了一些配置。 有些东西相似,有些东西不相似。 本文太长了,要获得更多解释,请在注释中询问您有关配置的问题。

PostCSS配置

很好,必须说这个Preprocessor与其朋友(例如SCSS是如此不同。 除安装外,还必须安装其插件。 有了插件,它将变得更强大。 而且,如果感觉到或发现存在PostCSS无法利用它的工作,则可以搜索并找到一个插件来解决问题。 我选择了一些足够的插件,因此让我们安装它们:

在项目文件夹的根,使另一个文件夹并将其命名为postcss ,使里面一个文件作为postcss.config.js ,并把下面的配置它。 也许您会问为什么不使用SCSS或更少。 如果您稍微了解一下它的好处,那么您无疑会相信这个很棒的预处理器。 它的好处之一是自动autoprefixer消除了您对mixins 只需进行一些配置,就可以更改浏览器的支持级别:

module.exports = {
ident: 'postcss',
syntax: 'postcss-scss',
map: {
'inline': true,
},
plugins: {
'postcss-partial-import': {
'prefix': '_',
'extension': '.pcss',
'glob': false,
'path': ['./../src/styles']
},
'postcss-nested-ancestors': {},
'postcss-apply': {},
'postcss-custom-properties': {},
'postcss-nested': {},
'postcss-cssnext': {
'features': {
'nesting': false
},
'warnForDuplicates': false
},
'postcss-extend': {},
'css-mqpacker': {
'sort': true
},
'autoprefixer': {
'browsers': ['last 15 versions']
},
}
};

现在的应用程序

显而易见,我们的应用程序有多个页面,并且应该存在一些管理这些页面路由的内容,在这里,我介绍我的亲爱的朋友React Router ,请注意,我想使用第4版,因此请特别安装下面的命令:

npm install --save [email protected]

然后在项目文件夹的根目录中创建一个文件夹,并将其命名为src然后创建一个文件并将其命名为client.jsx并在其中添加以下命令:

import React from 'react';
import {hydrate} from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import App from './app/App';
hydrate((
<BrowserRouter>
<App/>
</BrowserRouter>
), document.getElementById('root'));

然后在文件上方创建另一个文件,并将其命名为server.jsx并在其中粘贴以下代码:

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import {StaticRouter} from 'react-router-dom';
import {Helmet} from "react-helmet";
import Template from './app/template';
import App from './app/App';
export default function serverRenderer({clientStats, serverStats}) {
return (req, res, next) => {
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App/>
</StaticRouter>
);
const helmet = Helmet.renderStatic();
res.status(200).send(Template({
markup: markup,
helmet: helmet,
}));
};
};

现在,在client.jsxserver.jsx文件旁边创建两个文件夹,并将它们分别命名为appstyles 首先是我们的主要应用程序文件,其次是CSS样式文件。

申请文件

app文件夹内创建一个文件,并将其命名为template.jsx ,并将其中的主要HTML模板放入其中, React想要注入其标记。

export default ({ markup, helmet }) => {
return `<!DOCTYPE html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
</head>
<body ${helmet.bodyAttributes.toString()}>
<div id="root">${markup}</div>
<script src="/dist/client.js" async></script>
</body>
</html>`;
};

然后创建一个App.jsx文件,它是该项目的主文件,该主文件包含我们的React Application的每个部分。 我在其中放入了一些简单的代码,但是您可以折叠并制作更大的应用程序:

import React, {Component} from 'react';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Hello World!</h1>
</div>
);
}
}

稍微向前看,我将返回并添加更多代码,但是在看完我们简单的Hello World!之后,它已经足够理解代码了Hello World! 浏览器中的内容又回来了,使它更加完整。

只需添加一个简单的东西, React-Helmet ,它就很棒,并且具有很好的SEO效率。 实际上,这个很棒的组件可以动态填充head标签,并为管理页面提供了许多选项。 要安装React-Helmet请在终端内使用以下命令:

npm install --save [email protected]

立即安装。 当我们回到App.jsx完成时,您会意识到它的好处。

样式文件

内部styles文件夹中创建一个文件并将其命名为styles.pcss 并在其旁边创建一个文件夹并将其命名为partials ,然后在partials文件夹内部创建一个partial文件并将其命名为_partial.pcss并在每个文件内放置以下代码:

// styles.pcss
@import "partials/partial";
.component {
@extend %box;
color: #2f95ff;
}
.text {
display: flex;
@extend %box;
}
.test {
display: flex;
}
.active {
color: red;
}

// partials/_partial.pcss
%box {
box-shadow: 0 0 10px 1px #ff6fc3;
}

开发服务器配置

我们开发区的服务器是express.js 实际上,我们使用了一些middleware ,当我们在项目的每个文件(例如PostCSS文件)中更改某些内容时,构建系统将意识到并自动一次又一次地重建所有文件。

首先应该安装依赖包:

然后在root内部创建一个文件夹,并将其命名为express ,并在其中放入Development配置,为此,请创建一个文件并将其命名为development.js并按如下所示编写该文件:

const express = require('express');
const app = express();
const webpack = require('webpack');
const config = require('./../webpack/webpack.development.config.js');
const compiler = webpack(config);
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackHotServerMiddleware = require('webpack-hot-server-middleware');
app.use(webpackDevMiddleware(compiler, {
serverSideRender: true,
publicPath: "/dist/",
}));
app.use(webpackHotMiddleware(compiler.compilers.find(compiler => compiler.name === 'client')));
app.use(webpackHotServerMiddleware(compiler));
const PORT = process.env.PORT || 3000;
app.listen(PORT, error => {
if (error) {
return console.error(error);
} else {
console.log(`Development Express server running at http://localhost:${PORT}` );
}
});

让我们看看我们建造了什么

现在是时候测试并查看我们的构建了,对于此操作,需要在终端中的以下命令下运行:

node ./express/development.js

如果您使用的是Windows操作系统,则可能会看到NODE_ENV错误,请不要担心,请访问此地址 我写了您应该在那里做的事情,如果没有错误,您应该在终端区域内看到以下日志

Development Express server running at http://localhost:3000

如果您在终端机内看到上面的日志 ,则说明您已到达我们道路的一半。 现在,在浏览器中打开http:// localhost:3000 ,并欣赏您的杰作。

反应路由器配置

到现在为止,一切都令人赞叹,我们将许多物件真正地并在一起,它们都像瑞士表一样工作。 我坚持认为,如果您有任何问题,请发表评论,我一定会回答。

如果您还记得的话,我们只是安装了React Router并没有将其导入到应用程序中,现在是时候返回App.jsx ,现在请使用以下代码编辑该文件:

import React, {Component} from 'react';
import Helmet from "react-helmet";
import {Switch, Route} from 'react-router-dom';
import {Link, NavLink} from 'react-router-dom';
import styles from '../styles/styles.pcss';
class Menu extends Component {
render() {
return (
<div>
<ul>
<li>
<NavLink exact to={'/'} activeClassName={styles.active}>Homepage</NavLink>
</li>
<li>
<NavLink activeClassName={styles.active} to={'/about'}>About</NavLink>
</li>
<li>
<NavLink activeClassName={styles.active} to={'/contact'}>Contact</NavLink>
</li>
</ul>
</div>
);
}
}
class Homepage extends Component {
render() {
return (
<div className={styles.component}>
<Helmet title="Welcome to our Homepage"/>
<Menu/>
<h1>Homepage</h1>
</div>
);
}
}
class About extends Component {
render() {
return (
<div>
<Helmet title="About us"/>
<Menu/>
<h1>About</h1>
</div>
);
}
}
class Contact extends Component {
render() {
return (
<div>
<Helmet title="Contact us"/>
<Menu/>
<h1>Contact</h1>
</div>
);
}
}
export default class App extends Component {
render() {
return (
<div>
<Helmet
htmlAttributes={{lang: "en", amp: undefined}} // amp takes no value
titleTemplate="%s | React App"
titleAttributes={{itemprop: "name", lang: "en"}}
meta={[
{name: "description", content: "Server side rendering example"},
{name: "viewport", content: "width=device-width, initial-scale=1"},
]}
link={[{rel: "stylesheet", href: "/dist/styles.css"}]}
/>
<Switch>
<Route exact path='/' component={Homepage}/>
<Route path='/about' component={About}/>
<Route path='/contact' component={Contact}/>
</Switch>
</div>
);
}
}

听起来不错,让我解释一下这个新内容,我们有三个组件, HomepageAboutContact ,它们扮演着我们三个页面的角色。

在这三个页面之后,我们制作了一个Menu组件,用于在每个组件或实际上每个页面中显示菜单。

这里有一点 ,我们不应该像过去那样使用类名。 我们将使用CSS-Modules ,因此,我们应该像JavaScript对象一样添加样式根文件:

import styles from '../styles/styles.pcss';

并在类名称(如JavaScript对象)中使用它:

<div className={styles.container}>
<div className={styles['container-top']}
example
</div>
</div>

我认为这很明显,在PostCSS文件中保留了任何类名或名称间隔方法,例如BEM ,而在JSX内部,该名称像styles对象的子代一样使用,而不是直接名称。
当您准备部署版本(即生产版本)时,这种使用原因是,类名称将转换为哈希名称,并且可以将它们缩小到5字符。 当然,您构建的CSS文件将非常小巧。

我喜欢的另一件事是React Helmet ,您可以在上面的代码中清楚地看到。 在每个组件中, React Helmet存在;在根组件中,对于常规头部设置也存在。 这些设置非常简单,因此我拒绝在这里解释。

现在,让我们看看我们再次构建了什么,因此再次运行Development命令:

node ./express/development.js

是的,这就像一个奇迹,是基于React.js具有许多React.js技术的Web应用程序的框架。 当您单击每个菜单项时,相关页面将从服务器呈现,并且在浏览器中是动态的,但是完成了吗?

既肯定又否定, YES :因为我们建立了一个真棒发展区域, NO :因为我们不知道如何为生产区,部署做准备。

准备在生产环境上进行部署

到目前为止,我们已经为开发完成了所有工作,但是现在我们为生产确定了不同的配置,因此我们应该考虑以下三个目标:

  1. 捆绑和压缩应用程序中的所有文件,并销毁debuggerconsole.log并将其移植到ES5.1
  2. 在一个单独的文件中提取styles.css并压缩和忽略所有注释
  3. 实际上,构建stat.json文件是服务器操作中的webpack片段, express需要它们。

使用以下命令进入生产配置:

现在在webpack文件夹中创建另一个文件,其名称为webpack.production.config.js 其内容是:

const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const distDir = path.join(__dirname, '../dist');
const srcDir = path.join(__dirname, '../src');
module.exports = [
{
name: 'client',
target: 'web',
entry: `${srcDir}/client.jsx`,
output: {
path: distDir,
filename: 'client.js',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false,
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
})
}
],
},
plugins: [
new ExtractTextPlugin({
filename: 'styles.css',
allChunks: true
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new CleanWebpackPlugin(distDir),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
drop_console: true,
drop_debugger: true
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
]
},
{
name: 'server',
target: 'node',
entry: `${srcDir}/server.jsx`,
output: {
path: distDir,
filename: 'server.js',
libraryTarget: 'commonjs2',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules[\\\/])/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.pcss$/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
config: {
path: `${__dirname}/../postcss/postcss.config.js`,
}
}
}
]
}
],
},
plugins: [
new OptimizeCssAssetsPlugin({
cssProcessorOptions: {discardComments: {removeAll: true}}
}),
new StatsPlugin('stats.json', {
chunkModules: true,
modules: true,
chunks: true,
exclude: [/node_modules[\\\/]react/],
}),
]
}
];

然后在express文件夹中创建另一个文件,并将其命名为production.js并使用以下代码填充它:

const express = require('express');
const path = require('path');
const app = express();
const ClientStatsPath = path.join(__dirname, './../dist/stats.json');
const ServerRendererPath = path.join(__dirname, './../dist/server.js');
const ServerRenderer = require(ServerRendererPath).default;
const Stats = require(ClientStatsPath);
app.use('/dist', express.static(path.join(__dirname, '../dist')));
app.use(ServerRenderer(Stats));
const PORT = process.env.PORT || 3000;
app.listen(PORT, error => {
if (error) {
return console.error(error);
} else {
console.log(`Production Express server running at http://localhost:${PORT}` );
}
});

因此,现在,使用以下命令,我们可以构建生产环境所需的一些文件:

NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors

您会看到在名为dist的新文件夹中生成了一些文件。 您可以使用以下命令查看所有访客都会看到的半生产环境:

NODE_ENV=production node ./express/production.js

如果您在Google Chrome浏览器上安装了React Developer ToolsWappalyzer扩展,则可以看到React徽标,尤其是可以看到React Developer Tools变成蓝色 而且还不是红色

蓝色表示正在生产中, 红色表示正在开发中。

恭喜你! 这篇文章很简单,但有点复杂,当您在这里时,意味着您很完美。 找到所有内容需要一些时间。 但是最后,您将使用许多其他东西(如eslintReduxRedux-Saga或诸如Jest测试之类的东西来创建自定义项目。

在真实服务器上运行

准备好上述所有内容后,您应该告诉DevOps专家在生产服务器上安装pm2 ,因为带有大量访问者的大型React应用程序永远不会使用上述命令运行。 pm2应该使用以下命令进行安装:

npm install pm2 -g

最后运行时应使用以下命令:

NODE_ENV=production pm2 start ./express/production.js

您可以在自己的PC上使用pm2进行测试, pm2和浏览器中的node命令没有什么区别,但是DevOps专家知道它们的不同,例如负载平衡,管理缓存等。如果您想了解更多关于pm2 ,可以阅读其文档

结论

为了便于开发,我将一些命令放在package.json文件的scripts部分中。 您可以在GitHub存储库中访问它们或查看以下代码:

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "NODE_ENV=development node ./express/development.js",
"build": "NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors",
"prod": "NODE_ENV=production webpack -p --config ./webpack/webpack.production.config.js --progress --profile --colors && node ./express/production.js",
"pm2": "NODE_ENV=production pm2 start ./express/production.js"
}

最后一点,我在本文的所有文章中都使用了npm是正确的,例如,您可以使用npm run dev来运行开发,但是npm太慢了,当文件中发生每次更改时,系统都会构建新的开发文件大约需要2730秒! 后来,这很烦人。 然后,您可以使用浏览器Hard Reload查看更改。 太无聊了。
对于这个问题,我建议您使用yarn 您可以使用yarn dev命令开始进行开发,这令人难以置信,构建新的开发文件大约需要500毫秒。

希望本文能帮助您构建 React.js 应用程序

From: https://hackernoon.com/web-application-implementation-based-on-react-js-complete-example-ae77a23b4270