Skip to content
useLocalStorage
KnowlegeBase 知识库

使用本地存储的 React Hook:useLocalStorage

useLocalStorage 是一个自定义的 React Hook,它允许你在你的 React 应用中使用 Web 浏览器的本地存储(localStorage)。本地存储允许你在用户的浏览器中存储数据,即使用户关闭浏览器或者重启电脑,这些数据也会被保留。

useLocalStorage Hook 接受两个参数:一个键名(key)和一个初始值(initialValue)。它返回一个包含两个元素的数组:一个是存储的值,另一个是一个用于更新这个值的函数。

本知识库要解释的代码如下:

ts
import { useEffect, useState } from 'react'

export const useLocalStorage = <T>(
  key: string,
  initialValue: T
): [T, (value: T) => void] => {
  const [storedValue, setStoredValue] = useState(initialValue)

  useEffect(() => {
    // Retrieve from localStorage
    const item = window.localStorage.getItem(key)
    if (item) {
      setStoredValue(JSON.parse(item))
    }
  }, [key])

  const setValue = (value: T) => {
    // Save state
    setStoredValue(value)
    // Save to localStorage
    window.localStorage.setItem(key, JSON.stringify(value))
  }
  return [storedValue, setValue]
}

代码来源:https://github.com/vercel/ai-chatbot/blob/main/lib/hooks/use-local-storage.ts

其他相关代码:usehooks-ts https://usehooks-ts.com/react-hook/use-local-storage

函数定义

useLocalStorage 的定义如下:

ts
export const useLocalStorage = <T>(
  key: string,
  initialValue: T
): [T, (value: T) => void] => {
  // ...
}

在这个定义中,<T> 是一个泛型参数,表示存储的值的类型。key 是一个字符串,表示在本地存储中存储数据的键名。initialValue 是类型为 T 的初始值。

函数的返回类型是一个元组(tuple),包含两个元素:一个类型为 T 的值,和一个接受类型为 T 的参数并且没有返回值的函数。

在 TypeScript 中,元组(tuple)是一种特殊的数组,它有固定数量的元素,每个元素的类型都是已知的,而且元素的类型不必相同。

输入参数、输出、=>

这是一个名为 useLocalStorage 的 TypeScript 函数的定义,它使用了泛型 T

输入参数:

  1. key: string:这是一个字符串,表示要在本地存储(localStorage)中存储数据的键名。

  2. initialValue: T:这是一个类型为 T 的值,表示如果在本地存储中没有找到对应的键,应该返回的初始值。

输出:

这个函数返回一个元组(tuple),包含两个元素:

  1. 第一个元素的类型为 T,表示从本地存储中获取的值。如果在本地存储中没有找到对应的键,这个值会是 initialValue

  2. 第二个元素是一个函数,它接受一个类型为 T 的参数,没有返回值(void)。这个函数用于更新存储在本地存储中的值。

所以,你可以这样使用 useLocalStorage 函数:

typescript
const [myValue, setMyValue] = useLocalStorage<number>('myKey', 0);

在这个例子中,myValue 是从本地存储中获取的值(或者是初始值 0),setMyValue 是一个函数,用于更新这个值。

箭头函数的基本语法是:

javascript
const myFunction = (parameters) => {
  // function body
}

如果箭头函数只有一个参数,你可以省略括号:

javascript
const myFunction = parameter => {
  // function body
}

如果箭头函数的函数体只有一条语句,你可以省略大括号,并且这条语句的结果会被自动返回:

javascript
const myFunction = parameter => parameter * 2;

泛型参数 <T>

TypeScript docs: https://www.typescriptlang.org/docs/handbook/2/generics.html

在 TypeScript 中,<T> 是一个泛型参数的声明。泛型是一种创建可重用组件的方式,这些组件可以适用于多种类型。在这种情况下,T 是一个类型变量,它代表任何类型。

useLocalStorage 函数中,<T> 允许你指定存储在本地存储中的值的类型。例如,

  • 你可以使用 useLocalStorage<number> 来创建一个存储数字的 Hook,
  • 或者使用 useLocalStorage<string> 来创建一个存储字符串的 Hook。

这样,TypeScript 就可以在编译时检查你的代码,确保你正确地使用了本地存储的值。例如,如果你使用 useLocalStorage<number>,然后试图将一个字符串存储到本地存储中,TypeScript 就会给出一个错误。

Hook 实现

首先,它使用 useState 创建一个状态变量 storedValue 和一个更新这个状态的函数 setStoredValue

然后,它使用 useEffect 在组件挂载时从本地存储中获取值。它接受一个函数和一个依赖数组作为参数。当依赖数组中的值改变时,这个函数会被执行。在这个例子中,依赖数组只包含 key,所以当 key 改变时,这个函数会被执行。

