react中hooks使用

总结

1、react 中的hook是16.8引入的函数,不需要编写组件类。可以理解成是函数式编程。

2、使用的前提:只能在react函数组件中使用或者Hook 中调用 hook,不能在javascript中使用。

3、useCallback 的功能可以直接用 useMemo。

4、useEffect 的副作用,会导致异步问题,需要注意,文中有提供场景。

5、useEffect 和 useEffectLayout 调用的时机不同。

6、useLayoutEffect 和原来的 ComponentDidMount 和 ComponentDidUpdate 一致,是 react 完成 DOM 更新后马上同步调用代码,会阻塞页面渲染。

7、useEffect 是整个页面渲染完成才会调用代码。官方推荐是useEffect 。

一、常用的方法

1、useState

1.1 定义

useState 是保存组件状态,修改的值触发组件重新渲染。

使用场景:常用的数据请求前先loading,然后请求成功后loading关闭。简单理解就是存储状态的。

1.2 知识点

1、在类组件中我们是用this.state 来保存组件的状态。

2、使用 useState 参数后,有一个默认状态和改变状态。useState 是不会帮你处理状态的,修改状态需要用setState 覆盖式更新状态。

3、基础使用如下:

javascript 复制代码
cons [demo, setDemo] = useState(1)

// demo设置初始值,在某些条件或者 场景下要改变demo的值
setDemo(22)

2、useRef

2.1 定义

用于访问 DOM 节点或在渲染间保持一个可变的值。

useRef返回的是一个 ref 对象,.current 属性是初始化传入的参数,使用这个更新不会引起当前组件或者子组件的更新。

1、但使用 React.createRef ,每次重新渲染组件都会重新创建 ref。

2、如果使用useState定义值的时候有异步问题,就可以用useRef。

javascript 复制代码
const demo = useRef()

// 如果要给demo中添加值
demo.current={
    name: 'karla',
    year: 18
}

3、useEffect

3.1 定义

用于数据获取、订阅、请求数据

1、useEffect 不能瞎更新,如果监听某个值,这个值一直变化,就会进入死循环,临时状态可以用useRef 来存储,避免不必要的更新。

2、生命周期的写法,一些重复的功能都得在componentDidMount 和 componentDidUpdate 重复写,useEffect 不需要 。

3、useEffect 对于函数的保存状态,异步的问题解决不了。

4、在实际使用时如果想避免页面抖动 (在useEffect里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect里。关于使用useEffect导致页面抖动。

不过useLayoutEffect在服务端渲染时会出现一个warning,要消除的话得用useEffect代替或者推迟渲染时机。

5、基础使用如下:

javascript 复制代码
useEffect(() => {
  // 副作用代码
  return () => {
    // 清理操作
  };
}, [dependency]); // 依赖项数组,用于指定何时重新运行副作用

3.2 知识点

3.2.1 异步解决方法

在处理函数和异步的时候可能遇到的问题如下

1、函数引用的变化:每次渲染都会创建实例,导致无必要的 useEffect 执行

2、异步状态更新:异步时导致闭包

3、过时闭包:effect 回调的时候 获取的是创建时的状态,非最新状态

解决文案有以下:

(1)稳定函数引用
javascript 复制代码
// 使用 useCallback 避免函数引用变化
const fetchData = useCallback(async () => {
  const response = await fetch('/api/data');
  setData(await response.json());
}, []); // 依赖项为空表示函数不会变化

useEffect(() => {
  fetchData();
}, [fetchData]);
(2)处理异步操作
javascript 复制代码
useEffect(() => {
  let isMounted = true; // 清理标记
  
  const fetchData = async () => {
    try {
      const response = await fetch('/api/data');
      if (isMounted) {
        setData(await response.json());
      }
    } catch (error) {
      if (isMounted) {
        setError(error);
      }
    }
  };

  fetchData();

  return () => {
    isMounted = false; // 清理时标记为未挂载
  };
}, []); // 空依赖表示只运行一次
(3)获取最新状态
javascript 复制代码
// 使用 ref 保存最新状态
const [state, setState] = useState(initialState);
const stateRef = useRef(state);

useEffect(() => {
  stateRef.current = state; // 每次更新时同步到 ref
}, [state]);

useEffect(() => {
  const timer = setInterval(() => {
    console.log(stateRef.current); // 总是获取最新值
  }, 1000);
  
  return () => clearInterval(timer);
}, []);
(4)reducer 管理复杂的状态
javascript 复制代码
function reducer(state, action) {
  switch (action.type) {
    case 'FETCH_SUCCESS':
      return { ...state, data: action.payload, loading: false };
    case 'FETCH_ERROR':
      return { ...state, error: action.payload, loading: false };
    default:
      return state;
  }
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, {
    data: null,
    error: null,
    loading: false
  });

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_START' });
      try {
        const response = await fetch('/api/data');
        dispatch({ type: 'FETCH_SUCCESS', payload: await response.json() });
      } catch (error) {
        dispatch({ type: 'FETCH_ERROR', payload: error });
      }
    };

    fetchData();
  }, []);
}

