🎼 React 16.8 版本引入了 Hooks ,可以在不使用 Class 的情况下使用 React 特性。
Hooks 允许从函数组件 “hook into” React 状态和生命周期特性。
function Counter () {
const [count, setCount] = useState(0);
return (<>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}> +1 </button>
</>)
}
一个新的方案引入,一定是为了解决现存的问题。对于 Hooks 来说,就是为解决 Class 的诟病。
因此,引入了 Hooks:
Hooks 是否可以完全取代 render props 和 Hoc 组件?1 答:不能,例如虚拟滚动组件需要具有 renderItem prop,以及可视化容器组件可能具有自己的DOM结构。
✔️ Hooks 让我们根据代码所做的,而不是生命周期方法名称来分割代码。React 组件一直更像是函数,而 Hooks 则拥抱了函数。
✅ 在函数组件的顶层调用 Hooks ✅ 在 React 的函数组件或自定义Hooks中调用 Hook
下述以 useState(React 内置钩子) 为例:
// ✅ 在函数组件的顶层调用
function Counter () {
const [count, setCount] = useState(0);
}
// ✅ 在自定义Hooks的顶层调用
function useWindowWidth () {
const [width, setWidth] = useState(window.innerWidth);
}
不要在循环、条件、嵌套函数 或 try/catch/finally 块中调用。这样可以做到各个 Hook 在每一次渲染中,调用的顺序是一致的。
const [count, setCount] = useState(0);
数组结构语法允许我们为状态变量赋予不同的名称。这些名称不是 useState API 的一部分。
constructor
: 函数组件不需要 constructor,可以通过 useState
初始化(如果数据复杂,可以传入函数);getDerivedStateFromProps
:渲染过程更新shouldComponentUpdate
:使用 React.memo
componentDidMount
, componentDidUpdate
, componentWillUnmount
:Effect Hooks 可以替代示例:Class 形式:
class FriendStatusWithCounter extends React.Component {
constructor(props) {
super(props);
}
// 初始化:订阅
componentDidMount() {
ChatAPI.subscribeToFriendStatus(...);
}
// DOM更新:先取消再重新订阅
componentDidUpdate(prevProps) {
ChatAPI.unsubscribeFromFriendStatus(...);
ChatAPI.subscribeToFriendStatus(...);
}
// 卸载:取消订阅
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(...);
}
}
Hooks 形式:
function FriendStatus(props) {
useEffect(() => {
// 订阅
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
// 取消
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
}
})
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id);
return () => ChatAPI.unsubscribeFromFriendStatus(props.friend.id);
}, [props.userId]);
在上面的示例中,如果 Id 从 3 => 4,ChatAPI.unsubscribeFromFriendStatus(3)
将首先运行,然后 ChatAPI.subscribeToFriendStatus(4)
将运行。不需要获取 “previous Id”,因为 clean up 函数将在闭包中捕获它。
➰或者可以将以前的 state 或 props 存储。
function ScrollView({row}) {
const [isScrollingDown, setIsScrollingDown] = useState(false);
// 存储之前值
const [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// 自上次渲染后更改
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
可以用于记住用户输入的信息。
useState
声明可以直接更新的状态变量。useReducer
在 reducer 函数 中声明带有更新逻辑的 state 变量。从祖先组件接收信息,而无需将其作为 props 传递。
useContext
读取订阅上下文。// ① 创建context
const ThemeContext = createContext(null);
export default () => {
const [color, setColor] = useState('red');
return (<>
{/* ② 使用 context provider 进行包裹 */}
<ThemeContext.Provider value={color}>
<MyText />
</ThemeContext.Provider>
</>)
}
function MyText() {
// ③ 使用
const color = useContext(ThemeContext);
return (<>
<div>{color}</div>
</>)
}
保存一些不用于渲染的信息,比如 DOM 节点或 timeout ID。
useRef
声明 ref。你可以在其中保存任何值,但最常用于保存 DOM 节点。useImperativeHandle
自定义从组件中暴露的 ref,但是很少使用。连接到外部系统并与之同步。这包括处理网络、浏览器、DOM、动画、使用不同 UI 库编写的小部件以及其他非 React 代码。
useEffect
将组件连接到外部系统。useLayoutEffect
在浏览器重新绘制屏幕前执行,可以在此处测量布局。useInsertionEffect
在 React 对 DOM 进行更改之前触发,库可以在此处插入动态 CSS。优化重新渲染性能的一种常见方法是跳过不必要的工作。例如,可以告诉 React 重用缓存的计算结果,或者如果数据自上次渲染以来没有更改,则跳过重新渲染:
useMemo
缓存计算代价昂贵的计算结果。useCallback
将函数传递给优化组件之前缓存函数定义。将必须同步的阻塞更新(比如使用输入法输入内容)与不需要阻塞用户界面的非阻塞更新(比如更新图表)分离以提高性能:
useTransition
允许将状态转换标记为非阻塞,并允许其他更新中断它。useDeferredValue
允许延迟更新 UI 的非关键部分,以让其他部分先更新。useDebugValue
自定义 React 开发者工具为自定义 Hook 添加的标签。useId
将唯一的 ID 与组件相关联,其通常与可访问性 API 一起使用。useSyncExternalStore
订阅外部 store。