---
title: 'React'
---

圖片來源:[Build Your Own React – Hacker Noon](https://hackernoon.com/build-your-own-react-48edb8ed350d)
---
## Component
組件化勢必是未來的前端開發不可或缺的一項技術,獨立封裝,彈性擴充,可移植性,測試等等…
---
直接來開個空白新專案吧
---
```bash=
npm init -y
```

---
然後下載我個人 React 專案習慣用的工具
```bash=
npm i -D @babel/core @babel/preset-env @babel/preset-react
npm i -D babel-eslint babel-loader clean-webpack-plugin
npm i -D eslint eslint-config-airbnb eslint-plugin-import
npm i -D eslint-plugin-jsx-a11y eslint-plugin-react
npm i -D html-webpack-plugin webpack webpack-cli
npm i -D webpack-dev-server webpack-merge
```
```bash=
npm i classnames jss jss-preset-default normalize-jss
npm i react-jss prop-types react react-dom
```
---
這時我的 package.json 長這樣
```json=
{
"name": "react-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --progress --colors --config ./config/webpack.dev.js",
"build": "webpack --config ./config/webpack.prod.js",
"lint": "eslint src/**"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^2.0.2",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.13.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.32.0",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.4.1"
},
"dependencies": {
"classnames": "^2.2.6",
"jss": "^9.8.7",
"jss-preset-default": "^4.5.0",
"normalize-jss": "^4.0.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-jss": "^8.6.1"
}
}
```
---
配置 config/webpack.common.js
```javascript=
const path = require('path');
/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
module.exports = {
entry: [path.resolve(__dirname, '../src/index.jsx')],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist/'),
// publicPath: './',
},
module: {
rules: [
{
test: /.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
],
},
{
test: /.jsx$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react', '@babel/preset-env'],
},
},
],
},
],
},
};
```
---
配置 config/webpack.dev.js
```javascript=
const webpack = require('webpack');
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const common = require('./webpack.common');
/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
open: true,
historyApiFallback: true,
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
}),
new HtmlWebpackPlugin({
template: './index.html',
}),
],
});
```
---
配置 config/webpack.prod.js
```javascript=
const webpack = require('webpack');
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const common = require('./webpack.common');
/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
module.exports = merge(common, {
mode: 'production',
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
}),
],
});
```
---
配置 babel.config.js 於根目錄
```javascript=
module.exports = {
presets: [
[
'@babel/preset-env',
{ targets: { node: 'current' } },
],
'@babel/preset-react',
],
};
```
---
配置 .eslintrc.js 於根目錄
```javascript=
module.exports = {
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
env: {
browser: true,
es6: true,
jest: true,
},
extends: [
'plugin:react/recommended',
'airbnb-base',
],
plugins: [
'react'
],
rules: {
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'linebreak-style': [
'error',
'windows',
],
},
};
```
---
配置 index.html 於根目錄
```htmlmixed=
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no">
<title></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
```
---
此時我的資料夾長這樣

---
到目前為止
都只是基本的配置環境
現在才要開始寫 React
---
剛剛在 webpack 設置裡的 entry 是 src/index.jsx
先從這個進入點開始定義
---
src/index.jsx
```javascript=
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './Root.jsx';
ReactDOM.render(<Root />, document.querySelector('#app'));
```
---
src/Root.jsx
```javascript=
import React from 'react';
import PropTypes from 'prop-types';
import withStyles from 'react-jss';
import jss from 'jss';
import preset from 'jss-preset-default';
import normalize from 'normalize-jss';
jss.setup(preset());
jss.createStyleSheet(normalize).attach();
const styles = {
root: {
fontFamily: 'system-ui, -apple-system, "Roboto", "Helvetica", "Arial", sans-serif',
},
};
class Root extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
我是 Root
</div>
);
}
}
Root.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Root);
```
---
設置完 jsx 後
就可以來執行看看 這兩個 jsx 的 render 結果
```bash=
npm run dev
```
關於 dev 這個指令
```json=
// package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --progress --colors --config ./config/webpack.dev.js",
"build": "webpack --config ./config/webpack.prod.js",
"lint": "eslint src/**"
},
```
---

---
接下來我想要製作一個可以 alert 的 button
在點擊後會輸出我自己定義的 message
---
src/components/ButtonExecAlert.jsx
```javascript=
import React from 'react';
import PropTypes from 'prop-types';
import withStyles from 'react-jss';
/* eslint-disable class-methods-use-this */
const styles = {
button: {
width: 200,
height: 100,
},
};
class ButtonExecAlert extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
alert(this.props.message);
}
render() {
const { classes } = this.props;
return (
<button
className={classes.button}
onClick={this.handleClick}
>
點我有 alert
</button>
);
}
}
ButtonExecAlert.propTypes = {
classes: PropTypes.object.isRequired,
message: PropTypes.string,
};
export default withStyles(styles)(ButtonExecAlert);
```
---
src/Root.jsx
```javascript=
import React from 'react';
import PropTypes from 'prop-types';
import withStyles from 'react-jss';
import jss from 'jss';
import preset from 'jss-preset-default';
import normalize from 'normalize-jss';
import ButtonExecAlert from './components/ButtonExecAlert.jsx';
...省略
class Root extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
{/* 我是 Root */}
<ButtonExecAlert message="我的 alert" />
</div>
);
}
}
```
---
此時就得到一個會 alert 然後 width height 有設定過的 button

---
回到 src/Root.jsx
我複製一個 ButtonExecAlert 的組件但是設定不一樣的 message
```javascript=
...省略
class Root extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<ButtonExecAlert message="我的 alert" />
<ButtonExecAlert message="我的右邊的 alert" />
</div>
);
}
}
```
---
此時畫面上就會有兩個自定義的組件
因為設定不同的 message 所以 alert 的結果會不同

---
此時我的資料夾長這樣

---
最後我要輸出成最一般的 html js (沒有 css 因為我用了 jss)
```bash=
npm run build
```
關於 build 這個指令
```json=
// package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --progress --colors --config ./config/webpack.dev.js",
"build": "webpack --config ./config/webpack.prod.js",
"lint": "eslint src/**"
},
```
---
輸入完 npm run build 後會打包出 dist 資料夾

---
直接執行 dist 內的 index.html

---
這邊只是很粗淺的展示了 React 的 component
React 的生態還有非常非常多東西
不過這次只是打算快速介紹,所以就這樣
---
###### tags: `前端2`
下一章: [Material-UI](https://hackmd.io/p/Sy33TZ7nN#/)