介绍 于react 16.8版本引入,主要功能在于让你无需创建一个类定义,即可使用state
等react特性。
是什么
Hook 是一些可以让你在==函数组件==里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。
为什么使用hooks
-
逻辑代码难以在组件间复用
-
render props
- 需要重新组织代码结构,使得代码更加定制化,难以维护。
- higher-order components
-
-
非class模式下,使用更多的react特性
- Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
优势
-
没有破坏性改动
- 向后兼容
- 可选
- 函数式编程
API
useState
函数声明
useState:< S >(initialState: S | (() => S))=> [S, Dispatch<SetStateAction< S >>];
- SetStateAction< S > = S | ((prevState: S) => S);
- 复用逻辑,不复用数据,这意味着你在多处使用使用了
useState
的组件,获取的数据并不是同一个,所有 state 和副作用都是完全隔离的。 - 调用
Dispatch<SetStateAction< S >>
会触发函数组件的重新渲染。
useEffect
函数声明
useEffect:(effect: ()=>?()=>void, deps?: DependencyList)=> void;
- 组件销毁后,会调用
effect
返回的清洁函数(如果有)来取消副作用(比如订阅,计时器等) -
deps
标识effect所依赖的值数组。若为空数组[],则仅在第一次渲染后调用,并在卸载前销毁。(此时更类似componentDidMount
和componentWillUnmount
) -
每次渲染后调用,==包括==第一次(React类生命周期中,
componentDidMount
与componentDidUpdate
的合集)- 为什么需要在每一次更新后执行,参考如下代码
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } //假如在在组件展示在屏幕上时,friend.id变化了。此时组件没有正常取消原来的订阅逻辑,同时在取消订阅时传递了错误的好友id,可能导致一些bug
- 如果在class组件中
//需添加 componentDidUpdate 来解决这个问题 componentDidUpdate(prevProps) { if(prevPropsfriend.id !== this.props.friend.id){ // 取消订阅之前的 friend.id ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // 订阅新的 friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } }
- 使用Hook的函数组件
function FriendStatus(props) { // ... useEffect(() => { // ... ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; },[props.friend.id]);//仅在friend.id改变时更新
-
传递给 useEffect 的函数在每次渲染中都会有所不同,这是刻意为之的。事实上这正是我们可以在 effect 中获取最新的的值,而不用担心其过期的原因。每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染。
- 这里使用匿名函数作为参数传递给
effect
- 这里使用匿名函数作为参数传递给
-
useEffect会在每次浏览器绘制后,且下一次绘制前执行
- 与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
useMemo
函数声明
useMemo<T>(factory: () => T, deps: DependencyList | undefined)=> T;
- 保存上一次的计算结果,
deps
数组内依赖项改变时计算memoized - 传入
useMemo
的函数会在渲染期间执行,可用于缓存子节点的渲染结果。
const Button = React.memo((props) => {
// 你的组件
});
useCallback
函数声明
useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
- useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
- 可用于避免匿名函数造成的重复渲染。
function MyComponent(props) {
const clickCallback = React.useCallback(() => {
// ...
}, []);
// 这里如果直接传递匿名函数,会造成每次渲染结果与上一次不一致
// 在这个例子中:MyComponent会重新渲染,但button不会
return <button onClick={clickCallback}>Click Me!</button>;
}
useRef
函数声明
useRef<T>(initialValue: T)=> MutableRefObject<T>;
-
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)
- useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
- 本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。由于每一个函数组件都是一个闭包,
useRef
实现了一套穿透闭包的逻辑。
- 如果你将 ref 对象以
<div ref={myRef} />
形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
- 可以用于获取上一轮渲染的
props
或者state
(prePorps
及preState
)。
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
// 或者使用自定义hook
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return <h1>Now: {count}, before: {prevCount}</h1>;
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
React 是如何把对 Hook 的调用和组件联系起来的?
- React 保持对当先渲染中的组件的追踪。多亏了 Hook 规范,我们得知 Hook 只会在 React 组件中被调用(或自定义 Hook —— 同样只会在 React 组件中被调用)。
- 每个组件内部都有一个「记忆单元格」列表。它们只不过是我们用来存储一些数据的 JavaScript 对象。当你用 useState() 调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState() 调用会得到各自独立的本地 state 的原因。
-
React依赖Hook调用的顺序来确保
state
与useState
的对应关系。- 详见:Hook规则
逻辑共享
按照官方描述,这个是自定义Hook
-
自定义 Hook 是一个函数,其名称必须以“use” 开头,函数内部可以调用其他的 Hook。
- 否则React无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了Hook 的规则。
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
//第一处复用
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
//第二处复用
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
注意
- 只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中)
-
useCallback
与useMemo
主要用于性能优化,不要过早进行性能优化。否则无法比较优化结果,很可能某个不注意的角落反而会导致性能降低。 - eslint-linter 插件用于匹配以上规则
-
与 class 组件中的
setState
方法不同,useState
不会自动合并更新对象。你可以用函数式的setState
结合展开运算符来达到合并更新对象的效果。setState(prevState => { // 也可以使用 Object.assign return {...prevState, ...updatedValues}; });
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。