過去,React 中的函數組件都被稱爲無狀態函數式組件(stateless functional component),這是由於函數組件沒有辦法擁有本身的狀態,只能根據 Props 來渲染 UI ,其性質就至關因而類組件中的 render 函數,雖然結構簡單明瞭,可是做用有限。javascript
但自從 React Hooks 橫空出世,函數組件也擁有了保存狀態的能力,並且也逐漸可以覆蓋到類組件的應用場景,所以能夠說 React Hooks 就是將來 React 發展的方向。java
咱們知道組件化的思想就是將一個複雜的頁面/大組件,按照不一樣層次,逐漸抽象並拆分紅功能更純粹的小組件,這樣一方面能夠減小代碼耦合,另一方面也能夠更好地複用代碼;但實際上,在使用 React 的類組件時,每每難以進一步分拆複雜的組件,這是由於邏輯是有狀態的,若是強行分拆,會令代碼複雜性急劇上升;如使用 HOC 和 Render Props 等設計模式,這會造成「嵌套地獄」,使咱們的代碼變得晦澀難懂。react
這其實也是上一點的延續:要給一個擁有衆多狀態邏輯的組件寫單元測試,無疑是一件使人崩潰的事情,由於須要編寫大量的測試用例來覆蓋代碼執行路徑。git
對於類組件,咱們須要在組件提供的生命週期鉤子中處理狀態的初始化、數據獲取、數據更新等操做,處理起來自己邏輯就比較複雜,並且各類「反作用」混在一塊兒也令人頭暈目眩,另外還極可能忘記在組件狀態變動/組件銷燬時消除反作用。github
我認爲 React Hooks 的亮點不在於 React 官方提供的那些 API ,那些 API 只是一些基礎的能力;其亮點仍是在於自定義 Hooks —— 一種封裝複用的設計模式。web
例如,一個頁面上每每有不少狀態,這些狀態分別有各自的處理邏輯,若是用類組件的話,這些狀態和邏輯都會混在一塊兒,不夠直觀:設計模式
class Com extends React.Component {
state = {
a: 1,
b: 2,
c: 3,
}
componentDidMount() {
handleA()
handleB()
handleC()
}
}
複製代碼
而使用 React Hooks 後,咱們能夠把狀態和邏輯關聯起來,分拆成多個自定義 Hooks ,代碼結構就會更清晰:api
function useA() {
const [a, setA] = useState(1)
useEffect(() => {
handleA()
}, [])
return a
}
function useB() {
const [b, setB] = useState(2)
useEffect(() => {
handleB()
}, [])
return b
}
function useC() {
const [c, setC] = useState(3)
useEffect(() => {
handleC()
}, [])
return c
}
function Com() {
const a = useA()
const b = useB()
const c = useC()
}
複製代碼
咱們除了能夠利用自定義 Hooks 來拆分業務邏輯外,還能夠拆分紅複用價值更高的通用邏輯,好比說目前比較流行的 Hooks 庫:react-use;另外,React 生態中原來的不少庫,也開始提供 Hooks API ,如 react-router 。數組
React 提供了大量的組件生命週期鉤子,雖然在平常業務開發中,用到的很少,但光是 componentDidUpdate 和 componentWillUnmount 就讓人很頭痛了,一不留神就忘記處理 props 更新和組件銷燬須要處理反作用的場景,這不只會留下肉眼可見的 bug ,還會留下一些內存泄露的隱患。性能優化
類 MVVM 框架講究的是數據驅動,而生命週期這種設計模式,就明顯更偏向於傳統的事件驅動模型;當咱們引入 React Hooks 後,數據驅動的特性可以變得更純粹。
下面咱們以一個很是典型的列表頁面來舉個例子:
class List extends Component {
state = {
data: []
}
fetchData = (id, authorId) => {
// 請求接口
}
componentDidMount() {
this.fetchData(this.props.id, this.props.authorId)
// ...其它不相關的初始化邏輯
}
componentDidUpdate(prevProps) {
if (
this.props.id !== prevProps.id ||
this.props.authorId !== prevProps.authorId // 別漏了!
) {
this.fetchData(this.props.id, this.props.authorId)
}
// ...其它不相關的更新邏輯
}
render() {
// ...
}
}
複製代碼
上面這段代碼有3個問題:
若是改爲用 React Hooks 來實現,問題就能獲得很大程度上的解決了:
function List({ id, authorId }) {
const [data, SetData] = useState([])
const fetchData = (id, authorId) => {}
useEffect(() => {
fetchData(id, authorId)
}, [id, authorId])
}
複製代碼
改用 React Hooks 後:
最多見的反作用莫過於綁定 DOM 事件:
class List extends React.Component {
handleFunc = () => {}
componentDidMount() {
window.addEventListener('scroll', this.handleFunc)
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleFunc)
}
}
複製代碼
這塊也仍是會有上述說的,影響高內聚的問題,改爲 React Hooks :
function List() {
useEffect(() => {
window.addEventListener('scroll', this.handleFunc)
}, () => {
window.removeEventListener('scroll', this.handleFunc)
})
}
複製代碼
並且比較絕的是,除了在組件銷燬的時候會觸發外,在依賴項變化的時候,也會執行清除上一輪的反作用。
在使用類組件的時候,咱們須要利用 componentShouldUpdate 這個生命週期鉤子來判斷當前是否須要從新渲染,而改用 React Hooks 後,咱們能夠利用 useMemo 來判斷是否須要從新渲染,達到局部性能優化的效果:
function List(props) => {
useEffect(() => {
fetchData(props.id)
}, [props.id])
return useMemo(() => (
// ...
), [props.id])
}
複製代碼
在上面這段代碼中,咱們看到最終渲染的內容是依賴於props.id
,那麼只要props.id
不變,即使其它 props 再怎麼辦,該組件也不會從新渲染。
在咱們剛開始使用 React Hooks 的時候,常常會遇到這樣的場景:在某個事件回調中,須要根據當前狀態值來決定下一步執行什麼操做;但咱們發現事件回調中拿到的老是舊的狀態值,而不是最新狀態值,這是怎麼回事呢?
function Counter() {
const [count, setCount] = useState(0);
const log = () => {
setCount(count + 1);
setTimeout(() => {
console.log(count);
}, 3000);
};
return (
<button onClick={log}>報數</button>
);
}
/* 若是咱們在三秒內連續點擊三次,那麼count的值最終會變成 3,而隨之而來的輸出結果是? 0 1 2 */
複製代碼
「這是 feature 不是 bug 」,哈哈哈,說是 feature 可能也不太準確,由於這不正是 javascript 閉包的特性嗎?當咱們每次往setTimeout
裏傳入回調函數時,這個回調函數都會引用下當前函數做用域(此時 count 的值還未被更新),因此在執行的時候打印出來的就會是舊的狀態值。
那爲啥類組件中,每次都能取到最新的狀態值呢?這是由於咱們在類組件中取狀態值都是從this.state
裏取的,這至關因而類組件的一個執行上下文,永遠都是保持最新的。
經過
useRef
建立的對象,其值只有一份,並且在全部 Rerender 之間共享。
聽上去,這 useRef 其實跟 this.state 很類似嘛,都是一個能夠一直維持的值,那咱們就能夠用它來維護咱們的狀態了:
function Counter() {
const count = useRef(0);
const log = () => {
count.current++;
setTimeout(() => {
console.log(count.current);
}, 3000);
};
return (
<button onClick={log}>報數</button>
);
}
/* 3 3 3 */
複製代碼