🔔叮~,你有几个系统级交互的React hooks待查收

简介: 🔔叮~,你有几个系统级交互的React hooks待查收

最近摸鱼时间自己手动实现了几个系统级交互的hooks,由简单到复杂,依次分享给大家!

1.监听网络状态

定义

这个hook主要借助了navigator全局属性和offline/online事件监听

import { useEffect, useState } from "react"
export const useNetwork = () => {
  const [state, setState] = useState<boolean>(navigator.onLine)
  useEffect(() => {
    window.addEventListener('offline', () => setState(false))
    window.addEventListener('online', () => setState(true))
    return () => {
      window.removeEventListener('offline', () => setState(false))
      window.removeEventListener('online', () => setState(true))
    }
  }, [])
  return state
}

使用

const onlineState = useNetwork()
return onlineState ? <App /> : <OfflineTip />

类似的方法还可以探索很多有意思的事件属性,例如复制时加版权标识

2.复制加版权标识

定义

import { useEffect } from "react"
export const useCopy = () => {
  useEffect(() => {
    const onCopy = () => navigator.clipboard.readText()
      .then((text) => {
        navigator.clipboard.writeText(text + ': @copyright萌萌哒草头将军')
      })
    addEventListener('copy', () => onCopy())
    return removeEventListener('copy', () => onCopy())
  }, [])
}

使用

useCopy() // 复制:abc
// 粘贴:abc :@copyright萌萌哒草头将军

3.监听窗口大小变化

定义

import { useEffect, useState } from 'react';
export const useResize = () => {
  const [width, setWidth] = useState<number>(() =>
    window.document.body.offsetWidth);
  useEffect(() => {
    window.addEventListener('resize', (e) =>
      setWidth((e?.target as any).innerWidth),
    );
    return () => window.removeEventListener('resize', (e) =>
      setWidth((e?.target as any).innerWidth),
    );
  }, []);
  return width;
};

使用

const width = useResize()
return width > 1200 ? <PcApp /> : width > 720 ? <PadApp /> : <PhoneApp />

优化

为了防止因为频繁触发监听事件导致宽度也频繁变化,这里可以使用上期文章提到的useDeferredValue优化

const [width, setWidth] = useState<number>(() =>
    window.document.body.offsetWidth);
 // 延时更新⏰   
 const newWidth = useDeferredValue(value);
 useEffect(() => {
    window.addEventListener('resize', (e) =>
      setWidth((e?.target as any).innerWidth),
    );
    return () => window.removeEventListener('resize', (e) =>
      setWidth((e?.target as any).innerWidth),
    );
  }, []);
return newWidth;

或者添加防抖函数,不管触发多少次,在一定时间内,只更新最后一次

function debounce(fn, delay) {
  let timer = null;
  return function (...arg) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arg);
    }, delay);
  }
}

然后