3.3 场景

3.3.1 请求接口
javascript 复制代码
import React, { useState, useEffect } from 'react';

const Index = () => {
    // 定义一个名为 data 的 state,初始值为 null
    const [data, setData] = useState(null);

    // 使用 useEffect 进行数据获取
    useEffect(() => {
        // 模拟一个异步的数据获取操作
        const fetchData = async () => {
            const response = await fetch('XXXXXXXXXXXX');
            // 将获取到的数据更新到 data state 中
            setData(response );
        };

        // 调用数据获取函数
        fetchData();

        // 可以返回一个清理函数,用于在组件卸载时执行一些清理操作,比如取消订阅等
        return () => {
            console.log('组件卸载了');
        };
    }, []); // 传入空数组,表示这个 useEffect 只在组件第一次渲染后执行

    return (
        <div>
            {data? (
                // 如果 data 不为 null,显示数据的标题
                <p>{data.title}</p>
            ) : (
                // 如果 data 为 null,显示加载中
                <p>加载中...</p>
            )}
        </div>
    );
}

export default Index;
3.3.2 调用异步写法
javascript 复制代码
import { useEffect, useRef } from 'react';
const approveStatusList = useRef([])

/** 写法一 */
useEffect(() => {
    const fetch = async () => {
    /** 获取接口 */
    const res = await getList()
    approveStatusList.current = res|| [];
  };
  fetch();
}, []);


/** 写法二 */
useEffect(() => {
  (async function() {
    const res = await getList()
    approveStatusList.current = res|| [];
  })();
}, []);
3.3.3 监听某个值
javascript 复制代码
    useEffect(() => {
      const params = {
        tabsList
      };
      handleParams(params);
    }, [tabsList]);

4、useCallback

4.1 定义

用于优化性能,避免在组件重新渲染时不必要的函数重建

在react 中,重新渲染时,内部定义的函数都会重新创建,可能会导致:

1、不必要的子组件重新渲染(当函数作为 props 传递给子组件)

2、依赖该函数的 useEffect 被 不必要的触发

使用场景

1、函数作为 props 传递给子组件(特别是用 React.memo 优化的子组件)

2、函数作为其他 Hook 的依赖项(如 useEffect、useMemo 等)

3、在自定义 Hook 中返回稳定的函数引用

注意事项

1、不要过度使用 useCallback - 只有在确实需要优化性能时才使用

2、正确设置依赖项 - 遗漏依赖项会导致闭包问题

3、useCallback 不会使函数运行更快 - 它只是避免不必要的重新创建

函数式组件可以理解为 class 组件 render 函数的语法糖,每交重新渲染的时候,函数式组件内部会把代码都重新执行一遍。useCallback 能获取一个记忆后的函数。

