Skip to main content

管理面板 API:Redux 存储与 reducers

🌐 Admin Panel API: Redux store & reducers

Page summary:

register 期间使用 addReducers() 向 Redux 存储添加自定义状态。然后使用 useSelector 读取状态,使用 useDispatch 更新状态,并使用 useStore 订阅更改。admin_app 切片暴露主题、语言环境、权限和认证数据。

🌐 Use addReducers() during register to add custom state to the Redux store. Then read state with useSelector, update it with useDispatch, and subscribe to changes with useStore. The admin_app slice exposes theme, locale, permissions, and authentication data.

Strapi 的管理面板使用全局 Redux 存储来管理应用状态。插件可以访问此存储以读取状态、分发动作以及订阅状态变化。这使得插件能够与核心管理功能进行交互,例如主题设置、语言偏好和身份验证状态。

🌐 Strapi's admin panel uses a global Redux store to manage application state. Plugins can access this store to read state, dispatch actions, and subscribe to state changes. This enables plugins to interact with core admin functionality like theme settings, language preferences, and authentication state.

Prerequisites

在深入了解本页的概念之前,请确保你已经:

🌐 Before diving deeper into the concepts on this page, please ensure you have:

存储概览

🌐 Store overview

Redux 存储通过 React Redux 的 Provider 自动提供给所有插件组件。该存储包含几个切片:

🌐 The Redux store is automatically provided to all plugin components through React Redux's Provider. The store contains several slices:

  • admin_app:核心管理状态,包括主题、语言、权限和身份验证令牌
  • adminApi:管理员端点的 RTK 查询 API 状态
  • 插件特定的切片:插件添加的额外 reducer

添加自定义 reducers

🌐 Adding custom reducers

Reducers 是 Redux 可以用于在组件之间共享状态的 reducers。Reducer 在以下情况下可能有用:

  • 应用中的许多地方都需要大量的应用状态。
  • 应用状态经常更新。
  • 更新该状态的逻辑可能很复杂。

可以在 register 生命周期期间使用 addReducers() 函数将 reducers 添加到插件接口。

🌐 Reducers can be added to a plugin interface with the addReducers() function during the register lifecycle.

使用以下语法将化简器声明为对象:

🌐 A reducer is declared as an object with this syntax:

my-plugin/admin/src/index.js
import { exampleReducer } from './reducers'
import pluginId from './pluginId'

const reducers = {
// Reducer Syntax
[`${pluginId}_exampleReducer`]: exampleReducer
}

export default {
register(app) {
app.addReducers(reducers)
},
bootstrap() {},
};

使用 useSelector 读取状态

🌐 Reading state with useSelector

在你的插件组件中访问 Redux 状态最常见的方式是使用来自 react-reduxuseSelector 钩子:

🌐 The most common way to access Redux state in your plugin components is using the useSelector hook from react-redux:

admin/src/pages/HomePage.jsx
import { useSelector } from 'react-redux';

const HomePage = () => {
// Read current theme
const currentTheme = useSelector(
(state) => state.admin_app?.theme?.currentTheme
);

// Read current locale
const currentLocale = useSelector(
(state) => state.admin_app?.language?.locale
);

// Read authentication status
const isAuthenticated = useSelector((state) => !!state.admin_app?.token);

// Read available locales
const availableLocales = useSelector(
(state) => state.admin_app?.language?.localeNames || {}
);

return (
<div>
<p>Current Theme: {currentTheme}</p>
<p>Current Locale: {currentLocale}</p>
<p>Authenticated: {isAuthenticated ? 'Yes' : 'No'}</p>
</div>
);
};

可用状态属性

🌐 Available state properties

admin_app 切片包含以下状态属性:

🌐 The admin_app slice contains the following state properties: | 属性 | 类型 | 描述 ||---|---|---|| theme.currentTheme | string | 当前主题 ('light''dark''system') || theme.availableThemes | string[] | 可用主题名称数组 || language.locale | string | 当前语言代码(例如,'en''fr') || language.localeNames | object | 将语言代码映射到显示名称的对象 || token | string \| null | 认证令牌 || permissions | object | 用户权限对象 |

