这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战”
Hook
是React
16.8 的新特性,它可以让我们在函数式组件中使用state
为什么有了Hook
?
组件之间的复用状态逻辑
React
是构建用户界面的 JavaScript
库,可以轻松的通过 声明式 和 组件化 的形式创建交互式 UI
视图。它本事是注重 UI
层面的。
对于组件状态的复用,通常都是通过providers
、consumers
、render props
和 高阶组件,将一个个组件嵌套起来的,此时如果需要组织或者重构代码,你会发现是个很难的事情。这也是后来 redux
中大型项目中比较流行的原因。为了解决这个共享状态逻辑的痛点,Hook
的诞生,为你在无需修改组件结构的情况下复用状态逻辑提供了更加原生方式。
最常见使用方式就是把一些公共的逻辑状态提取到一个自定义的 hook
中:
function useLoadingStatus(fetch) {
const [isLoading, setIsLoading] = useState(null);
// ...
return isLoading;
}
组件的复杂度高,难以拆分为小的颗粒
在class
组件中,我们需要用到大量的生命周期函数,比如componentDidUpdate
、componentDidMount
等等,对复杂代码的拆分变的异常困难,而且很容易产生 bug
,大多情况下,很难将组件拆分成很多很小粒度的组件,即使是使用 redux
类的状态管理库。也正是为了解决这个问题,Hook
将组件中相互关联的部分拆分成更小的函,如设置订阅或请求数据,而并非强制按照生命周期划分。同时我们还可以使用reducer
来管理内部的状态,是状态更加的可以预测。
比较常见的就是我们可以使用多个 useEffect
将不同的逻辑分开处理,我们只需维护好它们各自的依赖([])。
下面是我自己做的一个倒计时的控制:
/** *
* @param {amountTime} 时间单位 s
* @param {idx} 当前题目
* @param {handleSubmit} 提交答案
*/
function CountTime({ amountTime, idx, handleSubmit }) {
const [time, setTime] = useState(amountTime);
useEffect(() => {
setTime(amountTime);
}, [idx, amountTime]);
useEffect(() => {
if(time === 0){
handleSubmit && handleSubmit(1);
return;
}
const timer = setTimeout(() => {
if (time > 0) {
setTime(time - 1);
} else {
handleSubmit();
}
}, 1000);
// eslint-disable-next-line consistent-return
return () => timer && clearTimeout(timer);
}, [time]);
return <Text style={styles.amountNumber}>{time}s</Text>;
}
常用的Hook
useState
useState()
的功能类似 class
组件的 state
和 setState()
,使我们可以在函数式的组件中使用state
,也为组件的拆分提供了方便、
const [state, setState] = useState(initialState);
另外 state 的初始化还有一个种 惰性初始化,依赖 props 进行初始化。
initialState 只会在组件的初始渲染中起作用,后续渲染时会被忽略。
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
如果要更新state
, 直接在合适的时机调用 setState(newState)
就可以了。
还有一种常见的通过函数计算的更新:新的state
值需要通过当前的值计算得出的
setState(state => state + 1)
useEffect
useEffect
的心智模型和 componentDidMount
以及其他生命周期函数式不同的。它的心智模型更接近于状态同步,而不是响应生命周期事件
它一般使用了执行一个带有副作用的函数,传给 useEffect
的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用,但是它总会在任何新的渲染前执行。 说到这里不得不提一下 useLayoutEffect
,它和useEffect
相似,但它会在所有的 DOM
布局完成之后同步调用 effect
触发重新渲染。通常用来获取元素的位置,比如scroll
的值。
通常在组件卸载的时候需要清除一些副作用,useEffect
函数只需返回一个清除函数即可。
useEffect
第二个参数接受一个数组作为它重新执行辅佐的依赖,只有数组内的值发生改变,才会重新执行。
可以参考此文useEffect指南
useCallback
我们通常把一个回调函数和回调函数依赖放到 useCallback
中,它会返回一个 memoized
回调函数,当回调函数依赖发生变化时,重新执行回调函数。
这里就有一个很常见的性能优化:
当父组件的方法通过props
传给了子组件时,我们将父组件的方法通过 useCallback
钩子包一下,同时需要将钩子的依赖设置为[]
空依赖。这样就可以减少子组件的rerender次数
。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
useMemo
它的作用和 useCallback
有点类似。把函数和依赖项数组作为参数传入useMemo
,它返回一个 memoized
值。它也是一种渲染性能优化,传入 useMemo
的函数会在渲染期间执行。
如果没有提供依赖项数组,useMemo
在每次渲染时都会计算新的值。
useRef
useRef
返回一个可变的 ref
对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref
对象在组件的整个生命周期内持续存在。通常作为访问DOM
的方式
const refInput = useRef(null);
...
...
return(
<input ref={(r) => refInput = r} type="text" />
)
当 ref
对象内容发生变化时,变更 .current
属性,但是不会引发组件重新渲染。所以有时候还可以用来保存一些额外的数据。
react Hooks
性能优化
减少rerender次数
当父组件重新属性更新触发重新渲染的时候,子组件也会重新渲染
- 其实我们的子组件属性没有任何变化,此时我们不希望子组件重新渲染,我们可以将子组件使用
memo
将子组件包一下。 - 当父组件的方法通过
props
传给了子组件时,对子组件只使用memo
是不行的,我们需要将父组件的方法通过useCallback
钩子包一下,同时需要将钩子的依赖设置为[]
空依赖。
减少rerender复杂度
使用memoize-one
,来减少数据计算
const add = (a, b) => a + b;
const memoizedAdd = memoizeOne(add);
结语
常用的hook
就介绍到这里,如果这篇文章帮到了你,欢迎点赞👍和关注⭐️
文章如有错误之处,希望在评论区指正🙏🙏。