javascript 复制代码
const Index = () => {
  const handleClick= useCallback(() => {
    console.log('处理逻辑')
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变

  return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}

export default Index

4.1 基础示例

4.1.1 计数器示例

带 useCallback

javascript 复制代码
import React, { useState, useCallback } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  // 使用 useCallback 缓存函数
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 空依赖数组表示函数不会改变

  const decrement = useCallback(() => {
    setCount(c => c - 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}
4.1.2 与子组件配合使用
javascript 复制代码
import React, { useState, useCallback } from 'react';

// 子组件(使用 React.memo 优化)
const Button = React.memo(({ onClick, children }) => {
  console.log(`Button ${children} rendered`);
  return <button onClick={onClick}>{children}</button>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [value, setValue] = useState('');

  // 使用 useCallback 缓存函数
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <Button onClick={increment}>Increment</Button>
      
      <input 
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
    </div>
  );
}

在这个例子中,即使 ParentComponent 因为 value 状态变化而重新渲染,increment 函数也不会重新创建,因此 Button 组件不会不必要地重新渲染。

5、useMemo

5.1 定义

对一个计算过程进行记忆化,只有当依赖项改变时才会重新计算。

useCallback 的功能完全可以用 useMemo 取代,如果想用 useMemo 返回一个记忆函数也查完全可以的。下面是使用方法:

javascript 复制代码
const Index = () => {
  const handleClick= useMemo(() => {
    console.log('处理逻辑')
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变

  return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}

export default Index

1、useMemo 和 useCallback 唯一的区别:useCallback 不会执行第一个参数函数,是将穹返回给你, useMemo 是会执行第一个函数并将结果返给你。

2、useCallback 是一个记忆事件函数,生成记忆后的事件函数传给子组件使用。useMemo 适合经过函数计算得到的一个确定的值。比如记忆组件,请看下面代码:

javascript 复制代码
const demo = ({ a, b }) => {
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  const child2 = useMemo(() => <Child2 b={b} />, [b]);

  return (
    <>
      {child1}
      {child2}
    </>
  )
}

当 a/b 改变时,child1/child2 才会重新渲染。从例子可以看出来,只有在第二个参数数组的值发生变化时,才会触发子组件的更新

6、useImperativeHandle

6.1 定义

用于在使用 forwardRef 时自定义暴露给父组件的实例值。(简单理解:透传 ref)

useImperativeHandle(ref, handle)

  • ref:父组件传递的引用。
  • handler:返回一个对象,定义要暴露给父组件的实例值。

6.2 基础使用

javascript 复制代码
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';

/** 子组件,使用 forwardRef 包装以便父组件可以引用子组件的 DOM 元素 */ 
const ChildInputComponent = forwardRef((props, ref) => {
  // 1、使用 useRef 创建一个引用对象
  const inputRef = useRef(null);

  // 2、使用 useImperativeHandle 自定义暴露给父组件的实例值
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus(); // 调用原生 input 的 focus 方法
    }
  }));

  // 3、返回一个 input 元素,并将 inputRef 附加到它上面
  return <input type="text" name="child input" ref={inputRef} />;
});

/** 父组件 */
const App = () => {
  // 1、使用 useRef 创建一个引用对象
  const inputRef = useRef(null);

  // 2、使用 useEffect 在组件加载时自动聚焦到子组件的 input 元素
  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus(); // 调用子组件暴露的 focus 方法
    }
  }, []);

  // 3、返回一个包含子组件的 JSX
  return (
    <p>
      <ChildInputComponent ref={inputRef} />
    </p>
  );
};

export default App;
  • forwardRef 是一个 React 高阶函数,用于创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到另一个组件中。

  • 在这个例子中,ChildInputComponent 是一个函数组件,它使用 forwardRef 包装,以便父组件可以通过 ref 引用子组件的 DOM 元素。

6.3 场景使用

情景:子组件的table中数据更新出暴露出来给父级,父亲能接收到这个更新后的数据,父级中定义ref,然后获取子级更新的数据。

javascript 复制代码
/** 父组件 **/
import { useRef, useState } from "react";
import CoverModel from "./coverModel";

const Index = (props, ref) => {
  const coverModelRef = useRef<any>(null); // 客户团覆盖机型

  const handleSave = () => {
    // 获取子组件中的
    console.log(coverModelRef?.current?.table);
  };

  return (
    <>
      <CoverModel ref={coverModelRef} />
    </>
  );
};

export default Index;

