React從Class方式轉Hooks詳解
目錄
- React Hooks
- 前言
- Why Hooks ?
- For Class
- For Function
- Class & Hooks 對比
- Hooks如何儲存元件狀態和使用生命週期?
- 1、useState:讓函式具有維持狀態的能力
- 2、useEffect:執行副作用
- 總結
React Hooks
前言
之前工作三年中一直在用class方式去寫前端功能頁面這些,其實接觸hooks也是有一定時間了。在第一次接觸的時候應該是看了一門關於electron+react的專案的課程的時候。當時主要是去看electron,所以對hooks沒有特別的關注。也有可能是長期以來對class的熟悉,當時對hooks這種函式式的寫法也有一些牴觸。但是由於業內對hooks的好評,曾經也想去用hooks去起一個專案,但是由於當時專案週期以及已有的技術棧等原因,一直想實踐也沒機會去實踐使用。
由於,最近接手新的專案一直使用oWXemhGKwG的是react hooks+ts這一套。於是,就得開始使用hooks了。其實就功能開發而言,照貓畫虎,其實工作也沒有什麼問題的。但是由於一直沒有系統的對hooks進行深入一步的瞭解,導致在很多時候,其實並不是很清楚為什麼要這樣去使用,於是最近去找了《React Hooks核心原理與實戰》去進行了學習。
在使用層面以及原因層面上,重新審視hooks的使用以及為什麼要使用hooks。函式式寫法究竟有哪些好處進行了更進一步的思考。其實,在一定程度而言,也還是淺嘗輒止,這裡僅僅將我這段時間學習記錄下來。
Why Hooks ?
Hooks很大的一個亮點是可以進行業務邏輯的重用。這一點在hooks中體現的尤為明顯。比如,往常的class中如果要去監聽視窗大小的變化的時候,就得在元件中在掛載後去新增監聽事件,但是如果當另外一個地方需要用到這種監聽視窗大小功能的話,這種邏輯程式碼並不可以複用,只能在那個元件中重新寫一遍。但是在hooks中,我們可以將這部分監聽的邏輯程式碼進行hooks方式封裝,完全可以做到邏輯上的複用。
For Class
- 使用Class作為React的載體的時候:
- 元件之間不會相互繼承,沒有利用到class的繼承的特性UI是狀態驅動的,所有的方法都是內部呼叫或者作為生命週期的方法內部自動呼叫。沒有使用到類的例項方法可以呼叫的特性
For Function
React中的一個核心就是要實現從State資料到View試圖層面的一個繫結。使用函式,其實更好的去解決State到View的一個對映問題。但是,用函式作為React的載體就會出現兩個問題,函式中狀態的儲存以及生命週期的方法。
- Hooks怎樣去解決上面兩個問題:把一個外部的資料繫結到函式的執行。當資料變化時,讓函式能夠自動重新執行。這樣的話,任何會影響 UI 展現的外部資料,都可以通過這個機制繫結到 React 的函式元件。
- Hooks鉤子的理解:把某個目標結果鉤到某個可能會變化的資料來源或者事件源上,那麼當被鉤到的資料或事件發生變化時,產生這個目標結果的程式碼會重新執行,產生更新後的結果
圖解:一個執行過程(Execution),例如是函式元件本身,可以繫結在(鉤在)傳統意義的 State,或者 URL,甚至可以是視窗的大小。這樣當 State、URL、視窗大小發生變化時,都會重新執行某個函式,產生更新後的結果。
Class & Hooks 對比
- 比起 Class 元件,函式元件是更適合去表達 React 元件的執行的,因為它更符合 State => View 這樣的一個邏輯關係。但是因為缺少狀態、生命週期等機制,讓它一直功能受限。Hooks解決了函式元件作為React載體的狀態生命週期等受限問題,讓其功能充分發揮出來
- Hooks 中被鉤的物件,可以是某個獨立的資料來源,也可以是另一個 Hook 執行的結果,這就帶來了 Hooks 的最大好處:邏輯的複用
- 簡化了邏輯複用
- Class方式中:使用高階元件的設計模式進行邏輯複用。比如:我們要去複用一個視窗resize的功能,我們需要去定義一個沒有UI的外層元件,去寫相關resize的邏輯定義,然後將資料結果用屬性的方式傳給子元件。元件要複用這個邏輯的話,必須外層用這個元件包裹並返回。針對整個而言,**為了傳遞一個外部的狀態,我們不得不定義一個沒有 UI 的外層元件,而這個元件只是為了封裝一段可重用的邏輯。**頻繁使用,每一個高階元件的使用都會多一層節點,會給除錯等帶來很大的負擔。
//Class中高階元件實現resize方法複用
//1、高階元件的宣告
const withWindowSize = Component => {
// 產生一個高階元件 WrappedComponent,只包含監聽視窗大小的邏輯
class WrappedComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
size: this.getSize()
};
}
componentDidMount() {
window.addEventListener("resize",this.handleResize);
}
componentWillUnmount() {
window.removeEventListener("resize",this.handleResize);
}
getSize() {
return window.innerWidth > 1000 ? "large" :"small";
}
handleResize = ()=> {
const currentSize = this.getSize();
this.setState({
size: this.getSize()
});
}
render() {
// 將視窗大小傳遞給真正的業務邏輯元件
return &oWXemhGKwGlt;Component size={this.state.size} />;
}
}
return WrappedComponent;
};
//2、元件MyComponent使用高階元件中的resize功能
class MyComponent extends React.Component{
render() {
const { size } = this.props;
if (size === "small") return <SmallComponent />;
else return <LargeComponent />;
}
}
// 使用 withWindowSize 產生高階元件,用於產生 size 屬性傳遞給真正的業務元件
export default withWindowSize(MyComponent);
- Hooks方式中:實現resize的話,視窗大小隻是外部的一個數據狀態。我們使用hooks方式對其封裝,只是將其變成了一個可以繫結的資料來源,當視窗大小發生變化的時候,這個元件也會重新渲染程式碼會更加簡潔直觀,並且不會產生額外的元件節點。
//Hooks中使用hooks方法進行resize邏輯複用 //定義useWindowSize這個hook const getSize = () => { return window.innerWidth > 1000 ? "large" : "small"; } const useWindowSize = () => { const [size,setSize] = useState(getSize()); useEffect(() => { const handler = () => { setSize(getSize()) }; window.addEventListener('resize',handler); return () => { window.removeEventListener('resize',handler); }; },[]); return size; }; //函式元件中使用這個hook const Demo = () => { const size = useWindowSize(); if (size === "small") return <SmallComponent />; else return <LargeComponent />; };
- 有助於關注分離
Hooks能夠讓針對同一個業務邏輯的程式碼儘可能聚合在一塊,在Class元件中不得不吧同一個業務邏輯程式碼分散在類元件的不同的生命週期方法中
圖解:左側是 Class 元件,右側是函式元件結合 Hooks。藍色和黃色代表不同的業務功能
Hooks如何儲存元件狀態和使用生命週期?
React一共提供了10個Hooks
,useState
、useEffect
、useCallback
、useMemo
、useRef
、useCowww.cppcns.comntext
等等
1、useState:讓函式具有維持狀態的能力
我們要遵循的一個原則就是:state 中永遠不要儲存可以通過計算得到的值,例如:
- 從 props 傳遞過來的值。有時候 props 傳遞過來的值無法直接使用,而是要通過一定的計算後再在 UI 上展示,比如說排序。那麼我們要做的就是每次用的時候,都重新排序一下,或者利用某些 cache 機制,而不是將結果直接放到 state 裡。
- 從 URL 中讀到的值。比如有時需要讀取 URL 中的引數,把它作為元件的一部分狀態。那麼我們可以在每次需要用的時候從 URL 中讀取,而不是讀出來直接放到 state 裡。
- 從 cookie、localStorage 中讀取的值。通常來說,也是每次要用的時候直接去讀取,而不是讀出來後放到 state 裡。
2、useEffect:執行副作用
副作用是指一段和當前執行結果無關的程式碼。比如說要修改函式外部的某個變數,要發起一個請求。形式:useEffect(callback,dependencies)
。涵蓋了componentDidMount
、componentDidUpdate
和componentWillUnmount
三個生命週期方法。簡而言之,useEffect 是每次元件 render 完後判斷依賴並執行。
使用useEffect應該注意的點:
沒有依賴項,則每次 render 後都會重新執行
useEffect(()=>{ console.log('re-render') //每次render完成一次後就執行 })
- 空陣列作為依賴項,則只在首次執行時觸發,對應到 Class 元件就是 componentDidMount
useEffect(()=>{ console.log('did mount') //相當於componentDidMount },[])
- 可以返回一個函式,用在元件銷燬的時候做一些清理的操作
const [size,setResize] = useState({}) useEffect(()=>{ const handler = () => { setResize() } window.addEventListener('resize',handler) return ()=>{ window.removeEventListener('resize',handler) } },[])
總結
- useEffect使用的四個場景 每次 render 後執行:不提供第二個依賴項引數。比如useEffect(() => {})。
- 僅第一次 rewww.cppcns.comnder 後執行:提供一個空陣列作為依賴項。比如useEffect(() => {},[])。
- 第一次以及依賴項發生變化後執行:提供依賴項陣列。比如useEffect(() => {},[deps])。
- 元件 unmount 後執行:返回一個回撥函式。比如useEffect() => { return () => {} },[])。
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注我們的更多內容!