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 的值或标签。
相关推荐
xiezhr6 分钟前
程序员为什么总是加班?
前端·后端·程序员
好_快10 分钟前
Lodash源码阅读-baseIsMatch
前端·javascript·源码阅读
excel10 分钟前
webpack 格式化模块工厂 第 一 节
前端
九筠13 分钟前
python网络爬虫开发实战之Ajax数据提取
前端·爬虫·ajax·网络爬虫
excel25 分钟前
webpack 核心编译器 十七 节
前端
斯普信专业组42 分钟前
ceph数据迁移数据迁移与bacula配置调整优化实战
前端·ceph·github
好_快1 小时前
Lodash源码阅读-baseMatches
前端·javascript·源码阅读
好_快1 小时前
Lodash源码阅读-baseHasIn
前端·javascript·源码阅读
好_快1 小时前
Lodash源码阅读-baseIsEqual
前端·javascript·源码阅读
古茗前端团队1 小时前
因网速太慢我把20M+的字体压缩到了几KB
react.js