React Hook介绍

React Hook介绍

简介

介绍:legacy.reactjs.org/docs/hooks-...

使用:react.dev/reference/r...

什么是 Hook?

Hook 是 React 16.8 的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。其本质上就是一类特殊JavaScript函数,它们约定以 use 开头,可以为 React Function Component 注入一些功能,赋予 Function Component 一些类似 Class Component 所具备的能力,比如状态管理和生命周期

为什么引入 Hook?

  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class

常见Hooks

  • useState
    • 作用:用于在函数组件中添加状态。可以给任何类型的值添加状态(基础数据类型、引用数据类型、自定义数据结构等)
    • 如何使用:
    • 使用场景:当你需要在组件中存储一些可变的值,并且当这些值变化时需要重新渲染组件
    • 注意事项:1、更新状态会使用新的状态值请求另一个渲染,但并不影响你已经运行的函数中的 状态的 的值。

2、引用数据类型更新时,需赋值新对象,而不是在旧对象上做修改。

3、存储 一个函数,需要加上 () => ,否则会被直接调用(见下方源码)

scss 复制代码
// 声明一个叫 "count" 的 state 变量。
  const [count, setCount] = useState(0);
  
  function addCount(){
    console.log("addCount start:", count);
    setCount(count + 1);
    console.log("addCount end:", count);
    
     //先保存在一个变量中
    // const nextCount = count + 1;
    // setCount(nextCount);
    // console.log(count); // 0
    // console.log(nextCount); // 1
  }

 function startCountInterval1(){
    setInterval(() => {
      console.log("startCountInterval start:", count);
      setCount(count + 1);
    }, 1000);
  }
  function startCountInterval2() {
    let count = 10;
    setInterval(() => {
      console.log("startCountInterval start:", count);
      count++;
    }, 1000);
  }

/**
 * React 会在 `person` 对象的内存引用发生变化时触发更新。
 * 如果直接修改 `person` 对象的属性而没有生成一个新的对象引用,
 * React 将不会识别状态已经改变,因此不会重新渲染组件。
 */
const [person,setPreson] = useState({name: '张三', age: 18});

function onSetPreson(){
    //页面会刷新
    setPreson({...person, name: '李四'});
    
    //页面不会刷新
    person.name = '李四';
    setPreson(person);
  }

//源码:如果传入一个函数,会被直接调用,生存初始值,所以想存储一个函数的状态,需要使用()=>
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    const initialStateInitializer = initialState;
    // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
    initialState = initialStateInitializer();
    if (shouldDoubleInvokeUserFnsInHooksDEV) {
      setIsStrictModeForDevtools(true);
      // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
      initialStateInitializer();
      setIsStrictModeForDevtools(false);
    }
  }
  • useEffect
    • 作用:用于处理副作用,类似于类组件的componentDidMount,componentDidUpdate, 和componentWillUnmount生命周期方法。副作用是指那些与组件的渲染无直接关联的操作,比如数据获取、订阅、或手动更改DOM等。
    • 如何使用:
    • 使用场景:1、数据获取:在组件渲染后获取异步数据。

2、订阅事件:在组件订阅外部事件,比如 Redux store 的变化。

3、手动操作 DOM:在组件渲染后对 DOM 进行操作。

  • 注意事项: 1、注意闭包陷阱,正确添加依赖(所有响应式值的列表),安装eslint插件,会帮助检测依赖

2、正确处理副作用的清理,特别是订阅和定时器

