Hook 前言

什么是Hook

自从 16.8 版本开始,hooks 的出现使得你可以在不编写 class 的情况下使用状态管理以及其它 React 的特性。

那么在 React Hooks 出现之前,class 类组件和 function 函数组件有什么区别?Hooks 出现之后,函数组件又是如何满足原来只有类组件才有的功能的?

1.类组件和没有 hooks 加持的函数组件:

函数组件常被称为无状态组件,意思就是它内部没有状态管理,只能做一些展示型的组件或者是完全受控组件。因此差别主要体现在:

  • 函数组件没有内部状态管理
  • 函数组件内部没有生命周期钩子
  • 函数组件不能被获取组件实例 ref,函数组件内也不能获取类组件的 ref

2.类组件和有 hooks 加持的函数组件:

有了 hooks 加持之后,函数组件具备了状态管理,除了可以使用内置的 hooks ,我们还可以自定义 hooks。

  • 类组件有完备的生命周期钩子,而函数组件只能具备:DidMount / WillUnmount / DidUpdate / willUpdate
  • 函数组件内部可以通过内置 hook 获取类组件 ref,也可以通过一些 API 的组合使用达到获取函数组件 ref 的功能
  • 函数组件具备了针对状态变量的 setter 监听(类似于 vue watch),类组件没有这种 API。(useCallback、useEffect、useMemo等)

类组件原本比函数组件更加完整,为什么还需要 hooks?

这要说到 React 的设计理论:

  • React 认为,UI 视图是数据的一种视觉映射,即 UI = F(DATA) ,这里的 F 需要负责对输入的数据进行加工、并对数据的变更做出响应
  • 公式里的 F 在 React 里抽象成组件,React 是以组件为粒度编排应用的,组件是代码复用的最小单元
  • 在设计上,React 采用 props 来接收外部的数据,使用 state 属性来管理组件自身产生的数据(状态),而为了实现(运行时)对数据变更做出响应需要,React 采用基于类 Class 的组件设计
  • 除此之外,React 认为组件是有生命周期的,因此开创性地将生命周期的概念引入到了组件设计,从组件的 create 到 destroy 提供了一系列的 API 共开发者使用

类组件 Class Component 的困局

组件状态逻辑复用困局

对于有状态组件的复用,React 团队和社区尝试过许多方案,早期使用 CreateClass + Mixins,使用 Class Component 后又设计了 Render Props 和 HOC,再到后来的 Hooks设计,React 团队对于组件复用的探索一直没有停止。

HOC 和 Render Props 都有自己的缺点,都不是完美的复用方案(详情了解 React HOC 和 Render Props),官方团队认为应该为共享状态逻辑提供更好的原生途径。在 Hooks 加持后,功能相对独立的部分完全抽离到 hook 实现,例如网络请求、登录状态、用户核验等;也可以将 UI 和功能(状态)分离,功能放到 hook 实现,例如表单验证。

复杂组件变得难以理解

我们经常维护一些组件,它们起初很简单,但是逐渐会被状态逻辑和副作用充斥。在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了挑战。Hook 可将组件中相互关联的部分拆分成更小的函数

JavaScript Class 的缺陷
  • this的指向问题(语言缺陷)
  • 编译后体积和性能的问题

同样功能的类组件和函数组件,在经过 Webpack 编译后体积相差明显,也伴随着一定的性能问题。这是因为 class 在 JavaScript 中本质是函数,在 React 内部也是当做 Function类 来处理的。而函数组件编译后就是一个普通的 function,function 对 JS 引擎是友好的。

内置 Hooks

useState

const [state, setState] = useState(initialState);

用来承担与类组件中的 state 一样的作用,组件内部的状态管理

function () {
const [ count, setCount ] = useState(0);
const onClick = () => {
setCount( count + 1 );
// setCount(count => count + 1);
}; return <div onClick={onClick}>{ count }</div>
}

除了直接传入最新的值,还可以函数式更新,这样可以访问到先前的 state。如果你的初始 State 创建比较昂贵时,可以传一个函数给 useState:

function Table(props) {
// ⚠️ createRows() 每次渲染都会被调用
const [rows, setRows] = useState(createRows(props.count));
// ...
} function Table(props) {
// ✅ createRows() 只会被调用一次
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}

如果是复杂类型的 state,需要传入修改后的完整的数据,不再像类组件中的 setState 可以自动合并对象,需要手动合并:

setState(prevState => ({...prevState, ...updatedValues}));

此外,useReducer 是另一种可选的方案。

useEffect

useEffect(func, [deps]);

可以用来模拟生命周期,即可以完成某些副作用。什么叫副作用?一般我们认为一个函数不应该对外部产生影响,一旦在函数内部有某些影响外部的操作,将其称之为副作用。例如改变 DOM、改变 Window对象(Global)、设置定时器、使用原生API绑定事件等等,如果处理不好,它们可能会产生 bug 并产生破坏。

