快速上手 React-Hooks

前言

去年在工作之余花了几天时间自学了React并独自开发了工作中的一个项目,但也仅仅为浅层技术。所以在上个月跳槽时当多个面试官问到React-Hooks时会觉得有点生疏,后来专门去了解学习才理解了其作用及使用。下面来分享一些自己学习的资料整理,希望可以帮助想要接触react的你。
有接触过react的朋友都知道react分为函数组件和类组件,我们先来看看这两个组件的区别。

1.pic.jpg

我们知道在函数组件中没办法使用状态,只能作为展示组件(就是个花瓶...哎)。
但是如果我想让函数组件也有自己的状态怎么办?难道函数组件就不配拥有状态吗?每次写个方法还得手动改变this指向?oh my god ! 有了Hooks,这些都再也不是问题了~

首先 让我们谈谈什么是react hook。先剖出官方解释:

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hook顾名思义就是钩子的意思。在函数组件中把React的状态和生命周期等这些特性钩入进入,这就是ReactHook,那么 React Hooks 相比于类组件到底有哪些好处呢?

  1. 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

Hook规则

Hook 可以让你在不编写 class 组件的情况下使用 state 以及其他的 React 特性。但是,有些规则是我们需要遵守的:

  • 只能在函数内部的最外层调用 Hook
    不要在循环,条件或嵌套函数中调用 Hook
    确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。
  • 只能在 React 的函数组件(非 class组件)中调用 Hook
    不要在普通的 JavaScript 函数或 class 组件中调用 Hook。你可以:
    (1)在 React 的函数组件中调用 Hook;
    (2)在自定义 Hook 中调用其他 Hook。

下面,我们开始认识常用的 Hook API—— useStateuseEffectuseContextuseReducer

useState 保存组件状态

在类组件中,我们使用 this.state 来保存组件状态,并对其修改触发组件重新渲染。比如下面这个简单的计数器组件,很好诠释了类组件如何运行:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

现在我们用hook来实现同等方法:

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

这样比较下来,很简单的一个加1计数器,Hook 写法比 class 组件是不是简洁了很多。
下面,我们来分析如何使用 useState Hook...

// 第一步:从 react 库中引入 useState Hook
import React, { useState } from 'react';