派发动作

🌐 Dispatching actions

要更新 Redux 存储,请使用 useDispatch 钩子:

🌐 To update the Redux store, use the useDispatch hook:

Note

下面的示例为了说明用途,将操作分发到核心管理状态(主题、语言环境)。在实际操作中,大多数插件应将操作分发到它们自己的自定义 reducer,而不是修改全局管理状态。

🌐 The examples below dispatch actions to core admin state (theme, locale) for illustration purposes. In practice, most plugins should dispatch actions to their own custom reducers rather than modifying global admin state.

admin/src/pages/HomePage.jsx
import { useSelector, useDispatch } from 'react-redux';

const HomePage = () => {
const dispatch = useDispatch();
const currentTheme = useSelector(
(state) => state.admin_app?.theme?.currentTheme
);

const handleToggleTheme = () => {
const newTheme =
currentTheme === 'light'
? 'dark'
: currentTheme === 'dark'
? 'system'
: 'light';
dispatch({
type: 'admin/setAppTheme',
payload: newTheme,
});
};

const handleChangeLocale = (locale) => {
dispatch({
type: 'admin/setLocale',
payload: locale,
});
};

return (
<div>
<button onClick={handleToggleTheme}>
Toggle Theme (Current: {currentTheme})
</button>
<button onClick={() => handleChangeLocale('en')}>Set English</button>
</div>
);
};

可用操作

🌐 Available actions

admin_app 切片提供以下操作:

🌐 The admin_app slice provides the following actions: | 操作类型 | 载荷类型 | 描述 ||---|---|---|| admin/setAppTheme | string | 设置主题('light''dark''system') || admin/setAvailableThemes | string[] | 更新 admin_app 中的 theme.availableThemes || admin/setLocale | string | 设置语言环境(例如 'en''fr') || admin/setToken | string \| null | 设置认证令牌 || admin/login | { token: string, persist?: boolean } | 使用令牌和持久化选项的登录操作 || admin/logout | void | 登出操作(无载荷) |

Note

在分发操作时,使用 Redux Toolkit 操作类型格式:'sliceName/actionName'。管理员切片名为 'admin',因此操作遵循模式 'admin/actionName'

🌐 When dispatching actions, use the Redux Toolkit action type format: 'sliceName/actionName'. The admin slice is named 'admin', so actions follow the pattern 'admin/actionName'.

访问存储实例

🌐 Accessing the store instance

对于高级用例,你可以使用 useStore 钩子直接访问存储实例:

🌐 For advanced use cases, you can access the store instance directly using the useStore hook:

admin/src/pages/App.jsx
import { useStore } from 'react-redux';
import { useEffect } from 'react';

const App = () => {
const store = useStore();

useEffect(() => {
const state = store.getState();
console.log('Redux Store State:', state);

const unsubscribe = store.subscribe(() => {
const currentState = store.getState();
console.log('Store state changed:', {
theme: currentState.admin_app?.theme?.currentTheme,
locale: currentState.admin_app?.language?.locale,
timestamp: new Date().toISOString(),
});
});

return () => {
unsubscribe();
};
}, [store]);

return <div>My Plugin</div>;
};

完整示例

🌐 Complete example

以下示例结合了本页描述的所有三种模式(useSelector、useDispatch、useStore):

🌐 The following example combines all 3 patterns (useSelector, useDispatch, useStore) described on the present page:

admin/src/pages/HomePage.jsx
import { Main } from '@strapi/design-system';
import { Button, Box, Typography, Flex } from '@strapi/design-system';
import { useSelector, useDispatch, useStore } from 'react-redux';
import { useEffect, useState } from 'react';

