React Hooks
React 中通常使用 类定义 或者 函数定义 创建组件:
在类定义中,我们可以使用到许多 React 特性,例如 state、 各种组件生命周期钩子等,但是在函数定义中,我们却无能为力, 因此 React 16.8 版本推出了一个新功能 (React Hooks),通过它,可以更好的在函数定义组件中使用 React 特性。
函数编程hooks的好处:
- 1、跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
- 2、类定义更为复杂:
- 不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
- 时刻需要关注this的指向问题;
- 代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
- 3、状态与UI隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。
注意
- 避免在 循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
- 只有 函数定义组件 和 hooks 可以调用 hooks,避免在 类组件 或者 普通函数 中调用;
- 不能在useEffect中使用useState,React 会报错提示;
- 类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存; 新版推荐使用函数定义组件
重要钩子:
- 状态钩子 (useState): 用于定义组件的 State,其到类定义中this.state的功能;
// useState 只接受一个参数: 初始状态
// 返回的是组件名和更改该组件对应的函数
const [flag, setFlag] = useState(true);
// 修改状态
setFlag(false)
生命周期钩子 (useEffect):
类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect)
这里可以看做componentDidMount、componentDidUpdate和componentWillUnmount的结合。
虽然本质上,依然是 componentDidMount 和 componentDidUpdate 两个生命周期被调用,
但是现在我们关心的不是 mount 或者 update 过程,而是“after render”事件,useEffect 就是告诉组件在“渲染完”之后做点什么事。
useEffect(callback, [source])接受两个参数 callback: 钩子回调函数; source: 设置触发条件,仅当 source 发生改变时才会触发;
useEffect钩子在没有传入[source]参数时,默认在每次 render 时都会优先调用上次保存的回调中返回的函数,后再重新调用回调;
useEffect(() => {
// 组件挂载后执行事件绑定
console.log('on')
addEventListener()
// 组件 update 时会执行事件解绑
return () => {
console.log('off')
removeEventListener()
}
}, [source]);
// 每次 source 发生改变时,执行结果(以类定义的生命周期,便于大家理解):
// --- DidMount ---
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- WillUnmount ---
// 'off'
通过第二个参数,我们便可模拟出几个常用的生命周期:
- componentDidMount: 传入[]时,就只会在初始化时调用一次;
const useMount = (fn) => useEffect(fn, [])
- componentWillUnmount: 传入[],回调中的返回的函数也只会被最终执行一次;
const useUnmount = (fn) => useEffect(() => fn, []);
mounted: 可以使用 useState 封装成一个高度可复用的 mounted 状态;
const useMounted = () => {
const [mounted, setMounted] = useState(false);
useEffect(() => {
!mounted && setMounted(true);
return () => setMounted(false);
}, []);
return mounted;
}
- componentDidUpdate: useEffect每次均会执行,其实就是排除了 DidMount 后即可;
const mounted = useMounted()
useEffect(() => {
mounted && fn()
})
依赖项
依赖项规则 | 副作用执行规则 |
---|---|
不传递依赖项 | 在每次渲染后都会执行。 |
若传递空数组 | 只在组件挂载和卸载时执行。 |
若传递依赖项 | 只在依赖项变化时执行。 |
生命周期
其它内置钩子:
useContext: 获取 context 对象
useReducer: 类似于 Redux 思想的实现,但其并不足以替代 Redux,可以理解成一个组件内部的 redux:
并不是持久化存储,会随着组件被销毁而销毁; 属于组件内部,各个组件是相互隔离的,单纯用它并无法共享数据; 配合useContext的全局性,可以完成一个轻量级的 Redux
useCallback: 缓存回调函数,避免传入的回调每次都是新的函数实例而导致依赖组件重新渲染,具有性能优化的效果;
useMemo: 用于缓存传入的 props,避免依赖的组件每次都重新渲染;
useRef: 获取组件的真实节点;
useLayoutEffect: DOM更新同步钩子。用法与useEffect类似,只是区别于执行时间点的不同。 useEffect属于异步执行,并不会等待 DOM 真正渲染后执行, 而useLayoutEffect则会真正渲染后才触发; 可以获取更新后的 state;
自定义钩子(useXxxxx): 基于 Hooks 可以引用其它 Hooks 这个特性,我们可以编写自定义钩子,如上面的useMounted。又例如,我们需要每个页面自定义标题:
function useTitle(title) {
useEffect(
() => {
document.title = title;
});
}
// 使用:
function Home() {
const title = '我是首页'
useTitle(title)
return (
<div>{title}</div>
)
}
在hooks里动态创建ref的方式
import React, {useRef, useState} from "react";
const Index = () => {
const dom = useRef({}) //默认值要设置成对象,不然动态将ref赋值给modo将会报错
const arr = [{name:'1'},{name:'2'}]
return <div>
{arr.map((item,index)=>{
return <div key={index} ref={node => dom.current['domRef'+index] = node}>
{item.name}
</div>
})}
<div>
}
export default Index
注意
useEffect 只应该依赖参与 React 自上而下数据流的变量,比如 props,state,或者由它们计算出来的值。
而 Ref 是用于在渲染数据流外改变的值,当 refs 发生变化,react 并不会知道,当然也就并不会触发重新渲染。