返回顶部
首页 > 资讯 > 前端开发 > JavaScript >React Hooks使用常见的坑
  • 262
分享到

React Hooks使用常见的坑

2024-04-02 19:04:59 262人浏览 八月长安
摘要

React Hooks 是 React 16.8 引入的新特性,允许我们在不使用 Class 的前提下使用 state 和其他特性。React Hooks 要解决的问题是状态共享,是

React Hooks 是 React 16.8 引入的新特性,允许我们在不使用 Class 的前提下使用 state 和其他特性。React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态逻辑复用方案,不会产生 jsX 嵌套地狱问题。

为什么会有Hooks?

介绍Hooks之前,首先要给大家说一下React的组件创建方式,一种是类组件,一种是纯函数组件,并且React团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。也就是说组件的最佳写法应该是函数,而不是类。。

函数组件比类组件更加方便实现业务逻辑代码的分离和组件的复用,函数组件也比类组件轻量,没有react hooks之前,函数组件是无法实现LocalState的,这导致有localstate状态的组件无法用函数组件来书写,这限制了函数组件的应用范围,而react hooks扩展了函数组件的能力。可是在使用的过程中,也要注意下面这些问题,否则就会掉进坑里,造成性能损失。按照下面的方法做,,才能避开这些陷阱。

1. 将与状态改变无关的变量和方法提取到组件函数外面

每次状态改变时,整个函数组件都会重新执行一遍。导致函数组件内部定义的方法和变量,都会重新创建,重新给它们分配内存,这会导致性能受到影响。


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

// 测试每次状态改变时,方法是不是重新分配内存
let testFooMemoAlloc = new Set();

const Page = (props:any) => {
  console.log('每次状态改变,函数组件从头开始执行')
  const [count, setCount] = useState(0);
  const calc = () => {
    setCount(count + 1);
  }

  const bar = {
    a:1,
    b:2,
    c: '与状态无关的变量定义'
  }
 
  const doFoo = () => {
    console.log('与状态无关的方法');

  }
  testFooMemoAlloc.add(doFoo)
  
  return (
    <>
      <button onClick={calc}>加1</button>
      <p>count:{count}</p>
      <p>testFooMemoAlloc.size增加的话,说明每次都重新分配了内存:{testFooMemoAlloc.size}</p>
    </>
  )
}

export default Page;

与改变状态相关的变量和方法,必须放在hooks组件内,而无状态无关的变量和方法,可以提取到函数组件外,避免每次状态更新,都重新分配内存。也可以分别使用useMemo和useCallback包裹变量与函数,也能达到同样的效果,后面会讲。


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

// 测试每次状态改变时,方法是不是重新分配内存
let testFooMemoAlloc = new Set();

const bar = {
  a:1,
  b:2,
  c: '与状态无关的变量定义'
}

const doFoo = () => {
  console.log('与状态无关的方法');

}

const Page = (props:any) => {
  console.log('每次状态改变,函数组件从头开始执行')
  const [count, setCount] = useState(0);
  const calc = () => {
    setCount(count + 1);
  }

  testFooMemoAlloc.add(doFoo)
  
  return (
    <>
      <button onClick={calc}>加1</button>
      <p>count:{count}</p>
      <p>testFooMemoAlloc.size增加的话,说明每次都重新分配了内存:{testFooMemoAlloc.size}</p>
    </>
  )
}

export default Page;

2. 用memo对子组件进行包装

父组件引入子组件,会造成一些不必要的重复渲染,每次父组件更新count,子组件都会更新。


import React,{useState} from "react";
const Child = (props:any) => {
    console.log('子组件?')
    return(
        <div>我是一个子组件</div>
    );
}
const Page = (props:any) => {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <Child />
        </>
    )
}

export default Page;

使用memo,count变化子组件没有更新


import React,{useState,memo} from "react";
const Child = memo((props:any) => {
    console.log('子组件?')
    return(
        <div>我是一个子组件</div>
    );
})
const Page = (props:any) => {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <Child />
        </>
    )
}

export default Page;

给memo传入第二个参数,开启对象深度比较。当子组件传递的属性值未发生改变时,子组件不会做无意义的render。

memo不仅适用于函数组件,也适用于class组件,是一个高阶组件,默认情况下只会对复杂对象做浅层比较,如果想做深度比较,可以传入第二个参数。与shouldComponentUpdate不同的是,deepCompare返回true时,不会触发 render,如果返回false,则会。而shouldComponentUpdate刚好与其相反。


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

