接下來這篇是我自己覺得最為重要的一篇,原因很簡單,我當初在做資料傳遞作業時,完全搞不清楚資料是怎麼傳遞的,一份同學們平均大概8小時的作業我花了2倍的時間還寫不出來,最後還是Gino跟Rafael從頭帶我一遍我才寫稍微暸解。
因此這章節除了複習props以外,也要了解React的資料流。
Props
props 簡單來說就是 properties(屬性)的縮寫,它是 React 中一個非常重要的概念。我們可以為自己定義的元件(component)設置屬性(props),並在元件中使用這些屬性。這些屬性可以是任何 JavaScript 值,包括字符串、數字、對象、函數等等,甚至是其他的 React 元件。透過 props 的傳遞,我們可以方便地實現組件之間的數據共享和通信。
這邊要特別強調:『 Component 的 props 是一種「唯讀且不可變的資料」,一旦由 component 的外部傳入後,就絕對不可以直接在內部修改其內容。』
這個規定不只是 React 實作設計上的刻意限制,也是一種 「讓 props 保證永遠是從外部傳入的原樣」的 pattern,進而讓 component 內更容易追蹤資料的來源,對於維持單向資料流的可靠性以及提升程式碼的可維護性都相當重要。
我們透過以下範例來了解props要怎麼使用:
首先我們將 props 傳遞給 <Avatar>
1 | const Profile = () => { |
之後在<Avatar>
裡面我們就可以使用,除了使用 props 外,我們也可以用解構的方式:
1 | //方法一: |
Props.children
有些元件組合的情況下,元件無法事先知道它下面會有哪些子元件,像是容器 (container) 類型的 Dialog 元件。
props
object 的屬性和 component 的屬性是一一對應的,但有個例外就是 props.children
這個內建屬性,它表示該元件下的所有子元件。
用法像是:
1 | const Card = (props) => { |
JSX spread syntax
有時,傳遞道具會變得非常重複:
1 | const Profile = ({ person, size, isSepia, thickBorder }) => { |
雖然這樣很容易了解它的意思,但有時候我們更看重簡潔,因此我們可以使用擴展運算子(Spread Operator):
1 | const Profile = (props) => { |
單向資料流
在 React 中,資料的流動是單向的,由父元件向子元件傳遞,不允許子元件直接修改父元件中的資料。React 的資料流基於以下兩個重要概念:
- 單向資料流:資料在元件之間只能單向傳遞,由父元件向子元件傳遞,不允許子組件直接修改父元件中的資料。
- State:State是一種特殊的資料,只能在元件內部使用。當元件狀態發生改變時,React 會自動重新渲染該元件,並通過比較新舊
Virtual DOM Tree
的差異,最後只更新需要更新的部分。
(渲染過程:Virtual DOM (React element) => 經過 root.render() => 產生畫面上的真實 DOM,詳細內容會在下一篇。)
那什麼是單向資料流呢?
我們先來看一下下面這張圖:
任何 UI 畫面只要不是完全靜態寫死的,則背後一定有其作為來源的原始資料,當我們獲得這些新的原始資料時,將這些資料套入預先定義好的模板以及渲染邏輯,進而產生使用者所看到的畫面。
而單向資料流的核心概念就是:畫面結果是原始資料透過模板與渲染邏輯所產生的延伸結果,而這個過程是單向且不可逆的。當資料發生變化時,畫面才會產生對應的變化,以資料去驅動畫面。
所謂「單向」的意思,就是只有資料變化時才能導致畫面更新,畫面無法在原始資料發生變化以外的情況隨意改變。且畫面本身也不允許以任何原因,主動逆向去直接修改原始資料。
由於這是一個單向的流程,因此畫面不會因為資料變化以外的任何原因而隨意改變,這樣就可以保證將 UI 產生的主要變因限縮在「資料」上,並且當資料更新時對應綁定的畫面就會自動發生變化,進而提升前端應用程式的可靠性與可維護性。
以上資料來源:Zet:單向資料流 & DOM 渲染策略
子元件如何把資料傳遞給父元件
剛剛有說過,在 React 資料只能從上往下傳遞,也就是由父元件傳遞到子元件。
但如果今天我要反過來傳遞有辦法嗎?
嚴格來說答案是不行的,但是我們可以透過callback function
來解決:
我們在父元件透過props傳遞
callback function
給子元件。1
2
3
4
5
6
7
8
9const App = () => {
const [todos, setTodos] = useState()
const handleAddTodoItem = () => {
setTodos(prevTodos => [...prevTodos, {id: Math.random().toString(), description: inputValue}] )
}
return (
<TodoForm handleAddTodoItem={handleAddTodoItem}/>
)
}子元件透過調用
callback function
,並將要傳遞的資料當作參數傳遞給該函式。1
2
3
4
5const TodoForm = (props) => {
const inputValue = inputRef.current!.value;
props.handleAddTodoItem(inputValue);
return (...)
}當父元件接收到子元件傳遞的資料後,再將其更新到自身的狀態或進行其他操作。
更加詳細的原理等到複習完render跟state在說明。