自定义Hooks,不怕不怕啦

461 阅读4分钟

记录一些自己写过或用到过的自定义hooks,尽量 持续更新

useSelector

实现了一个自定义的 useSelector Hook,它的作用类似于 React-Redux 中的 useSelector。通过该 Hook,你可以从全局状态中选择特定的部分,并订阅状态的变化。这个 Hook 利用了 store.getState() 来获取当前状态,并监听状态的变化,当状态发生变化时更新组件的状态。

import { useState, useEffect, useRef } from 'react'
const store = window.reactStore //获取全局状态管理的store

const defaultEqualityFn = (a, b) => a === b // defaultEqualityFn 用于检查新状态和当前状态是否相等

const useSelector = (selector, equalityFn = defaultEqualityFn) => {
    const [state, setState] = useState(() => selector(store.getState()))
    const latestSelector = useRef(selector) // useRef 保持值的持续性,而且不会触发组件重新渲染
    const latestEqualityFn = useRef(equalityFn)
    const latestState = useRef(state)

    useEffect(() => {
        latestSelector.current = selector
        latestEqualityFn.current = equalityFn
    })

    useEffect(() => {
        const checkForUpdate = () => {
            const newState = latestSelector.current(store.getState())
            if (!latestEqualityFn.current(newState, latestState.current)) {
                latestState.current = newState
                setState(newState)
            }
        }
        const unsubscribe = store.subscribe(checkForUpdate)
        checkForUpdate()
        return () => {
            unsubscribe()
        }
    }, [])

    return state
}

export default useSelector

解析:

1.定义 useSelector Hook

const useSelector = (selector, equalityFn = defaultEqualityFn)
  • useSelector 接受两个参数
    • selector:选择器函数,用于从全局状态中选择某一部分状态
    • equalityFn:用于比较新状态和当前状态是否相等的函数,默认使用 defaultEqualityFn

2.初始化状态

const [state, setState] = useState(() => selector(store.getState()))
  • 使用 useState 来创建一个状态 state ,通过 selector(store.getState()) 初始化状态
  • selector 是从全局状态 store 中选择的状态部分

3.更新 selectorequalityFn 引用

useEffect(() => {
   latestSelector.current = selector
   latestEqualityFn.current = equalityFn
})
  • useEffect 每次 selectorequalityFn 改变时会更新 latestSelectorlatestEqualityFn 的值
  • 确保 checkForUpdate 中始终使用最新的 selectorequalityFn

4.监听状态变化并且更新组件状态

useEffect(() => {
    const checkForUpdate = () => {
        const newState = latestSelector.current(store.getState())
        if (!latestEqualityFn.current(newState, latestState.current)) {
            latestState.current = newState
            setState(newState)
        }
    }
    const unsubscribe = store.subscribe(checkForUpdate)
    checkForUpdate()
    return () => {
        unsubscribe()
    }
}, [])
  • 第二个 useEffect 负责订阅全局 store 的状态变化,并在状态变化时更新组件的状态
  • checkForUpdate 用于检查状态是否发生变化,它通过 latestSelector.current(store.getState())获取新的状态,并与 latestState.current 进行比较
  • 如果新状态与当前状态不同,则更新组建的状态(调用 setState(newState) ),并更新 latestState.current 为新状态
  • store.subscribe(checkForUpdate)使得当全局状态变化时,checkForUpdate会被触发。返回的 unsubscribe 用于在组件卸载时取消订阅

总结:

  • 这个 useSelector Hook 允许你从全局 store 中选择并订阅某一部分状态。
  • 它通过 selector 函数从 store 中选择状态部分,通过 equalityFn 函数比较新旧状态是否相等,避免不必要的重新渲染。
  • 通过 useEffectstore.subscribe 机制,组件在状态变化时能够自动更新,从而确保 UI 与全局状态保持同步。

使用场景:

const hasAuthorisePermission = useSelector((state) => {
    const permissions = state.local.data.permissions
    return permissions.find((el) => el.permissionId === Permissions.AML_AUTHORISE && el.grantedRevoked === 'G') !== undefined
})

const customerId = useSelector((state) => {
    const customer = state.local.data.customer
    return customer?.customerId
})

useLocalStorage

这个自定义 Hook 用于与 localStorage 交互,实现数据的持久化存储。

import { useState } from 'react';

// 自定义 Hook:useLocalStorage
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error('Could not save to localStorage', error);
    }
  };

  return [storedValue, setValue];
}

// 使用自定义 Hook 的组件
function App() {
  const [name, setName] = useLocalStorage('name', 'John Doe');

  return (
    <div>
      <h1>Hello, {name}</h1>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
}

export default App;

解析:

  • useLocalStorage 是一个自定义 Hook,它将 localStorage 中的值和组件的状态相结合,确保在浏览器刷新后仍能保留状态。
  • 它通过 JSON.parseJSON.stringify 处理数据的序列化和反序列化。

setValue 是自定义 Hook useLocalStorage 中定义的一个函数,它会被传递给使用该 Hook 的组件作为返回值的一部分。setValue 是在 App 组件的 onChange 事件处理程序中被调用的。

关于 setValue 的调用时机

  1. useLocalStorage 返回 setValue:在 useLocalStorage 中,setValue 是作为返回值的一部分与 storedValue 一起返回。具体来说,useLocalStorage 返回一个数组,数组的第一个元素是从 localStorage 读取的 storedValue(或者是初始值),第二个元素是 setValue,它是用来更新 storedValuelocalStorage 的函数。
return [storedValue, setValue];
  1. setName(即 setValue)被调用:在 App 组件中,useLocalStorage 被调用,返回了一个数组,数组的第一个元素 namestoredValue,第二个元素 setNamesetValue。然后,setName(即 setValue)被用作输入框的 onChange 事件处理程序。
<input
  type="text"
  value={name}
  onChange={(e) => setName(e.target.value)}  // setName 被调用
/>
  1. onChange 事件触发:当用户在输入框中输入内容时,onChange 事件会被触发,并且事件对象 e 会传递给处理函数。在事件处理函数中,调用了 setName(e.target.value),也就是 setValue(e.target.value)

4.setValue 的执行:

  • setValue 函数会被调用,value 是输入框的最新值(e.target.value)
  • setStoredValue(value) 更新了 storedValue(即 name),它会触发组件的重新渲染。
  • 同时,window.localStorage.setItem(key, JSON.stringify(value)) 将新的值存储到 localStorage 中,这样即使刷新页面,新的值仍然会被保留。