Appearance
单一数据源原则
保持数据只有一份来源(Source of Truth),通过引用传递,避免多处拷贝导致数据不一致。
1. 单一数据源(SOT)概念
核心原则
数据只有一份来源(Source of Truth)
↓
所有 UI 通过引用访问同一数据
↓
修改数据 → 所有引用自动更新
↓
没有数据不一致为什么需要 SOT?
❌ 没有 SOT(数据分散):
├─ 页面 A 的 userName = '小明'
├─ 页面 B 的 userName = '小红'
├─ 页面 C 的 userName = ''
└─ 用户修改了 A → B 和 C 不知道 → 数据不一致
✅ 有 SOT(集中管理):
├─ AppStorage.currentUser = { name: '小明' }
├─ 页面 A 通过 @StorageLink 读取
├─ 页面 B 通过 @StorageLink 读取
├─ 页面 C 通过 @StorageLink 读取
└─ 修改一处 → 所有引用自动更新 ✅2. SOT 的实现方式
2.1 AppStorage(全局 SOT)
typescript
// 全局数据源
AppStorage.setOrCreate<UserInfo>('currentUser', {
id: '001',
name: '小明',
avatar: $r('app.media.avatar'),
balance: 1000
})
// 任意页面读取(不用转发)
@Component
struct UserProfile {
@StorageProp currentUser: UserInfo = {}
build() {
Text(`余额: ¥${this.currentUser.balance}`)
}
}
// 任意页面修改(自动同步所有引用)
@Component
struct Settings {
@StorageLink currentUser: UserInfo = {}
build() {
Button('修改余额')
.onClick(() => {
// 修改一处,所有引用自动更新
this.currentUser.balance = this.currentUser.balance + 100
})
}
}2.2 @Local(页面级 SOT)
typescript
@Entry
@Component
struct Index {
@Local pageData: PageData = {
items: [],
selectedId: -1,
filter: ''
}
build() {
Column() {
// 所有子组件通过引用访问同一数据
SearchBar({ filter: this.pageData.filter })
ListItem({ item: this.pageData.items[0], selected: this.pageData.selectedId === 0 })
ListItem({ item: this.pageData.items[1], selected: this.pageData.selectedId === 1 })
}
}
}2.3 页面级 Store(推荐模式)
typescript
// store/PageStore.ts
class PageStore {
static instance: PageStore | null = null
static getInstance(): PageStore {
if (!PageStore.instance) {
PageStore.instance = new PageStore()
}
return PageStore.instance
}
// 数据
private _items: Item[] = []
private _selectedId: number = -1
private _filter: string = ''
get items(): Item[] { return this._items }
get selectedId(): number { return this._selectedId }
get filter(): string { return this._filter }
// 修改方法
setFilter(value: string) {
this._filter = value
// 触发 UI 更新
}
selectItem(id: number) {
this._selectedId = id
}
}
export default PageStore.getInstance()3. 常见反模式
3.1 数据分散在多处
typescript
// ❌ 反模式:数据分散
@Component
struct Index {
@State userName: string = '小明'
@State userEmail: string = 'test@test.com'
@State userAge: number = 25
// 每次修改都要分别处理
updateName(name: string) { this.userName = name }
updateEmail(email: string) { this.userEmail = email }
updateAge(age: number) { this.userAge = age }
}
// ✅ 正模式:集中管理
@Component
struct Index {
@State user: UserInfo = {
name: '小明',
email: 'test@test.com',
age: 25
}
updateName(name: string) {
this.user = { ...this.user, name }
}
}3.2 数据在子组件中复制
typescript
// ❌ 反模式:子组件复制数据
@Component
struct Index {
@State user: UserInfo = { name: '小明', age: 25 }
build() {
ChildPage({
userName: this.user.name, // 复制了一份
userAge: this.user.age // 复制了一份
})
}
}
// ✅ 正模式:子组件通过引用访问
@Component
struct Index {
@State user: UserInfo = { name: '小明', age: 25 }
build() {
ChildPage({
user: this.user // 引用访问,不复制
})
}
}
// ChildPage.ts
@Component
struct ChildPage {
@Link user: UserInfo // 引用
build() {
Text(`${this.user.name} (${this.user.age}岁)`)
}
}4. SOT 的实践指南
4.1 数据分层
应用层 SOT(AppStorage)
├── 用户信息
├── 全局配置(主题、语言)
└── 全局状态(登录态)
页面层 SOT(@Local / @State)
├── 页面数据
├── 页面状态
└── 页面交互
组件层 SOT(@State)
├── 组件内部状态
└── 组件交互4.2 状态更新模式
typescript
// ✅ 推荐:通过统一方法更新
class PageStore {
private _items: Item[] = []
addItem(item: Item) {
this._items = [...this._items, item] // 创建新数组
}
removeItem(id: string) {
this._items = this._items.filter(i => i.id !== id)
}
}
// ❌ 不推荐:直接修改引用
this._items.push(item) // 可能不触发 UI 更新5. 面试高频考点
Q1: 什么是单一数据源原则?
回答:保持数据只有一份来源(Source of Truth),通过引用传递,避免多处拷贝导致数据不一致。
Q2: 如何实现 SOT?
回答:AppStorage(全局)、@Local/@State(页面级)、@StorageLink(双向绑定)。所有组件通过引用访问同一数据。
Q3: SOT 的好处?
回答:避免数据不一致、简化状态管理、便于调试和测试。
🐱 小猫提示:SOT 记住 "一份来源、引用传递、避免分散"。