const Child = memo((props:any) => {
    console.log('子组件')
  return (
      <>
      <div>我是一个子组件</div>
      <div>{ props.fooObj.a}</div>
      </>
    );
}, deepCompare)

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const [fooObj, setFooObj] = useState({ a: 1, b: { c: 2 } })
  console.log('页面开始渲染')
  const calc = () => {
    setCount(count + 1);
    if (count === 3) {
      setFooObj({ b: { c: 2 }, a: count })
    }
  }
  const doBar = () => {
    console.log('给子组件传递方法,测试一下是否会引起不必须的渲染')
  }
    return (
        <>
        <button onClick={calc}>加1</button>
        <p>count:{count}</p>
        <Child fooObj={fooObj} doBar={doBar} />
        </>
    )
}

export default Page;

// 深度比较两个对象是否相等
export default function deepCompare(prevProps: any, nextProps: any) {
  const len: number = arguments.length;
  let leftChain: any[] = [];
  let rightChain: any = [];
  // // console.log({ arguments });
  //
  if (len < 2) {
    // console.log('需要传入2个对象,才能进行两个对象的属性对比');
    return true;
  }
  // for (let i = 1; i < len; i++) {
  // leftChain = [];
  // rightChain = [];
  console.log({ prevProps, nextProps });
  if (!compare2Objects(prevProps, nextProps, leftChain, rightChain)) {
    // console.log('两个对象不相等');
    return false;
  }
  // }
  // console.log('两个对象相等');

  return true;
}

function compare2Objects(prevProps: any, nextProps: any, leftChain: any, rightChain: any) {
  var p;

  // 两个值都为为NaN时,在js中是不相等的, 而在这里认为相等才是合理的
  if (isNaN(prevProps) && isNaN(nextProps) && typeof prevProps === 'number' && typeof nextProps === 'number') {
    return true;
  }

  // 原始值比较
  if (prevProps === nextProps) {
    console.log('原始值', prevProps, nextProps);
    return true;
  }

  // 构造类型比较
  if (
    (typeof prevProps === 'function' && typeof nextProps === 'function') ||
    (prevProps instanceof Date && nextProps instanceof Date) ||
    (prevProps instanceof RegExp && nextProps instanceof RegExp) ||
    (prevProps instanceof String && nextProps instanceof String) ||
    (prevProps instanceof Number && nextProps instanceof Number)
  ) {
    console.log('function', prevProps.toString() === nextProps.toString());
    return prevProps.toString() === nextProps.toString();
  }

  // 两个比较变量的值如果是null和undefined,在这里会退出
  if (!(prevProps instanceof Object && nextProps instanceof Object)) {
    console.log(prevProps, nextProps, 'prevProps instanceof Object && nextProps instanceof Object');
    return false;
  }

  if (prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)) {
    console.log('prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)');
    return false;
  }

  // 构造器不相等则两个对象不相等
  if (prevProps.constructor !== nextProps.constructor) {
    console.log('prevProps.constructor !== nextProps.constructor');
    return false;
  }

  // 原型不相等则两个对象不相等
  if (prevProps.prototype !== nextProps.prototype) {
    console.log('prevProps.prototype !== nextProps.prototype');
    return false;
  }

  if (leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1) {
    console.log('leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1');
    return false;
  }

  // 遍历下次的属性对象,优先比较不相等的情形
  for (p in nextProps) {
    if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
      console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
      return false;
    } else if (typeof nextProps[p] !== typeof prevProps[p]) {
      console.log('typeof nextProps[p] !== typeof prevProps[p]');
      return false;
    }
  }
  // console.log('p in prevProps');
  // 遍历上次的属性对象,优先比较不相等的情形
  for (p in prevProps) {
    // 是否都存在某个属性值
    if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
      console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
      return false;
    }
    // 属性值的类型是否相等
    else if (typeof nextProps[p] !== typeof prevProps[p]) {
      console.log('typeof nextProps[p] !== typeof prevProps[p]');
      return false;
    }

    console.log('typeof prevProps[p]', typeof prevProps[p]);
    switch (typeof prevProps[p]) {
      // 对象类型和函数类型的处理
      case 'object':
      case 'function':
        leftChain.push(prevProps);
        rightChain.push(nextProps);

        if (!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)) {
          console.log('!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)');
          return false;
        }

        leftChain.pop();
        rightChain.pop();
        break;

      default:
        // 基础类型的处理
        if (prevProps[p] !== nextProps[p]) {
          return false;
        }
        break;
    }
  }

  return true;
}

3.用useCallback对组件方法进行包装

当父组件传递方法给子组件的时候,memo好像没什么效果,无论是用const定义的方法,还在用箭头函数或者bind定义的方法,子组件还是执行了