3、分离不想依赖的响应式值(useEffectEvent

scss 复制代码
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const [seconds, setSeconds] = useState(0)

useEffect(() => {
    console.log("每次页面渲染都会执行")
  });
  useEffect(() => {
    console.log("只在页面初次渲染执行")
  }, []);
  useEffect(() => {
    console.log("只在 count 变化时执行")
  }, [count])


//注意闭包陷阱
useEffect(() => {
    //设置一个定时器
    console.log("设置一个定时器")
    let timer = setInterval(() => {
      setSeconds(preSec => {
        console.log('preSec:', preSec)
        return preSec + 1
      })
        //setSeconds(seconds + 1);
        //这里会打印什么
       //console.log('seconds:', seconds)
    }, 1000)
    // 移除副作用
    return () => {
      console.log("移除定时器")
      clearInterval(timer)
    }
  }, [])

function Page({ url ,numberOfItems}) {
    useEffect(() => {
        logVisit(url, numberOfItems);
    }, [url]); // 🔴 React Hook useEffect 缺少依赖项: 'numberOfItems'
    // ...
}
  • useContext
    • 作用:用于让你访问 React 的 Context API,以便在组件树中传递数据。
    • 如何使用:1、创建context:createContext;2、使用CountContext.Provider 给value 赋值;3、在子组件中使用useContext
    • 使用场景:当需要在 React 应用中跨多层组件传递数据时,比如修改主题,用户身份验证,多语言支持
    • 注意事项:1、确保不要滥用 context,可能会导致组件不必要的重新渲染。当需要在多层嵌套的组件中共享全局数据时使用

2、确保提供了默认值,避免在没有 Provider 的情况下出错

javascript 复制代码
//1、创建context:createContext
const  ThemeContext = createContext('light');

function UseContextComponent() {
  const [theme,setTheme]= useState('light');
  
  return (
    //2、使用CountContext.Provider  给value 赋值
    <ThemeContext.Provider value={theme}>
      <div >
        <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>修改主题</button>
        <ChildComponent/>

      </div>
    </ThemeContext.Provider>
  );
}
export default UseContextComponent;


function ChildComponent() {
  //3、在子组件中使用useContext
  const theme = useContext(ThemeContext);
  return (
    <div className={`div-${theme}`}>
        <p>agajfi</p>
        <br/>
        <button className={`button-${theme}`}>添加按钮</button>
        <br/>
        <br/>
    </div>
  );
}
  • useRef
    • 作用:用于在函数组件中创建一个可变的ref对象,可以用来保存任何可变值,类似于在类组件中使用实例字段
    • 如何使用:
    • 使用场景:1、通过 ref 操作 DOM;2、当需要在函数组件中保存一个可变值,但又不希望因为该值的改变而导致组件重新渲染时
    • 注意事项:1、当 ref 对象内容变化时,不会触发组件重新渲染

2、除了初始化外不要在渲染期间写入或者读取ref.current,否则会使组件行为变得不可预测。

3、无法获取自定义组件的dom节点 ,需要借助forwardRef

javascript 复制代码
//1、通过 ref 操作 DOM  
//1.1、声明一个 初始值 为 null 的 ref 对象
  const inputRef = useRef<HTMLInputElement>(null);

 //1.3 当 React 创建 DOM 节点并将其渲染到屏幕时,React 将会把 DOM 节点设置为 ref 对象的 current 属性。
  //现在可以借助 ref 对象访问 <input> 的 DOM 节点
  const showDom = () => {
    if (inputRef.current !== null) {
      console.log(inputRef.current.value);
    }
  };

  <label>
        请输入文本内容
        {/* 1.2 将 ref 对象作为 ref 属性传递给想要操作的 DOM 节点的 */}
        <input type="text" ref={inputRef} />
        <button onClick={showDom}>获取dom</button>
   </label>


  //2、下面代码中 timeoutID是普通变量,在组件重新渲染时,会重新置为null,所以停止不了延时消息,这个时候需要使用useRef
  const [isSending, setIsSending] = useState(false);
  let timeoutID: NodeJS.Timeout;
  // const timeoutID = useRef<NodeJS.Timeout>();

  console.log("渲染");
  function handleSend() {
    setIsSending(true);
    timeoutID = setTimeout(() => {
      console.log("已发送");
      setIsSending(false);
    }, 3000);
    console.log("点击发送", timeoutID);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutID);
    console.log("终止发送", timeoutID);
  }
  • useCallback
    • 作用:用于在函数组件中缓存函数,以避免不必要的重复渲染。
    • 如何使用:
    • 使用场景:1、将函数作为 props 传递给包装在 [memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。Memoization允许组件仅在依赖项更改时重新渲染

2、传递的函数可能作为某些 Hook 的依赖。比如依赖于useEffect中的函数。

3、优化自定义 Hook

  • 注意事项:1、过度使用可能会导致性能问题,useCallback 钩子需要在内存中保持函数的引用,这意味着增加了内存的使用,如果你不确定是否需要它,最好是先不用,等到性能成为问题时再考虑引入 useCallback

2、正确添加依赖,注意闭包陷阱。遗漏了依赖项,那么当这些依赖项改变时,缓存的函数可能会引用旧的变量或状态值,导致错误或不一致的行为。添加了不必要的依赖项,函数可能会过于频繁地重新创建。

3、不允许在循环中为每一个列表项调用 useCallback 函数

javascript 复制代码
function UseCallBackComponent() {

  const [count,setCount] = useState(0);
  const [count1,setCount1] = useState(0);

  const callbackFun = useCallback(()=>{
    console.log('count',count);
  },[count]);

  // const callbackFun = ()=>{
  //   console.log('count',count);
  // };

  return (
    <div >
    <button onClick={() => setCount(count + 1)}>click count: {count}</button>
    <button onClick={() => setCount1(count1 + 1)}>click count1: {count1}</button>
    <ChildComponent callbackFun={callbackFun}/>
    </div>
  );
}

const ChildComponent = memo(function ChildComponent(props:any) {
  console.log('child render',props.callbackFun);
  return <div>
    我是子组件
    <button onClick={props.callbackFun}>callback</button>
  </div>
});
  • useMemo
    • 作用:用于在函数组件中缓存计算结果
    • 如何使用:
    • 使用场景:1、跳过代价昂贵的重新计算(如何衡量计算过程的开销是否昂贵)

你在 useMemo 中进行的计算明显很慢,而且它的依赖关系很少改变

  • 注意事项:

1、页面初次渲染或者依赖发生变化时,会执行useMemo的计算。

2、不保证被记忆,React 可能会在必要时清除记忆化的值。

3、仅在性能优化成为问题时使用,不要过度使用以避免增加复杂性

4、正确添加依赖。

5、不允许为循环中的每个列表项调用 useMemo

javascript 复制代码
function UseMemoComponent() {
  const [count, setCount] = useState(0);
  const [price, setPrice] = useState(10);

  const countSum = useMemo(() => {
    //复杂运算
    console.log("useMemo count", count, "price", price);
    return count * price;
  }, [count]);

  // const countSum = ()=>{
  //   console.log("count", count,'price',price);
  //   return count * price;
  // }

  const getSum = () => {
    console.log("countSum", countSum);
    // console.log("countSum", countSum());
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>click count: {count}</button>
      <button onClick={() => setPrice(price + 1)}>changePrice: {price}</button>
      <button onClick={getSum}>getAdd</button>
    </div>
  );
}
  • useReducer
    • 作用:用于在函数组件中添加状态,适用于状态逻辑复杂的场景,或者当下一个状态依赖于之前的状态时。
    • 如何使用:
    • 使用场景:1、状态逻辑复杂,涉及多个子值或下一个状态依赖于之前的状态。

2、状态更新逻辑可能在多个回调中重用。

3、你想要将组件的业务逻辑外提到组件以外,以便进行单元测试。

总的来说,useState 适用于简单的状态管理,而 useReducer 则适用于处理更复杂的状态逻辑

  • 注意事项:1、包含useState的注意事项

2、对于简单的状态管理,使用 useState 就足够了,只有当状态逻辑变得复杂时才考虑使用 useReducer。

javascript 复制代码
// 1、定义reducer函数
function reducer(state: { count: number }, action: any) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

export default function UseReducerComponanent() {
  // 2、使用useReducer并传入reducer函数和初始状态
  const [state, dispatch] = useReducer(reducer, { count: 5 });

  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </>
  );
}
  • 自定义hook

