摘要:使用完成副作用操作,賦值給的函數會在組件渲染到屏幕之后。如此很容易產生,并且導致邏輯不一致。同時,這也是很多人將與狀態管理庫結合使用的原因之一。當我們通過的第二個數組類型參數,指明當前的依賴,就能避免不相關的執行開銷了。
前言
本文內容大部分參考了 overreacted.io 博客一文,同時結合 React Hook 官方 文章,整理并歸納一些筆記和輸出個人的一些理解什么是 Hook ?
官方介紹:Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。React 中內置的 Hook API
基礎 Hook
useState
// 傳入初始值,作為 state
const [state, setState] = useState(initialState)
// `惰性初始 state`;傳入函數,由函數計算出的值作為 state
// 此函數只在初始渲染時被調用
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props)
return initialState
})
useEffect
該 Hook 接收一個包含命令式、且可能有副作用代碼的函數.
在函數組件主體內(這里指在 React 渲染階段)改變 DOM、添加訂閱、設置定時器、記錄日志以及執行其他包含副作用的操作都是不被允許的,因為這可能會產生莫名其妙的 bug 并破壞 UI 的一致性。
使用 useEffect 完成副作用操作,賦值給 useEffect 的函數會在組件渲染到屏幕之后。你可以把 effect 看作從 React 的純函數式世界通往命令式世界的逃生通道。
默認情況下,effect 將在每輪渲染結束后執行,但你可以選擇讓它 在只有某些值改變的時候才執行。詳情見后面。
清除 effect
通常,組件卸載時需要清除 effect 創建的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect 函數需返回一個清除函數。以下就是一個創建訂閱的例子:
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
// 清除訂閱
subscription.unsubscribe()
}
}, [依賴])
useContext
額外的 Hook
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
我們為什么選擇使用 Hook ? 1. 在組件之間復用狀態邏輯很難React 沒有提供將可復用性行為“附加”到組件的途徑(例如,把組件連接到 store)。如果你使用過 React 一段時間,你也許會熟悉一些解決此類問題的方案,比如 render props 和 高階組件。但是這類方案需要重新組織你的組件結構,這可能會很麻煩,使你的代碼難以理解。如果你在 React DevTools 中觀察過 React 應用,你會發現由 providers,consumers,高階組件,render props 等其他抽象層組成的組件會形成“嵌套地獄”。盡管我們可以在 DevTools 過濾掉它們,但這說明了一個更深層次的問題:React 需要為共享狀態邏輯提供更好的原生途徑。
你可以使用 Hook 從組件中提取狀態邏輯,使得這些邏輯可以多帶帶測試并復用。Hook 使你在無需修改組件結構的情況下復用狀態邏輯。 這使得在組件間或社區內共享 Hook 變得更便捷。
2. 復雜組件變得難以理解我們經常維護一些組件,組件起初很簡單,但是逐漸會被狀態邏輯和副作用充斥。每個生命周期常常包含一些不相關的邏輯。例如,組件常常在 componentDidMount 和 componentDidUpdate 中獲取數據。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設置事件監聽,而之后需在 componentWillUnmount 中清除。相互關聯且需要對照修改的代碼被進行了拆分,而完全不相關的代碼卻在同一個方法中組合在一起。如此很容易產生 bug,并且導致邏輯不一致。
在多數情況下,不可能將組件拆分為更小的粒度,因為狀態邏輯無處不在。這也給測試帶來了一定挑戰。同時,這也是很多人將 React 與狀態管理庫結合使用的原因之一。但是,這往往會引入了很多抽象概念,需要你在不同的文件之間來回切換,使得復用變得更加困難。
為了解決這個問題,Hook 將組件中相互關聯的部分拆分成更小的函數(比如設置訂閱或請求數據),而并非強制按照生命周期劃分。你還可以使用 reducer 來管理組件的內部狀態,使其更加可預測。
3. 用更少的代碼,實現同樣的效果下面的代碼可以直觀的體現出來,在某些場景下,使用 hook 來實現對應的功能,可以節省大部分的代碼
3.1 清除副作用更加緊湊對比 Class 組件來說,清除副作用要簡單的多,如下代碼,在 useEffect hook 里面返回一個函數,當我們的函數組件卸載的時候,就會自動執行這個函數,從而來清除副作用。想想我們在 Class 組件里面需要在 componentWillUnmount 生命周期里面去編寫對應的代碼。
對比兩者我們發現,使用 useEffect 的方式,能夠將掛載和卸載的邏輯更加緊密的耦合在一起,從而減少 BUG 的發生
useEffect(() => {
const id = setInterval(() => {
setCount(count => count + 1)
}, 1000)
return () => clearInterval(id)
}, [])
// 比如給 windows 掛載監聽函數
useEffect(() => {
window.addEventListener("reszie", handleRezie)
return () => {
window.removeEventListener("resize", handleRezie)
}
}, [])
如何正確的使用 Hook ?
1. 使用規則
只在最頂層使用 Hook:不要在循環,條件或嵌套函數中調用 Hook, 確保總是在你的 React 函數的最頂層調用他們。
不要在普通的 JavaScript 函數中調用 Hook。你可以
[x] 在 React 的函數組件中調用 Hook
[x] 在自定義 Hook 中調用其他 Hook
2. 只有在自己依賴更新時才執行 effect使用 useEffect 完成副作用操作,賦值給 useEffect 的函數會在組件渲染到屏幕之后;牢記這句話。
仔細觀察如下代碼,當函數組件里面,有多個 effect 的時候,默認的 effect 將在每次 UI render 之后被調用。當我們通過 useEffect 的第二個數組類型參數,指明當前 effect 的依賴,就能避免不相關的執行開銷了。
通過啟用 eslint-plugin-react-hooks 插件,來強制提醒我們在使用 effect 的時候,申明所需要的依賴
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
const CounterHook = () => {
const [count, setCount] = useState(0)
const [name, setName] = useState("heaven")
useEffect(() => {
document.title = `counterWithHook ${count}`
}, [count])
useEffect(() => {
console.log("you name is", name)
}, [name])
return (
Counter with Hook
You click {count} times
setName(e.target.value)} />
your name is {name}
)
}
2.1 不要忘記函數依賴
對于 useEffect 內部方法,一旦引用外部的函數,那么這個時候需要注意了:
需要把 useEffect 內部引用到的方式,聲明為當前 effect 的依賴
在下圖的代碼中,我們可以看到,在 effect 函數內部,引入外部的函數,我們的 eslint-plugin-react-hooks 插件會自動提示我們需要把對應的函數作為依賴添加進去
不規范示例:這里在安裝了插件的情況下,會自動提示我們將 fetchData 函數移入 effect 內部
const getFetchUrl = () => {
return `https://hn.algolia.com/api/v1/search?query=${query}`
}
const fetchData = async () => {
return axios.get(getFetchUrl())
}
useEffect(() => {
fetchData().then(resp => {
console.log(resp)
setData(resp.data)
})
}, [])
正確的寫法:
useEffect(() => {
const getFetchUrl = () => {
return `https://hn.algolia.com/api/v1/search?query=${query}`
}
const fetchData = async () => {
return axios.get(getFetchUrl())
}
fetchData().then(resp => {
console.log(resp)
setData(resp.data)
})
}, [query])
3、理解每一次的 Rendering
每一次渲染都有它自己的 Props and State
每一次渲染都有它自己的事件處理函數
每次渲染都有它自己的 Effects
運行如下代碼之后,在我們點擊 Show alert 按鈕之后,然后點擊 Click me 按鈕,alert 輸出的永遠是在點擊的那個時刻的 count;
換句話來說;在 hooks 組件里面,每一次的渲染,都相當于記錄當前次的『快照』
import React, { useEffect, useState } from "react"
const Counter = () => {
const [count, setCount] = useState(0)
const handleAlertClick = () => {
setTimeout(() => {
alert(`Yout clicked me: ${count}`)
}, 3000)
}
useEffect(() => {
setTimeout(() => {
console.log(`Yout clicked ${count} times`)
}, 3000)
})
return (
You clicked {count} times
)
}
export default Counter
使用自定義 Hook
通過自定義 Hook,可以將組件邏輯提取到可重用的函數中。
當我們想在兩個函數之間共享邏輯時,我們會把它提取到第三個函數中。而組件和 Hook 都是函數,所以也同樣適用這種方式。
自定義 Hook 是一個函數,其名稱以 “use” 開頭,函數內部可以調用其他的 Hook。
自定義 useService hookuseService.js 自定義的一個 server hook,該 hook 封裝了 ajax 請求中的 { loading, error, response } 三個基礎邏輯;有了這個 hook 我們就能很輕松的在每次網絡請求里面去處理各種異常邏輯了;詳細用法看文章最后的 Table 分頁操作實例
import { useEffect, useRef, useState, useCallback } from "react"
import { isEqual } from "lodash"
const useService = (service, params) => {
const prevParams = useRef(null)
const [callback, { loading, error, response }] = useServiceCallback(service)
useEffect(() => {
if (!isEqual(prevParams.current, params)) {
prevParams.current = params
callback(params)
}
})
return { loading, error, response }
}
const useServiceCallback = service => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [response, setResponse] = useState(null)
// 使用 useCallback,來判斷 service 是否改變
const callback = useCallback(
params => {
setLoading(true)
setError(null)
service(params)
.then(response => {
console.log(response)
setLoading(false)
setResponse(response)
})
.catch(error => {
setLoading(false)
setError(error)
})
},
[service]
)
return [callback, { loading, error, response }]
}
實例剖析
Table 分頁操作
如下代碼,使用 hook 的方式來實現表格的分頁,數據請求操作,
跑馬燈中獎使用 hook 實現一個簡易版的跑馬燈抽獎邏輯
參考資料官方 Hook 介紹
Hook 規則
Hook API 索引
如何在 Hook 中發起請求
useEffect 詳解
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://www.hztianpu.com/yun/109624.html
摘要:起飛指南作者元瀟方凳雅集出品目前放出來了個內置,但僅僅基于以下兩個,就能做很多事情。行代碼實現一個全局元瀟根組件掛上即可子組件調用隨時隨地實現一個局部元瀟的本質是的一個語法糖,感興趣可以閱讀一下的類型定義和實現。 React Hook起飛指南 作者:元瀟 方凳雅集出品 16.8目前放出來了10個內置hook,但僅僅基于以下兩個API,就能做很多事情。所以這篇文章不會講很多API,...
摘要:在讀了一些文章后,大致是找到自己總是掉坑的原因了沒理解中的特性。通過這個示例,相信會比較容易地理解特性,并如何使用來暫時繞過它。在知道并理解這個特性后,有助于進一步熟悉了的運行機制,減少掉坑的次數。 由于剛使用 React hooks 不久,對它的脾氣還拿捏不準,掉了很多次坑;這里的 坑 的意思并不是說 React hooks 的設計有問題,而是我在使用的時候,因為還沒有跟上它的理念導...
摘要:顧名思義,受控組件的值由控制,能為與用戶交互的元素提供值,而不受控制的元素不獲取值屬性。另外我發現受控組件更容易理解和于使用。只是一種把組件作為參數的函數,并且與沒有包裝器的組件相比,能夠返回具有擴展功能的新組件。其中三個基本的是,和。 翻譯:瘋狂的技術宅原文:https://www.toptal.com/react/... 本文首發微信公眾號:jingchengyideng歡迎關...
摘要:以我個人的觀點,要不要使用呢建議用的的人項目版本已經是了新建的項目一直對新技術保持關注,躍躍欲試的人對函數式編程愛好的人對的,,厭煩,甚至因為重新渲染整天在頭疼的人不建議用的人對極其厭惡,對生命周期編程是非愛好的人。 react hook發布也已經有幾個月了,相信有部分人已經開始使用了,還有些人在猶豫要不要用,可能更多人安于現狀,沒有要用的打算,甚至還有很多公司的react版本是15或...
摘要:簡介是的新增特性。我們統一把這些操作稱為副作用,或者簡稱為作用。由于副作用函數是在組件內聲明的,所以它們可以訪問到組件的和。副作用函數還可以通過返回一個函數來指定如何清除副作用。目前為止,有兩種主流方案來解決這個問題高階組件和。 Hook 簡介 Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。 us...
閱讀 3005·2023-04-26 02:44
閱讀 10639·2021-11-22 14:44
閱讀 2508·2021-09-27 13:36
閱讀 3128·2021-09-08 10:43
閱讀 1020·2019-08-30 15:56
閱讀 1606·2019-08-30 15:55
閱讀 3086·2019-08-28 18:12
閱讀 3080·2019-08-26 13:50