快速上手 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 (
      

You clicked {this.state.count} times

); } }

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

import React, { useState } from 'react';

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

  return (
    

You clicked {count} times

); }

这样比较下来,很简单的一个加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 (
    

You clicked {count} times

); }

通过上面的分析,我们可以看到使用 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 | 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 (
    

You clicked {count} times

); }

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 {value => 
{value}
}
; } function Foo() { return ; } function App() { return ( ); }

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

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

传递给 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}
      
      
    
  );
}

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

总结

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

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

你可能感兴趣的:(快速上手 React-Hooks)