欢迎来到DIVCSS5查找CSS资料与学习DIV CSS布局技术!
前端在过去很多年压根就没有听说过状态管理这东西,即使在 Angular.js 火热的那几年也很少有人谈前端的状态管理,直到 React 的出现,各种状态管理框架 Flux,Redux,Mobx, ... 层出不穷,让人眼花缭乱。如果你是一个 Angular 的开发者,貌似没有状态管理框架也可以正常的组件化开发,并没有发现缺什么东西。那么在 Angular 中如何优雅的管理前端状态呢?
 
首先前端组件化开发已经变成了标准,对于他的好处和概念网上有很多文章介绍,目前三大框架都是遵循组件化开发的思想,而且组件之间的通信基本都是单向数据流,就是说父组件通过属性绑定把数据传递给子组件,子组件想要修改传入的数据必须通过事件回调和父组件通信,React 中如果组件的层级比较深,同时父组件与很远的一个子组件之间需要共享数据,那就意味着数据会从父组件一层层往下传递,如果底层的组件需要修改数据,必须通过事件层层返回,这对于开发来说基本是灾难,代码变得难以维护,记得听说过一句很有哲学的话:任何解决不了的问题都可以引入一个第三方去解决,没错, 引入一个第三方存放维护这些状态,组件直接读取第三方把需要的状态展示在视图上,那么怎么样合理的设计这个第三方呢,那么 Flux,Redux,Mobx 这些状态管理类库基本都是所谓的第三方。
 
那么在 Angular 中为啥不是必须要状态管理框架呢?
 
首先在 Angular 中有个 Service的概念,虽然 Angular 对于 Service 基本上什么都没有做,连一个基类 BaseService 都没有提供,但是以下2个特性决定了在 Angular 中会很轻松的通过 Service 实现一个上述的第三方。
 
Angular 中定义了一个 Service 后可以通过依赖注入很轻松的把这个服务注入到组件中,这样组件就可以调用 Service 提供的各种方法;
 
我们可以把组件需要的状态数据存储在 Service 中,然后把注入的 Service 设成 public,这样在模版中可以直接通过表达式绑定 Service 中的数据 。
 
基于以上 2 个特性,基本上在使用 Angular 开发应用时一旦遇到组件之间共享数据,都可以使用 Service 轻松应对(当然做一个 SPA 单页应用,即使组件之间没有共享数据,也建议 使用 Service 作为数据层,统一维护业务逻辑),官方提供的英雄编辑器示例 MessageService,就是直接公开服务在组件模版上绑定的,代码如下,所以 Angular 不像 React 那样必须完全依赖状态管理框架才可以做组件之间的数据共享。:
 
export class MessageService {
 
  messages: string[] = [];
 
  add(message: string) {
 
    this.messages.push(message);
 
  }
 
  clear() {
 
    this.messages = [];
 
  }
 
}
 
@Component({
 
  selector: 'app-messages',
 
  template: ——
 
<div *ngIf="messageService.messages.length">
 
  <h2>Messages</h2>
 
  <button class="clear"
 
          (click)="messageService.clear()">clear</button>
 
  <div *ngFor='let message of messageService.messages'> {{message}} </div>
 
</div>
 
——
 
})
 
export class AppMessagesComponent implements OnInit {
 
  constructor(public messageService: MessageService) { }
 
  ngOnInit() {
 
  }
 
}
 
那么在 Angular 中使用 Service 做状态管理会遇到哪些问题呢,如果只是很简单的状态通过 Service 直接管理肯定没有任何问题,但是一旦 Service 存储的状态与每个组件需要展示的状态不一致就很难处理了。比如下图是我们经常遇到的场景,首先项目中会有很多自定义的视图,默认只展示 2 个视图,其余的视图在更多视图中。
 
我们可以很简单把所有的视图列表存放在 ViewService中, 针对视图的增删改逻辑都移动到 ViewService中, 伪代码如下,但是有个问题就是导航条组件和更多视图组件两个组件展示的视图数据不一样,需要把视图列表进行分割,导航条只展示 2 个视图,其余的在更多视图中。
 
class ViewService {
 
    views: ViewInfo[];
 
    addView(view: ViewInfo) {
 
        // 调用 API
 
        this.views.push(view);
 
    }
 
    updateView(view: ViewInfo) {
 
    }
 
    removeView(view: ViewInfo) {
 
    }
 
}
 
此时要想解决这个问题怎么办?我能想到快速解决的有两种方式
 
在 ViewService 除了存储所有的 views 外单独存储导航条的 2 个视图 toolbarShowViews 和更多视图 moreViews,这么做的缺点就是每次增删改视图后都需要重新计算这2个数组,Service 中的状态会增多,如果有一天需求变了,所有的视图直接显示,显示不下换行,那还得回过头来修改 ViewSevice 中的代码,这本来是应该是导航条和更多视图组件的状态,现在必须和全局的视图状态放在了一起,虽然可以解决问题,但是不完美;
 
还有一种更恶心的做法就是在导航条组件模版上循环所有视图,根据 index 只取前 2 个展示,更多组件模版循环所有视图只展示后面的视图,这种做法缺点是把逻辑代码放到了视图中,如果有更复杂的场景通过模版表达式未必可以做到,其二是循环了一些不需要的数据或许在某些场景下有性能损耗,至于示例中的那几个视图肯定没有性能问题。
 
那么除了上述 2 中解决方式外还有更优雅更好的方式么?答案就是 Observable( 可被订阅的对象) ,当然 Angular 框架本身就是依赖 RxJS 的,官方提供的 HttpClient Router 提供的 API 返回的都是 Observable对象。
 
回到这个例子上来,我们可以把 ViewService 中的 views 改成 BehaviorSubject<ViewInfo[]>,BehaviorSubject 对象既可以被订阅,又可以广播,同时还可以存储最后一次的数据, 操作数据后通过 views$.next(newViews) 广播出去,然后在导航条组件中订阅 views$ 流只取前 2 个视图,更多视图菜单组件订阅取后面的视图,如果还有其他组件显示所有的视图可以直接订阅视图列表流 viewService.views$ | async 显示所有视图。

如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h63609.shtml