React (Class component)

環境建置

webpack

install
1
2
3
4
mkdir react-self-app
cd react-self-app
npm init
yarn add webpack webpack-cli
webpack config
1
2
3
4
5
6
7
8
9
10
11
12
13
// ./webpack.config.js
const path = require("path");

module.exports = {
// source file
entry: "./src/index.js",
output: {
// output path
path: path.join(__dirname, "/dist"),
// output file name
filename: "bundle.js",
},
};
add file
  • ./index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!DOCTYPE html>
    <html lang="en">
    <!-- ./index.html -->

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello</title>
    </head>

    <body>
    hello
    <!-- add webpack generate file -->
    <script src="./dist/bundle.js"></script>
    </body>

    </html>
  • ./src/index.js

    1
    2
    3
    // ./src/index.js
    import { log } from "./utils";
    log("hello world");
  • ./src/utils.js

    1
    2
    3
    4
    // ./src/utils.js
    export function log(str) {
    console.log(str);
    }
add webpack script

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "react-self-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"//1": "development mode 打包",
"//2": "production mode 打包",
"scripts": {
"start": "webpack --mode development",
"build": "webpack --mode production",
"test": "echo \"Error: no test specified\" && exit 1"
},
....
run webpack script
1
2
3
4
5
6
7
8
9
npm run start
> react-self-app@1.0.0 start
> webpack --mode development
asset bundle.js 4.13 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
cacheable modules 124 bytes
./src/index.js 72 bytes [built] [code generated]
./src/utils.js 52 bytes [built] [code generated]
webpack 5.60.0 compiled successfully in 190 ms
load index.html from browser

console show -> hello world

babel

install babel
1
2
# babel-loader use by webpack
yarn add @babel/core babel-loader @babel/preset-env
babel config
  • .babelrc

    1
    2
    3
    4
    5
    // .babelrc
    {
    // set babel transfer condition
    "presets": ["@babel/preset-env"]
    }
  • ./webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // ./webpack.config.js
    const path = require("path");

    module.exports = {
    // source file
    entry: "./src/index.js",
    output: {
    // output path
    path: path.join(__dirname, "/dist"),
    // output file name
    filename: "bundle.js",
    },
    module: {
    rules: [
    // 用 babel-loade 打包所有 .js 檔案,排除目錄 node_modules
    {
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
    loader: "babel-loader",
    },
    },
    ],
    },
    };
change file
  • ./src/index.js
    1
    2
    3
    4
    5
    6
    7
    // ./src/index.js
    import { log } from "./utils";
    const obj = {
    text: "hello world!!!",
    };
    const { text } = obj;
    log(text);
run webpack script
1
2
3
4
5
6
7
8
9
npm run start
> react-self-app@1.0.0 start
> webpack --mode development
asset bundle.js 4.19 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
cacheable modules 186 bytes
./src/index.js 120 bytes [built] [code generated]
./src/utils.js 66 bytes [built] [code generated]
webpack 5.60.0 compiled successfully in 1094 ms
load index.html from browser

console show -> hello world!!!

react

