EP03 | React 18 全家桶複習 - 狀態(state)及參數(props)

狀態(state)使用方法 - useState()

  1. 導入 useState 函數。
  2. 調用 useState 函數,並傳入狀態初始值。
    const [數據名, 修改相應數據狀態方法] = useState(初始值)
  3. useState 函數返回值中,拿到狀態及修改狀態的方法。
  4. 在 JSX 中展示狀態。
  5. 調用修改狀態方法更新狀態。
    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
    // useState
    // 1. 導入 useState 函數。
    // 2. 執行 useState 函數並傳入初始值。
    // 3. [數據名, 修改相應數據狀態方法] = useState(初始值)
    // 4. 使用數據 修改數據

    import { useState } from 'react';

    export default function APP() {
    // [數據狀態, 修改相應數據狀態方法] -- 順序不可換
    // [count, setCount] 是一個解構賦值,代表useState()返回一個陣列
    const [count, setCount] = useState(0);

    return (
    <div>
    {/* 使用數據 */}
    {count}

    {/* 修改數據 */}
    <button
    onClick={() => {
    setCount(count + 1);
    }}
    >
    加1
    </button>
    </div>
    );
    }

注意事項: useState() 不能被嵌套在其他 if / for / 其他函數中。

錯誤 :no_entry_sign:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function APP() {
let num = 1;

// 錯誤
if(num == 2){
const [count, setCount] = useState(0);
}

return (
<div>
{count}
<button
onClick={() => {
setCount(count + 1);
}}
>
加1
</button>
</div>
);
}

重點: React的狀態(state) 是不可直接更改的,必須透過相應的狀態修改方法 才能正確修改。

受控組件及非受控組件

  • 受控組件

    可以被 React 狀態(state) 控制的組件。

    React 組件狀態是放在 state 中,而表單元素的 value 值也代表自己當前值。 React 通過將 state 與表單元素的 value 綁定再一起,在通過 state 來控制標單的值,從而保證單一數據元的特性。

  • 非受控組件

    通過手動操作 DOM 的方式來獲取表單的值(通過原生 DOM 來獲得),表單的值不受 React 的 state 控制。

    在 React 中透過使用 Refs 就可以取得 DOM 節點。

React 組件間通信 - 通過參數(props)

目的: 每個組件都是獨立且封閉的,默認情況下**只能使用自己的數據(state)**,但在開發過程值中,組件不可避免的需要互相傳遞一些數據,讓各組件間進行互相溝通、數據傳遞就叫組件通信。

父傳子

父組件將要傳入的值,透過標籤的自定義屬性加入。
子組件通過參數 props 可獲得父組件傳入的值(會是一個 object)。

父組件 App
<自定義標籤 標籤自定義屬性名=傳入子組件值 />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useState } from 'react';
// 必須先載入子組件
import Son from './Son';

export default function APP() {
const [message, setMessage] = useState("我是父組件");

return (
<div>
我是父組件
我是父組件的 message: {message}
<hr/>
<div>
我是子組件
<Son text={message} />
// 傳到子組件中時,會以 {text: "message 值"} 呈現
</div>
</div>
);
}

子組件 Son
Function Component 中會有一個參數 props,通過該參數即可獲得從上層傳入的所有值,會以 object 呈現。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useState } from 'react'

// 參數 props 必須要寫
export default function Son(props) {
console.log(props); // props 是一個對象

const [message, setMessage] = useState("我是子組件 Son");

return (
<p>
我是子組件 Son
我接收了父(上)層傳來的 message: {props.text}。<br/>
我代表了自己本身的 message: {message}。
</p>
)
}

子傳父

在父組件中定義一個 function,並將該 function 通過 props 的方式先傳入到子組件。在子組件調用該 function 時,就可以通過傳入子組件參數的方法來讓父組件獲得子組件的值。

父組件 App

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
import { useState } from 'react';
import Son from './Son';

