本文来自React官方文档推荐的一篇英文博客,它讲解了如何从零开始构建一个React应用,同时也可以帮助你掌握如何将React添加到已有的工程项目当中
更新:2018-8-31 迁移至 Babel 7.0.0
React不是完全开箱即用的。它使用了一些最近node才支持的关键字和语法(在本教程中我使用了v 9.3.0版本)。因此需要一些很麻烦的设置,但是Facebook为此提供了一个可以轻松创建React应用的方案。这听起来不错,那我为什么要说这一点呢?
问题在于,create-react-app抽象出了很多概念,在创建时它们并不会提示,你也不需要手动调整,这使得你并不完全清楚React完成了什么工作。但是在很多场景下,你需要自定义自己的应用或者需要在React底层上完成一些工作。
如上所述,当你创建你的React app时会遇到很多障碍。第一个障碍就是你当前的node不能处理所有的语法(比如 import/export 和 jsx )。第二点是你在开发过程中需要用React去构建文件或者提供服务给你当前的应用——后者尤为常见。
不过幸运的是,你可以使用Babel和Webpack来解决以上问题。
环境配置
在开始之前,你首先需要创建好存放React应用的目录。然后使用npm init
命令初始化你的工程并用你喜欢的编辑器(编辑器配置指南)打开该目录。这也是使用git init
最佳的时机之一。然后在工程目录下创建以下文件夹:
.
-- dist
-- public
-- src
这里注意到我们最终需要构建我们的应用,但是我们并不想提交我们构建生成的文件以及我们使用npm拉取的包目录,所以让我们再创建.gitignore
文件并在其中(至少)排除掉node_modules
和dist
目录。
public
目录用来存储静态样式文件,其中最关键的是入口文件index.html
,React将利用它来渲染你的应用。本文中用到的index.html
文件代码如下,它是在React官方文档给出的代码基础上进行少许改动得到的。把它放在public
目录下即可。
<!-- sourced from https://raw.githubusercontent.com/reactjs/reactjs.org/master/static/html/single-file-example.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>React Starter</title>
</head>
<body>
<div id="root"></div>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<script src="../dist/bundle.js"></script>
</body>
</html>
注意上面代码中的第十行<div id="root"></div>
,它是我们的React应用挂载的节点。还有第十四行<script src="../dist/bundle.js"></script>
调用的是我们后面React app构建得到的脚本。教程中使用了bundle.js
的名字,你也可以为构建的脚本指定其他任意合法的文件名。
现在我们已经得到了初始的HTML文件,不过我们还需要完成一些配置才能将之运行起来。首先我们需要工具来编译我们写出的代码,这里我们选用Babel。
Babel
先运行以下命令:
代码语言:txt复制npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-react
babel-core
是Babel的核心——我们需要它来对我们的代码进行转换。babel-cli
使你可以通过命令行来编译文件。babel-preset-env和babel-preset-react可以帮助我们转换指定风格的代码——preset
中设定env
属性后,它可以将ES6 代码转换为传统的JavaScript代码,react
属性也是如此,不过它是使用JSX实现这一点的。
在项目的根目录创建.babelrc
文件,然后设定preset
属性的值。
{
"presets": ["@babel/env", "@babel/react"]
}
如果你只特定的转换功能或者env
不能满足你的需求,Babel也提供了很多其他插件供选择。本文并不需要考虑这一点,如果你需要了解相关内容可以自行查阅。
Webpack
现在我们需要引入webpack并对其进行配置。为此我们需要安装一些包,你可以设定其为开发环境下的依赖并安装:
代码语言:txt复制npm install --save-dev webpack webpack-cli webpack-dev-server style-loader css-loader babel-loader
Webpack使用loaders来处理不同类型文件。它可以与开发时临时的本地服务器一起工作,在我们修改了React的组件之后本地服务器调出的网页可以进行实时的刷新。为了利用Webpack这些优点,我们需要配置Webpack来使用我们的loaders并设定本地服务器的端口等信息。
下面让我们在工程目录下创建配置文件webpack.config.js
。这个文件将导出一个包含Webpack配置的对象。
const path = require("path");
const webpack = require("webpack");
const bundlePath = path.resolve(__dirname, "dist/");
module.exports = {
entry: "./src/index.js",
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
options: { presets: ['@babel/env'] }
},
{
test: /.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
},
resolve: { extensions: ['*', '.js', '.jsx'] },
output: {
publicPath: bundlePath,
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname,'public'),
port: 3000,
publicPath: "http://localhost:3000/dist"
},
plugins: [ new webpack.HotModuleReplacementPlugin() ]
};
迅速浏览整个文件,可以看到:第六行entry将应用的启动文件路径提供给了Webpack,module对象定义了你输出的javascript模块如何进行转换以及你需要转换的文件格式(根据rules数组限定)。
文件中rules
数组的第一个元素设定了ES6以及JSX语法的转换。其中的test和exclude属性规定了路径的匹配模式。实例中的配置将匹配除了node_modules
以及bower_components
目录之外所有的js,jsx文件,我们还需要指定Webpack去使用Babel,在最后我们还要在options中指定presets
为env
。
下一条规则是用来处理CSS的。由于我们并没有使用CSS的预处理器或后处理器,所以只需要在use
属性中添加css-loader
和style-loader
即可。这里的写法为简写的形式。
resolve属性可以让Webpack为我们自动指定文件的后缀名——这使得我们在import所需模块的时候不需要再写上文件的后缀。
output属性告诉了Webpack打包好的js文件应该存放在哪里。publicPath属性指定了打包文件的存放目录也是webpack-dev-server
启动的路径。
我们在devServer属性设置了webpack-dev-server
所需的配置,只需要指定我们打包文件的源文件所在目录contentBase
、打包后的文件目录publicPath
以及服务器端口port
即可。
最后,我们使用了Hot Module Replacement插件,这样我们就不需要每次手动刷新来查看代码变更时页面的变化了。插件需要配置在plugins
属性中。
现在,我们终于完成了繁琐的配置过程,现在让我们看一下React能否正常工作。
React
这里我们还需要安装两个包:react@16.3.2和react-dom@16.3.2,和上面一样使用npm安装即可。
然后我们需要告知React它应该挂载在DOM(由index.html
定义)上的哪个节点。为此我们在src
目录下创建了index.js
文件,这个文件的行数并不多,但它完成了本文中React App的绝大多数工作。让我们一探究竟:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App.js";
ReactDOM.render(
<App />,
document.getElementById("root")
);
ReactDOM.render函数告知了React应当渲染的内容和位置——在例子中我们需要渲染的组件名为App
(下面我们会创建它),该组件会渲染在id为root
的DOM节点处(index.html文件的第十行)。
现在,在src
文件夹下创建另一个名为App.js
的文件。如果你用create-react-app
创建过项目的话你会发现下面的文件和它创建出的内容是很相似的。这个文件单独定义了一个React的Component。
import React, { Component} from "react";
import "./App.css";
class App extends Component{
render(){
return(
<div className="App">
<h1> Hello, World! </h1>
</div>
);
}
}
export default App;
然后我们还要创建App.js
中依赖的样式文件,在src
目录下创建App.css
文件:
.App {
margin: 1rem;
font-family: Arial, Helvetica, sans-serif;
}
现在,你的工程目录下的文件目录结构应该是这样:
代码语言:txt复制.
-- public
| -- index.html
-- src
| -- App.css
| -- App.js
| -- index.js
-- .babelrc
-- .gitignore
-- package-lock.json
-- package.json
-- webpack.config.js
现在你已经完成了一个能够正常工作的React应用了!让我们通过命令行执行webpack-dev-server --mode development
来启动我们的本地开发服务器。我建议你将这个命令保存在package.json
的start
属性下,这样你每次启动本地服务器的时候至少可以少打9个字母了: )
一些细节问题
细心的读者可能发现了一些有趣(意外)的事情,就是在你启动服务器的时候dist
目录中并没有生成文件!这是因为webpack-dev-server将打包的文件存放在了内存当中——当你停止服务器的时候,内存中的文件也会被同时删除。如果你想保存构建的文件,你可以在package.json文件中配置build命令为webpack --mode development,你也可以将development
替换为production
,但是如果你省略了--mode
,webpack会回滚到上一个版本的构建结果并抛出警告。
到此为止,我们已经掌握了如何不使用create-react-app
命令来渲染一个基本的React页面了。当然如果要实现一个更完整的应用的话你还需要添加更多的东西——比如说上面的例子中Webpack并没有处理图片,这里我给出处理的loader,你可以根据需要实现。毕竟如果你如果不需要文件处理那么这个功能就是冗余的了,不是吗?
我希望这篇文章可以帮助你理解React的应用是如何正常运转的以及其基本功能的底层是如何实现的。本文中我并没有深入探讨关于Babel和Webpack的细节,但是你可以通过我给出的参考链接或者直接阅读他们的官方文档进行学习。他们是非常棒的工具,虽然乍一看很困难,但是实际上并没有那么困难,理解它们可以将你的开发水平提升到一个新的高度。
本文的代码仓库