一提到 Angular 状态管理,很多人的第一反应就是:「上 NgRx!」。于是,Action, Reducer, Effect, Selector 等一堆新概念扑面而来,学习曲线陡峭,样板代码繁多,让人望而生畏。仿佛要管理好几个组件间的状态,就必须动用「国家中央银行」级别的武器。

但,杀鸡焉用牛刀?

今天,雪狼想告诉你一个秘密:在 Angular 的世界里,状态管理的「大道至简」之道,就藏在你最熟悉的工具里。在过去,这件神兵是 RxJS 的 BehaviorSubject;在今天,我们又多了一条更直接的「阳光大道」 —— signal。让我们来看看,这两条路,分别通往怎样的风景。

问题的根源:为何不能只用一个普通变量?#

一个天真的想法:我为什么不能在服务里只用一个普通属性来共享状态呢?

// NaiveService.ts
@Injectable({providedIn: 'root'})
export class NaiveService {
    public sharedState: string = '初始状态';
}

问题在于「时间」 。组件 A 在某个时刻更新了 sharedState,组件 B 如何得知这个变化?它总不能用一个 setInterval 每100毫秒就去检查一遍吧?我们需要一种机制,当状态变化时,能主动通知所有关心它的「人」。

经典解法:RxJS BehaviorSubject 的「公告板」模式#

BehaviorSubject 就像一个带「显示屏」的公告板。它永远会记住最近一次的公告,并要求必须有一个初始值。这完美地解决了普通变量的「时间」问题。

我们可以用它来打造一个「简易店铺」。

// user-state.rxjs.service.ts
@Injectable({providedIn: 'root'})
export class UserStateRxjsService {
    // 1. 「私有仓库」:用 BehaviorSubject 来存放我们的「货」(状态)
    private readonly userSource = new BehaviorSubject<User | null>(null);
    // 2. 「公开展柜」:把仓库里的货,通过一个只读的 Observable 管道暴露出去
    readonly user$ = this.userSource.asObservable();
    // 3. 「收银台/入库口」:提供明确的、唯一的入口来修改「库存」
    login(user: User): void {
        this.userSource.next(user);
    }
    // ...
}

消费方式:

// profile.component.ts
@Component({
    standalone: true,
    imports: [AsyncPipe],
    template: `
    @if (user$ | async; as user) {
      <h1>欢迎, {{ user.name }}</h1>
    } @else {
      <p>请登录</p>
    }
  `
})
export class ProfileComponent {
    user$ = inject(UserStateRxjsService).user$;
}

这个模式非常经典,它提供了单一数据源、响应式流和封装好的修改方法。在处理需要与复杂 RxJS 管道(如 debounceTime, switchMap)深度集成的状态时,它依然是最佳选择。

现代解法:signal 的「阳光大道」#

在 Signals 成为 Angular 响应式新基石的今天,对于大多数同步状态共享场景,我们有了一条更短、更直的「阳光大道」。

让我们用 signal 来重建上面的「店铺」。

// user-state.signal.service.ts
@Injectable({providedIn: 'root'})
export class UserStateSignalService {
    // 1. 私有、可写的 signal 作为「仓库」
    private readonly userSource = signal<User | null>(null);
    // 2. 公开、只读的 signal 作为「展柜」
    readonly user = this.userSource.asReadonly();
    // 3. 「收银台/入库口」
    login(user: User): void {
        this.userSource.set(user);
    }
    // ...
}

消费方式:

// profile.component.ts
@Component({
    standalone: true,
    template: `
    @if (user(); as u) {
      <h1>欢迎, {{ u.name }}</h1>
    } @else {
      <p>请登录</p>
    }
  `
})
export class ProfileComponent {
    // 直接注入服务,并获取只读的 signal
    user = inject(UserStateSignalService).user;
}

文生图:一个分叉路口的路牌。左边的路牌指向一条蜿蜒的河流,上面写着“RxJS BehaviorSubject - 异步事件流”。右边的路牌指向一条笔直、阳光明媚的大道,上面写着“Angular Signal - 同步状态”。风格:清晰、友好的卡通风格。

对比见真章:为何说 signal 是「阳光大道」?#

对比两种模式,signal 的优势显而易见:

  1. 心智模型更简单signal 就是一个「会通知别人的值」。你不需要理解 ObservableSubjectSubscription 这些流的概念。它的 API (set, update, computed) 也更直观。

  2. 模板中消费更直接:不再需要 | async 这个「管道工」。直接在模板中像调用函数一样 user() 即可。代码更干净。

  3. 响应式更精细signal 的更新是细粒度的,它只会通知模板中真正依赖它的那个「插值表达式」去更新,而 async 管道默认情况下会标记整个组件需要检查。

结语:如何选择?#

BehaviorSubject 已死,signal 当立」?不!

技术的选择,无关新旧,只关乎「合适」。

  • 当你的状态需要和复杂的异步事件流(如多个 API 请求的编排、用户输入的防抖处理)深度交互时,BehaviorSubject 和整个 RxJS 生态依然是你的「屠龙刀」。

  • 当你的需求是共享同步状态(如 UI 主题、用户信息、表单数据)时,signal 以其简洁、高效和现代的 API,成为了更优的「瑞士军刀」。

在绝大多数场景下,这个「大道至简」的 signal 店铺模式,就是能让你告别状态焦虑,回归开发本源的那颗新一代「定心丸」。

正如老子在《道德经》中所言:「道法自然。」 —— 意指顺应事物本身的规律而运行,不强求,不妄为,技术选择亦应如此,大道至简,适者为上。