mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 12:27:41 +08:00
Align with the existing @logger alias convention by introducing @application as a short alias for src/main/core/application. This reduces import verbosity across ~130 main-process files while keeping 4 intentional sub-path imports (@main/core/application/Application) unchanged to preserve the vi.mock bypass mechanism in tests. Configured in tsconfig.node.json and electron.vite.config.ts; Vitest inherits the alias automatically. Signed-off-by: fullex <0xfullex@gmail.com>
6.7 KiB
6.7 KiB
Preference Usage Guide
This guide covers how to use the Preference system in React components and services.
React Hooks
usePreference (Single Preference)
import { usePreference } from '@data/hooks/usePreference'
// Basic usage - optimistic updates (default)
const [theme, setTheme] = usePreference('app.theme.mode')
// Update the value
await setTheme('dark')
// With pessimistic updates (wait for confirmation)
const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })
usePreferences (Multiple Preferences)
import { usePreferences } from '@data/hooks/usePreference'
// Read multiple preferences at once
const { theme, language, fontSize } = usePreferences([
'app.theme.mode',
'app.language',
'chat.message.font_size'
])
Update Strategies
Optimistic Updates (Default)
UI updates immediately, then syncs to database. Automatic rollback on failure.
const [theme, setTheme] = usePreference('app.theme.mode')
const handleThemeChange = async (newTheme: string) => {
try {
await setTheme(newTheme) // UI updates immediately
} catch (error) {
// UI automatically rolls back
console.error('Theme update failed:', error)
}
}
Best for:
- Frequent changes (theme, font size)
- Non-critical settings
- Better perceived performance
Pessimistic Updates
Waits for database confirmation before updating UI.
const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })
const handleApiKeyChange = async (newKey: string) => {
try {
await setApiKey(newKey) // Waits for DB confirmation
toast.success('API key saved')
} catch (error) {
toast.error('Failed to save API key')
}
}
Best for:
- Security-sensitive settings (API keys, passwords)
- Settings that affect external services
- When confirmation feedback is important
PreferenceService Direct Usage
For non-React code or batch operations.
Renderer Process
import { preferenceService } from '@data/PreferenceService'
// Get single preference
const theme = await preferenceService.get('app.theme.mode')
// Get multiple preferences
const settings = await preferenceService.getMultiple([
'app.theme.mode',
'app.language'
])
// Returns: { 'app.theme.mode': 'dark', 'app.language': 'en' }
// Get with default value
const fontSize = await preferenceService.get('chat.message.font_size') ?? 14
Set Preferences
// Set single preference (optimistic by default)
await preferenceService.set('app.theme.mode', 'dark')
// Set with pessimistic update
await preferenceService.set('api.key', 'secret', { optimistic: false })
// Set multiple preferences at once
await preferenceService.setMultiple({
'app.theme.mode': 'dark',
'app.language': 'en',
'chat.message.font_size': 16
})
Subscribe to Changes
// Subscribe to preference changes (useful in services)
const unsubscribe = preferenceService.subscribe('app.theme.mode', (newValue) => {
logger.info('Theme changed to:', { theme: newValue })
})
// Cleanup when done
unsubscribe()
Main Process
In the main process, PreferenceService is lifecycle-managed. Access it via application.get():
import { application } from '@application'
const preferenceService = application.get('PreferenceService')
// Get/set preferences
const theme = preferenceService.get('app.theme.mode')
preferenceService.set('app.theme.mode', 'dark')
// Subscribe to changes
const unsubscribe = preferenceService.subscribeChange('app.theme.mode', (newValue) => {
logger.info('Theme changed to:', { theme: newValue })
})
Note: Do NOT import PreferenceService directly in main process code. The renderer
@data/PreferenceServiceis a separate singleton for the renderer process only.
Common Patterns
Settings Form
function SettingsForm() {
const [theme, setTheme] = usePreference('app.theme.mode')
const [language, setLanguage] = usePreference('app.language')
const [fontSize, setFontSize] = usePreference('chat.message.font_size')
return (
<form>
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
<select value={language} onChange={e => setLanguage(e.target.value)}>
<option value="en">English</option>
<option value="zh">中文</option>
</select>
<input
type="number"
value={fontSize}
onChange={e => setFontSize(Number(e.target.value))}
min={12}
max={24}
/>
</form>
)
}
Feature Toggle
function ChatMessage({ message }) {
const [showTimestamp] = usePreference('chat.display.show_timestamp')
return (
<div className="message">
<p>{message.content}</p>
{showTimestamp && <span className="timestamp">{message.createdAt}</span>}
</div>
)
}
Conditional Rendering Based on Settings
function App() {
const [theme] = usePreference('app.theme.mode')
const [sidebarPosition] = usePreference('app.sidebar.position')
return (
<div className={`app theme-${theme}`}>
{sidebarPosition === 'left' && <Sidebar />}
<MainContent />
{sidebarPosition === 'right' && <Sidebar />}
</div>
)
}
Batch Settings Update
async function resetToDefaults() {
await preferenceService.setMultiple({
'app.theme.mode': 'system',
'app.language': 'en',
'chat.message.font_size': 14,
'chat.display.show_timestamp': true
})
}
Adding New Preference Keys
For a complete guide on adding new preferences, including naming conventions and design principles, see Preference Schema Guide.
Quick summary:
- Add custom types to
preferenceTypes.ts(if needed) - Add key to
PreferenceSchemasinterface inpreferenceSchemas.ts - Add default value to
DefaultPreferences - Use in code with full type safety
Best Practices
- Choose update strategy wisely: Optimistic for UX, pessimistic for critical settings
- Batch related updates: Use
setMultiplewhen changing multiple related settings - Provide sensible defaults: All preferences should have default values
- Keep values atomic: One preference = one logical setting
- Use consistent naming: Follow
domain.feature.settingpattern
Preference vs Other Storage
| Scenario | Use |
|---|---|
| User theme preference | usePreference('app.theme.mode') |
| Window position | usePersistCache (can be lost without impact) |
| API key | usePreference with pessimistic updates |
| Search history | usePersistCache (nice to have) |
| Conversation history | DataApiService (business data) |