0. 起因
老板要搞个Log分析工具,数据存储选用的是Elasticsearch,起初想法是做个Kibana的插件,后来觉得依靠Kibana太庞大,而且后期想要把代码直接部署在GitHub page上,因此打算做成个独立的工具。最终选用了Node.js+React,主要原因是相中了一个React UI库EUI(Elastic Stack推出的一个开源UI库,风格与Kibana一样),毕竟,不是谁都能写出漂亮的UI,强如Linux之父Linus都表示“如果我被困在一个与世隔绝的岛上,逃离这座岛的唯一办法是写出漂亮的UI,那我估计就老死在岛上了”。
虽然最终由于内网权限问题没部署上GitHub page,但基本流程都跑通了。既然代码都写了,顺路写个总结吧。
1. 工程搭建
首先,我们需要安装Node.js,到Node.js官网随便下一个安装包安装,或者下载压缩包解压缩后手动设置环境变量使用。我比较喜欢直接使用压缩包,因为这样可以随意在多个版本间切换而且不用额外的工具辅助。例如在Ubuntu下下载压缩包解压缩并通过命令export PATH=$NODEJS_ROOT/bin:$PATH
即完成了安装。安装完成后可以通过以下命令查看是否安装成功:
node --version
npm --version
安装完成后,按照以下结构建立一个目录:
my-app/
package.json
public/
index.html
src/
index.js
其中my-app
可以改成任意你喜欢的名字,剩余的部分名字必须与例子给出的一致,这是工程可以构建的前提。
然后,打开package.json
,在其中填入以下内容并保存:
{
"name": "eui-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
上面的内容中,dependencies
和scripts
这两个对象必须有,其他的可选择性添加,即最小要求如下:
{
"dependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"
},
"scripts": {
"start": "react-scripts start",
}
}
package.json
文件准备好后执行以下命令:
npm install
npm start
执行完上面的命令,整个工程已经构建完毕,在浏览器中输入http://localhost:3000/
即可访问你刚构建起来的应用,虽然目前这个应用什么也没做也没有显示任何内容。
当然,其实还有另一个更加简单的方法来构建应用,Node.js安装完成后只需要在终端输入一条命令即可 ,同样,my-app
可以换成任意名字:
npx create-react-app my-app/
等待命令执行完毕即可。
2. Hello World?
如果工程是我们自己手动一步一步搭建起来的,通过浏览器访问http://localhost:3000/
是什么都不会显示的。下一步,我们就需要让它显示点什么。编程嘛,就从Hello World开始吧。
首先,我们打开my-app/public/index.html
,在里面输入一下信息并保存:
<html>
<head></head>
<body>
<div id='content'>
</div>
</body>
</html>
上面的超文本标记代码很简单,如果用浏览器打开的话还是什么也看不到。它只是为后续的React UI代码提供了一个挂载点 —— id为content
的一个div
,在我们的小例子中,对HTML的编辑就算完了,身下的就全部交给JS代码了。
接着,我们打开my-app/src/index.js
,输入一下代码:
import React from 'react';
import ReactDOM from 'react-dom';
function HelloWorld(props) {
return (<div><p>Hello World!</p></div>)
}
ReactDOM.render(
<HelloWorld />,
document.getElementById('content')
);
编辑完my-app/src/index.js
并保存之后,我们在my-app
目录中执行npm start
,就可以在浏览器中看到如图1结果:
在上面的代码中,我们做了三件事:
- 第一第二行代码分别从
react
以及react-dom
这两个模块中导入了React
以及ReactDOM
这两个类。值得注意的是,虽然我们没有直接看到使用导入的React
,但是这个导入语句是必须的,否则编译就会报错! - 接下来,定义了一个名为
HelloWorld
的函数,这个函数在React中称为函数组件*(Function Components),它与类组件(Class Components)一起组成了React 渲染UI的核心。这个函数只有一个参数props
,这个props
实际上是一个字典,可以通过它传递任意参数给函数组件;函数返回一个描述如何显示UI的React元素,虽然看着像超文本标记语言(HTML),但是它却不是。它的名字叫做JSX(JavaScript eXtension),它是JavaScript语法的扩展。 - 第三步,就是将我们定义的函数组件通过
ReactDOM.render()
函数渲染出来。ReactDOM.render()
需要一个挂载节点,在我们的例子中的挂载节点是前面提到的id为content
的一个div
,通过ReactDOM.render()
渲染的界面都托管在React DOM中,由React DOM负责管理以及更新。
3. 实践
有了Hello World的铺垫,我们现在可以正式搭建一个简单点的应用了。我们选用的UI框架是Elastic UI,单然如果你有自己喜欢的其他框架也是可以的。
假设我们要搭建一个Markdown编辑器。我们确定它的结构如图2,我们需要用EUI实现我们的目标:
3.1. 搭架子
我们在EUI中找到一个名叫Page
的布局空间,其布局如图3,正好符合我们的期望:
同样,我们分别找到导航组件(tree-view)、标签组件(tabs)以及Markdown编辑框组件(markdown-editor),将它们搭积木一样组合起来,并做些调整就能得到如图4所示的界面:
为了方便起见,我们将Page组件、导航栏组件、标签栏组件、编辑器组件代码放到独立JS文件中,分别命名为page.js, file-nav.js, tabs.js, markdown-editor.js
,具体结构如下:
my-app/
package.json
public/
index.html
src/
file-nav.js
index.js
markdown-editor.js
page.js
tabs.js
他们的代码分别如下所示:
// file-nav.js
import React from 'react';
import {
EuiIcon, EuiTreeView, EuiToken } from '@elastic/eui';
export default () => {
const showAlert = () => {
alert('You squashed a bug!');
};
const items = [
{
label: 'src',
id: 'src',
icon: <EuiIcon type="folderClosed" />,
iconWhenExpanded: <EuiIcon type="folderOpen" />,
isExpanded: true,
children: [
{
label: 'index.md',
id: 'item_a',
icon: <EuiIcon type="document" />,
},
{
label: 'level2 folder',
id: 'item_b',
icon: <EuiIcon type="folderOpen" />,
iconWhenExpanded: <EuiIcon type="folderOpen" />,
children: [
{
label: 'monosodium_glutammate.md',
id: 'item_cloud',
icon: <EuiIcon type="document" />,
},
{
label: "cobalt.md"