export default function APP() {
const [message, setMessage] = useState("我是父組件");
// 將該方法傳給子組件
const getChildValue = (childValue) => {
console.log(childValue);
// 這裡執行的是父組件的 setMessage(),不是子組件的
// 因為在函數被放入堆中時,就產生閉包將父組件的 setMessage()存入到閉包環境中
setMessage(`我是父組件的 message, ${childValue}`)
}
return (
<div>
我是父組件
我是父組件的 message: {message}

<hr/>
<div>
我是子組件
{/* props 可以接收方法 */}
<Son getChildValue={getChildValue} />
</div>
</div>
);
}

子組件 Son

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useState } from 'react'

export default function Son(props) {
const [message, setMessage] = useState("我是子組件 Son");
const [text, setText] = useState("數據被修改了");
return (
<p>
我是子組件 Son
我代表了自己本身的 message: {message}。
<button
onClick={() => {
// 調用父組件傳入的方法
props.getChildValue(text);
}}
>
改變父組件中的 message
</button>
</p>
)
}

兄弟間

兄弟間的通信非常麻煩,Son 組件必須先將值傳給父組件(App),再由父組件(App)通過 props 傳給 Daughter 組件。

父組件 App

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
import { useState } from 'react';
import Son from './Son';
import Daughter from './Daughter';

export default function APP() {

const [message, setMessage] = useState("我是父組件");

// 當子組件 Son 調用並傳入實參後,調用相應的 state 更新方法,將父組件的值更新,就會觸發重新渲染,並將新值傳給子組件 Daughter
const getChildValue = (childValue) => {
console.log(childValue);
setMessage(`我是父組件的 message,我要將 Son 組件的值 \"${childValue}\" 傳給 Daughter 組件`)
}

return (
<div>
我是父組件
我是父組件的 message: {message}

<hr/>
<div>
我是子組件群
<Son getChildValue={getChildValue} />
<Daughter message={message} />
</div>
</div>
);
}

子組件 Son

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useState } from 'react'

export default function Son(props) {
const [message, setMessage] = useState("我是子組件 Son ");
return (
<p>
我是子組件 Son
我代表了自己本身的 message: {message}。

{/* 使用父組件傳來的方法,將子組件的值往上傳*/}
<button
onClick={() => {
props.getChildValue(message);
}}
>
改變父組件中的 message
</button>
</p>
)
}

子組件 Daughter

1
2
3
4
5
6
7
8
9
export default function Daughter(props) {
return (
<div>
我是子組件Daughter
{/* 通過 props 接收父組件傳來的值 */}
我要接收 Son 組件訊息: {props.message}
</div>
)
}

多層嵌套的組件 - createContext 來實現跨組件通信

如果通信只有一層,使用上面的方法還不算麻煩,但是如果有多層的話,那簡直是惡夢。
如果是父組件要傳值給孫組件(2層),則可以通過 React 的 createContext 方法來解決通信問題。

每個組件都是一個檔案的情況下

  1. 創建一個新的檔案(Transmit.js)專門用來放置要傳遞的值的初始值,調用 createContext(defaultValue)。
1
2
import { createContext } from 'react';
export const Transmit = createContext({});
  1. 引入 Transmit ,並使用 Transmit.Provider 包裹根組件,並提供數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useState } from 'react';
import { Transmit } from './Transmit';
import Son from './Son';
export default function APP() {
const [message, setMessage] = useState("我是父組件");
return (
<Transmit.Provider value={message}>
{/* 根組件內容 */}
<div>
我是父組件
我是父組件的 message: {message}
<hr/>
<div>
我是子組件
<Son />
</div>
</div>
</Transmit.Provider>
);
}
  1. 需要用到數據的組件,使用 useContext(欲取得的數據的檔案) 來獲取數據。
1
2
3
4
5
6
7
8
9
10
11
12
import {useContext} from 'react'
import { Transmit } from './Transmit';

export default function GrandChild() {
const msg = useContext(Transmit);
return (
<div>
GrandChild 組件:
<span>{msg}</span>
</div>
)
}

可將 Transmit.js 是唯一個存放數據的空間,比較好理解。