概述
Hook 使你在非 class 的情况下可以使用更多的 React 特性。Hook 是一些可以在函数组件里钩入React state 及生命周期等特性的函数。Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则
- 只在最顶层使用 Hook。不要在循环,条件或嵌套函数中调用 Hook。
- 只在 React 函数中调用 Hook。不要在普通的 JavaScript 函数中调用 Hook。可以在
- React 的函数组件中调用 Hook
- 在自定义 Hook 中调用其他 Hook
Hook 是一种服用状态逻辑的方式,它不复用 state 本身。事实上 Hook 的每次调用都有一个完全独立的 state,因此可以在单个组件中多次调用同一个自定义 Hook。
React 依据 Hook 调用的顺序知道哪个 state 对应哪个 useState
State Hook
在 React 函数组件上添加内部 state
Effect Hook
默认情况下,React 会在每次渲染后调用副作用函数,包括第一次渲染的时候。通过使用 Hook,可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不是要把他们拆分到不同的生命周期函数里。
- 副作用 => 数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用
在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的
无需清除的 effect
在 React 更新 DOM 之后运行一些额外的代码。发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
每次重新渲染,都会生成新的 effect,替换掉之前的。effect 更像是渲染结果的一部分,每个 effect 属于一次特定的渲染
需要清除的 effect
订阅外部数据源这种副作用是需要清除的,可以防止引起内存泄漏。副作用函数可以通过返回一个函数来指定如何清除副作用
React 何时清除 effect?React 会在组件卸载的时候执行清除操作 + effect 在每次渲染的时候都会执行 => React 会在执行当前 effect 之前对上一个 effect 进行清除。
自定义 Hook
自定义 Hook 可以在不增加组件的情况下在组件之间重用一些状态逻辑,即可以将组件逻辑提取到可重用的函数中。自定义 Hook 更像是一种约定而不是功能。
- 自定义 Hook 必须以
use
开头。 - 在两个组件中使用相同 Hook 不会共享 state。每次使用自定义 Hook 时,其中所有 state 和副作用都是完全隔离的。
- 每次调用 Hook,自定义 Hook 都会获取独立的 state。直接调用自定义 Hook 时,从 React Dee角度来看,组件只是调用了
useState
和useEffect
常用 Hook
useState
const [state, setState] = useState(initialState);
setState
setState
可以接收
- 新值,从而更新
state
,使得state
为新值 - 函数,参数为先前的
state
,state
为函数的返回值const [count, setCounnt] = useState(0); setCounnt(prevCount => prevCount + 1);
useState
- 如果初始
state
需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的state
,此函数只在初始渲染时被调用const [state, setState] = useState(() => { const initState = someExpensiveComputation(props); return initState; });
useEffect
useEffect
可以看做 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
通过传递数组作为 useEffect
的第二个可选参数从而跳过 Effect 进行性能优化。数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量。
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。
useEffect 的 effect 的执行时机
与 componentDidMount
、componentDidUpdate
不同的是,在浏览器完成布局与绘制之后,传给 useEffect
的函数会延迟调用,所以不应在函数中执行阻塞浏览器更新屏幕的操作
useRef
const redContainer = useRef(initValue);
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
本质上,useRef
就像是可以在其 .current
属性中保存一个可变值的"盒子"。
useRef()
Hook 不仅可以用于 DOM refs,「ref」对象是一个 current
属性可变且可以容纳任意值的通用容器。
例1
function TextInputWithFocusButton() {
const inputEle = useRef(null);
const onButtonClick = () => {
// current 指向已挂载到 DOM 上的文本输入元素
inputEle.current.focus();
}
return (
<>
<input ref={inputEle} type='text' />
<button onClick={onButtonClick}>Focus the input</button>
</>
)
}
例2:useRef
可以很方便的保存任何可变值
function Timer() {
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setinterval(() => {
// ...
});
return () => {
clearInterval(intervalRef.current);
}
});
}
注: 当 ref 对象内容发生变化时,useRef
并不会通知。变更 .current
属性不会引起组件重新渲染。如果想要在 React 绑定和解绑 DOM 节点的 ref 时运行某些代码,则需要使用 useCallback
来实现
useCallback
返回一个 memoized 函数。
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将非常有用。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
useMemo
返回一个 memoized 值
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把创建函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
如果没有提供依赖项,useMemo
在每次渲染时都会计算新的值。可以把 useMemo
作为性能优化的手段,但不要把它当成语义上的保证
注:传入 useMemo
的函数会在渲染期间执行。不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect
的适用范畴
注意:
-
useState
不会自动合并并更新对象。可是使用函数式的setState
达到合并并更新对象的效果setState(prevState => ({...prevState, ...updateValues}));
- 如果在渲染期间执行了高开销的计算,则可以使用
useMemo
来进行了优化
网友评论