refactor(subscription): Phase 2 - 迁移到 Redux 状态管理
重构目标: 使用 Redux 管理订阅数据,替代本地状态 Phase 2 完成: ✅ 创建 subscriptionSlice.js (143行) - Redux Toolkit createSlice + createAsyncThunk - 管理订阅信息、loading、error、Modal 状态 - fetchSubscriptionInfo 异步 thunk - resetToFree reducer (登出时调用) ✅ 注册到 Redux Store - 添加 subscriptionReducer 到 store ✅ 重构 useSubscription Hook (182行) - 从本地状态迁移到 Redux (useSelector + useDispatch) - 保留所有权限检查逻辑 - 新增: isSubscriptionModalOpen, open/closeSubscriptionModal - 自动加载订阅数据 (登录时) ✅ 重构 HomeNavbar 使用 Redux - 替换 useSubscriptionData → useSubscription - 删除 ./hooks/useSubscriptionData.js 架构优势: ✅ 全局状态共享 - 多组件可访问订阅数据 ✅ Redux DevTools 可调试 ✅ 异步逻辑统一管理 (createAsyncThunk) ✅ 与现有架构一致 (authModalSlice 等) 性能优化: ✅ Redux 状态优化,减少不必要渲染 ✅ useSelector 精确订阅,只在相关数据变化时更新 累计优化: - 原始: 1623行 - Phase 1后: 1573行 (↓ 50行) - Phase 2后: 1533行 (↓ 90行, -5.5%) - 新增 Redux 逻辑: subscriptionSlice (143行) + Hook (182行) 下一步: Phase 3+ 继续拆分组件 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,9 @@ import BrandLogo from './components/BrandLogo';
|
||||
import LoginButton from './components/LoginButton';
|
||||
import CalendarButton from './components/CalendarButton';
|
||||
|
||||
// Phase 2 优化: 使用 Redux 管理订阅数据
|
||||
import { useSubscription } from '../../hooks/useSubscription';
|
||||
|
||||
/** 二级导航栏组件 - 显示当前一级菜单下的所有二级菜单项 */
|
||||
const SecondaryNav = ({ showCompletenessAlert }) => {
|
||||
const navigate = useNavigate();
|
||||
@@ -574,14 +577,13 @@ export default function HomeNavbar() {
|
||||
// 添加标志位:追踪是否已经检查过资料完整性(避免重复请求)
|
||||
const hasCheckedCompleteness = React.useRef(false);
|
||||
|
||||
// 订阅信息状态
|
||||
const [subscriptionInfo, setSubscriptionInfo] = React.useState({
|
||||
type: 'free',
|
||||
status: 'active',
|
||||
days_left: 0,
|
||||
is_active: true
|
||||
});
|
||||
const [isSubscriptionModalOpen, setIsSubscriptionModalOpen] = React.useState(false);
|
||||
// Phase 2: 使用 Redux 订阅数据
|
||||
const {
|
||||
subscriptionInfo,
|
||||
isSubscriptionModalOpen,
|
||||
openSubscriptionModal,
|
||||
closeSubscriptionModal
|
||||
} = useSubscription();
|
||||
|
||||
const loadWatchlistQuotes = useCallback(async () => {
|
||||
try {
|
||||
@@ -790,49 +792,7 @@ export default function HomeNavbar() {
|
||||
}
|
||||
}, [isAuthenticated, userId, checkProfileCompleteness, user]); // ⚡ 使用 userId
|
||||
|
||||
// 加载订阅信息
|
||||
React.useEffect(() => {
|
||||
// ✅ 移除 ref 检查,直接根据登录状态加载
|
||||
if (isAuthenticated && user) {
|
||||
const loadSubscriptionInfo = async () => {
|
||||
try {
|
||||
const base = getApiBase();
|
||||
logger.debug('HomeNavbar', '开始加载订阅信息', { user_id: user?.id });
|
||||
const response = await fetch(base + '/api/subscription/current', {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
logger.debug('HomeNavbar', 'API 返回订阅数据', data);
|
||||
if (data.success && data.data) {
|
||||
// 数据标准化处理:确保type字段是小写的 'free', 'pro', 或 'max'
|
||||
const normalizedData = {
|
||||
type: (data.data.type || data.data.subscription_type || 'free').toLowerCase(),
|
||||
status: data.data.status || 'active',
|
||||
days_left: data.data.days_left || 0,
|
||||
is_active: data.data.is_active !== false,
|
||||
end_date: data.data.end_date || null
|
||||
};
|
||||
logger.info('HomeNavbar', '订阅信息已更新', normalizedData);
|
||||
setSubscriptionInfo(normalizedData);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('HomeNavbar', '加载订阅信息失败', error);
|
||||
}
|
||||
};
|
||||
loadSubscriptionInfo();
|
||||
} else {
|
||||
// 用户未登录时,重置为免费版
|
||||
logger.debug('HomeNavbar', '用户未登录,重置订阅信息为免费版');
|
||||
setSubscriptionInfo({
|
||||
type: 'free',
|
||||
status: 'active',
|
||||
days_left: 0,
|
||||
is_active: true
|
||||
});
|
||||
}
|
||||
}, [isAuthenticated, userId, user]); // ✅ React 会自动去重,不会造成无限循环
|
||||
// Phase 2: 加载订阅信息逻辑已移至 useSubscriptionData Hook
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1127,7 +1087,7 @@ export default function HomeNavbar() {
|
||||
<Box
|
||||
position="relative"
|
||||
cursor="pointer"
|
||||
onClick={() => setIsSubscriptionModalOpen(true)}
|
||||
onClick={openSubscriptionModal}
|
||||
>
|
||||
<CrownIcon subscriptionInfo={subscriptionInfo} />
|
||||
<Avatar
|
||||
@@ -1153,7 +1113,7 @@ export default function HomeNavbar() {
|
||||
{isSubscriptionModalOpen && (
|
||||
<SubscriptionModal
|
||||
isOpen={isSubscriptionModalOpen}
|
||||
onClose={() => setIsSubscriptionModalOpen(false)}
|
||||
onClose={closeSubscriptionModal}
|
||||
subscriptionInfo={subscriptionInfo}
|
||||
/>
|
||||
)}
|
||||
@@ -1193,7 +1153,7 @@ export default function HomeNavbar() {
|
||||
</Box>
|
||||
|
||||
{/* 订阅管理 */}
|
||||
<MenuItem icon={<FaCrown />} onClick={() => setIsSubscriptionModalOpen(true)}>
|
||||
<MenuItem icon={<FaCrown />} onClick={openSubscriptionModal}>
|
||||
<Flex justify="space-between" align="center" w="100%">
|
||||
<Text>订阅管理</Text>
|
||||
<Badge colorScheme={subscriptionInfo.type === 'free' ? 'gray' : 'purple'}>
|
||||
@@ -1206,7 +1166,7 @@ export default function HomeNavbar() {
|
||||
{isSubscriptionModalOpen && (
|
||||
<SubscriptionModal
|
||||
isOpen={isSubscriptionModalOpen}
|
||||
onClose={() => setIsSubscriptionModalOpen(false)}
|
||||
onClose={closeSubscriptionModal}
|
||||
subscriptionInfo={subscriptionInfo}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user