不經過編寫類組件的狀況下,能夠在組件內部使用狀態(state) 和其餘 React 特性(生命週期,context)的技術react
在以前的 React 版本中,組件分爲兩種:函數式組件(或無狀態組件(StatelessFunctionComponent))和類組件,而函數式組件是一個比較的純潔的 props => UI
的輸入、輸出關係,可是類組件因爲有組件本身的內部狀態,因此其輸出就由 props
和 state
決定,類組件的輸入、輸出關係就再也不那麼純潔。同時也會帶來下列問題:ios
render props
或者 hoc
這些方案,可是這兩種模式對組件的侵入性太強。另外,會產生組件嵌套地獄的問題。state hook 提供了一種能夠在 function component 中添加狀態的方式。經過 state hook,能夠抽取狀態邏輯,使組件變得可測試,可重用。開發者能夠在不改變組件層次結構的狀況下,去重用狀態邏輯。更好的實現關注點分離。git
一個簡單的使用 useState
栗子github
import React, { useState } from "react";
const StateHook = () => {
const [count, setCount] = useState(0);
return (
<div> <p>you clicked {count} times</p> <button type="button" onClick={() => setCount(count + 1)}> click me </button> </div>
);
};
複製代碼
幾點說明:redux
useState
推薦一種更加細粒度的控制狀態的方式,即一個狀態對應一個狀態設置函數,其接受的參數將做爲這個狀態的初始值。其返回一個長度爲2的元組,第一項爲當前狀態,第二項爲更新函數。axios
useState
的執行順序在每一次更新渲染時必須保持一致,不然多個 useState 調用將不會獲得各自獨立的狀態,也會形成狀態對應混亂。好比在條件判斷中使用 hook,在循環,嵌套函數中使用 hook,都會形成 hook 執行順序不一致的問題。最後致使狀態的混亂。另外,全部的狀態聲明都應該放在函數頂部,首先聲明。數組
useState
和 setState
的區別網絡
useState
將setState
進行覆蓋式更新,而 setState 則將狀態進行合併式更新。less
一個不正確的栗子async
import React, { useState, ChangeEvent } from "react";
const UserForm = () => {
const [state, setUser] = useState({ name: "", email: "" });
const { name, email } = state;
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target: { value: name } } = event;
// 這裏不能夠單獨的設置某一個字段 新的狀態必須與初始的狀態類型保持一致
// 若是隻設置了其中一個字段,編譯器會報錯,同時其他的字段也會丟失
setUser({ name, email });
};
const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target: { value: email } } = event;
// 這裏不能夠單獨的設置某一個字段 新的狀態必須與初始的狀態類型保持一致
setUser({ name, email });
};
return (
<>
<input value={name} onChange={handleNameChange} />
<input value={email} onChange={handleEmailChange} />
</>
);
}
複製代碼
正確的作法
import React, { useState, ChangeEvent } from "react";
const UserForm = () => {
// 一個狀態對應一個狀態更新函數
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target: { value: name } } = event;
// hear could do some validation
setName(name);
};
const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target: { value: email } } = event;
// hear could do some validation
setEmail(email);
};
return (
<>
<input value={name} onChange={handleNameChange} />
<input value={email} onChange={handleEmailChange} />
</>
);
}
複製代碼
數據獲取,設置訂閱,手動的更改 DOM,均可以稱爲反作用,能夠將反作用分爲兩種,一種是須要清理的,另一種是不須要清理的。好比網絡請求,DOM 更改,日誌這些反作用都不要清理。而好比定時器,事件監聽。
一個簡單使用 effect hook 去修改文檔標題的栗子。
import React, { useState, useEffect } from "react";
const effectHook = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `you clicked ${count} times`;
}, [count]);
return (
<div> <p>you clicked {count} times</p> <button type="button" onClick={() => setCount(count + 1)}> click me </button> </div>
);
};
複製代碼
在調用 useEffect 後,至關於告訴 React 在每一次組件更新完成渲染後,都調用傳入 useEffect 中的函數,包括初次渲染以及後續的每一次更新渲染。
幾點說明:
useEffect(effectCallback: () => void, deps: any[])
接收兩個參數,第二個參數依賴項是可選的,表示這個 effect 要依賴哪些值。也可使用 useEffect
和 useState
實現自定義 hook。
一個給 DOM 元素添加事件監聽器的栗子。
import { useRef, useEffect } from "react";
type EventType = keyof HTMLElementEventMap;
type Handler = (event: Event) => void;
const useEventListener = (
eventName: EventType,
handler: Handler,
element: EventTarget = document
) => {
// 這裏使用 `useRef` 來保存傳入的監聽器,
// 在監聽器變動後,去更新 `useRef` 返回的對象的 `current` 屬性
const saveHandler = useRef<Handler>();
useEffect(() => {
saveHandler.current = handler;
}, [handler]);
useEffect(() => {
const supported = element && element.addEventListener;
if (!supported) {
return;
}
const listener: Handler = (event: Event) => (saveHandler.current as Handler)(event);
element.addEventListener(eventName, listener);
return () => {
element.removeEventListener(eventName, listener);
};
}, [eventName, element]);
}
複製代碼
一個使用 useReducer
來實現加、減計數器的栗子。這裏雖然使用 useReducer
建立了相似 redux 的 功能,可是若是有多個組件都引用了這個 hook,那麼這個 hook 提供的狀態是相互獨立、互不影響的,即 useReducer
只提供了狀態管理,可是並無提供數據持久化的功能。redux 卻提供了一種全局維護同一個數據源的機制。因此能夠利用 useReducer
和 Context
來實現數據持久化的功能。
import React, { useReducer } from "react";
const INCREMENT = "increment";
const DECREMENT = "decrement";
const initHandle = (initCount) => {
return { count: initCount };
};
const reducer = (state, action) => {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
case "reset":
return { count: action.payload };
default:
return state;
}
};
const Counter = ({ initialCount }) => {
const [state, dispatch] = useReducer(reducer, initialCount, initHandle);
const { count } = state;
return (
<div> Counter: {count} <button type="button" onClick={() => dispatch({ type: "reset", payload: initialCount })}> Reset </button> <button type="button" onClick={() => dispatch({ type: INCREMENT })}> + </button> <button type="button" onClick={() => dispatch({ type: DECREMENT })}> - </button>j </div>
);
};
複製代碼
一個對封裝數據請求栗子。
import { useState, useEffect } from "react";
import axios, { AxiosRequestConfig } from "axios";
interface RequestError {
error: null | boolean;
message: string;
}
const requestError: RequestError = {
error: null,
message: "",
};
/**
* @param url request url
* @param initValue if initValue changed, the request will send again
* @param options request config data
*
* @returns a object contains response's data, request loading and request error
*/
const useFetchData = (url: string, initValue: any, options: AxiosRequestConfig = {}) => {
const [data, saveData] = useState();
const [loading, updateLoading] = useState(false);
const [error, updateError] = useState(requestError);
let ignore = false;
const fetchData = async () => {
updateLoading(true);
const response = await axios(url, options);
if (!ignore) saveData(response.data);
updateLoading(false);
};
useEffect(() => {
try {
fetchData();
} catch (error) {
updateError({ error: true, message: error.message });
}
return () => {
ignore = true;
};
}, [initValue]);
return { data, loading, error };
};
export { useFetchData };
複製代碼
隨來 hooks 帶來了新的組件編寫範式,可是下面兩條規則仍是要開發者注意的。
hooks 的帶來,雖然解決以前存在的一些問題,可是也帶來了新的問題。
componentDidCatch
來捕獲組件做用域內的異常,作一些提示。可是在 hooks 中 ,咱們只能使用 try {} catch(){}
` 去捕獲,使用姿式也比較彆扭。this.setState()
中支持第二個參數,容許咱們在狀態變動後,傳入回調函數作一些其餘事情。可是 useState
不支持。詳見。連接**