const HomePage = () => {
const dispatch = useDispatch();
const store = useStore();

// Reading state
const currentTheme = useSelector(
(state) => state.admin_app?.theme?.currentTheme
);
const currentLocale = useSelector(
(state) => state.admin_app?.language?.locale
);
const isAuthenticated = useSelector((state) => !!state.admin_app?.token);
const availableLocales = useSelector(
(state) => state.admin_app?.language?.localeNames || {}
);

// Dispatching actions
const handleToggleTheme = () => {
const newTheme =
currentTheme === 'light'
? 'dark'
: currentTheme === 'dark'
? 'system'
: 'light';
dispatch({ type: 'admin/setAppTheme', payload: newTheme });
};

const handleChangeLocale = (locale) => {
dispatch({ type: 'admin/setLocale', payload: locale });
};

// Subscribing to store changes
const [storeChangeCount, setStoreChangeCount] = useState(0);
const [lastChange, setLastChange] = useState('');

useEffect(() => {
const unsubscribe = store.subscribe(() => {
setStoreChangeCount((prev) => prev + 1);
setLastChange(new Date().toLocaleTimeString());
});
return () => unsubscribe();
}, [store]);

return (
<Main>
<Box padding={8}>
<Typography variant="alpha" as="h1">
Redux Store Examples
</Typography>

<Flex direction="column" gap={4} paddingTop={6}>
<Box padding={4} background="neutral100" hasRadius>
<Typography variant="omega" fontWeight="bold" paddingBottom={2}>
Reading state
</Typography>
<Flex direction="column" gap={2}>
<Typography variant="omega">
Current Theme: <strong>{currentTheme || 'system'}</strong>
</Typography>
<Typography variant="omega">
Current Locale: <strong>{currentLocale || 'en'}</strong>
</Typography>
<Typography variant="omega">
Authentication Status:{' '}
<strong>
{isAuthenticated ? 'Authenticated' : 'Not Authenticated'}
</strong>
</Typography>
</Flex>
</Box>

<Box padding={4} background="neutral100" hasRadius>
<Typography variant="omega" fontWeight="bold" paddingBottom={2}>
Dispatching actions
</Typography>
<Flex direction="row" gap={2} wrap="wrap">
<Button onClick={handleToggleTheme} variant="secondary">
Toggle Theme
</Button>
{Object.keys(availableLocales).map((locale) => (
<Button
key={locale}
onClick={() => handleChangeLocale(locale)}
variant={currentLocale === locale ? 'default' : 'tertiary'}
>
Set {availableLocales[locale] || locale}
</Button>
))}
</Flex>
</Box>

<Box padding={4} background="neutral100" hasRadius>
<Typography variant="omega" fontWeight="bold" paddingBottom={2}>
Subscribing to store changes
</Typography>
<Flex direction="column" gap={2}>
<Typography variant="omega">
Store has changed <strong>{storeChangeCount}</strong> time(s)
</Typography>
{lastChange && (
<Typography variant="omega">
Last change at: <strong>{lastChange}</strong>
</Typography>
)}
</Flex>
</Box>
</Flex>
</Box>
</Main>
);
};

export { HomePage };

最佳实践

🌐 Best practices

  • 使用 useSelector 来读取状态。 相较于直接访问 store,更推荐使用 useSelector。它会自动订阅更新,并在所选状态变化时重新渲染组件。
  • 清理订阅。 始终在 useEffect 清理函数中取消订阅存储订阅,以防止内存泄漏。
  • 考虑类型安全。 在插件中访问 Redux 状态时,使用带有插件本地类型的 react-redux 钩子(例如 useSelectoruseDispatch)(例如 RootStateAppDispatch)。如果使用 Strapi 管理工具,请从 @strapi/admin/strapi-admin 导入它们(而不是 @strapi/admin)。在它们明确被记录为稳定之前,避免依赖未记录的带类型的 Redux 钩子作为 Strapi 的公共 API。
  • 避免不必要的分发。 只有在需要更新状态时才分发操作。读取状态不需要分发操作。
  • 尊重核心状态。 修改核心管理状态(如主题或语言环境)时要小心,因为这会影响整个管理面板。请考虑你的插件是否应该修改全局状态,还是保持自己的本地状态。
Tip

要将你自己的状态添加到 Redux 存储中,请参阅上面的 添加自定义 reducer

🌐 To add your own state to the Redux store, see Adding custom reducers above.