前言
Hook 是 React 16.8 的新增特性。它是完全可選的,並且100%向后兼容。它可以讓你使用函數組件的方式,運用類組件以及 react 其他的一些特性,比如管理狀態、生命周期鈎子等。從概念上講,React 組件一直更像是函數。而 Hook 則擁抱了函數,同時也沒有犧牲 React 的精神原則。
優點:
1、代碼可讀性更強,原本同一塊功能的代碼邏輯被拆分在了不同的生命周期函數中,容易使開發者不利於維護和迭代,通過 React Hooks 可以將功能代碼聚合,方便閱讀維護。例如,每個生命周期中常常會包含一些不相關的邏輯。一般我們都會在 componentDidMount 和 componentDidUpdate 中獲取數據。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設置事件監聽,而之后需在 componentWillUnmount 中清除。相互關聯且需要對照修改的代碼被進行了拆分,而完全不相關的代碼卻在同一個方法中組合在一起。如此很容易產生 bug,並且導致邏輯不一致。
2、組件樹層級變淺。在原本的代碼中,我們經常使用 HOC/render props 等方式來復用組件的狀態,增強功能等,無疑增加了組件樹層數及渲染,在 React DevTools 中觀察過 React 應用,你會發現由 providers,consumers,高階組件,render props 等其他抽象層組成的組件會形成“嵌套地獄”。而在 React Hooks 中,這些功能都可以通過強大的自定義的 Hooks 來實現。
3、不用再去考慮 this 的指向問題。在類組件中,你必須去理解 JavaScript 中 this 的工作方式。
缺點:
一、響應式的useEffect
寫函數組件時,你不得不改變一些寫法習慣。你必須清楚代碼中useEffect
和useCallback
的“依賴項數組”的改變時機。有時候,你的useEffect依賴某個函數的不可變性,這個函數的不可變性又依賴於另一個函數的不可變性,這樣便形成了一條依賴鏈。一旦這條依賴鏈的某個節點意外地被改變了,你的useEffect就被意外地觸發了,如果你的useEffect是冪等的操作,可能帶來的是性能層次的問題,如果是非冪等,那就糟糕了。
所以,對比componentDidmount
和componentDidUpdate
,useEffect帶來的心智負擔更大。
二、狀態不同步
函數的運行是獨立的,每個函數都有一份獨立的作用域。函數的變量是保存在運行時的作用域里面,當我們有異步操作的時候,經常會碰到異步回調的變量引用是之前的,也就是舊的(這里也可以理解成閉包)。比如下面的一個例子:
import React, { useState } from "react"; const Counter = () => { const [counter, setCounter] = useState(0); const onAlertButtonClick = () => { setTimeout(() => { alert("Value: " + counter); }, 3000); }; return ( <div> <p>You clicked {counter} times.</p> <button onClick={() => setCounter(counter + 1)}>Click me</button> <button onClick={onAlertButtonClick}> Show me the value in 3 seconds </button> </div> ); }; export default Counter;
當你點擊Show me the value in 3 seconds
的后,緊接着點擊Click me
使得counter的值從0變成1。三秒后,定時器觸發,但alert出來的是0(舊值),但我們希望的結果是當前的狀態1。
這個問題在class component
不會出現,因為class component
的屬性和方法都存放在一個instance
上,調用方式是:this.state.xxx
和this.method()
。因為每次都是從一個不變的instance
上進行取值,所以不存在引用是舊的問題。
其實解決這個hooks的問題也可以參照類的instance
。用useRef
返回的immutable RefObject
(current屬性是可變的)來保存state,然后取值方式從counter
變成了: counterRef.current
。如下:
import React, { useState, useRef, useEffect } from "react"; const Counter = () => { const [counter, setCounter] = useState(0); const counterRef = useRef(counter); const onAlertButtonClick = () => { setTimeout(() => { alert("Value: " + counterRef.current); }, 3000); }; useEffect(() => { counterRef.current = counter; }); return ( <div> <p>You clicked {counter} times.</p> <button onClick={() => setCounter(counter + 1)}>Click me</button> <button onClick={onAlertButtonClick}> Show me the value in 3 seconds </button> </div> ); }; export default Counter;
結果
我們所期待,alert的是當前的值1。
怎么避免react hooks的常見問題
- 不要在
useEffect
里面寫太多的依賴項,划分這些依賴項成多個單一功能的useEffect
。其實這點是遵循了軟件設計的“單一職責模式”。 - 如果你碰到狀態不同步的問題,可以考慮下手動傳遞參數到函數。如:
// showCount的count來自父級作用域 const [count,setCount] = useState(xxx); function showCount(){ console.log(count) } // showCount的count來自參數 const [count,setCount] = useState(xxx); function showCount(c){ console.log(c) }
但這個也只能解決一部分問題,很多時候你不得不使用上述的useRef
方案。
3. 重視eslint-plugin-react-hooks
插件的警告。
4. 復雜業務的時候,使用Component代替hooks。