如果只传一个参数,每次组件渲染都会执行回调函数(挂载+跟新),相当于 componentDidMount() + componentDidUpdate()

返回值函数:在组件更新前、组件卸载时执行,相当于 componentWillUnmount() + componentWillUpdate()

useEffect(() => { // 每次渲染后执行此函数,获取到的值是最新的
console.log("Effect after render", count);
return () => { // 每次执行useEffect前,先执行此函数,获取到的数据是更新之前的值
console.log("remove last", count);
}
});

第二个参数是依赖列表,当依赖的状态数据发生改变时会执行回调

1.如果是一个空数组,表示没有依赖项

  • 回调函数:只在组件挂载的时候执行一次,相当于 componentDidMount()
  • 返回值函数:只在组件卸载的时候执行一次,相当于 componentWillUnmount()

2.如果有值

  • 回调函数:除了具有 componentDidMount(),还当 数组内的变量发生变化时执行 componentDidUpdate()
  • 返回值函数:除了具有 componentWillUnmount(),还当 数组内的值发生变化时执行 componentWillUpdate()

需要注意的是,

1.第二个参数的比较其实是浅比较,传入引用类型进去是无意义的
    2.一个组件内可以使用多个 useEffect,它们相互之间互不影响
    3.useEffect 第一个参数不能是 async 异步函数,因为它总是返回一个 Promise,这不是我们想要的。你可以在其内部定义 async 函数并调用

useLayoutEffect

它与 useEffect 的用法完全一样,作用也基本相同,唯一的不同在于执行时机,它会在所有的 DOM 变更之后同步调用 effect,可以使用它来

useEffect 不会阻塞浏览器的绘制任务,它会在页面更新之后才执行。而 useLayoutEffect 跟 componentDidMount 和 componentDidUpdate 的执行时机一样,会阻塞页面渲染,如果当中有耗时任务的话,页面就会卡顿。大多数情况下 useEffect 比 class 的生命周期函数性能更好,我们应该优先使用它。

如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 useLayoutEffect 与 componentDidMount、componentDidUpdate 的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect。

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

useState 的替代方案,它接收一个 (state, action) => newState 的 reducer 处理函数,并返回当前的 state 和 配套的 dispatch 方法。使用方法与 redux 非常相似。

某些场景下,useReducer 比 useState 更加适用:

  • 当状态变量比较复杂且包含多个子值的时候
  • 下一个 state 依赖之前的 state
const initialState = {count: 0};

function init(initialCount) {
return {count: initialCount};
} 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(props) {
const [state, dispatch] = useReducer(reducer, initialState);
// const [state, dispatch] = useReducer(reducer, props.initialCount, init); return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

此外,它还可以模拟 forceUpdate()

const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

function handleClick() {
forceUpdate();
}

useCallback

const memoizedCallback = useCallback(func, [deps]);

useCallback 缓存了方法的引用。它有的作用:性能优化,父组件更新,传递给子组件的函数指针不会每次都改变,只有当依赖项发生改变的时候才会改变指针。避免了子组件的无谓渲染

它的本质是对函数依赖进行分析,依赖变更时才重新执行。

useMemo & React.memo

useMemo 用于缓存一些耗时的计算结果(返回值),只有当依赖项改变时才重新进行计算。

useCallback(func, [deps])  等同于  useMemo(() => func, [deps])

useCallback 缓存的是方法的引用,useMemo 缓存的是方法的返回值,适用场景都是避免不必要的子组件渲染。

在类组件中有 React.PureComponent,与之对应的函数组件可以使用 React.memo,它们都会在自身 re-render 时,对每一个 props 项进行浅对比,如果引用没有发生改变,就不会触发渲染。

那么,useMemo 和 React.memo 有什么共同点呢?前者可以在组件内部使用,可以拥有比后者更细粒度的依赖控制。它们两个与 useCallback 的本质一样,都是进行依赖控制。

useContext

专门为函数组件提供的 context hook API,可以更加方便地获取 context 的值。

const value = useContext(MyContext);

useContext(MyContext) 接收一个 context 对象,当前获取到的值由上层组件中距离最近的 <MyContext.Provider> 的 value 决定。

useContext(MyContext) 相当于之前的 static contextType = MyContext 或者 <MyContext.Consumer>

useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变。

注意:此 hook 可以获取 DOM 元素、类组件示例,但无法获取函数组件实例,因为函数组件根本没有实例。如果想让函数组件被获取到 ref,可以使用 useImperativeHandle 来达到这样的效果

另外,useRef 获取到的“ref”对象是一个 current 属性可变且可以容纳任意值的通用容器。可以实现如下功能:

  • 模拟实例变量
  • 获取 prevProps、prevState
// 当做 class 实例变量
function Timer() {
const intervalRef = useRef(); useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
}); // ...
} // 获取prevProps,prevState
function Counter(props) {
const [count, setCount] = useState(0);
const prevProps = useRef(props);
const prevCount = useRef(count); useEffect(() => {
prevCount.current = count;
prevProps.current = props;
}); return <h1>Now: {count} - {props}, before: {prevCount.current} - {prevProps.current}</h1>;
}

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义对外暴露的属性。官方指出,它应当与 forwardRef 一起使用。

