Skip to content

单一数据源原则

保持数据只有一份来源(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 记住 "一份来源、引用传递、避免分散"