Skip to content

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的功能;
js
// 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 时都会优先调用上次保存的回调中返回的函数,后再重新调用回调;

js
useEffect(() => {
// 组件挂载后执行事件绑定
	console.log('on')
	addEventListener()

	// 组件 update 时会执行事件解绑
	return () => {
		console.log('off')
		removeEventListener()
	}

}, [source]);

// 每次 source 发生改变时,执行结果(以类定义的生命周期,便于大家理解):
// --- DidMount ---
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- WillUnmount ---
// 'off'

通过第二个参数,我们便可模拟出几个常用的生命周期:

  • componentDidMount: 传入[]时,就只会在初始化时调用一次;
js
const useMount = (fn) => useEffect(fn, [])
  • componentWillUnmount: 传入[],回调中的返回的函数也只会被最终执行一次;
js
const useUnmount = (fn) => useEffect(() => fn, []);

mounted: 可以使用 useState 封装成一个高度可复用的 mounted 状态;

js
const useMounted = () => {
	const [mounted, setMounted] = useState(false);
	useEffect(() => {
		!mounted && setMounted(true);
		return () => setMounted(false);
	}, []);
	return mounted;
}
  • componentDidUpdate: useEffect每次均会执行,其实就是排除了 DidMount 后即可;
js
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。又例如,我们需要每个页面自定义标题:

jsx
function useTitle(title) {
	useEffect(
		() => {
			document.title = title;
		});
}

// 使用:
function Home() {
	const title = '我是首页'
	useTitle(title)

	return (
		<div>{title}</div>
	)
}

在hooks里动态创建ref的方式

text
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 并不会知道,当然也就并不会触发重新渲染。

Contributors

作者:Long Mo
字数统计:1.5k 字
阅读时长:5 分钟
Long Mo
文章作者:Long Mo
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Longmo Docs