Hooks函数

React16.8版本中引入了Hooks函数,用于解决常年以来在ClassComponent中存在的各种问题,实现更高效的编写React组件。目前使用Hooks和FunctionComponent是编写React程序的标准方式,而ClassComponent虽然仍能继续使用,但已经趋于废弃,React周边生态也逐渐不再支持。

这篇笔记我们介绍React中常用的Hooks函数功能及适用场景。

Hooks为什么叫Hooks?

Hooks函数这一概念最早可以追溯到计算机早期的操作系统中断处理程序,它们可以在某些事件发生时被调用,用来扩展或改变操作系统的某些行为。后来比较广泛的应用是早期的GUI框架,比如Win32中的SetWindowsHookEx函数就可以向操作系统注册钩子函数,用来捕捉键盘、鼠标等事件(也广泛用于木马程序的开发)。

React Hooks之所以叫Hooks,是因为它们可以让你将外部的数据“钩入”到函数的执行中,当数据被更新时自动触发函数的重新执行,从而实现外部数据和UI组件渲染结果的绑定。理解了这一点,其实我们就会发现使用Hooks比ClassComponent更加直观。

useState

useState用于访问和设置组件的状态,前面的章节中我们已经多次使用。下面例子代码中,我们实现了点击按钮1次就将cnt值加1的逻辑。

import { useState } from "react";

const App = () => {
  const [cnt, setCnt] = useState(0);

  const incrementCnt = () => {
    setCnt(cnt + 1);
  };

  return (
    <div>
      <button onClick={incrementCnt}>ClickMe</button>
      {cnt}
    </div>
  );
};

export default App;

useState返回值是包含2个元素的数组,第1个元素用于访问State值,第2个元素是一个Set函数,用于设置State值。直接修改cnt是没有任何效果的,我们必须通过调用setCnt()修改其值。

此外还有一点要尤其注意,State的Set函数本质上是异步的,代码中如果出现调用Set函数设置State后立即取值,一定是无法取得当前值的,典型的错误写法如下:

const incrementCnt = () => {
  // 设置cnt值
  setCnt(cnt + 1);
  // 取cnt当前值
  console.log(cnt);
};

在早期版本的React中设置State值的setState()函数支持回调函数参数,但Hooks中不支持,这需要通过useEffect()来实现,下面是一个正确写法的例子:

const incrementCnt = () => {
  setCnt(cnt + 1);
};

useEffect(() => {
  console.log(cnt);
}, [cnt]);

useEffect

useEffect可以用于执行带副作用的操作,所谓的副作用是指发送网络请求、修改DOM、订阅或取消订阅事件等和组件渲染没有关系的操作,它默认会在组件每次绘制完成后下一次重新绘制前执行。比如设置当前页面的标题,它需要操作DOM的属性,因此必须通过useEffect来实现。

useEffect(() => {
  document.title = 'Hello React';
});

上面代码会在每次组件重新绘制时执行,如果不需要useEffect()在每次组件重新绘制时都执行,可以传递useEffect()的第二个参数,它是个数组参数,只有当数组的值变化时才重新执行。

import { useEffect } from "react";

const App = (props) => {
  useEffect(() => {
    document.title = props.text;
  }, [props.text]);

  return <div></div>;
};

export default App;

上面例子代码中,只有当props.text改变时,useEffect内部的函数才会再次执行。

有时我们需要在组件卸载时,一同卸载useEffect产生的副作用,这需要在useEffect指定一个清除副作用函数。

import { useEffect } from "react";

const App = () => {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log("hello");
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <div></div>;
};

export default App;

useEffect第1个参数函数的返回值就是清除函数,我们在其中卸载副作用即可。

useEffect另一个常见的用法是替代原ClassComponent的mount和update生命周期函数,常用于实现页面初始化请求之类功能的实现。这只需要将useEffect的第2个参数设置为空数组即可。

