不經過編寫類組件的狀況下,能夠在組件內部使用狀態(state) 和其餘 React 特性(生命週期,context)的技術
在以前的 React 版本中,組件分爲兩種:函數式組件(或無狀態組件(StatelessFunctionComponent))和類組件,而函數式組件是一個比較的純潔的 props => UI
的輸入、輸出關係,可是類組件因爲有組件本身的內部狀態,因此其輸出就由 props
和 state
決定,類組件的輸入、輸出關係就再也不那麼純潔。同時也會帶來下列問題:react
render props
或者 hoc
這些方案,可是這兩種模式對組件的侵入性太強。另外,會產生組件嵌套地獄的問題。state hook 提供了一種能夠在 function component 中添加狀態的方式。經過 state hook,能夠抽取狀態邏輯,使組件變得可測試,可重用。開發者能夠在不改變組件層次結構的狀況下,去重用狀態邏輯。更好的實現關注點分離。
一個簡單的使用 useState
栗子ios
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> ); };
幾點說明:git
useState
推薦一種更加細粒度的控制狀態的方式,即一個狀態對應一個狀態設置函數,其接受的參數將做爲這個狀態的初始值。其返回一個長度爲2的元組,第一項爲當前狀態,第二項爲更新函數。useState
的執行順序在每一次更新渲染時必須保持一致,不然多個 useState 調用將不會獲得各自獨立的狀態,也會形成狀態對應混亂。好比在條件判斷中使用 hook,在循環,嵌套函數中使用 hook,都會形成 hook 執行順序不一致的問題。最後致使狀態的混亂。另外,全部的狀態聲明都應該放在函數頂部,首先聲明。useState
和 setState
的區別github
useState
將setState
進行覆蓋式更新,而 setState 則將狀態進行合併式更新。
一個不正確的栗子redux
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} /> </> ); }
正確的作法axios
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 中的函數,包括初次渲染以及後續的每一次更新渲染。網絡
幾點說明:less
useEffect(effectCallback: () => void, deps: any[])
接收兩個參數,第二個參數依賴項是可選的,表示這個 effect 要依賴哪些值。也可使用 useEffect
和 useState
實現自定義 hook。async
一個給 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
不支持。詳見。參考連接
making-sense-of-react-hooks
rehooks
awesome-react-hooks
如何使用useEffect來獲取數據
hooks 是如何工做的
更多關於 hooks 的討論