【前端知识】React 基础巩固(四十五)——其他Hooks(useMemo、useRef、useImperativeHandle、useLayoutEffect)

React 基础巩固(四十五)——其他Hooks(useMemo、useRef、useImperativeHandle、useLayoutEffect)

一、useMemo的使用

useCallback会返回一个函数的memoized(记忆的)值,其目的是不希望子组件进行多次渲染,并非为了函数进行缓存。

反观useMemo,useMemo返回的也是一个memoized(记忆的)值,这个值是函数返回结果的值,其目的也是为了进行性能的优化。

//   useMemo、useCallback 对比:一个是对函数做优化,一个是对函数返回结果做优化
const increment = useCallback(fn, []);
const increment2 = useMemo(() => fn, []);

useMemo的使用:

import React, {
    
     memo, useCallback, useMemo, useState } from "react";

function calcNumTotal(num) {
    
    
  console.log("calcNumTotal的计算过程被调用~");
  let total = 0;
  for (let i = 1; i <= num; i++) {
    
    
    total += i;
  }
  return total;
}

export default memo(function App() {
    
    
  const [count, setCount] = useState(0);

  const result = useMemo(() => {
    
    
    return calcNumTotal(50 * 2);
  }, [count]);

  //   useMemo、useCallback 对比:一个是对函数做优化,一个是对函数返回结果做优化
  //   const increment = useCallback(fn, []);
  //   const increment2 = useMemo(() => fn, []);

  return (
    <div>
      <h2>计算结果:{
    
    result}</h2>
      <h2>计数器:{
    
    count}</h2>
      <button onClick={
    
    (e) => setCount(count + 1)}>+1</button>
    </div>
  );
});

二、useRef的使用

useRef返回一个ref对象,返回的ref对象在组件的整个生命周期中保持不变。

用法一:绑定dom元素

import React, {
    
     memo, useRef } from "react";

export default memo(function App() {
    
    
  const titleRef = useRef();
  const inputRef = useRef();

  function showTitleDom() {
    
    
    console.log(titleRef.current);
    inputRef.current.focus();
  }

  return (
    <div>
      <h2 ref={
    
    titleRef} className="title">
        hello world
      </h2>
      <input type="text" ref={
    
    inputRef} />
      <button onClick={
    
    showTitleDom}>查看title的dom</button>
    </div>
  );
});

用法二:绑定值(解决闭包陷阱问题)

保存一个数据,这个对象在整个生命周期中可以保持不变。

import React, {
    
     memo, useCallback, useRef, useState } from "react";

let obj = null;

export default memo(function App() {
    
    
  const [count, setCount] = useState(0);
  const nameRef = useRef();
  // 验证:判断是否为同一对象
  console.log(obj === nameRef);
  obj = nameRef;

  // 通过useRef解决闭包陷阱
  const countRef = useRef();
  countRef.current = count;

  const increment = useCallback(() => {
    
    
    setCount(countRef.current + 1);
  }, []);

  return (
    <div>
      <h2>hello world: {
    
    count}</h2>
      <button onClick={
    
    (e) => setCount(count + 1)}>+1</button>
      <button onClick={
    
    increment}>+1</button>
    </div>
  );
});

三、useImperativeHandle的使用

useImperativeHandle可以避免直接将子组件的DOM返回给父组件,因为直接暴露给父组件可能带来不可控的问题,父组件可以利用DOM进行任意的操作,这是我们不希望看到的。

通过useImperativeHandle我们可以控制子组件DOM暴露给父组件的操作,例如暴露focus()方法时,在父组件中就只能够通过focus()进行聚焦,不能进行其他的操作;暴露setValue()方法时,在父组件中就只能狗通过setValue()进行value修改,不能进行其他的操作。

import React, {
    
     forwardRef, memo, useImperativeHandle, useRef } from "react";

const HelloWorld = memo(
  forwardRef((props, ref) => {
    
    
    const inputRef = useRef();

    // 子组件对父组件传入的ref进行处理
    useImperativeHandle(ref, () => {
    
    
      return {
    
    
        focus() {
    
    
          console.log("focus");
          inputRef.current.focus();
        },
        setValue(value) {
    
    
          inputRef.current.value = value;
        },
      };
    });

    return <input ref={
    
    inputRef} type="text" />;
  })
);

export default memo(function App() {
    
    
  const titleRef = useRef();
  const inputRef = useRef();

  function handleDom() {
    
    
    console.log(titleRef.current);
    console.log(inputRef.current);
    inputRef.current.focus();
    inputRef.current.setValue("666");
  }

  return (
    <div>
      <h2 ref={
    
    titleRef}>hello world</h2>
      <HelloWorld ref={
    
    inputRef} />
      <button onClick={
    
    handleDom}>DOM操作</button>
    </div>
  );
});

四、useLayoutEffect的使用

useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;而useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新。

当我们希望某些操作发生之后再更新DOM,可以将操作放在useLayoutEffect中:

import React, {
    
     memo, useEffect, useLayoutEffect, useState } from "react";

export default memo(function App() {
    
    
  const [count, setCount] = useState(0);

  useLayoutEffect(() => {
    
    
    console.log("useLayoutEffect");
  });

  useEffect(() => {
    
    
    console.log("useEffect");
  });
  
  return (
    <div>
      <h2>{
    
    count}</h2>
      <button onClick={
    
    (e) => setCount(count + 1)}>+ 1</button>
    </div>
  );
});

当数据准备渲染到页面上之前,发现数据不对,能够及时进行调整。例如:点击按钮,将count置为0。当发现count为0时,以随机数替换处理。

我们尝试用useEffect来实现:

import React, {
    
     memo, useEffect, useState } from "react";

export default memo(function App() {
    
    
  const [count, setCount] = useState(100);
  useEffect(() => {
    
    
    console.log("useEffect");
    if (count === 0) {
    
    
      setCount(Math.random() + 99);
    }
  });

  return (
    <div>
      <h2>count: {
    
    count}</h2>
      <button onClick={
    
    (e) => setCount(0)}>设置为 0</button>
    </div>
  );
});

因为useEffect是在渲染后执行,所以界面上会在短时间内先显示0,再显示我们想要取代0的随机数,从而会造成闪烁现象。

我们再尝试用useLayoutEffect来实现:

  useLayoutEffect(() => {
    
    
    console.log("useEffect");
    if (count === 0) {
    
    
      setCount(Math.random() + 99);
    }
  });

此时,count在渲染之前就被发现为0,并替换为随机数,随后渲染。此时,则避免了闪烁现象。

猜你喜欢

转载自blog.csdn.net/weixin_42919342/article/details/132053200