前言
因為我前段時間學了 React,我會在這邊文章紀錄我學到的內容。
建立一個 Hello World
在終端機輸入下面的指令,會在該目錄下生成一個名為 my-app 的 React 應用目錄。
註:npx 是 npm 5.2+ 或更高版本附帶的包運行器工具。
npx
1
|
npx create-react-app my-app
|
npm
1
|
npm init react-app my-app
|
Yarn
Yarn 0.25+ 才能使用 yarn create <starter-kit-package>
1
|
yarn create react-app my-app
|
Project 結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
├── serviceWorker.js
└── setupTests.js
|
這部份跟 Vue-cli 蠻相似的,只是在目錄結構上不如 Vue-cli。
檔案或文件 |
用途 |
node_modules/ |
依賴 |
package.json |
包管理文件 |
public/ |
公共資源目錄 |
src/ |
源碼 |
src/index.js |
根頁面 |
我這裡是建議 src/
改成跟 Vue-cli 初始化時相同的結構。可以自己定義想要怎麼樣的結構,所以不一定要像我下面寫的一樣。
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
|
my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── components
│ └── App
│ ├── App.css
│ ├── App.js
│ └── App.test.js
├── index.css
├── index.js
├── router
│ └── index.js
├── store
│ └── index.js
├── static
│ ├── css
│ ├── img
│ │ └── logo.svg
│ └── js
├── serviceWorker.js
├── setupTests.js
└── views
├── Home
│ ├── Home.css
│ └── Home.js
└── About
├── About.css
└── About.js
|
指令
下面兩個之一的指令可以運行項目。
下面是打包指令
1
2
|
npm run build
yarn build
|
JSX
語法
JSX 就是一種可以用 JS 寫 HTML 的語法,最後編譯會轉成 HTML,光是可以用 JS 寫網頁就可以使開發上更加靈活。
下面是一個 JS 的普通對象,包括一開始初始化項目的 App.js
也是對象。可以改造成下面例子的樣子。
對象與組件是不同的東西,組件命名開頭必須是大寫,對象則不用,element = <h1>你好,世界!</h1>;
可以想像成是變量與值,element
被賦值組件值。
1
2
3
4
5
6
7
8
9
10
11
|
import React, { Children } from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
const element = <h1>你好,世界!</h1>;
// <App /> 普通對象
let root = document.getElementById("root");
ReactDOM.render(element, root);
|
簡單的 Clock 實例
這例子是使用間隔函數不斷重新渲染頁面,但是因為根頁面渲染被限制住了,所以不適合。
註:在選取元素 ID 不一定要用 document.getElementById("root")
也可以使用 document.querySelector('#root')
方式操作 DOM。
1
2
3
4
5
6
7
8
9
10
11
|
function clock() {
let time = new Date().toLocaleTimeString();
let element = <h1>Current Time: { time }</h1>;
let root = document.querySelector('#root')
ReactDOM.render(element, root)
}
clock();
// 間隔函數
setInterval(clock, 1000);
|
下面的例子是上面的改版,是函數式組件。
註:組件名稱都必須是開頭大寫,不然無法被 JSX 語法識別。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// react 函數式組件
// 組件開頭必須大寫
function Clock(props) {
return (
<h1>Current Time: { props.date.toLocaleTimeString() }</h1>
);
}
function run() {
// date 傳參
ReactDOM.render(
<Clock date={ new Date() } />,
document.querySelector('#root')
)
}
setInterval(run, 1000);
|
Style
寫 Style 對象時參數是駝峰命名法,當然也可以使用像是 "background-img": "url(https...)"
的方法表示樣式參數,雖然可以使用原來的命名,但是不建議用這方法,因為 Code 的規範。
因為 class
在 JS 中表示類,是 Keyword,所以要改用 className
表示 HTML 元素中的類樣式,兩者是不同的概念。
在 JSX 語法中要寫註解的話,分成 HTML 使用 {/* */}
這方法,因為 {}
是放 JS 語法的地方,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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
// 駝峰命名
// style 中如果存在多個單字的屬性組合,第二個單字開始,首字母大寫。
// 可以不首字母大寫,但要換寫法
let exampleStyle = {
background: "skyblue",
borderBottom: "1px solid red",
// 不用首字母大寫的方法 e.g.
// "background-img": "url(https...)"
}
let element = (
<div>
<h1 style={exampleStyle}>Hello World</h1>
</div>
)
// class 在 Js 中是關鍵字,所以要改成 className
// className 和 style 等屬性不能是 string,必須是對象參數
// 不能有多個 className 或是多個 style,會被自動刪掉剩一個
// className 可以用 string 相加的方式
let classStr = "abc"
let element1 = (
<div>
<h1 className={"cba " + classStr}>Hello World</h1>
</div>
)
// react 中 className array 無法像 Vue 一樣可以自動拆解
// 所以要加上 join 去做間隔
let classArray = ['abc', 'cba'].join(" ");
let elementArrayClass = (
<div>
{/* 在 HTML 寫註釋 */}
<h1 className={classArray}>Hello World</h1>
</div>
// 在 JS 寫註釋
)
ReactDOM.render(
element1,
document.querySelector('#root')
)
|
組件
命名開頭都必須是大寫。
函數式組件
下面是前面簡單 Clock 實例的函數式組件,傳參進行渲染。
- 函數式組件是靜態組件,頂多傳參數。
- 函數式組件 stateless 無生命週期。
1
2
3
4
5
|
function Clock(props) {
return (
<h1>Current Time: { props.date.toLocaleTimeString() }</h1>
);
}
|
類組件
下面是最簡單的類組件
- 類組件可以定義方法。
- 類組件 stateful 有生命週期。
- 類組件中可以在包含組件 -> 複合組件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 類組件 - 可以定義方法
// 有事件或是動態的 使用類組件:e.g. 點擊事件
// 函數式組件是靜態組件,頂多傳參數
// 函數式組件 stateless 無生命週期
// 類組件 stateful 有生命週期
// 類組件中可以在包含組件 -> 複合組件
class HelloWorld extends React.Component {
render(){
console.log(this)
return (
<div>
<h1>類組件 Hello World</h1>
{/* 類組件傳參 */}
{/* <h2>hello: {this.props.name}</h2> */}
</div>
)
}
}
ReactDOM.render(
<HelloWorld />,
document.querySelector('#root')
)
|
類組件實現簡單 Clock 實例
- React State 相當於 Vue 的 Data。**
- React 類組件中要修改 State 的話,不能直接修改,要使用
this.setState()
方法進行修改。
constructor()
是構造函數,render()
是渲染函數。
super()
是繼承父類的方法,props
可以用來傳參。
下面的例子只是簡單實現的 Clock 顯示,這裡只是舉個例子讓你了解怎麼使用類組件。下面例子最尾部的註解是因為一開始我們沒有使用生命周期函數 componentDidMount()
去做時間的更新,後面因為用到了生命周期函數,所以要將原本的註解掉,算是錯誤示範。
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
|
// React State 相當於 Vue 的 Data
// 用類組件實現
class ClockClass extends React.Component {
// 構造函數
constructor(props) {
super(props)
// 狀態 (數據) -> View
this.state = {
time: new Date().toLocaleTimeString()
}
}
render() {
// this.state.time = new Date().toLocaleTimeString();
return (
<div>
<h1>{this.state.time}</h1>
</div>
)
}
// 生命周期函數
// 組件渲染完成時調用的函數
componentDidMount() {
setInterval(() => {
// 錯誤的改變方式
// this.state.time = new Date().toLocaleTimeString();
// 正確的修改,使用 setState
// 切勿直接修改 state 數據,直接 state 重新渲染內容,需使用 setState
// setState 是異步
// 通過 this.setState 修改完數據後,並不會立即修改 DOM 裡面的內容
// react 會在這個修改函數內容所有設置改變後,統一對比虛擬 DOM 對象,然後再統一修改,提升性能
this.setState({
time: new Date().toLocaleTimeString()
})
}, 1000)
}
}
ReactDOM.render(
<ClockClass />,
document.querySelector('#root')
)
// 不推薦的方法,因為跟組件 Dom 渲染綁在一起
// setInterval(() => {
// ReactDOM.render(
// <ClockClass />,
// document.querySelector('#root')
// )
// }, 1000)
|
類組件方法綁定
類的方法需要進行綁定 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
|
class Tab extends React.Component {
constructor(props) {
super(props)
// 設置狀態和數據
this.state = {
isActive: "",
strClass: ""
}
this.clickEvent = this.clickEvent.bind(this)
}
clickEvent() {
console.log("click event")
}
render() {
return (
<div>
<button onClick={this.clickEvent}>content 1</button>
<button>content 2</button>
<div className="content active">
<h1>content 1</h1>
</div>
<div className="content">
<h1>content 2</h1>
</div>
</div>
)
}
}
|
組件傳值
父傳子
在 JSX 中,只能父傳子組件數據,單向流動,不能子傳父。
注意:props
可以傳遞函數,props
可以傳遞父函數的元素,就可以去修改父元素的 state
,從而達到子組件傳遞數據給父元素。
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
|
// Props
// 父傳子組件數據,單向流動,不能子傳父
// props 可以設置默認值
// 注意:props 可以傳遞函數,props 可以傳遞父函數的元素,就可以去修改父元素的 state,從而達到傳遞數據給父元素
// 在父元素中使用 state 去控制子元素 props 的從而達到父元素數據傳遞給子元素
class ParentCom extends React.Component {
constructor(props) {
super(props)
this.state = {
isAction: true
}
// 綁定事件
this.changeShow = this.changeShow.bind(this)
}
render() {
return (
<div>
<button onClick={this.changeShow}>控制子元素顯示</button>
<ChildrenCom isAction={this.state.isAction}/>
</div>
)
}
changeShow() {
this.setState({
isAction: !this.state.isAction
})
}
}
class ChildrenCom extends React.Component {
constructor(props) {
super(props)
}
render() {
let strClass = null;
if(this.props.isAction) {
strClass = 'active'
} else {
strClass = ''
}
return (
<div className={"content " + strClass}>
<h1>我是子元素</h1>
</div>
)
}
}
ReactDOM.render(
<ParentCom />,
document.querySelector('#root')
)
|
子傳父
調用父元素的函數從而操作子元素的數據,從而實現 子 -> 父。
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
|
// 子傳父
// 調用父元素的函數從而操作子元素的數據,從而實現 子 -> 父
class ParentCom2 extends React.Component {
constructor(props) {
super(props)
this.state = {
childData: null
}
}
render() {
return (
<div>
<h1>子傳父的數據:{this.state.childData}</h1>
{/* 傳遞函式給子組件 */}
<ChildrenCom2 setChildData={this.setChildData}/>
</div>
)
}
setChildData = (data) => {
this.setState({
childData: data
})
}
}
class ChildrenCom2 extends React.Component {
constructor(props) {
super(props)
this.state = {
msg: "hello"
}
}
render() {
return (
<div>
<button onClick={this.sendData}>傳遞 hello 給父元素</button>
{/* 更簡單的方法 */}
<button onClick={ () => {this.props.setChildData('直接傳')}}>傳遞 hello 給父元素</button>
</div>
)
}
// 搞成箭頭函數
sendData = () => {
console.log(this.state.msg)
// 用 props 拿到父組件的函數
// 將子元素數據傳遞給父元素
this.props.setChildData(this.state.msg)
}
}
ReactDOM.render(
<ParentCom2 />,
document.querySelector('#root')
)
|
Event 事件綁定
onClick
用來綁定事件,在綁定的時候不能像 Vue 中 @click="..."
一樣使用 String,要使用 {}
傳入一個函數。下面的事件使用的箭頭函數,這樣就不需要在構造函數寫綁定。
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
|
// React event
// 綁定事件使用駝峰命名法
// {} 傳入一個函數,不是 String
// 原生 js 阻止默認行為時,可以直接返回 return false
// react 中,阻止默認必須使用 e.preventDefault();
class ClickCom extends React.Component {
render() {
return (
<div>
<form action="https://www.google.com" target="_blank">
<button onClick={this.preventEvent}>submit</button>
</form>
{/* es6 箭頭函數 */}
<button
onClick={(e) => {
this.preventEvent1("msg: 123", e);
}}
>
submit
</button>
{/* 不使用 es6 箭頭函數傳遞多個參數的方式 */}
<button
onClick={function (e) {
this.preventEvent1("msg: 123", e);
}.bind(this)}
>
submit
</button>
</div>
);
}
preventEvent = (e) => {
console.log(e.preventDefault);
e.preventDefault();
// js 原生寫法
// return false
};
preventEvent1 = (msg, e) => {
console.log(msg);
// js 原生寫法
// return false
};
}
ReactDOM.render(<ClickCom />, document.querySelector("#root"));
|
條件渲染
下面是簡單的條件渲染,情況二因為可以是三元運算式,直接透過運算賦值給一個變量成一個 JSX 對象,所以在這裡沒有寫,以下面情況一的例子就是 let element = this.state.isLogin ? <UserGreet/> : <UserLogin/>
然後 return element
,差別不大。
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
|
// React 條件渲染
// 1. 直接通過條件運算返回要渲染的 JSX 對象
// 2. 通過條件運算得出 JSX 對象,將對象渲染到模板
// 情況一
function UserGreet(params) {
return (<h1>welcome to sign in</h1>)
}
function UserLogin(params) {
return (<h1>請先登入</h1>)
}
class ParentCom extends React.Component {
constructor(props) {
super(props)
this.state = {
isLogin: false
}
}
render() {
if(this.state.isLogin) {
return (<UserGreet/>)
} else {
return (<UserLogin/>)
}
}
}
ReactDOM.render(<ParentCom />, document.querySelector("#root"));
// 情況二
// 自己類推
|
List 渲染(循環渲染)
下面是簡單的 List 渲染例子:
arrayHTML
是 JSX 對象 Array。但是這個例子因為使用上不夠優雅靈活,所以不適合,應該要使用像是傳參動態渲染的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// List 渲染
let array = ["banana", "apple", "peach"];
let arrayHTML = [<li>banana</li>, <li>apple</li>, <li>peach</li>]
class Welcome extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<ul>
{array}
{arrayHTML}
</ul>
</div>
)
}
}
ReactDOM.render(<Welcome />, document.querySelector("#root"));
|
下面是更進的改造版,將一部份作為函式數組件,傳入參數可以進行渲染,相當於模板。
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
|
// 作為模板
function ListItem(props) {
return (
<li key={props.index}>
<h3>
{props.index} : {props.data.title}
</h3>
<p>{props.data.content}</p>
</li>
);
}
// 有動態事件的方式
class ListItem2 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<li
key={this.props.index}
onClick={(event) => {
this.clickEvent(this.props.index, this.props.data.title, event);
}}
>
<h3>
{this.props.index} : {this.props.data.title}
</h3>
<p>{this.props.data.content}</p>
</li>
);
}
clickEvent = (index, title, event) => {
alert(index + " - " + title);
};
}
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
title: "NO1 111",
content: "11111",
},
{
title: "NO2 222",
content: "222",
},
{
title: "NO3 333",
content: "333",
},
],
};
}
render() {
// 最原始方法
// let listArr = [];
// for(let i = 0; i < this.state.list.length; i++) {
// let item = (
// <ul>
// <li><h3>{this.state.list[i].title}</h3></li>
// <li><h5>{this.state.list[i].content}</h5></li>
// </ul>
// )
// listArr.push(item);
// }
// 使用數組 map 方法,對每一項數據進行 JSX 的形式進行加工,
// 最終得到 1 個每一項都是 JSX 對象的數組,將數組渲染到模板。
// Key 需要放入每一項中
let listArr = this.state.list.map((item, index) => {
return (
// <li key={index}>
// <h3>{index} : { item.title }</h3>
// <p>{ item.content }</p>
// </li>
// <ListItem data={item} index={index} key={index} />
<ListItem2 data={item} index={index} key={index} />
);
});
return (
<div>
{/* 最原始方式 */}
{/* {listArr} */}
<ul>{listArr}</ul>
</div>
);
}
}
ReactDOM.render(<Welcome />, document.querySelector("#root"));
|
組件生命週期
組件從實例化到最終從頁面銷毀,整個過程就是生命週期,在這生命週期中,我們有許多可以調用的方法,俗稱鉤子函數。
三個狀態
- Mounting 將組件插入到 DOM 中
- Updating 將數據更新到 DOM 中
- UnMounting 將組件移除 DOM
生命周其中的鉤子函數(方法、事件)
- ComponentWillMount:組件將要渲染(已過時,不能用)
- ComponentDidMount:組件渲染完畢
- ComponentWillReceiveProps:組件將要接受 props 數據(已過時,不能用)
- ShouldComponentUpdate:組件接收到新的 state 或是 props,判斷是否更新,返回布爾值
- ComponentWillUpdate:組件將要更新(已過時,不能用)
- ComponentDidUpdate:組件已經更新完畢
- ComponentWillUnMount:組件將要卸載
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
|
// 可以解構
// 這樣就不用每次都寫 react.Component
import { Component } from 'react'
class ComLife extends Component {
constructor(props) {
super(props) // 調用繼承 Component 的 構造函數
this.state = {
msg: "hello world msg"
}
console.log("constructor 構造函數")
}
componentWillMount() {
// 已過時
// 通常用來 ajax 請求
// 添加動畫前的類
console.log("ComponentWillMount 組件將要渲染")
}
componentDidMount() {
// 用來渲染動畫
console.log("ComponentDidMount 組件渲染完畢")
}
componentWillReceiveProps() {
// 已過時
// 用來查看 props 內容是什麼
console.log("ComponentWillReceiveProps 組件將要接受 props 數據")
}
componentWillUpdate() {
// 已過時
console.log("ComponentWillUpdate 組件將要更新")
}
componentDidUpdate() {
console.log("ComponentDidUpdate 組件已經更新完畢")
}
componentWillUnmount() {
console.log("ComponentWillUnMount 組件將要卸載")
}
render() {
console.log("render 渲染函數")
return (
<div>
<h1>hello</h1>
</div>
)
}
}
|
插槽
組件中寫入內容,這些內容可以被識別和控制。React 需要自己開發支持插槽功能。
原理:組件中寫入的 HTML,可以傳入到 props 中。
註:這裡的 data-{ name } 是 HTML 中自定義屬性。e.g. 下面例子中的 data-position
和 data-index
。
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
|
// 組件中寫入內容,這些內容可以被識別和控制。React 需要自己開發支持插槽功能
// 原理:組件中寫入的 HTML,可以傳入到 props 中
class ParentCom extends React.Component {
constructor(props) {
super(props);
this.state = {
arr: [1, 2, 3],
};
}
render() {
console.log(this.props);
return (
<div>
<h1>組件插槽</h1>
{this.props.children}
<ChildCom>
<h1 data-position="header">這是放置到頭部的內容</h1>
<h1 data-position="main">這是放置到主要的內容</h1>
<h1 data-position="footer">這是放置到尾部的內容</h1>
</ChildCom>
</div>
);
}
}
class ChildCom extends React.Component {
render() {
let headerCom, mainCom, footerCom;
this.props.children.forEach((item, index) => {
if (item.props["data-position"] === "header") {
headerCom = item;
} else if (item.props["data-position"] === "main") {
mainCom = item;
} else {
footerCom = item;
}
});
return (
<div>
<div className="header">{headerCom}</div>
<div className="main">{mainCom}</div>
<div className="footer">{footerCom}</div>
</div>
);
}
}
class RootCom extends React.Component {
render() {
return (
<ParentCom>
{/* 插槽 */}
{/* 添加 data 屬性可以傳參,data- 後面接想要取的屬性名 */}
<h2 data-name="a" data-index={this.state.arr[0]}>
子組件一
</h2>
<h2 data-name="b" data-index={this.state.arr[1]}>
子組件二
</h2>
<h2 data-name="c" data-index={this.state.arr[2]}>
子組件三
</h2>
</ParentCom>
);
}
}
ReactDOM.render(<RootCom />, document.querySelector("#root"));
|
React Router 路由
根據不同的路徑,顯示不同的組件(內容),React 使用庫 react-router-dom。
安裝
1
|
npm install react-router-dom
|
使用
ReactRouter 三大組件
Router:所有路由組件的根組件(底層組件),包裏路由規則的最外層容器
Route:路由規則匹配組件,顯示當前規則對應的組件
Link:路由跳轉組件
注意:如果要精準匹配,那麼可以在 route 上設置 exact 屬性。精準匹配的意思就是完整路徑包含父路徑。
Hash 與 History 模式,Hash 就是有 ‘#’ 符號,我們使用 History。
引入
1
2
3
4
5
6
|
// hash 模式
// as 是取別名
// import { HashRouter as router, Link, Route } from 'react-router-dom'
// History 模式 / 後端匹配使用
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
|
基本使用
<Link></Link>
相當於 Vue 中的 <route-link></route-link>
,用於路由跳轉。
<Router></Router>
路由配置,可以設置基本路徑(Base Path),裡面包 <Route></Route>
,<Route></Route>
是路由,component
指定路由對應的組件。
<Link replace></Link>
的 replace
屬性可以進行路由的取代替換,取代當前瀏覽器路由地址並跳轉。
- 動態路由:
<Route path="/news/:id" component={News}></Route>
。
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
|
function Home(params) {
return (
<div>
<h1>admin首頁</h1>
</div>
);
}
function Me(params) {
console.log(params);
return (
<div>
<h1>admin個人頁面</h1>
</div>
);
}
function Product(params) {
return (
<div>
<h1>admin產品頁面</h1>
</div>
);
}
function News(params) {
console.log(params)
return (
<div>
新聞頁
新聞 id: {params.match.params.id}
</div>
)
}
class App extends React.Component {
render() {
// 這裡的 search 是 query string
// 可以傳 state
let meObj = {
pathname: "/me", // 路徑
search: "?username=admin", // get 請求參數
hash: "#abc", // 設置 hash 錨值
state: { msg: "helloWorld" }, // 傳入組件的數據
};
return (
<div id="app">
{/* 全局 */}
<div>所有頁面都顯示的內容</div>
{/* Router 可以在一個組件中寫多個 */}
{/* <Router>
<Route path="/" exact component={() => (<div>首頁</div>)}></Route>
<Route path="/me" component={() => (<div>me</div>)}></Route>
<Route path="/product" component={() => (<div>product</div>)}></Route>
</Router> */}
{/* Router 設置基礎路徑 basename */}
<Router basename="/admin">
<div className="nav">
<Link to="/">首頁</Link>
<Link to="/product">產品</Link>
{/* Link 可以設置 to 屬性進行頁面跳轉,to 屬性可以直接寫路徑的字符串,也可以通過 1 個對象,進行路進的配置 */}
{/* replace 屬性 將新地址制換成歷史訪問紀錄的原地址 */}
<Link to={meObj} replace>個人中心</Link>
{/* 動態路由 */}
<Link to="/news/456789">news</Link>
</div>
<Route path="/" exact component={Home}></Route>
<Route path="/product" exact component={Product}></Route>
<Route path="/me" exact component={Me}></Route>
{/* 動態路由 */}
<Route path="/news/:id" component={News}></Route>
</Router>
</div>
);
}
}
export default App;
|
重定向組件與 Switch 組件
重定向組件:如果訪問某個組件時,如果有重定向組件,那麼就會修改頁面路徑,使得頁面內容顯示為所定向路徑的內容。
Switch 組件:讓 Switch 組件內容的 Route 只匹配一個,只要匹配到了,剩餘的規則就不再匹配。
建議配置路由時要使用 Switch 組件,這樣路由的配置邏輯比較謹慎。
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
|
// 重定向組件
// 如果訪問某個組件時,如果有重定向組件,那麼就會修改頁面路徑,使得頁面內容顯示為所定向路徑的內容
// Switch 組件
// 讓 Switch 組件內容的 Route 只匹配一個,只要匹配到了,剩餘的規則就不再匹配
import { Redirect, Switch } from "react-router-dom";
function LoginInfo(params) {
// params.loginSuccess = 'success'
// params.loginSuccess = 'fail
if (params.location.state.loginState === "success") {
return (
// 重定向組件
<Redirect to="/admin"></Redirect>
);
} else {
return <Redirect to="/login"></Redirect>;
}
}
let formCom = () => {
let pathObj = {
pathname: "/loginInfo",
state: {
loginState: "success",
},
};
return (
<div>
<h1>表單驗證</h1>
<Link to={pathObj}>登入後表單驗證</Link>
</div>
);
};
class ChildCom extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button onClick={this.clickEvent}>跳轉到首頁</button>
</div>
);
}
clickEvent = () => {
console.log(this.props);
// 可以傳值
// this.props.history.push("/", {msg: "這是由 ChildCom 發給首頁的數據"})
// this.props.history.replace("/", {msg: "這是由 ChildCom 發給首頁的數據"})
// 前進
this.props.history.go(1);
this.props.history.goForward();
// 後退
this.props.history.go(-1);
this.props.history.goBack();
};
}
class App extends React.Component {
render() {
return (
<div>
<Router>
<Switch>
<Route
path="/"
exact
component={(props) => {
console.log(props);
return <h1>首頁</h1>;
}}
></Route>
<Route path="/form" exact component={formCom}></Route>
<Route
path="/login"
exact
component={() => <h1>登入頁</h1>}
></Route>
<Route path="/loginInfo" exact component={LoginInfo}></Route>
<Route path="/admin" exact component={() => <h1>Admin</h1>}></Route>
{/* Router 會全部匹配,所以如果有兩個相同的 path 會兩個都匹配 */}
{/* 所以需要用到 Switch 去匹配,匹配到一個成功就不會繼續匹配 */}
<Route path="/abc" exact component={() => <h1>abc1</h1>}></Route>
<Route path="/abc" exact component={() => <h1>abc2</h1>}></Route>
<Route path="/child" exact component={ChildCom}></Route>
</Switch>
</Router>
</div>
);
}
}
export default App;
|
狀態管理
解決 React 數據管理(狀態管理),用於中大型項目,數據量龐大,組件之間數據交互較多的情況下使用。
如果你不知道是否需要使用 Redux ,那麼你就不需要用他。Redux 是個給 JavaScript 應用程式所使用的可預測 state 容器,Redux 跟 React 並沒有關係,你可以用 React、Angular、Ember、jQuery 或甚至原生 JavaScript 來撰寫 Redux 應用程式。
功能
redux
安裝
1
|
npm install --save redux
|
工具
- store: 數據倉庫,保存數據的地方
- State: state 是一個對象,這個對象包含整個應用所需要的數據
- Action: 一個動作,觸發數據改變的方法
- Dispatch: 將動作觸發成方法
- Reducer: 是一個函數,通過獲取動作,改變數據,生成一個新的狀態,從而改變頁面
使用
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
|
import { createStore } from "redux";
// 用於通過動作,創建新的 state
// reduce 有兩個作用,一個釋初始化數據,第二個是通過獲取動作,改變數據
const reducer = function (state = { num: 0 }, action) {
switch (action.type) {
case "add":
state.num++;
break;
case "decrement":
state.num--;
break;
default:
break;
}
return { ...state }; // 相當於對象的 COPY
};
// 創建倉庫
const store = createStore(reducer);
function add() {
// 通過倉庫的方法 dispatch 進數據修改
// dispatch 觸發 reducer
store.dispatch({ type: "add" });
console.log(store.getState());
}
function decrement() {
store.dispatch({ type: "decrement" });
console.log(store.getState());
}
const Counter = function () {
let state = store.getState();
return (
<div>
<h1>計數數量:{state.num}</h1>
<button onClick={add}>+1</button>
<button onClick={decrement}>-1</button>
</div>
);
};
ReactDOM.render(<Counter />, document.querySelector("#root"));
// 監聽數據變化,重新渲染
// 當數據改變時觸發
store.subscribe(() => {
ReactDOM.render(<Counter />, document.querySelector("#root"));
});
|
react-redux
react-redux 是 redux 的擴展套件,用來綁定 redux。
安裝
1
|
npm install --save react-redux
|
工具
- Provider:自動將 store 裡的 state 和組件進行關聯
- connect:將數據倉庫的 state 和修改 state 的方法映射到組件上,形成新的組件
使用
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
|
import { createStore } from "redux";
import { Provider, connect } from 'react-redux'
class Counter extends React.Component {
render() {
// 計數,通過 store 的 state 傳給 props,直接通過 props 就可以將 state 的數據獲取
const value = this.props.value;
// 將修改數據的事件或者方法傳入到 props
const onAddClick = this.props.onAddClick;
// 等同於 VueX 的 mapMutation mapState
return (
<div>
<h1>計數數量:{value}</h1>
<button onClick={onAddClick}>+1</button>
</div>
)
}
}
// 動作
const addAction = {
type: 'add'
}
const reducer = function (state = { num: 0 }, action) {
switch (action.type) {
case "add":
state.num++;
break;
case "decrement":
state.num--;
break;
default:
break;
}
return { ...state }; // 相當於對象的 COPY
};
const store = createStore(reducer);
// 將 state 映射到 props 函數
function mapStateToProps(state) {
return {
value: state.num
}
}
// 將修改 state 數據的方法,映射到 props,默認會傳入 store 裡的 dispatch 方法
function mapDispatchToProps(dispatch) {
return {
onAddClick: () => {
dispatch(addAction)
}
}
}
// 將上面的這兩個方法,將數據倉庫的 state 和修改 state 的方法映射到組件上,形成新的組件
const NewApp = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
// Provider 組件:自動將 store 裡的 state 和組件進行關聯
ReactDOM.render(
<Provider store={store}>
<NewApp></NewApp>
</Provider>,
document.querySelector("#root")
)
|
Reference