install
1
yarn add react react-dom @babel/preset-react
config
1
2
3
4
5
6
// .babelrc
{
// set babel transfer condition
// add @babel/preset-react
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
add file for react
  • ./index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE html>
    <html lang="en">
    <!-- ./index.html -->

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello</title>
    </head>

    <body>
    <!-- add for react -->
    <div id="root"></div>
    <!-- add webpack generate file -->
    <script src="./dist/bundle.js"></script>
    </body>

    </html>
  • ./src/index.js

    1
    2
    3
    4
    5
    6
    // ./src/index.js
    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    // App render to root
    ReactDOM.render(<App />, document.getElementById("root"));
  • ./src/App.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // ./src/App.js
    import React, { Component } from "react";

    class App extends Component {
    render() {
    return <h1>Hello React</h1>;
    }
    }

    export default App;
run webpack script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
npm run start
> react-self-app@1.0.0 start
> webpack --mode development
asset bundle.js 1020 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
modules by path ./node_modules/ 974 KiB
modules by path ./node_modules/scheduler/ 26.3 KiB
modules by path ./node_modules/scheduler/*.js 412 bytes 2 modules
modules by path ./node_modules/scheduler/cjs/*.js 25.9 KiB 2 modules
modules by path ./node_modules/react/ 70.6 KiB
./node_modules/react/index.js 190 bytes [built] [code generated]
./node_modules/react/cjs/react.development.js 70.5 KiB [built] [code generated]
modules by path ./node_modules/react-dom/ 875 KiB
./node_modules/react-dom/index.js 1.33 KiB [built] [code generated]
./node_modules/react-dom/cjs/react-dom.development.js 874 KiB [built] [code generated]
./node_modules/object-assign/index.js 2.06 KiB [built] [code generated]
modules by path ./src/*.js 3.65 KiB
./src/index.js 222 bytes [built] [code generated]
./src/App.js 3.43 KiB [built] [code generated]
webpack 5.60.0 compiled successfully in 1732 ms
load index.html from browser

browser show -> Hello React

setup webpack dev derver

change bundle.js add hash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ./webpack.config.js
const path = require("path");

module.exports = {
// source file
entry: "./src/index.js",
output: {
// output path
path: path.join(__dirname, "/dist"),
// output file name
// 預防 browser cache 住檔案
filename: "bundle.[hash].js",
},
module: {
rules: [
// 用 babel-loade 打包所有 .js 檔案,排除目錄 node_modules
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
};
install html-webpack-plugin
1
yarn add html-webpack-plugin
./webpack.config.js add html-webpack-plugin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// ./webpack.config.js
const path = require("path");
// use html-webpack-plugin
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
// source file
entry: "./src/index.js",
output: {
// output path
path: path.join(__dirname, "/dist"),
// output file name
// 預防 browser cache 住檔案
filename: "bundle.[hash].js",
},
module: {
rules: [
// 用 babel-loade 打包所有 .js 檔案,排除目錄 node_modules
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
// use html-webpack-plugin
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
}),
],
};
./index.html remove <script src=”./dist/bundle.js”> </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<!-- ./index.html -->

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello</title>
</head>

<body>
<!-- add for react -->
<div id="root"></div>
</body>

</html>
run webpack script
1
npm run start
load ./dist/index.html from browser

browser show -> Hello React

install webpack dev server
1
yarn add webpack-dev-server
change webpack script

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "react-self-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"//3": "production mode 打包 run webpack dev server",
"//2": "production mode 打包",
"scripts": {
"start": "webpack-dev-server --mode development --open --hot",
"build": "webpack --mode production",
"test": "echo \"Error: no test specified\" && exit 1"
},
....
run webpack script
1
2
3
4
5
6
npm run start
> react-self-app@1.0.0 start
> webpack-dev-server --mode development --open --hot
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8080/
# auto open http://localhost:8080/ by browser

基礎

Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ./src/App.js
import React, { Component } from "react";

class Title extends Component {
render() {
return <h1>Title</h1>;
}
}

class Text extends Component {
render() {
return <p>text</p>;
}
}

class App extends Component {
render() {
return (
<div>
<Title />
<Text />
</div>
);
}
}

export default App;

Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ./src/App.js
import React, { Component } from "react";

class Title extends Component {
render() {
// event
function sayHi() {
alert("Hi!");
}
return <h1 onClick={sayHi}>Title</h1>;
}
}

class Text extends Component {
render() {
return <p>text</p>;
}
}

class App extends Component {
render() {
return (
<div>
<Title />
<Text />
</div>
);
}
}

export default App;

State

使用 on click function #1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ./src/App.js
import React, { Component } from "react";

class App extends Component {
constructor(props) {
super(props);
// state
this.state = {
counter: 1,
};

// 使用 on click function 要加
this.handleClick = this.handleClick.bind(this);
}

// on click function
handleClick() {
this.setState({
counter: this.state.counter + 1,
});
}

render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello</h1>
<div>{this.state.counter}</div>
</div>
);
}
}

export default App;
使用 on click function #2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// ./src/App.js
import React, { Component } from "react";

class App extends Component {
constructor(props) {
super(props);
// state
this.state = {
counter: 1,
};

// 使用 on click function 要加 example #1
// this.handleClick = this.handleClick.bind(this);
}

// on click function
handleClick() {
this.setState({
counter: this.state.counter + 1,
});
}

render() {
return (
<div>
{/* 使用 on click function 要加 example #2 */}
<h1 onClick={this.handleClick.bind(this)}>Hello</h1>
<div>{this.state.counter}</div>
</div>
);
}
}

export default App;
使用 箭頭函數 example
  • 箭頭函數 的 this, 為呼叫時之 this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// ./src/App.js
