mirror of https://github.com/msgbyte/tailchat
feat: 切换频道时记录最后一次切换到的频道。减少无意义的操作
parent
944d7a7c72
commit
5e00ed8e25
@ -0,0 +1,26 @@
|
||||
import { useRef } from 'react';
|
||||
import type { useEffect, useLayoutEffect } from 'react';
|
||||
|
||||
// Reference: https://github.com/alibaba/hooks/blob/master/packages/hooks/src/createUpdateEffect/index.ts
|
||||
|
||||
type EffectHookType = typeof useEffect | typeof useLayoutEffect;
|
||||
|
||||
export const createUpdateEffect: (hook: EffectHookType) => EffectHookType =
|
||||
(hook) => (effect, deps) => {
|
||||
const isMounted = useRef(false);
|
||||
|
||||
// for react-refresh
|
||||
hook(() => {
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
hook(() => {
|
||||
if (!isMounted.current) {
|
||||
isMounted.current = true;
|
||||
} else {
|
||||
return effect();
|
||||
}
|
||||
}, deps);
|
||||
};
|
@ -0,0 +1,85 @@
|
||||
/* eslint-disable no-empty */
|
||||
import { useState } from 'react';
|
||||
import { useMemoizedFn } from '../useMemoizedFn';
|
||||
import { useUpdateEffect } from '../useUpdateEffect';
|
||||
import _isFunction from 'lodash/isFunction';
|
||||
import _isUndefined from 'lodash/isUndefined';
|
||||
|
||||
export interface IFuncUpdater<T> {
|
||||
(previousState?: T): T;
|
||||
}
|
||||
export interface IFuncStorage {
|
||||
(): Storage;
|
||||
}
|
||||
|
||||
export interface Options<T> {
|
||||
serializer?: (value: T) => string;
|
||||
deserializer?: (value: string) => T;
|
||||
defaultValue?: T | IFuncUpdater<T>;
|
||||
}
|
||||
|
||||
export function createUseStorageState(getStorage: () => Storage | undefined) {
|
||||
function useStorageState<T>(key: string, options?: Options<T>) {
|
||||
let storage: Storage | undefined;
|
||||
|
||||
// https://github.com/alibaba/hooks/issues/800
|
||||
try {
|
||||
storage = getStorage();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
const serializer = (value: T) => {
|
||||
if (options?.serializer) {
|
||||
return options?.serializer(value);
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
};
|
||||
|
||||
const deserializer = (value: string) => {
|
||||
if (options?.deserializer) {
|
||||
return options?.deserializer(value);
|
||||
}
|
||||
return JSON.parse(value);
|
||||
};
|
||||
|
||||
function getStoredValue() {
|
||||
try {
|
||||
const raw = storage?.getItem(key);
|
||||
if (raw) {
|
||||
return deserializer(raw);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (_isFunction(options?.defaultValue)) {
|
||||
return options?.defaultValue();
|
||||
}
|
||||
return options?.defaultValue;
|
||||
}
|
||||
|
||||
const [state, setState] = useState<T>(() => getStoredValue());
|
||||
|
||||
useUpdateEffect(() => {
|
||||
setState(getStoredValue());
|
||||
}, [key]);
|
||||
|
||||
const updateState = (value: T | IFuncUpdater<T>) => {
|
||||
const currentState = _isFunction(value) ? value(state) : value;
|
||||
setState(currentState);
|
||||
|
||||
if (_isUndefined(currentState)) {
|
||||
storage?.removeItem(key);
|
||||
} else {
|
||||
try {
|
||||
storage?.setItem(key, serializer(currentState));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return [state, useMemoizedFn(updateState)] as const;
|
||||
}
|
||||
return useStorageState;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import _isFunction from 'lodash/isFunction';
|
||||
|
||||
// From https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useMemoizedFn/index.ts
|
||||
|
||||
type Noop = (this: any, ...args: any[]) => any;
|
||||
|
||||
type PickFunction<T extends Noop> = (
|
||||
this: ThisParameterType<T>,
|
||||
...args: Parameters<T>
|
||||
) => ReturnType<T>;
|
||||
|
||||
export function useMemoizedFn<T extends Noop>(fn: T) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (!_isFunction(fn)) {
|
||||
console.error(
|
||||
`useMemoizedFn expected parameter is a function, got ${typeof fn}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fnRef = useRef<T>(fn);
|
||||
|
||||
// why not write `fnRef.current = fn`?
|
||||
// https://github.com/alibaba/hooks/issues/728
|
||||
fnRef.current = useMemo(() => fn, [fn]);
|
||||
|
||||
const memoizedFn = useRef<PickFunction<T>>();
|
||||
if (!memoizedFn.current) {
|
||||
memoizedFn.current = function (this, ...args) {
|
||||
return fnRef.current.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
return memoizedFn.current as T;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { createUpdateEffect } from './factory/createUpdateEffect';
|
||||
|
||||
export const useUpdateEffect = createUpdateEffect(useEffect);
|
@ -0,0 +1,3 @@
|
||||
import { createUseStorageState } from 'tailchat-shared';
|
||||
|
||||
export const useLocalStorageState = createUseStorageState(() => localStorage);
|
@ -0,0 +1,5 @@
|
||||
import { createUseStorageState } from 'tailchat-shared';
|
||||
|
||||
export const useSessionStorageState = createUseStorageState(
|
||||
() => sessionStorage
|
||||
);
|
@ -0,0 +1,30 @@
|
||||
import { useSessionStorageState } from './useSessionStorageState';
|
||||
import { useMemoizedFn } from 'tailchat-shared';
|
||||
|
||||
interface UserSessionPerference {
|
||||
/**
|
||||
* 用户最后访问群组的面板id
|
||||
* 用于切换群组时回到最后一个
|
||||
*/
|
||||
groupLastVisitPanel?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户偏好
|
||||
* 用于在本地缓存一些不是那么重要的数据
|
||||
*/
|
||||
export function useUserSessionPreference<T extends keyof UserSessionPerference>(
|
||||
scope: T
|
||||
): [UserSessionPerference[T], (value: UserSessionPerference[T]) => void] {
|
||||
const [preference = {}, setPreference] =
|
||||
useSessionStorageState<UserSessionPerference>('sessionPreference');
|
||||
const value = preference[scope];
|
||||
const setValue = useMemoizedFn((value: UserSessionPerference[T]) => {
|
||||
setPreference({
|
||||
...preference,
|
||||
[scope]: value,
|
||||
});
|
||||
});
|
||||
|
||||
return [value, setValue];
|
||||
}
|
Loading…
Reference in New Issue