const [width, setWidth] = useState<number>(() =>
    window.document.body.offsetWidth);
 useEffect(() => {
    const fn = debounce((e) =>
      setWidth((e?.target as any).innerWidth),
    ), 2000)
    window.addEventListener('resize', (e) => fn(e);
    return () => window.removeEventListener('resize', (e) => fn(e)
 }, []);
 return width;

只要思想不滑坡,方法总比困难多!

4.监听系统主题色变化

定义

import { useEffect, useState } from "react";
export const useTheme = () => {
  const themeMedia = window.matchMedia("(prefers-color-scheme: light)");
  const [value, setValue] = useState<string | null>(themeMedia.matches ? 'light' : 'dark');
  useEffect(() => {
    themeMedia.addEventListener('change', e => 
      setValue(e.matches ? 'light' : 'dark')
    );
    return () => themeMedia.removeEventListener("change", e => 
      setValue(e.matches ? 'light' : 'dark')
    );
  }, []);
  return value;
};

使用

const theme = useTheme()
return (
  <ConfigProvider
    theme={{
      algorithm: theme === 'light'
        ? theme.defaultAlgorithm
        : theme.darkAlgorithm,
    }}
  >
    <App />
  </ConfigProvider>
)

5.监听storage变化

定义

❌错误定义
import { useEffect, useState } from "react";
export const useStorage = () => {
  const [value, setValue] = useState<string | null>();
  useEffect(() => {
    window.addEventListener("storage", (ev) => setValue(ev.newValue));
    return () =>
      window.removeEventListener("storage", (ev) => setValue(ev.newValue));
  }, []);
  return value;
};

有了前面几个hook定义经验,这个不是手到擒来啊。结果一使用根本不起作用,百度原因才发现storage仅仅对同源下的不同页面起作用,作为单页面应用SPA,还得再想办法。

思前想后,在不大动大改的前提下,我重写了window.localStorage下的方法,命名保持一直,这样所有之前使用过的页面只需要引入我定义好的localStorage,同时去掉window.

export const localStorage = {
  getItem: (key: string) => window.localStorage.getItem(key),
  setItem: (key: string, value: any) => window.localStorage.setItem(key, value),
  clear: () => return window.localStorage.clear(),
  removeItem: (key: string) => window.localStorage.removeItem(key),
  key: window.localStorage.key,
  length: window.localStorage.length,
}

接下来让每次的修改、删除、清空都可以被监听到。这里我借助的是前面文章提到的中介者模式,负责监听storage的变化。所以正确的定义方法如下

✔正确定义
import { useState } from "react"
// 中介者
const mediator = (function () {
  let topics: {[key: string]: {callback: (value: any) => void,
    uuid: number}[]} = {},
    uuid = 0;
  function subscribe (topic: string, callback: (value: any) => void) {
      uuid ++
      topics[topic] = topics[topic]
          ? [...topics[topic], { callback, uuid }]
          : [{ callback, uuid }]
  }
  function publish (topic: string, value: any) {
      if (topics[topic]) {
          topics[topic].map(item => item.callback(value))
      }
  }
  return {
      install: function (obj: any) {
          obj.uuid = uuid
          obj.publish = publish
          obj.subscribe = subscribe
          return obj
      } 
  }
})()
// 创建中介者函数
const createMediator = (obj: object) => mediator.install(obj)
// 记录所有监听的key
const keys: string[] = []
// 重新 window.localStorage
export const localStorage = {
  getItem: (key: string) => {
    return window.localStorage.getItem(key)
  },
  setItem: (key: string, value: any) => {
    // 防止重复发布
    if (!keys.includes(key)) keys.push(key)
    const sub = createMediator({})
    // 被修改就发布事件
    sub.publish(key, value)
    return window.localStorage.setItem(key, value)
  },
  clear: () => {
    const sub = createMediator({})
    // 被删除就每个key发布事件
    keys.map(key => sub.publish(key, undefined))
    // 发布后清空记录key的数组
    keys.length = 0
    return window.localStorage.clear()
  },
  removeItem: (key: string) => {
    keys.splice(keys.indexOf(key), 1)
    const sub = createMediator({})
    // 被移除就发布 undefined
    sub.publish(key, undefined)
    return window.localStorage.removeItem(key)
  },
  key: window.localStorage.key,
  length: window.localStorage.length,
}
// 监听key最新变化
export const useStorage = (key: string) => {
  // 默认初始值
  const [value, setValue] = useState<null | string>(window.localStorage.getItem(key))
  const sub = createMediator({})
  // 为指定的key订阅变更事件
  sub.subscribe(key, (value: any) => setValue(value))
  return value
}

使用

import { localStorage, useStorage } from './useStorage.ts'
export const App = () => {
  const random = useStorage('random')
  useEffect(() => console.log(random), [random])
  return (
    <div
      onClick={() =>
        localStorage.setItem('random', Math.random().toString())
      }
    >random: {random} </div>
  )
}

在确定可以监听到的时候,我的心情

好了,今天的分享到这了,如果发现错误,可以联系我,多谢指正~

相关文章
|
12天前
|
前端开发 JavaScript 开发者
深入理解React Hooks:提升前端开发效率的关键
【10月更文挑战第5天】深入理解React Hooks:提升前端开发效率的关键
|
4天前
|
前端开发 JavaScript
React Hooks 全面解析
【10月更文挑战第11天】React Hooks 是 React 16.8 引入的新特性,允许在函数组件中使用状态和其他 React 特性,简化了状态管理和生命周期管理。本文从基础概念入手,详细介绍了 `useState` 和 `useEffect` 的用法,探讨了常见问题和易错点,并提供了代码示例。通过学习本文,你将更好地理解和使用 Hooks,提升开发效率。
17 4
|
7天前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
12天前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具
|
4天前
|
前端开发 JavaScript 开发者
React Hooks
10月更文挑战第13天
12 1
|
11天前
|
前端开发
|
16天前
|
存储 JavaScript 前端开发
|
16天前
|
存储 前端开发 测试技术
React Hooks 的工作原理
【10月更文挑战第1天】
|
9天前
|
前端开发 JavaScript API
自定义React Hooks综合指南
本文介绍了React Hooks及其在组件开发中的作用,重点讲解了自定义Hook的创建和使用方法。通过实例展示了如何创建`useWindowWidth`、`useFetch`和`useForm`等自定义Hook,并分享了使用自定义Hook的最佳实践。文章强调了自定义Hook在提高代码复用性和组件可维护性方面的重要性。
19 0
|
10天前
|
存储 前端开发 JavaScript
深入理解React组件的生命周期与Hooks
【10月更文挑战第7天】深入理解React组件的生命周期与Hooks
36 0