feat: 切换频道时记录最后一次切换到的频道。减少无意义的操作

pull/81/head
moonrailgun 3 years ago
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);

@ -47,6 +47,7 @@ export { Trans } from './i18n/Trans';
export { useLanguage } from './i18n/language';
// hooks
export { createUseStorageState } from './hooks/factory/createUseStorageState';
export { useAvailableServices } from './hooks/model/useAvailableServices';
export { useUsernames } from './hooks/model/useUsernames';
export {
@ -58,6 +59,7 @@ export { useAsyncFn } from './hooks/useAsyncFn';
export { useAsyncRefresh } from './hooks/useAsyncRefresh';
export { useAsyncRequest } from './hooks/useAsyncRequest';
export { useDebounce } from './hooks/useDebounce';
export { useMemoizedFn } from './hooks/useMemoizedFn';
export { useMountedState } from './hooks/useMountedState';
export { usePrevious } from './hooks/usePrevious';
export { useRafState } from './hooks/useRafState';

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { t, useGroupPanelInfo } from 'tailchat-shared';
import _isNil from 'lodash/isNil';
import { MembersPanel } from './MembersPanel';
@ -7,10 +7,26 @@ import { usePanelWindow } from '@/hooks/usePanelWindow';
import { OpenedPanelTip } from '@/components/OpenedPanelTip';
import { IconBtn } from '@/components/IconBtn';
import {
DMPluginPanelActionProps,
GroupPluginPanelActionProps,
pluginPanelActions,
} from '@/plugin/common';
import { useUserSessionPreference } from '@/hooks/useUserPreference';
/**
* 访id
*/
function useRecordGroupPanel(groupId: string, panelId: string) {
const [lastVisitPanel, setLastVisitPanel] = useUserSessionPreference(
'groupLastVisitPanel'
);
useEffect(() => {
setLastVisitPanel({
...lastVisitPanel,
[groupId]: panelId,
});
}, [groupId, panelId]);
}
/**
*
@ -27,6 +43,7 @@ interface GroupPanelWrapperProps {
export const GroupPanelWrapper: React.FC<GroupPanelWrapperProps> = React.memo(
(props) => {
const panelInfo = useGroupPanelInfo(props.groupId, props.panelId);
useRecordGroupPanel(props.groupId, props.panelId);
if (_isNil(panelInfo)) {
return null;

@ -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];
}

@ -2,7 +2,6 @@ import {
getCachedRegistryPlugins,
getStorage,
PluginManifest,
t,
} from 'tailchat-shared';
import { initMiniStar, loadSinglePlugin } from 'mini-star';
import _once from 'lodash/once';

@ -1,21 +1,42 @@
import React, { useEffect } from 'react';
import { useHistory, useParams } from 'react-router';
import { GroupPanelType, useGroupInfo } from 'tailchat-shared';
import { GroupPanelType, useGroupInfo, useUpdateRef } from 'tailchat-shared';
import _isNil from 'lodash/isNil';
import { useUserSessionPreference } from '@/hooks/useUserPreference';
export const GroupPanelRedirect: React.FC = React.memo(() => {
const { groupId } = useParams<{
groupId: string;
}>();
const history = useHistory();
const [lastVisitPanel] = useUserSessionPreference('groupLastVisitPanel');
const lastVisitPanelRef = useUpdateRef(lastVisitPanel);
const groupInfo = useGroupInfo(groupId);
useEffect(() => {
if (!groupInfo) {
return;
}
if (!Array.isArray(groupInfo?.panels) || groupInfo?.panels.length === 0) {
return;
}
const firstAvailablePanel = groupInfo?.panels.find(
const lastVisitPanelId = lastVisitPanelRef.current?.[groupInfo._id]; // 用户上一次访问
const panels = groupInfo.panels;
/**
*
*
* group
*/
const panelExist = panels.some((p) => p.id === lastVisitPanelId);
if (panelExist) {
history.replace(`/main/group/${groupId}/${lastVisitPanelId}`);
return;
}
const firstAvailablePanel = panels.find(
(panel) => panel.type !== GroupPanelType.GROUP
);
if (!_isNil(firstAvailablePanel)) {

Loading…
Cancel
Save