import React, { useState,memo } from 'react';
//子组件会有不必要渲染的例子
interface ChildProps {
  changeName: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('普通函数子组件')
  return(
      <>
          <div>我是普通函数子组件</div>
          <button onClick={changeName}>普通函数子组件按钮</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('箭头函数子组件')
  return(
      <>
          <div>我是箭头函数子组件</div>
          <button onClick={changeName.bind(null,'test')}>箭头函数子组件按钮</button>
      </>
  );
}
const ArrowMemo = memo(ArrowChild);

const BindChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('Bind函数子组件')
  return(
      <>
          <div>我是Bind函数子组件</div>
          <button onClick={changeName}>Bind函数子组件按钮</button>
      </>
  );
}
const BindMemo = memo(BindChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const name = "test";

  const changeName = function() {
    console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染');
  }

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>加1</button>
          <p>count:{count}</p>
          <ArrowMemo  changeName={()=>changeName()}/>
          <BindMemo  changeName={changeName.bind(null)}/>
          <FunMemo changeName={changeName} />
      </>
  )
}

export default Page;

使用useCallback,参数为[],页面初始渲染后,改变count的值,传递普通函数的子组件不再渲染, 传递箭头函数和bind方式书写的方法的子组件还是会渲染


import React, { useState,memo ,useCallback} from 'react';
//子组件会有不必要渲染的例子
interface ChildProps {
  changeName: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('普通函数子组件')
  return(
      <>
          <div>我是普通函数子组件</div>
          <button onClick={changeName}>普通函数子组件按钮</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('箭头函数子组件')
  return(
      <>
          <div>我是箭头函数子组件</div>
          <button onClick={changeName.bind(null,'test')}>箭头函数子组件按钮</button>
      </>
  );
}
const ArrowMemo = memo(ArrowChild);

const BindChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('Bind函数子组件')
  return(
      <>
          <div>我是Bind函数子组件</div>
          <button onClick={changeName}>Bind函数子组件按钮</button>
      </>
  );
}
const BindMemo = memo(BindChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const name = "test";

  const changeName = useCallback(() => {
    console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染');
  },[])

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>加1</button>
          <p>count:{count}</p>
          <ArrowMemo  changeName={()=>changeName()}/>
          <BindMemo  changeName={changeName.bind(null)}/>
          <FunMemo changeName={changeName} />
      </>
  )
}

export default Page;

4.用useMemo对组件中的对象变量进行包装

在子组件使用了memo,useCallback的情况下,给子组件传递一个对象属性,对象值和方法都未发生改变的情况下,父组件无关状态变更,子组件也会重新渲染。


import React, { useState,memo ,useCallback} from 'react';
//子组件会有不必要渲染的例子-使用了memo,useCallback的情况下,给子组件传递一个对象属性值
interface ChildProps {
  childStyle: { color: string; fontSize: string;};
  changeName: ()=>void;
}
const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => {
  console.log('普通函数子组件')
  return(
      <>
          <div style={childStyle}>我是普通函数子组件</div>
          <button onClick={changeName}>普通函数子组件按钮</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const childStyle = {color:'green',fontSize:'16px'};

  const changeName = useCallback(() => {
    console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染');
  },[])

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>加1</button>
          <p>count:{count}</p>
          <FunMemo childStyle={childStyle} changeName={changeName} />
      </>
  )
}

export default Page;

使用useMemo可以解决给子组件传递对象属性时的不必要更新问题。


import React, { useState,memo, useMemo, useCallback} from 'react';
//子组件会有不必要渲染的例子
interface ChildProps {
  childStyle: { color: string; fontSize: string;};
  changeName: ()=>void;
}
const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => {
  console.log('普通函数子组件')
  return(
      <>
          <div style={childStyle}>我是普通函数子组件</div>
          <button onClick={changeName}>普通函数子组件按钮</button>
      </>
  );
}
const FunMemo = memo(FunChild);

const Page = (props:any) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");
  const childStyle = {color:'green',fontSize:'16px'};

  const changeName = useCallback(() => {
    setName('变一下名称')
  }, [])
  const childStyleMemo = useMemo(() => {
    return {
      color: name === '变一下名称' ? 'red':'green',
      fontSize: '16px'
    }
  }, [name])

  return (
      <>
          <button onClick={(e) => { setCount(count+1) }}>加1</button>
          <p>count:{count}</p>
          <FunMemo childStyle={childStyleMemo} changeName={changeName} />
      </>
  )
}

export default Page;

以上就是React Hooks使用避坑指南的详细内容,更多关于React Hooks使用的资料请关注编程网其它相关文章!

--结束END--

本文标题: React Hooks使用常见的坑