function Example() {
  /* 第二步:通过调用 useState Hook 声明了一个新的 state 变量。
   * 它返回一对值(数组)解构到我们命名的变量上。
   * 第一个返回的是状态 count,它存储的是点击次数。我们通过传 0 作为 useState 唯一的参数来将其初始化 0。
   * 第二个返回的值本身就是一个函数。它让我们可以更新 count 的值,所以我们叫它 setCount。
   */
  const [count, setCount] = useState(0);  // 声明一个叫 "count" 的 state 变量

  return (
    <div>
      <!-- 第三步:读取 state,即count -->
      <p>You clicked {count} times</p>
       <!-- 第四步:更新 state,通过 setCount() -->
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

通过上面的分析,我们可以看到使用 useState Hook 管理状态简直太爽了。不用写繁琐的 class组件,不用担心 this 指向,代码是如此的清晰。
现在就会有朋友问了,那如何如何使用多个 state 变量呢,如下:
state 变量声明为一对[something, setSomething]也很方便,因为如果我们想使用多个 state 变量,它允许我们给不同的 state 变量取不同的名称:

function ExampleWithManyStates() {
  // 声明多个 state 变量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
  ...

在以上组件中,我们有局部变量 agefruittodos,并且我们可以单独更新它们:

  function handleOrangeClick() {
    // 和 this.setState({ fruit: 'orange' }) 类似
    setFruit('orange');
  }

你不必使用多个 state 变量。State 变量可以很好地存储对象和数组,因此,你仍然可以将相关数据分为一组。然而,不像 class 中的 this.setState,更新 state 变量总是替换它而不是合并它。

useEffect 处理副作用

函数组件能保存状态,但是却无法执行异步请求等副作用的操作,所以 React 提供了 useEffect来帮助开发者处理函数组件的副作用操作。
Effect Hook定义:useEffect 传入一个 callback 函数。

useEffect(effect: React.EffectCallback, deps?: ReadonlyArray<any> | undefined)
Effect Hook作用:

处理函数组件中的副作用,如异步操作、延迟操作等,可以替代Class ComponentcomponentDidMountcomponentDidUpdatecomponentWillUnmount等生命周期。

Effect Hook特性:
  • effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。
  • 副作用操作可以分两类:需要清除的和不需要清除的。
  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它和 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
  • useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行。该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
  • componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同
    useEffect 使用示例:
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
    
  // 类似 componentDidMount 和 componentDidUpdate
  useEffect(() => {
    // 使用浏览器 API 去更新 document 标题
    document.title = `You clicked ${count} times`;
  });
  
  // 类似 componentDidMount
  useEffect(() => {
    // 使用浏览器 API 去更新 document 标题
    document.title = `You clicked ${count} times`;
  }, []); // 慎用!监听空数组,当 callback 使用到 state 或 props 时最好不要用,因为只能获取初始化的数据
  
  // 返回一个函数用于清除操作
  useEffect(() => {
    document.title = `You clicked ${count} times`;
    
    window.addEventListener('load', loadHandle); // loadHandle 函数定义省略
    
    return () => {
      window.removeEventListener('load', loadHandle); // 执行清理:callback 下一次执行前调用
    };
  }, [count]); // 只有当count的值发生变化时,才会重新执行 callback 

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect 用法很简单,但是有两个地方需要特别注意:

  • deps 参数很重要
    (1) useEffect 可以接受第二个参数 deps,用于在 re-render 时判断是否重新执行 callback
    (2) deps数组项必须是 immutable 的,比如:不能也不必传 useRefdispatch
    (3) deps 的比较是浅比较,传入对象、函数是无意义
    (4) 作为最佳实践,使用 useEffect 时请尽可能都传 deps
  • 清除副作用
    (1)useEffect 传入的 callback 要么返回一个清除副作用的函数,要么什么都不返回。所以,callback 不能用 async 函数(面试题:如何在 useEffect 中使用 async 函数)
    (2)useEffect 传入的 callback 返回一个函数,在下一次执行 callback 前将会执行这个函数,从而达到清理 effect 的效果

useEffect 的用法大概就是这样的,有一些坑和更复杂操作这里没有涉及。当然,要深入理解的话需要去啃源码了,这里不做过多的解释。

useContext 祖孙传值

useContextReact 帮你封装好的,用来处理多层级传递数据的方式,在以前组件树种,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,还可以使用useContext来解决爷孙组件的传值问题,新的Context API使用订阅发布者模式方式实现在爷孙组件中传值

const { Provider, Consumer } = React.createContext(null);
function Bar() {
  return <Consumer>{value => <div>{value}</div>}</Consumer>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Provider value={"hello context"}>
      <Foo />
    </Provider>
  );
}

通过 React createContext 的语法,在 APP 组件中可以跨过 Foo 组件给 Bar 传递数据。而在 React Hooks 中,我们可以使用 useContext 进行改造。

const Context = React.createContext("hello");
function Bar() {
  const val = useContext(Context); // 使用useContext直接取值
  return <div>{val}</div>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Context.Provider value={"hello react"}>
      <Foo />
    </Context.Provider>
  );
}

传递给 useContext 的是 context 而不是 consumer,返回值即是想要透传的数据了。用法很简单,使用 useContext 可以解决 Consumer 多状态嵌套的问题。
而使用 useContext 则变得十分简洁,可读性更强且不会增加组件树深度。

useReducer

看到useReducer,肯定会想到Redux,没错它和Redux的工作方式是一样的。useReducer的出现是useState的替代方案,能够让我们更好的管理状态,但是缺点也是存在的,就是无法实现redux的中间件

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}

用法跟 Redux 基本上是一致的,如果你学过redux,对于useReducer基本没有学习门槛

总结

Hook 让我们可以在函数组件中使用状态state,函数组件一统 React 的时代来了,这很棒。
Hook 可以让我们摒弃那些繁琐的生命周期、不用考虑 this 的指向、复用逻辑也不用写HOC(高阶组件)了,这很棒。
Hook 还有更多 API 等着我们去探索,同时也支持自定义 Hook。

(以上为网络中资料的整理,若侵权删)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,548评论 0 11
  • 彩排完,天已黑
    刘凯书法阅读 4,182评论 1 3
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,047评论 2 7