示例:

function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

此时,通过 ref 获取到 FancyInput 的"实例",其 current 属性内只有 foucs 属性可供访问

React Hooks总结的更多相关文章

  1. 通过 React Hooks 声明式地使用 setInterval

    本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: setInterval 用起来没你想的简单. Ryan Florence 在他 ...

  2. 初探React Hooks & SSR改造

    Hooks React v16.8 发布了 Hooks,其主要是解决跨组件.组件复用的状态管理问题. 在 class 中组件的状态封装在对象中,然后通过单向数据流来组织组件间的状态交互.这种模式下,跨 ...

  3. React hooks实践

    前言 最近要对旧的项目进行重构,统一使用全新的react技术栈.同时,我们也决定尝试使用React hooks来进行开发,但是,由于React hooks崇尚的是使用(也只能使用)function c ...

  4. 理解 React Hooks

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由志航发表于云+社区专栏 TL;DR 一句话总结 React Hooks 就是在 react 函数组件中,也可以使用类组件(classe ...

  5. React Hooks新特性学习随笔

    React Hooks 是 React 16.8 的新增特性.它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性. 前言 本篇主要以讲几个常用的api为主. 1.u ...

  6. 关于React Hooks,你不得不知的事

    React Hooks是React 16.8发布以来最吸引人的特性之一.在开始介绍React Hooks之前,让咱们先来理解一下什么是hooks.wikipedia是这样给hook下定义的: In c ...

  7. react hooks 全面转换攻略(三) 全局存储解决方案

    针对 react hooks 的新版本解决方案 一.redux维持原方案 若想要无缝使用原来的 redux,和其配套的中间件 promise,thunk,saga 等等的话 可以使用 redux-re ...

  8. react新特性 react hooks

    本文介绍的是react新特性react hooks,本文面向的是有一定react开发经验的小伙伴,如果你对react还不是很熟悉的话我建议你先学习react并多多联系. 首先我们都知道react有3种 ...

  9. 30分钟精通React今年最劲爆的新特性——React Hooks

    你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周 ...

  10. React Hooks 深入系列 —— 设计模式

    本文是 React Hooks 深入系列的后续.此篇详细介绍了 Hooks 相对 class 的优势所在, 并介绍了相关 api 的设计思想, 同时对 Hooks 如何对齐 class 的生命周期钩子 ...

随机推荐

  1. 更快地访问stackoverflow

    使用火狐浏览器,安装扩展组件 Decentraleyes, 完成 原理:由于爆栈本身并没有被墙, 但使用了google的api,而google的api是被墙的. 该组件替换了国内不能访问的api,所以 ...

  2. template_showpost

    使用<a href='...'>name<\a>实现点击"name"与转向'...'网址的超链接操作 from django.shortcut import ...

  3. developerWorks 中文社区

    https://www.ibm.com/developerworks/community/groups/service/html/communityview?communityUuid=3302cc3 ...

  4. vue中SPA的优缺点和理解

    说说你对SPA的理解,他的优缺点分别是什么? SPA(single-page application) 尽在Web页面初始化时加载相应的HTML,JavaScript和CSS.一旦页面加载完成,SPA ...

  5. Labyrinth 树的直径加DFS

    The northern part of the Pyramid contains a very large and complicated labyrinth. The labyrinth is d ...

  6. Git敏捷开发--rebase命令

    git rebase是git下比较常用的命令,以下记录自己遇到较多的使用场景. 合并分支 在多人协作的项目中,拉分支是很常见的事情,经常需要同步自己的分支与远端master分支一致,有两种方式: gi ...

  7. xshell下使用vim的编辑一个文件Ctrl+S和Ctrl+Q

    xshell下使用vim的编辑一个文件,保存的时候习惯性的按了Ctrl+S 结构悲剧了.屏幕锁死了.按其他键都没有反应,exc也不行. 经过问度娘才知道. 原来Ctrl+S在Linux里,是锁定屏幕的 ...

  8. C# 基础知识系列- 12 任务和多线程

    0. 前言 照例一份前言,在介绍任务和多线程之前,先介绍一下异步和同步的概念.我们之间介绍的知识点都是在同步执行,所谓的同步就是一行代码一行代码的执行,就像是我们日常乘坐地铁通过安检通道一样,想象我们 ...

  9. Trie树-提高海量数据的模糊查询性能

    今天这篇文章源于上周在工作中解决的一个实际问题,它是个比较普遍的问题,无论做什么开发,估计都有遇到过.具体是这样的,我们有一份高校的名单(2657个),需要从海量的文章标题中找到包含这些高校的标题,其 ...

  10. tensorflow1.0 lstm学习曲线

    import tensorflow as tf import numpy as np import matplotlib.pyplot as plt BATCH_START = 0 TIME_STEP ...