本文链接: https://lsjlt.com/news/128649.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • React Hooks使用常见的坑
    React Hooks 是 React 16.8 引入的新特性,允许我们在不使用 Class 的前提下使用 state 和其他特性。React Hooks 要解决的问题是状态共享,是...
    99+
    2024-04-02
  • React Hooks常用场景的使用(小结)
    目录一、State Hook1、基础用法2、更新3、实现合并4、惰性初始化 state5、一些重点二、Effect Hook1、基础用法2、清除操作3、执行时期4、性能优化5、模拟 ...
    99+
    2024-04-02
  • React Hooks与setInterval的坑怎么解决
    这篇文章主要讲解了“React Hooks与setInterval的坑怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“React Hooks与setInterval...
    99+
    2023-06-30
  • React Hooks如何使用
    这篇文章主要介绍了React Hooks如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇React Hooks如何使用文章都会有所收获,下面我们一起来看看吧。hooks介绍在react...
    99+
    2023-07-05
  • React Hooks 在 SSR 模式下有哪些常见问题
    这篇文章主要介绍“React Hooks 在 SSR 模式下有哪些常见问题”,在日常操作中,相信很多人在React Hooks 在 SSR 模式下有哪些常见问题问题上存在疑惑,小编查阅了各式资料,整理出简单...
    99+
    2024-04-02
  • React-hooks中的useEffect使用步骤
    目录1.理解函数副作用什么是副作用常见的副作用2.基础使用使用步骤示例代码3.依赖项控制useEffect 永远是在 DOM渲染完成之后执行 1.理解函数副作用 什么是副作用 对于R...
    99+
    2024-04-02
  • 怎么在React中使用Hooks
    这篇文章给大家介绍怎么在React中使用Hooks,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、State Hook1、基础用法function State(){  const&nbs...
    99+
    2023-06-14
  • hooks怎么在react中使用
    hooks怎么在react中使用?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。State Hooks案例:import { useState }&...
    99+
    2023-06-14
  • react中常见hook的使用方式
    目录1、什么是hook? 2、为什么要出现hook? 3、有哪些常用的hook? (1) useState (2) useEffect (3) useContext (4) useR...
    99+
    2024-04-02
  • React组件学习之Hooks使用
    目录一、前言二、React Hooks2.1 useState2.2 useEffect2.3 useMemo2.4 useCallback2.5 useContext2.6 use...
    99+
    2024-04-02
  • React Hooks使用startTransition与useTransition教程示例
    目录引言需求分析startTransition使用useTransition总结引言 今天带来的是react18版本推出的全新hooks:useTransition,它的使用范围主要...
    99+
    2023-01-10
    React Hooks startTransition useTransition React Hooks
  • Java中ReentrantLock4种常见的坑
    目录前言Lock 简介ReentrantLock 使用ReentrantLock 中的坑1.ReentrantLock 默认为非公平锁2.在 finally 中释放锁3.锁不能被释放...
    99+
    2024-04-02
  • 怎么使用不同的React hooks来解决日常所遇到的问题
    这篇文章主要讲解了“怎么使用不同的React hooks来解决日常所遇到的问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么使用不同的React hoo...
    99+
    2024-04-02
  • React hooks使用规则和作用是什么
    这篇文章主要讲解了“React hooks使用规则和作用是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“React hooks使用规则和作用是什么”吧!useStat...
    99+
    2023-07-05
  • 在 React 项目中全量使用 Hooks的方法
    目录前言React HooksuseStateuseReducer基础用法进阶用法useContextuseEffectuseLayoutEffectuseRefuseImperat...
    99+
    2022-11-13
    React使用 Hooks  React 项目使用 Hooks
  • 使用React Hooks时要避免哪些错误
    小编给大家分享一下使用React Hooks时要避免哪些错误,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!主要介绍一下 Reac...
    99+
    2024-04-02
  • Java中常见的坑有哪些
    今天小编给大家分享一下Java中常见的坑有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.前言同一个代码“坑”,踩第一...
    99+
    2023-06-27
  • Python ORM 的常见坑洼指南:避免常见的错误
    Python ORM 常见坑洼指南:避免常见的错误 连接池问题: 连接池创建不当:确保为应用程序创建和管理一个连接池,以避免与数据库服务器建立和关闭连接的开销。 连接池大小不当:连接池应足够大,以满足应用程序的需求,但又不至于太大以致浪...
    99+
    2024-03-15
    ORM
  • 使用 React Hooks 重构类组件的示例详解
    目录1. 管理和更新组件状态2. 状态更新后的操作3. 获取数据4. 卸载组件时清理副作用5.  防止组件重新渲染6. Context API7. 跨重新渲染保留值8. 如...
    99+
    2024-04-02
  • React Hooks--useEffect代替常用生命周期函数方式
    目录useEffect代替常用生命周期函数原始生命周期函数对React Hooks(useState和useEffect) 的总结思考一、为什么用React Hooks(面向生命周期...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作