useEffect 的函数中,它首先尝试从本地存储中获取键为 key 的值。如果找到了这个值,它会使用 JSON.parse 将这个值从一个 JSON 字符串转换为 JavaScript 值,并使用 setStoredValue 更新状态。

最后,定义了一个 setValue 函数,用于更新状态和本地存储中的值。这个函数接受一个新的值作为参数,使用 setStoredValue 更新状态,然后使用 window.localStorage.setItem 将新的值存储到本地存储中。

返回值是一个包含 storedValuesetValue 的数组。

如何使用这一 Hook

你可以这样使用 useLocalStorage

ts
const [myValue, setMyValue] = useLocalStorage<number>('myKey', 0);

在这个例子中,myValue 是从本地存储中获取的值(或者是初始值 0),setMyValue 是一个函数,用于更新这个值。

实际使用示例:

ts
const [_, setNewChatId] = useLocalStorage('newChatId', id)

附:TypeScript 的泛型

https://www.typescriptlang.org/docs/handbook/2/generics.html

TypeScript 是 JavaScript 的一个超集,它添加了静态类型检查的功能,使得开发者能够在编译阶段就捕获到潜在的错误。泛型(Generics)是 TypeScript 中一个强大的特性,它允许开发者编写可重用的组件,这些组件能够与多种类型一起工作,而不是仅限于单一的类型。

泛型通过类型参数化,使得函数、类或接口能够适用于多种数据类型。这类似于编程中的多态性,但泛型是在编译时而非运行时确定具体类型。泛型的主要优势在于代码的复用性和类型安全。

泛型函数

泛型函数是泛型最常见的使用场景。例如,一个简单的泛型函数 identity,它接受一个参数并返回相同的值。在不使用泛型的情况下,我们可能需要为函数指定一个具体的类型,如 numberstring。但是,使用泛型后,我们可以创建一个可以处理任何类型的 identity 函数。

ts
function identity<Type>(arg: Type): Type {
  return arg;
}

在这个例子中,Type 是一个类型变量,它代表了任何可能的类型。当我们调用这个函数时,可以显式地指定类型参数,或者让 TypeScript 编译器根据传入的参数自动推断类型。

泛型参数默认值

TypeScript 允许为泛型参数指定默认值,这样在调用泛型函数或构造泛型类时,就可以省略对应的类型参数。

ts
declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>
  (element?: T, children?: U): Container<T, U>;

在这个例子中,T 有一个默认值 HTMLDivElement,而 U 的默认值是 T[]

进一步讨论与优化

try...catch

ts
import { Dispatch, SetStateAction, useEffect, useState } from 'react';

export const useLocalStorage = <T>(
  key: string,
  defaultValue: T
): [T, Dispatch<SetStateAction<T>>] => {
  const [value, setValue] = useState<T>(defaultValue);

  useEffect(() => {
    try {
      const item = window.localStorage.getItem(key);
      if (item) {
        setValue(JSON.parse(item));
      }
    } catch (error) {
      console.log(error);
    }
  }, [key]);

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
};

Dispatch<SetStateAction<T>>

在React中,Dispatch<SetStateAction<T>>这样的写法是为了明确指定useState hook中setter函数的类型。让我们来解释一下这个写法的含义:

  • Dispatch<A>:Dispatch是一个泛型类型,它接受一个类型参数A,表示一个dispatch函数,用于接收一个action并执行相应的操作。在这里,Dispatch<SetStateAction<T>>表示一个dispatch函数,用于接收一个SetStateAction类型的action,用于更新状态。
  • SetStateAction<S>:SetStateAction是一个泛型类型,它接受一个类型参数S,表示可以用来更新状态的动作。它可以是一个新的状态值S,也可以是一个函数(prevState: S) => S,用于根据先前的状态计算新的状态。在这里,SetStateAction<T>表示可以更新类型为T的状态的动作。

因此,这样的写法可以确保useState hook中的setter函数接受的是一个符合SetStateAction<T>类型的动作,从而保证状态更新的正确性和类型安全性。

在React中,使用这样的写法可以带来以下优势:

  1. 类型安全性:通过明确定义setter函数的类型为Dispatch<SetStateAction<T>>,可以在编译时捕获潜在的类型错误。这样可以避免意外传递错误类型的值给setter函数,提高代码的可靠性。
  2. 代码自文档化:可以让其他开发人员更容易地理解代码。通过类型定义,可以清晰地了解setter函数的期望参数类型,从而提高代码的可读性和可维护性。
  3. 智能提示:IDE可以提供更准确的智能提示和自动补全功能。这有助于开发人员更快地编写代码,并减少潜在的错误。

总之,这样的类型可以提高代码的可靠性、可读性和开发效率,是在React中推荐的最佳实践之一。

(本页部分内容由 AI 辅助生成,仅供参考,使用前请仔细鉴别。)

最新更新:

Alang.AI - Make Great AI Applications