useEffect(() => {
  // ... 具体的代码逻辑
}, []);

useContext

useContext在前面数据传递章节我们已经使用过了,它用于在多个组件之间共享数据。

ComponentA.jsx

import React, { useState } from "react";
import ComponentB from "./ComponentB";

export const UserContext = React.createContext({});

const ComponentA = () => {
  const [user, setUser] = useState({
    username: "tom",
    password: "abc123",
  });

  return (
    <UserContext.Provider
      value={{
        user,
        setUser,
      }}
    >
      <ComponentB></ComponentB>
    </UserContext.Provider>
  );
};

export default ComponentA;

ComponentB.jsx

import ComponentC from "./ComponentC";

const ComponentB = () => {
  return (
    <>
      <ComponentC></ComponentC>
    </>
  );
};

export default ComponentB;

ComponentC.jsx

import { useContext } from "react";
import { UserContext } from "./ComponentA";

const ComponentC = () => {
  const { user } = useContext(UserContext);

  return <div>{JSON.stringify(user)}</div>;
};

export default ComponentC;

上面代码中,组件A通过Context.Provider设置了数据,组件C中通过useContext()可以访问到。

useReducer

使用useReducer前首先我们得知道什么是Reducer。Reducer描述的是这样的一种函数:纯函数,接收参数是stateaction,根据action进行对应处理,返回新的stateuseReducer这个Hooks函数就是按这个思路设计的。

import { useReducer } from "react";

const reducer = (n, action) => {
  if (action.type === "inc") {
    return n + 1;
  } else {
    return n - 1;
  }
};

const App = () => {
  const [cnt, dispatch] = useReducer(reducer, 0);

  return (
    <div>
      <button onClick={() => dispatch({ type: "inc" })}>Inc</button>
      <button onClick={() => dispatch({ type: "dec" })}>Dec</button>
      {cnt}
    </div>
  );
};

export default App;

我们可以看到useReducer是一个功能更加强大的useState,它能将更新逻辑抽离到reducer函数中,将不同的动作通过dispatchaction暴露出来,这样能让组件状态的更新逻辑更加清晰。

useMemo

useMemo用于缓存计算结果以优化组件的渲染性能。如果我们的组件需要计算一个值,useMemo能够缓存它的计算结果,仅在依赖项改变时重新触发计算。

import { useMemo } from "react";

const App = (props) => {
  const sum = useMemo(() => props.a + props.b, [props.a, props.b]);

  return <div>{sum}</div>;
};

export default App;

上面例子代码中,我们使用useMemo缓存了props.aprops.b相加的和sum,除非这两个值发生改变,否则sum是不会重新计算的。

自定义Hooks函数

在实际开发中,除了使用React内置的Hooks函数,我们也可以通过自定义Hooks函数的方式抽离代码逻辑实现代码复用。自定义Hooks函数并不复杂,函数名需要以use开头,内部可以调用React内置的Hooks函数,其使用规则与组件中使用Hooks函数基本一致。

myhooks.js

import { useEffect, useState } from "react";

export const useMousePosition = () => {
  const [position, setPosition] = useState({
    x: null,
    y: null,
  });

  useEffect(() => {
    const moveHandler = (e) => {
      setPosition({
        x: e.screenX,
        y: e.screenY,
      });
    };
    document.addEventListener("mousemove", moveHandler);
    return () => {
      document.removeEventListener("mousemove", moveHandler);
    };
  }, []);

  return position;
};

App.jsx

import { useMousePosition } from "./myhooks";

const App = () => {
  const pos = useMousePosition();
  return <div>{pos.x + " " + pos.y}</div>;
};

export default App;

上面例子代码中我们自定义了useMouse函数,它用于实时获取鼠标的位置,useMouse函数内部我们通过useEffect绑定鼠标移动事件到状态数据中。App组件中我们引用了这个Hooks函数,并将鼠标的位置实时显示了出来。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。