import React, { Component } from "react";

class App extends Component {
constructor(props) {
super(props);
// state
this.state = {
counter: 1,
};

// 使用 on click function 要加 example #1
// this.handleClick = this.handleClick.bind(this);
}

// on click function
handleClick() {
this.setState({
counter: this.state.counter + 1,
});
}

render() {
return (
<div>
{/* 使用 箭頭函數 example */}
<h1
onClick={() => {
this.setState({
counter: this.state.counter + 1,
});
}}
>
Hello
</h1>
<div>{this.state.counter}</div>
</div>
);
}
}

export default App;

this 值,取決於如何被呼叫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ./src/App.js
import React, { Component } from "react";

class Test extends Component {
setName(name) {
this.name = name;
}
say() {
console.log(this);
}
}

class App extends Component {
constructor(props) {
super(props);
// state
this.state = {
counter: 1,
};

// 使用 on click function 要加
this.handleClick = this.handleClick.bind(this);
}

// on click function
handleClick() {
// this 值,取決於如何被呼叫
const t = new Test();
t.setName("Robert");
t.say(); // name: "Robert" ..

const func = t.say;
func(); // undefine

this.setState({
counter: this.state.counter + 1,
});
}

render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello</h1>
<div>{this.state.counter}</div>
</div>
);
}
}

export default App;

props and event 溝通

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// ./src/App.js
import React, { Component } from "react";

class Title extends Component {
render() {
return <h1 onClick={this.props.handleClick}>{this.props.children}</h1>;
}
}

class Counter extends Component {
render() {
return <div>{this.props.number}</div>;
}
}

class App extends Component {
constructor(props) {
super(props);
// state
this.state = {
counter: 1,
};

// 使用 on click function 要加
this.handleClick = this.handleClick.bind(this);
}

// on click function
handleClick() {
this.setState({
counter: this.state.counter + 1,
});
}

render() {
return (
<div>
{/* <h1 onClick={this.handleClick}>Hello</h1> */}
<Title handleClick={this.handleClick}>Hello</Title>
<Counter number={this.state.counter} />
{/* <div>{this.state.counter}</div> */}
</div>
);
}
}

export default App;

載入 CSS

install
1
npm install style-loader css-loader
modify ./webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ./webpack.config.js
......

