一个前端,爱跑步、爱吉他、爱做饭、爱生活、爱编程、爱南芳姑娘,爱我所爱。世间最温暖又无价的是阳光、空气与爱,愿它们能带你去更远的地方。

  • 文章
  • 心情
  • 照片墙
  • 留言板
  • 工具
  • 友链
  • biaoblog

    专注web开发技术分享

    react杂七杂八学习记录(2025-6-13归档)

    技术 32 2025-06-13 15:46

    1.useMemo

    在优化代码和提高组件计算性能时推荐使用useMemo

    作用:记住计算结果,避免重复计算

    当你有一个计算量比较大的值时,或者这个值的计算依赖某些 props/state,useMemo 可以在依赖没变的情况下跳过计算,直接复用上一次的结果

    示例说明

    const MyComponent = ({ items }) => {
     const expensiveCalculation = (data) => {
      console.log("Calculating...");
      return data.reduce((acc, item) => acc + item, 0);
     };
    
     const total = useMemo(() => expensiveCalculation(items), [items]);
    
     return <div>Total: {total}</div>;
    };
    

    如果 items 没变,即使组件重新渲染,expensiveCalculation 也不会重新执行。



    2.useRequest

    注意着不是react原生提供的hook,而是ahooks库封装的

    目的是用来简化和增强请求操作的体验

    1. 自动处理 loading、error、data 状态
    2. 支持手动触发/自动触发请求
    3. 支持取消请求、轮询、刷新、分页、并发等高级功能
    4. 支持缓存、依赖刷新等功能
    5. 更少的样板代码,更高的可维护性


    与传统方法对比的示例:

    useRequest

    import { useRequest } from 'ahooks';
    import { getUserInfo } from './services';
    
    export default () => {
     const { data, error, loading } = useRequest(getUserInfo);
    
     if (loading) return <div>加载中...</div>;
     if (error) return <div>出错了</div>;
    
     return <div>用户名:{data.name}</div>;
    };
    

    getUserInfo 通常是一个promise方法

    import axios from 'axios';
    
    export async function getUserInfo() {
      const response = await axios.get('/api/user/info');
      return response.data;
    }
    
    

    手动触发请求:

    const { run, loading } = useRequest(fetchData, { manual: true });
    
    <Button onClick={() => run(params)}>点击获取数据</Button>
    

    手动触发(manual: true):


    传统写法

    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(null);
    
    useEffect(() => {
      setLoading(true);
      fetchData().then(res => {
        setData(res);
      }).finally(() => {
        setLoading(false);
      });
    }, []);
    



    3.React.memo/useMemo/useCallback 配合使用

    用于子组件的缓存

    默认情况下 当父组件重新渲染时,子组件也会重新渲染

    但是如果这个子组件的复杂度很大,且依赖的父组件数据没有变化,则不需要重新渲染

    就可以使用React.memo套一下,把子组件缓缓起来

    const Parent = () => {
      const [count, setCount] = useState(0);
    
      console.log('👨‍👦 父组件渲染');
    
      return (
        <div>
          <button onClick={() => setCount(count + 1)}>加一</button>
          <Child name="小明" />
        </div>
      );
    };
    
    const Child = React.memo(({ name }) => {
      console.log('👶 子组件渲染');
      return <div>Hello {name}</div>;
    });
    


    想要正确的使用react.memo 需要看传递的数据 或者方法

    如果是方法 就必须使用useCallback来缓存方法

    如果是数据类型的,且是引用类型的,就必须使用useMemo来缓存引用类型

    (useCallback 似乎只有在配合React.memo时才有效果

    不像useMemo 它似乎也可以单独的去缓存使用)

    import React, { useState, useCallback, useMemo } from 'react';
    
    const Child = React.memo(({ count, onClick, config }) => {
      console.log('👶 Child rendered');
      return (
        <div style={{ border: '1px solid #ccc', padding: 10 }}>
          <p>Count: {count}</p>
          <p>Theme: {config.theme}</p>
          <button onClick={onClick}>Click Me</button>
        </div>
      );
    });
    
    export default function App() {
      const [parentCount, setParentCount] = useState(0);
    
      // ✅ 原始值:直接传,不需要 useMemo
      const count = parentCount;
    
      // ✅ useCallback:缓存函数引用
      const handleClick = useCallback(() => {
        console.log('Button clicked!');
      }, []);
    
      // ✅ useMemo:缓存对象引用
      const config = useMemo(() => ({ theme: 'dark' }), []);
    
      return (
        <div>
          <h2>Parent Count: {parentCount}</h2>
          <button onClick={() => setParentCount(c => c + 1)}>+1 Parent Count</button>
    
          <Child count={count} onClick={handleClick} config={config} />
        </div>
      );
    }
    


    4.多层组件嵌套时,useeffect执行顺序

    多层嵌套里,useEffect 执行顺序依然是从最外层父组件开始,逐层往里依次执行。也就是说:

    假设组件关系:A -> B -> C -> D

    执行顺序是:

    A useEffect
    B useEffect
    C useEffect
    D useEffect
    

    卸载清理函数执行顺序是:

    子组件先清理 → 一层层向上到父组件,也就是:

    D 卸载清理函数
    C 卸载清理函数
    B 卸载清理函数
    A 卸载清理函数
    


    5.reselect.js

    主要是配合redux来通过缓存仓库数据,提高性能

    使用场景多个页面使用了同样的仓库中衍生出来的计算结果(派生数据)

    希望把这个结果给缓存下来,避免重复的计算

    A/B/C 三个页面都需要用到一个仓库的派生数据

    如果先到A页面,调用并计算了使用了这个派生数据,

    跳转到B,C页面时,如果仓库的源数据没有变化,那么就不会重复计算,直接返回上一次的派生数据结果

    B,C页面就避免了重复的计算过程

    代码示例

    react 组件a 引用

      const noPoachingCompany = useSelector(getNoPoachingCompany);
    

    selector.js 声明

    import { createSelector } from 'reselect';
    
    const getCompany = (state) => state.model.companies;
    
    export const getNoPoachingCompany = createSelector(
      [getCompany],
      (companies) => {
        return companies.filter((c) => c.noPoaching);
      },
    );
    

    6/26补充:

    如果在state组件中使用getNoPoachingCompany

    需要通过connect的mapStateToProps方法中 把state传入方法中 并返回

    const mapStateToProps = (state) => {
      return {
    	noPoachingCompany : getNoPoachingCompany (state),
      };
    };
    export default withTranslation(['v3talent'])(connect(mapStateToProps)(Search));
    

    6.长列表数据匹配优化方案

    通过id匹配 仓库中存储的用户列表(举例10万个用户),获取名字 这种方式

    第一种优化方法:使用reselect来进行匹配(如果传递的id重复出现,不会重复匹配计算,直接命中缓存,直接返回),但是有局限性,只能命中匹配上一次输入的id,无法记录多种数据

    第二种优化方法:自己写一个匹配方法,每次成功匹配后,将结果存为一份mapping格式的数据,如果匹配到多个或者单个,直接返回,不需要重复循环匹配

    第三种优化方法:直接以mapping格式将用户表存入仓库,后续取值直接取值使用,不用循环匹配


    最佳的优化方案是第三种,因为这种方式是O(1)(输入多少都一样快,常数级时间)


    拓展

    O(1)是直接访问 不涉及遍历

    O(n)是遍历所有元素一变

    O(n²)是每个元素都和每个元素比较


    O(1):常数时间复杂度。
    直接访问数据,不管数据多少,操作次数基本固定。
    例子:数组通过索引访问 arr[5]
    O(n):线性时间复杂度。
    遍历所有元素,操作次数和数据量成正比。
    例子:遍历数组求和。
    O(n²):平方时间复杂度。
    对每个元素,都要和其他所有元素进行比较,操作次数是数据量的平方。
    例子:冒泡排序,两层嵌套循环比较数组中的每对元素。



    7.记录一次redux污染bug

    export const getTeamNames = (teamIds, teamNames = []) => {
      const reduxState = store.getState();
      let treeList = reduxState?.newController.folder.get("treeList")
    
      //平铺一层
      let spredTeamList = setSpreadTreeData(treeList);
    
      //删除无用key
      spredTeamList.forEach((tree) => {
        delete tree.children;
      });
    
      for (let teamId of teamIds) {
        for (let spredTeam of spredTeamList) {
          if (spredTeam.id == teamId) {
            teamNames.push(spredTeam.name);
            break;
          }
        }
      }
    
      return teamNames;
    };
    


    这个方法间接的没有迹象的(dispatch)污染了仓库的值

    需要深度拷贝cloneDeep(treeList),这种写法是有很大风险的

    改了仓库的值 但是又不知道哪里修改了,隐藏的比较深。。


    7. useRef / forwardRef / useImperativeHandle / react中父组件获取子组件的state(单向数据流设计)

    场景就是:

    父组件是一个表单,子组件是表单中的一个勾选 选中之后会选择一些数据,

    我理想的状态是父组件提交表单的的时候直接拿这个子组件的勾选状态,如果勾选了就把里面的选中的数据一起做处理

    demo示例

    子组件:
    // PassedAutoGroupSelect.jsx
    import React, { useState, forwardRef, useImperativeHandle } from "react";
    
    const PassedAutoGroupSelect = forwardRef((props, ref) => {
      const [checked, setChecked] = useState(false);
      const [selectedData, setSelectedData] = useState([]);
    
      useImperativeHandle(ref, () => ({
        getValue: () => ({
          checked,
          selectedData,
        }),
      }));
    
      return (
        <div>
          <label>
            <input
              type="checkbox"
              checked={checked}
              onChange={(e) => setChecked(e.target.checked)}
            />
            是否启用选项
          </label>
    
          {checked && (
            <div>
              <button onClick={() => setSelectedData(["A", "B"])}>模拟选择 A,B</button>
              <p>已选中:{selectedData.join(", ")}</p>
            </div>
          )}
        </div>
      );
    });
    
    export default PassedAutoGroupSelect;
    


    父组件引用:

    import React, { useRef } from "react";
    import PassedAutoGroupSelect from "./PassedAutoGroupSelect";
    
    const MyForm = () => {
      const ref = useRef();
    
      const handleSubmit = () => {
        const childValue = ref.current?.getValue();
    
        console.log("子组件返回的数据:", childValue);
    
        if (childValue?.checked) {
          // 勾选了,做进一步处理
          console.log("需要提交的选中数据:", childValue.selectedData);
        } else {
          console.log("未勾选,不处理");
        }
      };
    
      return (
        <div>
          <h2>我的表单</h2>
          <PassedAutoGroupSelect ref={ref} />
    
          <button onClick={handleSubmit}>提交</button>
        </div>
      );
    };
    
    export default MyForm;
    




    文章评论

    评论列表(0