Hooks是一类特殊的函数,适用于React的函数组件,可以让我们在不编写class的情况下使用state及其他的React特性,比如副作用处理及生命周期等。
Hooks主要可以解决以下几类问题:
//安装
npm install eslint-plugin-react-hooks --save-dev
//eslint配置文件
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
// 检查 Hooks 的使用规则
"react-hooks/rules-of-hooks": "error",
// 检查依赖项的声明
"react-hooks/exhaustive-deps": "warn"
}
}
备注:react通过调用hooks的调用顺序,来确认state对应那个useState,如果使用了条件判断语句,可能导致前后的调用顺序不一致,就会导致bug的出现
useState能够给函数组件引入state来实现函数组件的 状态管理,组件内部可以通过setState来修改state的值。state的变化同样会触发组件的重新渲染。
// 引入
import React, { useState } from 'react'
...
// 数组解构,stateInit只会在初始创建state时有效,后续重复渲染时,会采用已存在的state
const [state, setState] = useState(initialState)
// 设置state
setState(state)
...
useEffect在渲染后判断依赖并调用,通过useEffect可以实现在函数组件中执行副作用(数据获取、设置订阅、记录日志以及手动更改React组件中的DOM)操作。useEffect支持通过返回一个清除函数的方式,来实现组件卸载的时候执行必要的一些清除操作。
// 引入
import React, { useEffect } from 'react'
...
useEffect(() => {
document.title = `You clicked ${count} times`;
});
// useEffect
useEffect(()=>{
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
})
// useEffect支持传入第二个参数,以此实现按需重新执行
useEffect(()=>{
console.log(count)
}, [count])
该hook用来订阅上层组件中传递的context value值。当值发生变化的时候会重新的触发组件的渲染,该context值有上层组件中距离当前组件最近的xxx.Provider的value prop决定,useContext的作用相当于在全局设置了一个变量,但是相对于全局变量而言,useContext的值发生改变之后,是会触发所有用到了useContext的地方自动刷新。
import React,{useContext} from 'react'
const TextContext = React.createContext('React!')
export default function HookUseContext() {
return (
<div>
<TextContext.Provider value={111}>
<CompA />
</TextContext.Provider>
</div>
)
}
function CompA(){
return (
<div>
<CompB />
</div>
)
}
function CompB(){
const content = useContext(TextContext)
return (
<div>
<span>
{content}
</span>
</div>
)
}
useContext的使用要依赖于react的一些其他API,具体如下:
把内联回调函数及依赖项数组作为参数传入 useCallback,在初次渲染的时候执行,并返回该回调函数的 memoized(缓存)版本,初次渲染完成后,该回调函数仅在某个依赖项改变时才会更新。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
React.memo()包裹的组件(一般为函数组件,类组件可以采用PureComponent/shouldComponentUpdate进行props的比较),当props相同时,会直接复用上次的组件渲染结果,以此来提高性能。当组件中存在useState,useReducer或useContext的调用时,state或者context的变化依旧会触发组件的重新渲染。React.memo()默认只做浅层次比较,但支持传入第二个参数来实现props的深层次对比。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
当我们需要将函数作为prop传递给子组件时,由于每次渲染都会导致函数的重新创建,从而触发子组件的重新渲染,使用useCallback可以将函数类型的props的值进行缓存,仅当依赖项发生变化时,才会触发新的函数生成,从而减少带有函数类型prop的子组件的重新渲染。
import "./styles.css";
import { useEffect, useState, useMemo, useCallback } from "react";
export default function App() {
const [value, setValue] = useState(1);
let result = useMemo(() => {
console.log(123);
}, []);
const handleClick = useCallback(() => {
console.log("useMemo被执行了")
}, [value]);
return (
<div className="App">
<input onChange={(e) => setValue(2)} />
<Button handleClick={handleClick} />
</div>
);
}
const Button = (props) => {
const { handleClick } = props;
console.log("button被渲染了");
return <button onClick={handleClick}>点击</button>;
};
该hook在首次渲染的时候会执行,并返回一个缓存的值。之后仅当依赖项数组中的内容发生变化时,才会重新执行第一个参数带入的函数,然后返回新的值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import React, { useMemo } from 'react';
export default (props = {}) => {
console.log(`--- component re-render ---`);
return useMemo(() => {
console.log(`--- useMemo re-render ---`);
return
number is : {props.number}
}, [props.number]);
}
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducer可以作为useState的替代方案。入参reducer类似于一个==(state, action) => newState==的reducer。该hooks返回当前的state及配套的dispatch方法。
// 指定初始化的方式
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
>
);
}
useReducer的使用场景
const refContainer = useRef(initialValue);
useRef返回一个可变的Ref对象,其.current属性被初始化为传入的参数(initialValue)。返回的ref对象在组件的整个生命周期内持续存在,因此useRef适用于跨渲染的数据存储和共享。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
>
);
}
知识关联——ref的关联方式:
1.字符串声明(不被推荐)
2.React.createRef声明,通过ref属性关联react元素
3.回调ref,将一个回调函数传递给ref,React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。这方便我们处理在绑定和卸载ref时候做相关的特殊处理。
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。useImperativeHandle 应当与 forwardRef 一起使用。
import React, { useImperativeHandle, useRef, forwardRef } from 'react';
function FancyInput(props, ref) {
useImperativeHandle(ref, () => ({
focus: () => {
ref.current.focus();
},
}));
// inputRef属性传递
return ;
}
FancyInput = forwardRef(FancyInput)
export default function HooksUseImperativeHandle() {
const inputRef = useRef(null);
console.log(inputRef)
return (
);
}
知识拓展——React.forwardRef
React.forwardRef 接受一个渲染函数作为入参,返回一个创建好的React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签
hook的一大优势就在于能够封装公用逻辑,并方便复用。除了react提供的hook之外,还可以自定义hook来实现逻辑的封装。自定义hook是一个名为“use”开头的函数,函数内部调用了其他的Hook,常用来将可重用的逻辑单独抽离成一个可重用的函数。
采用自定义Hooks相对于封装工具类而言,hook的优势在于可以管理当前组件组件的state,而普通的工具类是无法修改组件的state的,也就无法在数据改变的时候触发组件的重新渲染。
典型的集中自定义hooks的场景有:
类组件存在生命周期的钩子函数,在转换到函数组件时,可以分别对应到一些hook中。比如componentDidUpdate、componentDidMount、componentWillUnmount可以对应useEffect。而部分其他的生命周期函数如getSnapshotBeforeUpdate、componentDidCatch、getDerivedStateFromError等,目前则暂时没有对用的hooks去实现。
memo、useMemo及useCallback解析