module: {
rules: [
// 用 babel-loade 打包所有 .js 檔案,排除目錄 node_modules
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
}, // load css-loader and style-loader
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
import css
1
2
3
4
5
6
// ./src/App.js
import React, { Component } from "react";
// import css
import "./style.css";

....
add css
1
2
3
4
5
6
/* ./style.css */

h1 {
color: red;
}

styleds-components

install
1
npm install styled-components
use styled-components
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ./src/App.js
import React, { Component } from "react";
// import styled-component
import styled from "styled-components";

const H1 = styled.h1`
color: red;
font-size: 50px;
`;

class Title extends Component {
render() {
return <H1 onClick={this.props.handleClick}>{this.props.children}</H1>;
}
}

Ref

  • Ref 是藉由使用 React.createRef() 所產生的,它藉由 ref 參數被依附在 React element。Ref 常常會在一個 component 被建立出來的時候,被賦值在某個 instance 屬性,這樣一來他們就可以在整個 component 裡面被參考。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import React from "react";
    export default class APP extends React.Component {
    constructor(props) {
    super(props);
    // 產生一個可以儲存 textInput DOM element 的 ref
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
    }

    focusTextInput() {
    // 特別利用原生的 DOM API 來關注文字的輸入
    // 注意:我們正利用「current」來取得 DOM 節點
    this.textInput.current.focus();
    }

    render() {
    // 告訴 React 我們想要將 <input> ref
    // 和我們在 constructor 產生的 `textInput` 連結
    return (
    <div>
    <input type="text" ref={this.textInput} />
    <input
    type="button"
    value="Focus the text input"
    onClick={this.focusTextInput}
    />
    </div>
    );
    }
    }
  • 你不能在 function component 上使用 ref,因為他們沒有 instance。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function MyFunctionComponent() {
    return <input />;
    }

    class Parent extends React.Component {
    constructor(props) {
    super(props);
    this.textInput = React.createRef();
    }
    render() {
    // This will *not* work!
    return (
    <MyFunctionComponent ref={this.textInput} />
    );
    }
    }

React 的生命週期

進階

input 共用 onChnage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var Hello = React.createClass({
constructor(props) {
super(props);
// state
this.state = {
input1: 0,
input2: 0,
};

this.handleChange = this.handleChange.bind(this);
}

handleChange (e) {
this.setState({[e.target.name]: e.target.value});
}

render: {
const total = this.state.input1 + this.state.input2;

return (
<div>{total}<br/>
<input type="text" value={this.state.input1} name="input1" onChange={this.handleChange} />
<input type="text" value={this.state.input2} name="input2" onChange={this.handleChange} />
</div>
);
}
});

練習

Form 報名表單

  • ./style.css */

    1
    2
    3
    4
    5
    /* ./style.css */
    .App {
    margin: 10px auto;
    width: 300px;
    }
  • ./src/App.js - form

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    // ./src/App.js - form
    import React, { Component } from "react";
    import "./style.css";

    function CheckBox({ value, id, label, checked, onChange }) {
    return (
    <span>
    <input
    id={id}
    type="checkbox"
    value={value}
    name="time"
    checked={checked}
    onChange={onChange}
    />
    <label htmlFor={id}>{label}</label>
    </span>
    );
    }

    class App extends Component {
    constructor(props) {
    super(props);
    this.state = {
    name: "123",
    address: "sdsd",
    review: "ds",
    gender: "female",
    city: "taipei",
    time: ["2"],
    };

    this.handleSubmit = this.handleSubmit.bind(this);
    this.handelInputChange = this.handelInputChange.bind(this);
    this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
    }

    handleSubmit(e) {
    e.preventDefault();
    console.log(this.state);
    }

    handelInputChange(e) {
    console.log(e.target.value);
    console.log(e.target);
    this.setState({ [e.target.name]: e.target.value });
    }

    handleCheckboxChange(e) {
    const { time } = this.state;
    const value = e.target.value;
    const newTime = time.filter((item) => item !== value);
    this.setState({
    time: newTime.length != time.length ? newTime : [...time, value],
    });
    }

    // onBlur 離開表單
    handleVerify(e) {
    console.log("onBlur name:", e.target.name);
    }

    render() {
    const { name, address, review, gender, city, time } = this.state;
    return (
    <div className="App">
    <form onSubmit={this.handleSubmit}>
    <div>
    姓名 :{" "}
    <input
    name="name"
    type="text"
    value={name}
    onChange={this.handelInputChange}
    onBlur={this.handleVerify}
    />
    </div>
    <div>
    地址 :{" "}
    <input
    name="address"
    type="text"
    value={address}
    onChange={this.handelInputChange}
    onBlur={this.handleVerify}
    />
    </div>
    <div>
    心得 :{" "}
    <textarea
    name="review"
    value={review}
    onChange={this.handelInputChange}
    onBlur={this.handleVerify}
    />
    </div>
    <div>
    性別:
    <input
    id="gender_male"
    checked={gender === "male"}
    type="radio"
    value="male"
    name="gender"
    onChange={this.handelInputChange}
    />
    <label htmlFor="gender_male">男</label>
    <input
    id="gender_female"
    checked={gender === "female"}
    type="radio"
    value="female"
    name="gender"
    onChange={this.handelInputChange}
    />
    <label htmlFor="gender_female">女</label>
    <input
    id="gender_other"
    checked={gender === "other"}
    type="radio"
    value="other"
    name="gender"
    onChange={this.handelInputChange}
    />
    <label htmlFor="gender_other">其他</label>
    </div>
    <div>
    縣市:
    <select
    name="city"
    value={city}
    onChange={this.handelInputChange}
    onBlur={this.handleVerify}
    >
    <option value="taipei">台北市</option>
    <option value="new_taipei">新北市</option>
    <option value="other">其他</option>
    </select>
    </div>
    <div>
    有空時間:
    <CheckBox
    value="1"
    id="week_1"
    checked={time.indexOf("1") >= 0}
    label="星期一"
    onChange={this.handleCheckboxChange}
    />
    <CheckBox
    value="2"
    id="week_2"
    checked={time.indexOf("2") >= 0}
    label="星期二"
    onChange={this.handleCheckboxChange}
    />
    <CheckBox
    value="3"
    id="week_3"
    checked={time.indexOf("3") >= 0}
    label="星期三"
    onChange={this.handleCheckboxChange}
    />
    </div>
    <div>
    <input type="submit" />
    </div>
    </form>
    </div>
    );
    }
    }

    export default App;