React hooks的基本使用

82 阅读5分钟

hooks 之 useState

useState(initialState)

  • useState 使函数组件可以拥有自己的状态

弱化了class组件,推崇函数式编程

  • useState 使用时声明要在当前组件的作用域最顶层
  • 初始化的时候可以传递参数,这个参数可以是一个值,也可以传一个函数进去,注意: 如果传递的是一个函数,这个函数只会执行一次
  const [count, setCount] = useState(() => 2);
  const [count, setCount] = useState(2);
  • set function 在调用的时候不会立即执行,而是在react的下一次渲染阶段更新(commit render)
  • 如果更新引用值的状态,我们必须传递一个新的引用
  const [obj, setObj] = useState({})
  //更新时必须传递一个新的引用,才会更新
  setObj(() => { ...obj, age: 18 })

hooks 之 useEffect

useEffect(setup, dependencies?)

  • useEffect 处理副作用的,比如: http请求,dom操作等

异步操作多数都会产生副作用

  • 建议尽可能的把副作用放在useEffect中运行,因为副作用会产生意料以外的结果,为了便于追踪
  • useEffect 接收两个参数
    • setup: 函数, 如果需要自己清除副作用,可以返回一个函数,这个返回函数做副作用清除工作
    • dependencies?: 依赖,必须是一个数组
  • useEffect 执行时机
    1. 当使用useEffect注册了setup之后,React会在该组件每次挂载完毕到页面中都会执行这个setup函数,异步执行
    2. 当依赖项发生变化的,useEffect会重新执行setup函数

hooks 之 useCallback

useCallback(fn, dependencies)

  • useCallback 维护某一个函数的引用,时空切片: 如果依赖项不变,会一直引用上一次的时空切片,如果这个函数需要用到变化的数据,而不是上次时空切片的数据,就需要重新生成一个引用(依赖发生变化会重新创建一个函数的引用)
    • useCallback 需要两个参数
      1. 需要复制的函数体(函数声明)
      2. 依赖项,当依赖项发生变化,对应的函数引用会重新生成

如果不给任何依赖,拿到的数据始终是首次渲染时的时空切片的数据

为性能考虑,函数最好使用useCallback包裹一下,如果不使用useCallback,每次组件渲染都会创建一个新的函数引用,会造成性能浪费

hooks 之 useMemo

  • useMemo 缓存数据,当依赖发生变化才会重新执行,缓存引用数据,相当于Vue的computed

hooks 之 useRef

  • useRef: 构建一个状态,这个状态脱离React控制,重新渲染组件不会被初始化

useRef创建的数据是可读可写的,在数据发生变化的时候不会造成组件重新渲染

 const timerRef = useRef(null);

 const startTime = useCallback(() => {
   timerRef.current = setInterval(() => {

   }, 1000)
 }, [])

 const stopTime = useCallback(() => {
     if(timerRef.current) clearInterval(timerRef.current)
 }, 100)

useRef 获取真实dom 如果使用状态获取,会造成组件不必要的重复渲染


 const ref = useRef(null)
 
 retrun <div ref={ref}></div>

高阶组件 fowardRef

  • 函数组件无法使用useRef
  • forwarRef是一个高阶组件: 接收一个组件,返回一个新的组件
  • forwardRef给组件扩展了一个属性 ref
  • fowardRef 将允许给函数挂载ref,同时给函数组件扩展了一个属性,作为函数组件的第二个参数(ref)存在,可以通过这个参数传递任何想要传递的数据
  • forwardRef 一般都是和组件ref连用,不会单独使用

hooks 之 useImperativeHandle

useImperativeHandle(ref, createHandle, dependencies?)

  • 通过ref向外暴露方法
  • useImperativeHandle 内部修改了ref.current
 const MyInput = forwardRef(function MyInput(props, ref) {
   useImperativeHandle(ref, () => {
     return {
       // ... your methods ...
     };
   }, []);

   return <input {...props} />;
 });

hooks 之 useLayoutEffect

useLayoutEffect(setup, dependencies?)

  • useLayoutEffect 是同步执行的hooks(慎用,会阻塞UI渲染,只在某些特殊场景下可用)
  • useLayoutEffect 运行规则: 组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入到同步对列等待执行, 所以useLayoutEffect会完全阻塞后续的更新工作

hooks 之 useTransition

useTransition()

  • 调整UI更新的优先级,不阻塞UI的情况下更新状态
  • 如果某个操作非常耗费性能,阻塞了后续的执行产生卡顿等,但是后续的某个操作又需要立即执行,这时可以把这个操作放到startTransition,startTransition不会阻塞用户交互,也不会让页面丢失响应
 const [isPending, startTransition] = useTransition()

  startTransition(() => {
    // 
  });

hooks 之 useSyncExternalStore

useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

  • 可以订阅外部store的react hook
  • subscribe: 是外部仓库提供的 subscribe 方法;
  • getSnapshot: 用于获取指定 store 状态的方法 用于客户端渲染
  • getServerSnapshot 是用于获取指定 store 状态的方法,用于服务端渲染;返回值就是我们在组件中可用的 store 状态
   import { useSyncExternalStore } from 'react';

   export default function ChatIndicator() {
     const isOnline = useSyncExternalStore(subscribe, getSnapshot);
     return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
   }

   function getSnapshot() {
     return navigator.onLine;
   }

   function subscribe(callback) {
     window.addEventListener('online', callback);
     window.addEventListener('offline', callback);
     return () => {
       window.removeEventListener('online', callback);
       window.removeEventListener('offline', callback);
     };
   }
//hooks 自定义封装
   import { useSyncExternalStore } from 'react';

   export function useOnlineStatus() {
     const isOnline = useSyncExternalStore(subscribe, getSnapshot);
     return isOnline;
   }

   function getSnapshot() {
     return navigator.onLine;
   }

   function subscribe(callback) {
     window.addEventListener('online', callback);
     window.addEventListener('offline', callback);
     return () => {
       window.removeEventListener('online', callback);
       window.removeEventListener('offline', callback);
     };
   }

注意: 如果getSnapshot返回值与上次不同,React将重新渲染组件。这就是为什么,如果你总是返回一个不同的值,你会进入一个无限循环并得到这个错误。

  function getSnapshot() {
    // 🔴 Do not return always different objects from getSnapshot
    return {
      todos: myStore.todos
    };
  }

只有在实际发生更改时,getSnapshot对象才应返回不同的对象。如果您的存储包含不可变的数据,则可以直接返回该数据

function getSnapshot() {
  // ✅ You can return immutable data
  return myStore.todos;
}

subscribe 订阅函数不应该放在组件内部,因为每次re-render会重新创建订阅函数, 或者,将subscribe包装到useCallback中,只在某些参数更改时重新订阅

  // 只有在某个依赖发生变化时才重新更新订阅函数
  const subscribe = useCallback(() => {
    // ...
  }, [userId]);