今天我們來製作useLocalStorage的custom hook,我們希望他可以像useState一樣製作一個方便控制的localstorage的custom hook
EX:
const [state,setState] = useLocalStorage("key","初始value")
不管今天我們要做測試還是自定義hook,我們都要先思考可能需要哪些功能
const [theme,setTheme] = useLocalStorage("theme","初始value")
function changeToDarkMode(){
setTheme("dark")
}
// theme就變成dark,localStorage.getItem("theme")也存入"dark"
那我們就開始吧
首先寫好useLocalStorage的function,我們需要傳兩個參數,一個是localStorage的key,一個是要傳入的值,首先key一定會是string,所以我們可以先設定key:string ,initialValue 怎麼辦呢?
export function useLocalStorage(key: string, initialValue: ?) {
}
我們這時候有一種選擇是直接用any
export function useLocalStorage(key: string, initialValue: any) {
}
但這時候就有一個顯而易見的缺陷是,它並沒有準確的定義返回值的型別:
any
允許任意型別。但是我們預期的是,陣列中每一項都應該是一開始輸入的 initialValue
的型別
還有~ 如果碰到每個不知道的都用any
了,就乾脆直接用Js寫就好了呀,那怎麼辦呢~
這時候我們就要用到typescript好用的泛型拉~
泛型的意思是說,使用者可以自訂型別,後面也會依照型別判斷,很難懂嗎~,用下面範例解釋吧
export function useLocalStorage(key: string, initialValue: any) {
}
// 使用者就可以這樣用了
const [count,setCount]= useLocalStorage<number>("count",0)
const [input,setInput]= useLocalStorage<string>("input","this is an init input")
// 之後要使用setInput裡面就要使用string,setCount裡面只能放number
之後可能還會用到泛型,這個很重要!!! 要記住喔!!!
接下來,我們要建立初始值了
import { useState, useEffect, useCallback } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
const readInitValue = useCallback(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (err) {
console.log(err);
return initialValue;
}
}, [key, initialValue]);
const [storeState, setStoreState] = useState(readInitValue());
}
接下來我們來寫setLocalStorage吧
因為我們希望他跟平常useState的setState一樣,所以我們也希望可以傳入兩種形式,一種是一般value,一種是useState的updater方式傳入一個function ()⇒{}
我們有以下兩點要注意
value instanceof Function
判斷,如果要使用typeof
也可以,但我更傾向用instanceof
JSON.stringify()
轉換成字串,因為localStorage只能存字串import { useState, useEffect, useCallback } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
function setLocalStorage(value: T | ((prev: T) => T)) {
try {
const valueToStore =
value instanceof Function ? value(storeState) : value;
setStoreState(valueToStore);
localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log("Set LocalStorage Error", error);
}
}
}
剩最後一點點了~~
我們還剩兩件事
storeState
與setLocalStorage
回傳出去import { useState, useEffect, useCallback } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
useEffect(() => {
setLocalStorage(readInitValue());
}, []);
// 在 TypeScript 中,as const 用於表示一個 literal expression 是不可變的(即它是唯讀的)以及為了獲得最精確的類型推導
// 簡單來說,這代表這是一個具有固定長度(2)和固定類型的 tuple。
// index 0 是 storeState 的類型
// index 1 是 setLocalStorage 的類型
// 這個回傳值中不會有第三個或其他的元素
return [storeState, setLocalStorage] as const;
}
import { useState, useEffect, useCallback } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
const readInitValue = useCallback(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (err) {
console.log(err);
return initialValue;
}
}, [key, initialValue]);
const [storeState, setStoreState] = useState(readInitValue());
function setLocalStorage(value: T | ((prev: T) => T)) {
try {
const valueToStore =
value instanceof Function ? value(storeState) : value;
setStoreState(valueToStore);
localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log("Set LocalStorage Error", error);
}
}
useEffect(() => {
setLocalStorage(readInitValue());
}, []);
return [storeState, setLocalStorage] as const;
}
如果不太熟的話,可以用useSessionStorage嘗試看看,我會將useSessionStorage的寫法也放在下面(雖然一模一樣拉…)
明天就要開始寫測試了,想試試看的可以今天先寫寫看,明天對答案喔,這個測試我會用三種寫法來教學,敬請期待喔~
useSessionStorage.ts
import { useState, useEffect, useCallback } from "react";
export function useSessionStorage<T>(key: string, initialValue: T) {
const readInitValue = useCallback(() => {
try {
const item = sessionStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (err) {
console.log(err);
return initialValue;
}
}, [key, initialValue]);
const [storeState, setStoreState] = useState(readInitValue());
function setSessionStorage(value: T | ((prev: T) => T)) {
try {
const valueToStore =
value instanceof Function ? value(storeState) : value;
setStoreState(valueToStore);
sessionStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log("Set sessionStorage Error", error);
}
}
useEffect(() => {
setSessionStorage(readInitValue());
}, []);
return [storeState, setSessionStorage] as const;
}