This commit is contained in:
66
docs/VUE_PERFORMANCE_GUIDE.md
Normal file
66
docs/VUE_PERFORMANCE_GUIDE.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Vue 3 Performance Best Practices
|
||||||
|
|
||||||
|
This document outlines performance optimization strategies for the Vue 3 frontend in the Cultivation World Simulator project.
|
||||||
|
|
||||||
|
## 1. Handling Large Objects (`shallowRef`)
|
||||||
|
|
||||||
|
### Context
|
||||||
|
When receiving large, deeply nested JSON objects from the backend (e.g., full avatar details, game state snapshots), Vue's default reactivity system (`ref` or `reactive`) converts every single property at every level into a Proxy. This process is synchronous and runs on the main thread.
|
||||||
|
|
||||||
|
For complex objects like `AvatarDetail` which may contain lists of relations, materials, and effects, this deep conversion can take significant time (10ms - 100ms+), causing noticeable UI freezes during assignment.
|
||||||
|
|
||||||
|
### Solution: `shallowRef`
|
||||||
|
Use `shallowRef` instead of `ref` for these large, read-only data structures.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { shallowRef } from 'vue';
|
||||||
|
|
||||||
|
// BAD: Deep conversion, slow for large objects
|
||||||
|
const bigData = ref<ComplexType | null>(null);
|
||||||
|
|
||||||
|
// GOOD: Only tracks .value changes, no deep conversion
|
||||||
|
const bigData = shallowRef<ComplexType | null>(null);
|
||||||
|
```
|
||||||
|
|
||||||
|
### When to Use
|
||||||
|
- **Read-Only Display Data**: Data fetched from the API that is primarily for display and not modified field-by-field in the frontend.
|
||||||
|
- **Large Lists/Trees**: Game state, logs, inventory lists, relation graphs.
|
||||||
|
|
||||||
|
### Important Trade-offs
|
||||||
|
With `shallowRef`, **deep mutations do NOT trigger updates**.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const data = shallowRef({ count: 1, nested: { name: 'foo' } });
|
||||||
|
|
||||||
|
// ❌ This will NOT update the UI
|
||||||
|
data.value.count++;
|
||||||
|
data.value.nested.name = 'bar';
|
||||||
|
|
||||||
|
// ✅ This WILL update the UI (replace the entire object)
|
||||||
|
data.value = { ...data.value, count: data.value.count + 1 };
|
||||||
|
// OR assignment from API response
|
||||||
|
data.value = apiResponse;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Usage Example
|
||||||
|
See `web/src/stores/ui.ts`:
|
||||||
|
```typescript
|
||||||
|
// Used for the Info Panel detail data which can be very large
|
||||||
|
const detailData = shallowRef<AvatarDetail | RegionDetail | SectDetail | null>(null);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Component Rendering
|
||||||
|
|
||||||
|
### Virtual Scrolling
|
||||||
|
For lists that can grow indefinitely (e.g., event logs, entity lists), avoid `v-for` rendering all items. Use virtual scrolling (rendering only visible items) to keep the DOM light.
|
||||||
|
|
||||||
|
### Memoization
|
||||||
|
Use `computed` for expensive derived state rather than methods or inline expressions in templates.
|
||||||
|
|
||||||
|
## 3. Reactivity Debugging
|
||||||
|
If UI operations feel sluggish:
|
||||||
|
1. Check the "Scripting" time in Chrome DevTools Performance tab.
|
||||||
|
2. If high, look for large object assignments to `ref` or `reactive`.
|
||||||
|
3. Switch to `shallowRef` if deep reactivity is not strictly required.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref } from 'vue';
|
import { ref, shallowRef } from 'vue';
|
||||||
import { avatarApi } from '../api';
|
import { avatarApi } from '../api';
|
||||||
import type { AvatarDetail, RegionDetail, SectDetail } from '../types/core';
|
import type { AvatarDetail, RegionDetail, SectDetail } from '../types/core';
|
||||||
|
|
||||||
@@ -16,7 +16,8 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
const selectedTarget = ref<Selection | null>(null);
|
const selectedTarget = ref<Selection | null>(null);
|
||||||
|
|
||||||
// 详情数据 (可能为空,或正在加载)
|
// 详情数据 (可能为空,或正在加载)
|
||||||
const detailData = ref<AvatarDetail | RegionDetail | SectDetail | null>(null);
|
// 使用 shallowRef 避免深层响应式转换带来的性能开销 (对于大型嵌套对象,如 AvatarDetail)
|
||||||
|
const detailData = shallowRef<AvatarDetail | RegionDetail | SectDetail | null>(null);
|
||||||
const isLoadingDetail = ref(false);
|
const isLoadingDetail = ref(false);
|
||||||
const detailError = ref<string | null>(null);
|
const detailError = ref<string | null>(null);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user