動(dòng)機(jī)
一直在考慮為什么要使用,我們明明使用state使用的好好的掖桦,那會(huì)發(fā)明HOOK的動(dòng)機(jī)是什么呢限府?
- 在組件中復(fù)用狀態(tài)邏輯很復(fù)雜
使用Hooks猴鲫,可以從組件中提取有狀態(tài)邏輯,以便可以獨(dú)立測(cè)試并復(fù)用谣殊。Hooks允許在不更改組件層次結(jié)構(gòu)的情況下復(fù)用有狀態(tài)的邏輯。 這樣可以輕松地在許多組件之間共享Hooks牺弄。 - 復(fù)雜組件變得難以理解
使用state會(huì)導(dǎo)致相關(guān)的邏輯被切分姻几,一些相關(guān)的代碼卻被分割在不同的函數(shù)文件中,這樣導(dǎo)致代碼的可讀性變?nèi)酰琀ook 將組件中相互關(guān)聯(lián)的部分拆分成更小的函數(shù)(比如設(shè)置訂閱或請(qǐng)求數(shù)據(jù))蛇捌,而并非強(qiáng)制按照生命周期劃分抚恒,也可以使用reducer來(lái)進(jìn)行相關(guān)邏輯的復(fù)用 - 難以理解的 class
class使用的時(shí)候,要先了解JavaScript的this使用络拌,而且還要進(jìn)行事件的綁定俭驮,對(duì)于函數(shù)組件與 class 組件的差異也存在分歧,甚至還要區(qū)分兩種組件的使用場(chǎng)景春贸。hook 使用非 class 的情況下可以使用更多的 React 特性混萝。 從概念上講,React 組件一直更像是函數(shù)萍恕。而 Hook 則擁抱了函數(shù)逸嘀,同時(shí)也沒(méi)有犧牲 React 的精神原則。
概覽
hooks其實(shí)就是一種特殊的函數(shù)允粤,這個(gè)函數(shù)有勾住狀態(tài)和生命周期的功能崭倘,但我們希望在組件中使用狀態(tài)的時(shí)候,我們就會(huì)使用hook用來(lái)代替class中的state类垫。Hook 不能在 class 組件中使用司光。
hook和class的對(duì)比
使用class的形式來(lái)寫組件的方法
import React from 'react'
class Person extends React.Component {
constructor(props) {
super(props);
this.state = {
username: 'kim'
}
}
componentDidMount() {
console.log('組件掛載后做的操作')
}
componentWillUnmount() {
console.log('組件將要卸載')
}
componentDidUpdate(prevProps, prevState) {
// 當(dāng)username發(fā)生改變的時(shí)候進(jìn)行渲染
if (prevState.username !== this.state.username) {
console.log('組件更新后的操作')
}
}
render() {
return (<Input onChange={(event) => this.setState({ username: event.target.value })} />)
}
}
用hook來(lái)寫函數(shù)組件
import React, { useEffect, useState } from 'react'
export const Person = () => {
const [name, setName] = useState('');
useEffect(() => {
console.log('組件掛載后要做的操作');
return () => {
console.log('組件卸載要做的操作')
}
}, [])
useEffect(() => {
console.log('當(dāng)name組件發(fā)生改變的時(shí)候顯示的樣式')
}, [name])
return (<div>
<p>歡迎 {name}</p>
<input type="text" placeholder="input a username" onChange={(event) => setName(event.target.value)}></input>
</div>
)
}
API
useState
- 參數(shù):是一個(gè)常量,組件初始化的時(shí)候就會(huì)進(jìn)行定義
- 參數(shù):是一個(gè)函數(shù)悉患,只有開(kāi)始渲染的時(shí)候函數(shù)才會(huì)執(zhí)行
- 返回值:長(zhǎng)度為2的數(shù)組残家,第一項(xiàng)是返回的state的值,第二項(xiàng)是改變?cè)搒tate的函數(shù)
用來(lái)初始化狀態(tài)购撼,在函數(shù)的內(nèi)部調(diào)用跪削,為函數(shù)添加內(nèi)部的state,useState會(huì)返回一對(duì)值迂求,分別是要更新的狀態(tài)和一個(gè)讓你更新他的函數(shù)碾盐,useState唯一的參數(shù)就是初始化的值。
// 聲明多個(gè) state 變量揩局!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// 聲明一個(gè)叫 "count" 的 state 變量,初始值為0毫玖,后續(xù)通過(guò)setCount改變它能讓視圖重新渲染
export const Count = () => {
const [count, setCount] = useState(0);
// initiState 只會(huì)在組件初渲染的時(shí)候起作用,后續(xù)的渲染會(huì)被忽略不計(jì)
const [value,setValue] = setValue(()=>{
const initialCount = someExpensiveComputation(props);
return initialState;
})
return (<div>
<p> 你點(diǎn)擊了{(lán)count}次</p>
<button onClick={() => setCount(count + 1)}> Click me</button>
</div>)
}
useEffect
- 參數(shù):第一個(gè)是含有副作用的命令式的代碼凌盯,
- 參數(shù):第二個(gè)參數(shù)是一個(gè)數(shù)組付枫,數(shù)組中的值用來(lái)控制第一個(gè)參數(shù)中的函數(shù)是否執(zhí)行,當(dāng)?shù)诙€(gè)參數(shù)不傳的時(shí)候驰怎,是每一次有數(shù)據(jù)更新的時(shí)候阐滩,執(zhí)行第一個(gè)參數(shù)中的函數(shù),相當(dāng)于class組件中的componentDidMount和componentDidupdate的生命周期县忌。
export const Count = () => {
const [count, setCount] = useState(0);
// 功能類似componentDidMount and componentDidUpdate
useEffect(() => {
document.title = `You clicked ${count} times`;
});
// 只有count改變時(shí)才會(huì)執(zhí)行
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
// 組件掛載時(shí)只執(zhí)行一次
useEffect(() => {
console.log("只執(zhí)行一次掂榔,類似componentDidMount")
}, []);
return (<div>
<p> 你點(diǎn)擊了{(lán)count}次</p>
<button onClick={() => setCount(count + 1)}> Click me</button>
</div>)
}
可以在組件渲染后實(shí)現(xiàn)各種不同的副作用继效。有些副作用可能需要清除,比如說(shuō)全局設(shè)定鼠標(biāo)監(jiān)聽(tīng)事件装获。
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
useContext
當(dāng)hook接受到一個(gè)context的對(duì)象的時(shí)候并返回context的當(dāng)前值瑞信,當(dāng)前的 context 值由上層組件中距離當(dāng)前組件最近的 <MyContext.Provider> 的 value prop 決定。
// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)穴豫。
const ThemeContext = React.createContext(themes.light);
const App = () => {
// 在root中傳入這個(gè)值凡简,使用context進(jìn)行包裹,便于后續(xù)取值
return (
<ThemeContext.Provider value={themes.dark}>
<ToolBar />
</ThemeContext.Provider>
)
}
// 中間層不用傳遞參數(shù)
const ToolBar = () => {
return (
<ThemedButton />
)
}
// 在使用層直接進(jìn)行使用就可以了
const ThemedButton = () => {
const theme = useContext(ThemeContext)
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
)
}
Context 主要應(yīng)用場(chǎng)景在于很多不同層級(jí)的組件需要訪問(wèn)同樣一些的數(shù)據(jù)精肃。請(qǐng)謹(jǐn)慎使用秤涩,因?yàn)檫@會(huì)使得組件的復(fù)用性變差。
我們看一下上面的代碼以class的形式編寫肋杖,會(huì)是什么樣子的:
// Context 可以讓我們無(wú)須明確地傳遍每一個(gè)組件溉仑,就能將值深入傳遞進(jìn)組件樹(shù)。
// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)状植。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一個(gè) Provider 來(lái)將當(dāng)前的 theme 傳遞給以下的組件樹(shù)浊竟。
// 無(wú)論多深,任何組件都能讀取這個(gè)值津畸。
// 在這個(gè)例子中振定,我們將 “dark” 作為當(dāng)前的值傳遞下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中間的組件再也不必指明往下傳遞 theme 了肉拓。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 讀取當(dāng)前的 theme context后频。
// React 會(huì)往上找到最近的 theme Provider,然后使用它的值暖途。
// 在這個(gè)例子中卑惜,當(dāng)前的 theme 值為 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
useReducer
- 第一個(gè)參數(shù)是reducer純函數(shù)
- 第二個(gè)參數(shù)是初始的state
- 第三個(gè)參數(shù)可以修改初始state驻售,將初始 state 設(shè)置為 init(initialArg)
是useState的一種替代場(chǎng)景露久,如果我們都是使用useState就會(huì)散落到程序的各個(gè)地方,我們通過(guò)代碼比較一下二者的差別:
我們使用setState來(lái)完成這一段代碼
const LoginPage = () => {
const [name, setName] = useState(''); // 用戶名
const [err, setError] = useState(''); //錯(cuò)誤信息
const [pwd, setPwd] = useState(''); // 密碼
const [isLoading, setIsLoading] = useState(false); // 發(fā)送請(qǐng)求之后數(shù)據(jù)是否已經(jīng)返回
const [isLogged, setIsLogged] = useState(false) // 是否已經(jīng)登陸
const login = (e) => {
e.preventDefault();
setError('');
setIsLoading(true);
login({ name, pwd }).then(() => {
setIsLoggedIn(true); // 標(biāo)示登陸成功
setIsLoading(false); // 標(biāo)示loading完成
}).catch((error) => {
setError(error.message);
setName('');
setPwd('');
setIsLoading(false);
})
}
return(<div>jsx的界面</div>)
}
我們發(fā)現(xiàn)setState散落到方法的各個(gè)地方欺栗,導(dǎo)致代碼的已讀性大大的減弱了毫痕,我們?cè)倏匆幌?我們使用useReducer來(lái)進(jìn)行這個(gè)操作:
const loginReducer = (state, action) => {
switch (action.type) {
case 'login':
return {
...state,
isLoading: false,
error: ''
}
case 'success':
return {
...state,
isLoggedIn: true,
isLoading: false,
}
case 'error':
return {
...state,
error: action.payload.error,
name: '',
pwd: '',
isLoading: false
}
default:
return state;
}
}
function LoginPage() {
const [state, dispatch] = useReducer(loginReducer, initState);
const { name, pwd, isLoading, error, isLoggedIn } = state;
const login = (event) => {
event.preventDefault();
dispatch({ type: 'login' });
login({ name, pwd })
.then(() => {
dispatch({ type: 'success' });
})
.catch((error) => {
dispatch({
type: 'error',
payload: { error: error.message }
});
});
}
return (
<div></div>
// 返回頁(yè)面JSX Element
)
}
- 我們發(fā)現(xiàn)使用reducer寫的代碼,雖然代碼長(zhǎng)度變長(zhǎng)了迟几,但是代碼的可讀性大大的增加了消请,我們可以更加清晰的看到這么寫代碼的意圖
- LoginPage不需要關(guān)心如何處理這幾種行為,那是loginReducer需要關(guān)心的类腮,表現(xiàn)和業(yè)務(wù)分離臊泰。
- 所有的state處理都集中到了一起,使得我們對(duì)state的變化更有掌控力蚜枢,同時(shí)也更容易復(fù)用state邏輯變化代碼因宇,比如在其他函數(shù)中也需要觸發(fā)登錄error狀態(tài)七婴,只需要dispatch({ type: 'error' })。
總結(jié)一下使用state的場(chǎng)景:
- 如果你的state是一個(gè)數(shù)組或者對(duì)象
- 如果你的state變化很復(fù)雜察滑,經(jīng)常一個(gè)操作需要修改很多state
- 如果你希望構(gòu)建自動(dòng)化測(cè)試用例來(lái)保證程序的穩(wěn)定性
- 如果你需要在深層子組件里面去修改一些狀態(tài)(關(guān)于這點(diǎn)我們下篇文章會(huì)詳細(xì)介紹)
- 如果你用應(yīng)用程序比較大,希望UI和業(yè)務(wù)能夠分開(kāi)維護(hù)