不经过编写类组件的状况下,能够在组件内部使用状态(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 的讨论