/** 子组件 */
import React, {
  forwardRef,
  useImperativeHandle,
  useMemo,
  useState,
} from "react";
import { observer } from "mobx-react";
import {Button, DataSet, Table} from 'choerodon-ui/pro';

const Index = observer(
  forwardRef((props: any, ref: any) => {
    useImperativeHandle(ref, () => {
      return {
        table: tableDs.toData(),
      };
    });

    /** table ds */
    const tableDs = useMemo(
      () =>
        new DataSet({
          ...tableList(id),
          events: {
            load: () => {
              // 数据加载完成后更新 ref,父级中会接收到
              if (ref) {
                ref.current = {
                  table: tableDs.toData(),
                };
              }
            },
          },
        }),
      [id]
    );
    const columns = useMemo(() => {
      return [
        { name: "name" },
        { name: "code" },
        { name: "manageIndustryName" },
      ];
    }, []);

    return (
      <>
        <Table
          dataSet={tableDs}
          columns={columns}
          buttons={tableButtons}
          pagination={false}
          style={{ marginBottom: "16px" }}
          renderEmpty={() => {
            return <div>暂无数据</div>;
          }}
        />
      </>
    );
  })
);

export default Index;

7、useContext

7.1 定义

8、useReducer

8.1 定义

useReducer 这个 Hooks 在使用上几乎跟 Redux/React-Redux 一模一样,唯一缺少的就是无法使用 redux 提供的中间件。

可以理解成一个 mini 的 redux

9、useLayoutEffect

9.1 定义

其功能与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用,可以用于读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

javascript 复制代码
useLayoutEffect(() => {
  // 读取 DOM 布局并同步触发重渲染的代码
  return () => {
    // 清理操作
  };
}, [dependency]); // 依赖项数组,用于指定何时重新运行副作用

9.2 知识点

大部分情况下,使用 useEffect 可以帮我们处理组件的副作用,但想要同步调用一些副作用,例如操作DOM,可以使用 useEffectLayoutEffect,这个会在DOM更新后同步更新。

9.3 场景使用

在下面的 demo 中,useLayoutEffect 会在 render,DOM 更新之后同步触发函数,会优于 useEffect 异步触发函数。

javascript 复制代码
import React, { useState, useLayoutEffect, useRef } from 'react';

function App() {
  const [width, setWidth] = useState(0);
  const titleRef = useRef(null); // 使用 useRef 来存储 DOM 元素的引用
  const lastWidthRef = useRef(0); // 使用 useRef 来存储上一次的宽度值

  useLayoutEffect(() => {
    if (titleRef.current) {
      const titleWidth = titleRef.current.getBoundingClientRect().width;
      console.log("useLayoutEffect");

      if (lastWidthRef.current !== titleWidth) {
        lastWidthRef.current = titleWidth; // 更新上一次的宽度值
        setWidth(titleWidth); // 更新状态
      }
    }
  });

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

  return (
    <p>
      <h1 id="title" ref={titleRef}>hello</h1> {/* 使用 ref 而不是 querySelector */}
      <h2>{width}</h2>
    </p>
  );
}

export default App;

10、useDebugValue

用于在 React DevTools 中显示自定义 Hook 的标签。这个 Hook 通常不直接使用在生产代码中,主要用于调试。

javascript 复制代码
useDebugValue(value); // 在 DevTools 中显示 value 的值或标签。
相关推荐
前进的搬砖er11 小时前
4.three.js的几何体、纹理、光源、材质、uv属性 讲义
前端
前端毕业班11 小时前
uni-app 小程序支持 teleport 了
前端·javascript·vue.js
东风破_11 小时前
LLM 为什么记不住你?无状态、messages 和上下文管理
javascript·人工智能
梨子同志11 小时前
qiankun
前端
梨子同志12 小时前
Nginx
前端
梨子同志12 小时前
NestJS
前端
梨子同志12 小时前
Next.js
前端
谭光志12 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
Hilaku12 小时前
前端架构师的克制:什么时候该坚决对新技术说不?
前端·javascript·程序员
陈随易13 小时前
前端项目部署只要30秒
前端·后端·程序员