
React 的 logo 是一個(gè)原子圖案, 原子組成了物質(zhì)的表現(xiàn)。類似的, React 就像原子般構(gòu)成了頁面的表現(xiàn); 而 Hooks 就如夸克, 其更接近 React 本質(zhì)的樣子, 但是直到 4 年后的今天才被真正設(shè)計(jì)出來。 —— Dan in React Conf(2018)
why Hooks?
一: 多個(gè)組件間邏輯復(fù)用: 在 Class 中使用 React 不能將帶有 state 的邏輯給單獨(dú)抽離成 function, 其只能通過嵌套組件的方式來解決多個(gè)組件間邏輯復(fù)用的問題, 基于嵌套組件的思想存在 HOC 與 render props 兩種設(shè)計(jì)模式。但是這兩種設(shè)計(jì)模式是否存在缺陷呢?
- 嵌套地獄, 當(dāng)嵌套層級過多后, 數(shù)據(jù)源的追溯會變得十分困難, 導(dǎo)致定位 bug 不容易; (hoc、render props)
- 性能, 需要額外的組件實(shí)例存在額外的開銷; (hoc、render props)
- 命名重復(fù)性, 在一個(gè)組件中同時(shí)使用多個(gè) hoc, 不排除這些 hoc 里的方法存在命名沖突的問題; (hoc)
二: 單個(gè)組件中的邏輯復(fù)用: Class 中的生命周期 componentDidMount、componentDidUpdate 甚至 componentWillUnMount 中的大多數(shù)邏輯基本是類似的, 必須拆散在不同生命周期中維護(hù)相同的邏輯對使用者是不友好的, 這樣也造成了組件的代碼量增加。
三: Class 的其它一些問題: 在 React 使用 Class 需要書寫大量樣板, 用戶通常會對 Class 中 Constructor 的 bind 以及 this 的使用感到困惑; 當(dāng)結(jié)合 class 與 TypeScript 一起使用時(shí), 需要對 defaultValue 做額外聲明處理; 此外 React Team 表示 Class 在機(jī)器編譯優(yōu)化方面也不是很理想。
useState 返回的值為什么是數(shù)組而非對象?
原因是數(shù)組的解構(gòu)比對象更加方便, 可以觀察以下兩種數(shù)據(jù)結(jié)構(gòu)解構(gòu)的差異。
返回?cái)?shù)組時(shí), 可以直接解構(gòu)成任意名字。
[name, setName] = useState('路飛')
[age, setAge] = useState(12)
返回對象時(shí), 卻需要多一層的命名。
{value: name, setValue: setName} = useState('路飛')
{value: name, setValue: setName} = useState(12)
Hooks 傳遞的設(shè)計(jì)
Hooks 是否可以設(shè)計(jì)成在組件中通過函數(shù)傳參來使用? 比如進(jìn)行如下調(diào)用?
const SomeContext = require('./SomeContext)
function Example({ someProp }, hooks) {
const contextValue = hooks.useContext(SomeContext)
return <div>{someProp}{contextValue}</div>
}
使用傳遞的劣勢是會出現(xiàn)冗余的傳遞。(可以聯(lián)想 context 解決了什么)
Hooks 與 Class 中調(diào)用 setState 有不同的表現(xiàn)差異么?
Hooks 中的 setState 與 Class 中最大區(qū)別在于 Hooks 不會對多次 setState 進(jìn)行合并操作。如果要執(zhí)行合并操作, 可執(zhí)行如下操作:
setState(prevState => {
return { ...prevState, ...updateValues }
})
此外可以對 class 與 Hooks 之間 setState 是異步還是同步的表現(xiàn)進(jìn)行對比, 可以先對以下 4 種情形 render 輸出的個(gè)數(shù)進(jìn)行觀察分析:
是否能使用 React Hooks 替代 Redux
在 React 16.8 版本之后, 針對不是特別復(fù)雜的業(yè)務(wù)場景, 可以使用 React 提供的 useContext、useReducer 實(shí)現(xiàn)自定義簡化版的 redux, 可見 todoList 中的運(yùn)用。核心代碼如下:
import React, { createContext, useContext, useReducer } from "react"
// 創(chuàng)建 StoreContext
const StoreContext = createContext()
// 構(gòu)建 Provider 容器層
export const StoreProvider = ({reducer, initialState, children}) => {
return (
<StoreContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StoreContext.Provider>
)
}
// 在子組件中調(diào)用 useStoreContext, 從而取得 Provider 中的 value
export const useStoreContext = () => useContext(StoreContext)
但是針對特別復(fù)雜的場景目前不建議使用此模式, 因?yàn)?context 的機(jī)制會有性能問題。具體原因可見 react-redux v7 回退到訂閱的原因
Hooks 中如何獲取先前的 props 以及 state
React 官方在未來很可能會提供一個(gè) usePrevious 的 hooks 來獲取之前的 props 以及 state。
usePrevious 的核心思想是用 ref 來存儲先前的值。
function usePrevous(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
Hooks 中如何調(diào)用實(shí)例上的方法
在 Hooks 中使用 useRef() 等價(jià)于在 Class 中使用 this.something。
/* in a function */
const X = useRef()
X.current // can read or write
/* in a Class */
this.X // can read or write
Hooks 中 getDerivedStateFromProps 的替代方案
在 React 暗器百解 中提到了 getDerivedStateFromProps 是一種反模式, 但是極少數(shù)情況還是用得到該鉤子, Hooks 沒有該 api, 那其如何達(dá)到 getDerivedStateFromProps 的效果呢?
function ScrollView({row}) {
const [isScrollingDown, setISScrollingDown] = setState(false)
const [prevRow, setPrevRow] = setState(null)
// 核心是創(chuàng)建一個(gè) prevRow state 與父組件傳進(jìn)來的 row 進(jìn)行比較
if (row !== prevRow) {
setISScrollingDown(prevRow !== null && row > prevRow)
setPrevRow(row)
}
return `Scrolling down ${isScrollingDown}`
}
Hooks 中 forceUpdate 的替代方案
可以使用 useReducer 來 hack forceUpdate, 但是盡量避免 forceUpdate 的使用。
const [ignored, forceUpdate] = useReduce(x => x + 1, 0)
function handleClick() {
forceUpdate()
}
Hooks 中 shouldComponentUpdate 的替代方案
在 Hooks 中可以使用 useMemo 來作為 shouldComponentUpdate 的替代方案, 但 useMemo 只對 props 進(jìn)行淺比較。
React.useMemo((props) => {
// your component
})
useMemo 與 useCallback 的區(qū)別
useMemo(() => <component />) 等價(jià)于 useCallback(<component />)
- useCallback: 一般用于緩存函數(shù)
- useMemo: 一般用于緩存組件
依賴列表中移除函數(shù)是否是安全的?
通常來說依賴列表中移除函數(shù)是不安全的。觀察如下 demo
const { useState, useEffect } = React
function Example({ someProp }) {
function doSomething() {
console.log(someProp) // 這里只輸出 1, 點(diǎn)擊按鈕的 2 并沒有輸出。
}
useEffect(
() => {
doSomething()
},
[] // ?? 這是不安全的, 因?yàn)樵?doSomething 函數(shù)中使用了 someProps 屬性
)
return <div>example</div>
}
export default function() {
const [value, setValue] = useState(1)
return (
<>
<Example someProp={value} />
<Button onClick={() => setValue(2)}>button</Button>
</>
)
}
在該 demo 中, 點(diǎn)擊 button 按鈕, 并沒有打印出 2。解決上述問題有兩種方法。
方法一: 將函數(shù)放入 useEffect 中, 同時(shí)將相關(guān)屬性放入依賴項(xiàng)中。因?yàn)樵谝蕾囍懈淖兊南嚓P(guān)屬性一目了然, 所以這也是首推的做法。
function Example({ someProp }) {
useEffect(
() => {
function doSomething() {
console.log(someProp)
}
doSomething()
},
[someProps] // 相關(guān)屬性改變一目了然
)
return <div>example</div>
}
方法二: 把函數(shù)加入依賴列表中
function Example({ someProp }) {
function doSomething() {
console.log(someProp)
}
useEffect(
() => {
doSomething()
},
[doSomething]
)
return <div>example</div>
}
方案二基本上不會單獨(dú)使用, 它一般結(jié)合 useCallback 一起使用來處理某些函數(shù)計(jì)算量較大的函數(shù)。
function Example({ someProp }) {
const doSomething = useCallback(() => {
console.log(someProp)
}, [someProp])
useEffect(
doSomething(),
[doSomething]
)
return <div>example</div>
}
如何避免重復(fù)創(chuàng)建昂貴的對象
- 方法一: 使用
useState的懶初始化, 用法如下
const [value, setValue] = useState(() => createExpensiveObj)
- 方法二: 使用自定義 useRef 函數(shù)
function Image(props) {
const ref = useRef(null)
function getExpensiveObj() {
if (ref.current === null) {
ref.current = ExpensiveObj
}
return ref.current
}
// if need ExpensiveObj, call getExpensiveObj()
}