擁有了hooks,你再也不需要寫Class了,你的所有組件都將是Function
class版
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
使用hook后
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
組件變成了一個函數(shù),但這個函數(shù)卻有自己的狀態(tài)(count),同時它還可以更新自己的狀態(tài)(setCount)。這個函數(shù)之所以這么了不得,就是因為它注入了一個hook--useState,就是這個hook讓我們的函數(shù)變成了一個有狀態(tài)的函數(shù)
Hooks本質(zhì)上就是一類特殊的函數(shù),它們可以為你的函數(shù)型組件(function component)注入一些特殊的功能
React為什么要搞一個Hooks?
1.想要復(fù)用一個有狀態(tài)的組件太麻煩了!
我們都知道react都核心思想就是,將一個頁面拆成一堆獨立的,可復(fù)用的組件,并且用自上而下的單向數(shù)據(jù)流的形式將這些組件串聯(lián)起來。但假如你在大型的工作項目中用react,你會發(fā)現(xiàn)你的項目中實際上很多react組件冗長且難以復(fù)用。尤其是那些寫成class的組件,它們本身包含了狀態(tài)(state),所以復(fù)用這類組件就變得很麻煩。
那之前,官方推薦怎么解決這個問題呢?答案是:渲染屬性(Render Props)和高階組件(Higher-Order Components)。 這兩種模式看上去都挺不錯的,很多庫也運用了這種模式,比如我們常用的React Router。但我們仔細看這兩種模式,會發(fā)現(xiàn)它們會增加我們代碼的層級關(guān)系。
2.生命周期鉤子函數(shù)里的邏輯太亂!
我們通常希望一個函數(shù)只做一件事情,但我們的生命周期鉤子函數(shù)里通常同時做了很多事情。比如我們需要在componentDidMount中發(fā)起ajax請求獲取數(shù)據(jù),綁定一些事件監(jiān)聽等等。同時,有時候我們還需要在componentDidUpdate做一遍同樣的事情。當(dāng)項目變復(fù)雜后,這一塊的代碼也變得不那么直觀。
3.this指向
為了保證this的指向正確,我們要經(jīng)常寫這樣的代碼:this.handleClick = this.handleClick.bind(this),或者是這樣的代碼:<button onClick={() => this.handleClick(e)}>。
什么是State Hooks
useState是react自帶的一個hook函數(shù),它的作用就是用來聲明狀態(tài)變量。useState這個函數(shù)接收的參數(shù)是我們的狀態(tài)初始值(initial state),它返回了一個數(shù)組,這個數(shù)組的第[0]項是當(dāng)前當(dāng)前的狀態(tài)值,第[1]項是可以改變狀態(tài)值的方法函數(shù)。
react規(guī)定我們必須把hooks寫在函數(shù)的最外層,不能寫在ifelse等條件語句當(dāng)中,來確保hooks的執(zhí)行順序一致
let showFruit = true;
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
if(showFruit) {
const [fruit, setFruit] = useState('banana');
showFruit = false;
}
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
//第一次渲染
useState(42); //將age初始化為42
useState('banana'); //將fruit初始化為banana
useState([{ text: 'Learn Hooks' }]); //...
//第二次渲染
useState(42); //讀取狀態(tài)變量age的值(這時候傳的參數(shù)42直接被忽略)
// useState('banana');
useState([{ text: 'Learn Hooks' }]); //讀取到的卻是狀態(tài)變量fruit的值,導(dǎo)致報錯
注意:每次渲染都會有自己獨立的props和state
什么是Effect Hooks
Effect Hook 可以讓你能夠在 Function 組件中執(zhí)行副作用(side effects):
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 類似于componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 更新文檔的標(biāo)題
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
第一,react首次渲染和之后的每次渲染都會調(diào)用一遍傳給useEffect的函數(shù)。而之前我們要用兩個聲明周期函數(shù)來分別表示首次渲染(componentDidMount),和之后的更新導(dǎo)致的重新渲染(componentDidUpdate)。
第二,useEffect中定義的副作用函數(shù)的執(zhí)行不會阻礙瀏覽器更新視圖,也就是說這些函數(shù)是異步執(zhí)行的,而之前的componentDidMount或componentDidUpdate中的代碼則是同步執(zhí)行的。這種安排對大多數(shù)副作用說都是合理的,但有的情況除外,比如我們有時候需要先根據(jù)DOM計算出某個元素的尺寸再重新渲染,這時候我們希望這次重新渲染是同步發(fā)生的,也就是說它會在瀏覽器真的去繪制這個頁面前發(fā)生。
怎么跳過一些不必要的副作用函數(shù)
我們只需要給useEffect傳第二個參數(shù)即可。用第二個參數(shù)來告訴react只有當(dāng)這個參數(shù)的值發(fā)生改變時,才執(zhí)行我們傳的副作用函數(shù)(第一個參數(shù))
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 只有當(dāng)count的值發(fā)生變化時,才會重新執(zhí)行`document.title`這一句
當(dāng)我們第二個參數(shù)傳一個空數(shù)組[]時,其實就相當(dāng)于只在首次渲染的時候執(zhí)行。也就是componentDidMount加componentWillUnmount的模式。不過這種用法可能帶來bug
模擬場景
componentDidMount、componentDidUpdate
import React,{useEffect} from 'react'
useEffect(()=>{
//每次componentDidMount、componentDidUpdate都會調(diào)用這兒
})
componentDidMount 加第二個參數(shù)[]
useEffect(()=>{
//componentDidMount執(zhí)行
},[])
componentDidUnMount 加第二個參數(shù)[]
useEffect(()=>{
//componentDidMount執(zhí)行
return ()=>{
//componentDidUnMount執(zhí)行
}
},[])
React.memo和useMemo 減少不必要的渲染
React.momo其實并不是一個hook,它其實等價于PureComponent,但是它只會對比props
import React, { useState } from 'react';
export const Count = React.memo((props) => {
const [data, setData] = useState({
count: 0,
age: 18,
});
const handleClick = () => {
const { count } = data;
setData({
...data,
count: count + 1,
})
};
return (<button onClick={handleClick}>count:{data.count}</button>)
});