組件化勢必是未來的前端開發不可或缺的一項技術,獨立封裝,彈性擴充,可移植性,測試等等…
直接來開個空白新專案吧
npm init -y
然後下載我個人 React 專案習慣用的工具
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
npm i classnames jss jss-preset-default normalize-jss npm i react-jss prop-types react react-dom
這時我的 package.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
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
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
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 於根目錄
module.exports = { presets: [ [ '@babel/preset-env', { targets: { node: 'current' } }, ], '@babel/preset-react', ], };
配置 .eslintrc.js 於根目錄
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 於根目錄
<!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
import React from 'react'; import ReactDOM from 'react-dom'; import Root from './Root.jsx'; ReactDOM.render(<Root />, document.querySelector('#app'));
src/Root.jsx
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 結果
npm run dev
關於 dev 這個指令
// 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
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
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
...省略 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)
npm run build
關於 build 這個指令
// 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 的生態還有非常非常多東西
不過這次只是打算快速介紹,所以就這樣
前端2
下一章: Material-UI