本系列涵蓋了使用 React 的全部知識,分爲上、中、下三篇。此爲最後一篇。前端
本系列涵蓋 React v16.9,但更多的是 React 全面解析,具體 React v16.9 新特性可查看 [譯]React v16.9 新特性。react
完整系列包含:git
上篇主講 React 元素渲染,可查看:深刻解讀 React 核心之元素篇。github
中篇主講 React 元素組件,可查看:深刻解讀 React 核心之組件篇。算法
下篇主講 React Hooks,可查看:深刻解讀 React 核心之 Hooks 篇。數組
Hook 是一個特殊的函數,它可讓你「鉤入」 React 的特性。全部 Hooks 都以 use 開頭。其中一些可爲函數組件增長狀態(如 useState
),一些可用於管理反作用(如useEffect),一些可用於緩存 memoize 函數和對象(如useCallback、 useMemo)。瀏覽器
React hook 函數只能用於函數組件,不能在類組件中使用它們。緩存
下面是一個基本示例:函數
const Button = () => {
let count = 0;
return (
<button>{count}</button>
);
};
ReactDOM.render(<Button />, mountNode); 複製代碼
咱們給 button
組件增長一個 onClick
事件:post
const Button = () => {
let count = 0;
return (
<button onClick={() => console.log('Button clicked')}> {count} </button>
);
};
ReactDOM.render(<Button />, mountNode); 複製代碼
每次咱們點擊 Button
按鈕時,onClick
都會調用內聯箭頭函數,向控制檯輸出 Button clicked
。
請注意:React 處理的全部與 DOM 相關的屬性都必須採用駝峯式,不然,React 將會報錯。同時,React 還支持使用自定義 HTML 屬性,而且必須採用全小寫格式。
React 中的一些 DOM 屬性與它們在原生 DOM API 中的屬性略有不一樣。例如
onChange
事件。在原生瀏覽器中,當你在表單字段中單擊時,一般會觸發它。在 React 中,onChange
只要更改了表單字段的值就會觸發。React 中的某些屬性的命名與 HTML 等效的不一樣。例如 React 屬性
className
,它至關於class
在 HTML中使用屬性。有關 React 屬性和 DOM 屬性之間差別的完整列表,請參閱jscomplete.com/react-attri…。
跟蹤狀態更新並觸發虛擬樹協調算法更新到真實 DOM 上,React 須要瞭解組件中使用的全部元素髮生的任何更改。爲了更有效的執行此操做,React 須要爲組件中引入的每一個狀態元素使用特殊的 getter
和 setter
。這就是 useState
發揮做用的地方。它定義了一個狀態元素,併爲它提供了一個 getter
和 setter
!
useState
是容許你在 React 函數組件中添加 state 的 Hook。
如下是咱們嘗試實現的 count state 元素所需的內容:
const [count, setCount] = React.useState(0);
複製代碼
React.useState()
函數返回一個包含 2 個元素的數組。咱們使用數組解構來命名,第一項是 useState
返回的第一個值 count
(getter),第二項是返回的第二個值 setCount
函數(setter)。等價於下面的代碼:
var countStateVariable = useState(0); // 返回一個有兩個元素的數組
var count = countStateVariable[0]; // 數組裏的第一個值
var setCount = countStateVariable[1]; // 數組裏的第二個值
複製代碼
第一項爲定義的 state 變量名稱,示例中叫 count
, 可是咱們能夠叫他任何名字,好比 hello
。這是一種在函數調用時保存變量的方式 —— useState
是一種新方法,它與 class 裏面的 this.state
提供的功能徹底相同。通常來講,在函數退出後變量就就會」消失」,而 state 中的變量會被 React 保留。
第二項 "function" 將在調用時更改 state
元素的值(若是須要,它將觸發DOM處理)。每次 setCount
調用該函數時,React 都將從新渲染 Button
組件,該組件將刷新組件中定義的全部變量(包括 count
值)。咱們傳遞給 setCount
的參數將成爲新的值 count
。
React.useState()
方法裏面惟一的參數就是初始 state。不一樣於 class 的是,它能夠是字符串,數字,數組等,而不必定是對象。在示例中,咱們傳了 0
做爲變量的初始 state
。(若是咱們想要在 state 中存儲兩個不一樣的變量,只需調用 useState()
兩次便可。)
const Button = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}> {count} </button>
);
};
ReactDOM.render(<Button />, mountNode); 複製代碼
請注意咱們不對 UI 自己進行任何操做,咱們只是實現了一個 action
來改變 JavaScript 對象(在內存中)!React 負責將咱們的聲明性描述轉換爲瀏覽器中的實際UI。
除了使用別名
React.useState
,還能夠直接導入useState
使用:import React, { useState } from 'react'; 複製代碼
將 Button
組件拆分爲如下兩個部分:
Button
組件爲按鈕元素,並有一個靜態標籤;Display
組件以顯示計數值。新 Display
組件將是純粹的表示組件,沒有本身的狀態或交互。這很正常。並不是每一個 React 組件都必須與狀態或交互掛鉤。
const Display = (props) => (
<pre>COUNT VALUE HERE...</pre>
);
複製代碼
Display
組件的職責是簡單地顯示它將做爲 props
接收的值。例如,pre
元素用於託管值是該職責的一部分。該應用程序中的其餘組件對此沒有發言權!
咱們如今有兩個要渲染的元素:Button
和 Display
。咱們不能將它們直接相互渲染,以下所示:
// This will not work
ReactDOM.render(<Button /><Display />, mountNode); 複製代碼
在React中,相鄰的元素不能像這樣呈現,由於當 JSX 被轉換時,它們中的每個都被轉換爲函數調用。有如下幾個解決方案。
方案一:數組形式
ReactDOM.render([<Button />, <Display />], mountNode); 複製代碼
當您渲染的全部元素都來自動態源時,這一般是一個很好的解決方案。可是,對於咱們在這裏作的狀況,它並不理想。
方案二:添加父組件
爲全部 React 元素添加一個共同的父組件。例如,咱們能夠將它們包含在 div
元素中。
ReactDOM.render(
<div> <Button /> <Display /> </div>,
mountNode
);
複製代碼
React API 支持此嵌套。事實上,你可使用 React.Fragment
,它不會引入任何新的 DOM 父節點。
方案三:使用 React.Fragment
ReactDOM.render(
<React.Fragment> <Button /> <Display /> </React.Fragment>, mountNode ); 複製代碼
方案三+:使用 <></>
你也能夠這樣寫:
ReactDOM.render(
<> <Button /> <Display /> </>, mountNode ); 複製代碼
React 會將空標記轉換爲 React.Fragment
語法。
可是,咱們應該爲 React 建立單一根節點,而不是咱們剛剛執行的嵌套樹。
讓咱們建立一個頂層組件來託管 Button
和 Display
組件。如今的問題是:咱們應該爲這個新的父組件命名什麼?
命名組件及其
state
/props
元素是一項很是艱鉅的任務,會影響這些組件的工做和執行方式。正確的名稱將迫使你作出正確的設計決策。花些時間考慮一下你爲 React 應用程序引入的每一個新名稱。
因爲這種新的父組件包含一個 Display
顯示計數值,一個 Button
增長計數值,咱們能夠把它命名爲 CountManager
。
const CountManager = () => {
return (
<> <Button /> <Display /> </> ); }; ReactDOM.render(<CountManager />, mountNode); 複製代碼
因爲咱們將在新 Display
組件中顯示計數值,所以咱們再也不須要將計數值顯示在按鈕上。相反,咱們能夠在按鈕上顯示 「+1」。
const Button = () => {
return (
<button onClick={() => console.log('TODO: Increment counter')}> +1 </button>
);
};
複製代碼
請注意,我還從 Button
組件中刪除了 state
元素,由於咱們不能再使用它了。根據新要求,組件 Button
和Display
組件都須要訪問 count
state 元素。
當組件須要訪問其兄弟組件所擁有的狀態元素時,一種解決方案是將該狀態元素「提高」到其最近共同父組件上。對於這種狀況,父級是 CountManager
。
經過將狀態移動到 CountManager
,咱們如今可使用組件 props
將數據從父級傳到子級。
const Display = ({ content }) => (
<pre>{content}</pre>
);
const CountManager = () => {
const [count, setCount] = useState(0);
return (
<>
<Button />
<Display content={count} />
</>
);
};
ReactDOM.render(<CountManager />, mountNode);
複製代碼
經過 props
將 count
值傳遞給 Display
組件時,注意我使用了不一樣的名稱(content
)。這很正常。你沒必要使用徹底相同的名稱。事實上,在某些狀況下,引入新的通用名稱對於子組件更好,由於它使狀態可重用更高。
因爲 count
state元素如今位於 CountManager
組件中,所以咱們須要在 CountManager
中處理更新它。咱們來命名更新函數爲 incrementCounter
。該函數的邏輯實際上與咱們以前在組件中的 handleClick
函數中使用的邏輯相同。新 incrementCounter
函數將更新 CountManager
組件 count
狀態:
const CountManager = () => {
// ....
const incrementCounter = () => {
setCount(count + 1);
}
// ...
};
複製代碼
爲了使 Button
組件可以調用組件中的 incrementCounter
函數,CountManager
將 incrementCounter
做爲 props
參數給 Button
組件。
咱們命名這個 props
爲 clickAction
,值爲 incrementCounter
,它是咱們在 CountManager
組件中定義的函數的引用。
const Button = ({ clickAction }) => {
return (
<button onClick={clickAction}> +1 </button>
);
};
// ...
const CountManager = () => {
// ...
return (
<div>
<Button clickAction={incrementCounter} />
<Display content={count} />
</div>
);
};
複製代碼
此 clickAction
屬性容許 Button
組件調用 CountManager
組件的incrementCounter
功能。
咱們分析代碼就會知道, Button
組件是不知道單擊它時會發生什麼。它只遵循父級定義的規則並調用泛型 clickAction
。父組件控制該行爲的執行內容。這遵循責任隔離的概念。這裏的每一個組件都有必定的責任,它們專一於此。
再看一下 Display
。從它的角度來看,計數值不是一個狀態。它只是一個 CountManager
組件傳遞給它的內容。 Display
組件將始終顯示該 prop
。這也是職責分離。
做爲這些組件的設計者,你能夠選擇他們的職責級別。
咱們建立 CountManager
組件負責管理計數狀態。在項目開發中,又該如何作喃?
我遵循的作法是在共享父節點中定義一個狀態元素,該元素儘量接近須要訪問該狀態元素的全部子節點。對於像這樣的小應用程序,這一般意味着頂級組件自己。在較大的應用程序中,子樹能夠管理本身的狀態「分支」,而不是依賴於在頂級根組件上定義的全局狀態元素。
頂級組件一般用於管理共享應用程序狀態和操做,由於它是全部其餘組件的父級。
請注意,更新頂級組件上的state元素意味着將從新呈現整個組件樹(在內存中)。
到目前爲止,這是此示例的完整代碼:
const Button = ({ clickAction }) => {
return (
<button onClick={clickAction}> +1 </button>
);
};
const Display = ({ content }) => (
<pre>{content}</pre>
);
const CountManager = () => {
const [count, setCount] = useState(0);
const incrementCounter = () => {
setCount(count + 1);
};
return (
<div>
<Button clickAction={incrementCounter} />
<Display content={count} />
</div>
);
};
複製代碼
組件都須要考慮可重用性。上慄中,Button
組件也能夠重用,它可使用任何值增長 count
計數,而不只僅是 +1
。
首先 Button
在 CountManager
組件中添加更多元素,以便咱們能夠測試這個新功能:
const CountManager = () => {
// ..
return (
<>
<Button clickAction={incrementCounter} /> {/* +1 */}
<Button clickAction={incrementCounter} /> {/* +5 */}
<Button clickAction={incrementCounter} /> {/* +10 */}
<Display count={count} />
</>
);
};
複製代碼
Button
上面呈現的全部元素當前都有一個 +1
標籤,它們將使計數增長1。咱們但願使它們顯示特定於每一個按鈕的不一樣標籤,並使它們根據特定於每一個按鈕的值執行不一樣的操做。請記住,你能夠將任何值做爲 prop
傳遞給 React 元素。
例如:
在咱們完成這個練習以前,花點時間嘗試本身實現它。
咱們須要作的第一件事是使組件中的 +1
標籤 Button
成爲可自定義的標籤。
爲了在 React 組件中進行自定義,咱們引入了一個新的 prop(父組件能夠控制)並使組件使用其值。在咱們的例子中,咱們可讓 Button
組件接收增量(1
,5
,10
),例如 clickValue
。咱們能夠更改 render
方法,CountManager
將咱們想要測試的值傳遞給這個新的 prop
。
return (
<>
<Button clickAction={incrementCounter} clickValue={1} />
<Button clickAction={incrementCounter} clickValue={5} />
<Button clickAction={incrementCounter} clickValue={10} />
<Display content={count} />
</>
);
複製代碼
到目前爲止,請注意有關此代碼的一些事項:
count
。該 Button
組件無需瞭解其 click
事件的含義。它只須要觸發 click
事件時傳遞它。clickValue
屬性的值 (clickValue={5})
。我沒有在那裏使用字符串 (clickValue="5")
。這是由於這裏這裏操做的是數字運算(每次 Button
點擊時),我須要這些值爲數字。若是我將它們做爲字符串傳遞,我將不得不在執行添加操做時將它轉化爲數字。將數字做爲字符串傳遞是React中的常見錯誤。有關更多與React相關的常見錯誤,請參閱此文章。
在 CountManager
組件中須要作的另外一件事是 incrementCounter
功能。爲了使函數通用,咱們讓它接收一個參數並使用該參數的值。例如:
incrementCounter = (incrementValue) => {
setCount(count + incrementValue);
};
複製代碼
如今咱們須要作的就是讓 Button
組件使用 clickValue
prop
做爲其標籤,並使其做爲參數調用其 onClick
事件 clickValue
。
const Button = ({ clickValue, clickAction }) => {
return (
<button onClick={() => clickAction(clickValue)}> +{clickValue} </button>
);
};
複製代碼
使用內聯箭頭函數包裝 onClick prop 以使其綁定到 Button 的 clickValue
。
如今,三個按鈕應以三個不一樣的點擊值遞增:
const Button = ({ clickValue, clickAction }) => {
return (
<button onClick={() => clickAction(clickValue)}> +{clickValue} </button>
);
};
const Display = ({ content }) => (
<pre>{content}</pre>
);
const CountManager = () => {
const [count, setCount] = useState(0);
const incrementCounter = (increment) =>
setCount(count + increment);
return (
<div>
<Button clickAction={incrementCounter} clickValue={1} />
<Button clickAction={incrementCounter} clickValue={5} />
<Button clickAction={incrementCounter} clickValue={10} />
<Display content={count} />
</div>
);
}
ReactDOM.render(<CountManager />, mountNode);
複製代碼
想象一下,咱們須要計算文本區域中用戶類型的字符,就像 Twitter 的推文形式同樣。對於每一個字符的用戶類型,咱們須要使用新的字符數更新 UI 。
這是一個顯示 textarea
輸入元素的組件,其中包含字符數的佔位符 div :
const CharacterCounter = () => {
return (
<div> <textarea cols={80} rows={10} /> <div>Count: X</div> </div> ); }; ReactDOM.render(<CharacterCounter />, mountNode); 複製代碼
要在用戶輸入時更新計數 textarea
,咱們須要自定義用戶鍵入時觸發的事件。React 爲此事件提供了 onChange
方法。咱們還須要使用 state 元素來計算字符數,並在 onChange
事件中觸發其 updater 函數。
在 onChange
咱們須要提出的新事件處理程序中,咱們須要訪問在 textarea
元素中鍵入的文本。
有兩種主要方法來讀取值。
document.getElementById
DOM API 來獲取該元素,而後使用 element.value
調用來讀取它的值textarea
元素,咱們能夠經過 React ref 來獲取 React 元素,而後訪問值咱們也能夠 onChange
直接經過事件的目標對象訪問元素。每一個事件都暴露其目標,而且在目標上的 onChange
事件 textarea
是 textarea
元素。
這意味着咱們須要作的就是:
const CharacterCounter = () => {
const [count, setCount] = useState(0);
const handleChange = (event) => {
// 獲取目標元素
const element = event.target;
setCount(element.value.length);
};
return (
<div> <textarea cols={80} rows={10} onChange={handleChange} /> <div>Count: {count}</div> </div> ); }; ReactDOM.render(<CharacterCounter />, mountNode); 複製代碼
這是最簡單的解決方案,這個解決方案的不理想之處在於咱們正在混淆問題。該 handleChange
事件具備調用 setCount
函數和計算文本長度的反作用。
咱們須要混淆這些問題的緣由是 React 不知道輸入的是什麼。這是一個 DOM 更新,而不是 React 更新。
咱們能夠經過覆蓋其值 textarea
並經過 React 將其由狀態更新變成 React 更新。在 onChange
處理程序中,咱們只設置在組件狀態上鍵入的值,而不是對字符進行計數。如下是使用此策略的解決方案的一個版本:
const CharacterCounter = () => {
const [inputValue, setInputValue] = useState('');
const handleChange = (event) => {
const element = event.target;
setInputValue(element.value);
};
return (
<div> <textarea cols={80} rows={10} value={inputValue} onChange={handleChange} /> <div>Count: {inputValue.length}</div> </div> ); }; ReactDOM.render(<CharacterCounter />, mountNode); 複製代碼
雖然這裏代碼量更多,但它有明確的關注點分離。React 如今知道並控制輸入元素狀態。此模式稱爲 React 中的受控組件模式。
此版本也更容易擴展。若是咱們要計算用戶輸入的單詞數量,這將成爲另外一個 UI 計算值。
首次在瀏覽器中渲染 React 組件稱爲 「安裝」 ,將其從瀏覽器中刪除稱爲「卸載」。
安裝,更新和卸載組件可能須要具備「反作用」。例如,React TODOs 應用程序可能須要在瀏覽器頁面的標題中顯示活動 TODO 項目的數量。直接使用 React API 是完成不了的。你須要使用 DOM API 。一樣,在渲染輸入表單時,你可能但願自動對焦文本框。這也必須使用 DOM API 完成。
反作用一般須要在 React 的渲染任務以前或以後發生。這就是爲何 React 在類組件中提供「生命週期方法」以容許你在 render 方法以前或以後執行自定義操做的緣由。你能夠在組件首次安裝在 componentDidMount
方法中後執行操做,你也能夠在組件 componentDidUpdate
方法中獲取更新後執行操做,或者能夠在 componentWillUnmount
方法中刪除以前執行操做。
對於函數組件,使用 React.useEffect
hook 函數管理反作用,該函數有2個參數:回調函數和依賴項數組。
useEffect(() => {
// Do something after each render
// but only if dep1 or dep2 changed
}, [dep1, dep2]);
複製代碼
第一次 React 呈現一個有 useEffect
調用的組件時,它將調用它的回調函數。在每一個新組件呈現以後,若是依賴項的值與以前渲染中的值不一樣,則 React 將再次調用回調函數。
更新或卸載函數組件時,React 能夠調用反作用「cleanup」功能。能夠從
useEffect
回調函數返回該清理函數。
反作用方法對於分析應用程序中正在發生的事情以及進一步優化 React 的性能也很是方便。
想看更過系列文章,點擊前往 github 博客主頁
走在最後,歡迎關注:前端瓶子君,每日更新