react hooks中的自定义hook

Hooks 使用的注意事项

  • 只在最顶层使用 Hook,不要在循环、条件或嵌套函数中调用 Hook:确保 Hook在每次组件渲染时都以相同的顺序被调用。
  • 只在 React 函数中调用 Hook:在 React 的函数组件中调用 Hook,在自定义 Hook 中调用其他 Hook。
  • 为了避免不必要的渲染,正确使用依赖项数组 :尤其是在useEffect,useCallback, 和useMemo中。
  • 优化性能 :使用useCallbackuseMemo时,确保它们是必要的,因为它们会增加应用程序的内存使用。
  • 规则的遵守:使用 ESLint 插件来强制执行 Hooks 规则,帮助避免常见错误。
  • 严格模式:在开发模式下,当组件被包裹在 <React.StrictMode> 组件中时,React 会对组件进行两次初始化,useEffect 和 useLayoutEffect 的回调会在每次渲染后执行两次,useState 和其他 Hooks 的初始化会调用两次,这是为了帮助开发者发现组件中的一些常见问题,比如副作用(side effects)不应该在组件的初始化阶段执行。

hook源码解析:cloud.tencent.com/developer/a...

源码:github.com/facebook/re...

相关推荐
HashTang1 小时前
【AI 编程实战】第 12 篇:从 0 到 1 的回顾 - 项目总结与 AI 协作心得
前端·uni-app·ai编程
狂炫冰美式1 小时前
把手从键盘上抬起来:AI 编程的 3 个不可逆阶段
前端·后端·ai编程
codingWhat2 小时前
uniapp 多地区、多平台、多环境打包方案
前端·架构·node.js
HelloReader2 小时前
从 Tauri 2.0 Beta 升级到 2.0 Release Candidate Capabilities 权限前缀与内置 Dev Server 网络策略变
前端
只与明月听3 小时前
RAG深入学习之Chunk
前端·人工智能·python
一枚前端小姐姐3 小时前
低代码平台表单设计系统架构分析(实战一)
前端·低代码·架构
HelloReader3 小时前
Tauri 1.0 升级到 Tauri 2.0从“能跑”到“跑得稳”的迁移实战指南(含移动端准备、配置重构、插件化 API、权限系统)
前端
JunjunZ4 小时前
uniapp 文件预览:从文件流到多格式预览的完整实现
前端·uni-app
_Eleven4 小时前
React 19 深度解析:Actions 与 use API 源码揭秘
前端