Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何使用Electron开发可视化编程工具(二) #3

Open
woxixiulayin opened this issue Apr 9, 2020 · 0 comments
Open

如何使用Electron开发可视化编程工具(二) #3

woxixiulayin opened this issue Apr 9, 2020 · 0 comments

Comments

@woxixiulayin
Copy link
Owner

技术背景介绍和选型思考

接着上一篇继续介绍开发中用到的相关技术

Rxjs的使用

RxJS 是使用 Observables 的响应式编程的库,它使编写异步或基于回调的代码更容易
Z-Factory中主要是在Service类内使用Observables类型的变量,当某些界面或Service依赖该变量时,可以直接监听该变量并使用Rxjs提供的函数式处理方法,也就是响应式编程
Rxjs中文网

理清Rxjs中的几个概念

  • Observable(可观察对象)
    • 一个数据类型,表示一个可调用的未来值或事件的集合,可以通过其上的observable.subscribe方法监听这些值
    • Observables 是使用 Rx.Observable.create 或创建操作符创建的,并使用观察者来订阅它,然后执行它并发送 next / error / complete 通知给观察者,而且执行可能会被清理
    • 还可以通过操作符生成Observable(比较常用):of, from,fromEvent
    • 4个步骤:创建、订阅、执行、清理
  • Observer(观察者)
    • 一组回调函数的集合,每个回调函数对应一种Observable发送的通知类型
    • 也可以传递部分回调函数或者,则其他类型的通知会被会略掉
    • 如果只在observable.subscribe中传递一个回调函数,则observable.subscribe内部会创建一个观察者,并把回调函数作为next的处理方法
  • Subscription (订阅)
    • observable是惰性的,意味着只有在subscribe之后,才会执行
  • Operators(操作符)
    • 函数式编程风格的纯函数:map、filter、scan、debounce和throttle等
    • 处理Observable推送的值
  • 主体
    • 与observable的区别,惰性执行和单一来源
  • 调度器
import { Observable } from 'rxjs'
// 创建observable
var observable = Observable.create(function subscribe(observer) {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  // observer.error('error');
  observer.complete();
});

// 创建观察者:包含3个或部分通知类型的处理函数
var observer = {
  next: x => console.log('Observer got a next value: ' + x),
  error: err => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
}

// 订阅:只有subscribe后,observable内部才会执行
const subscription1 = observable.subscribe(observer)
// 清理:清除订阅方法(React组件退出时用的多)
subscription1.unsubscribe()

var button = document.querySelector('button');
// 常规事件监听
button.addEventListener('click', () => console.log('Clicked!'));
import { fromEvent } from 'rxjs'
// Rxjs创建, fromEvent: 事件对象 + 事件名称
const observable = fromEvent(button, 'click')


const callback = data => console.log(data)

// 内部将callback函数作为观察者的next方法,并忽略其他类型的通知
const subscription1 = observable.subscribe(callback)

当然使用event回调也可以实现响应回调的功能,但是它们有以下几个不同点

  • event需要申明事件类型,Rxjs中只需要申明Observables类型的变量
  • 事件名称不好管理,我们只需直接引用Observables类型的变量
  • 有丰富的函数式操作符,比如filter、merge,可以监听一个源,然后做过滤分发,在与engine的通信中用的比较多
  • 支持链式调用,可以实现流式处理,可以对一个源头的数据进行不同的分流分类处理,比如与engine的通信,底层用一个响应式数据接收数据,业务上可能有不同的处理方式,可以接一个源头数据,进行分流分类处理
  • 监听BehaviorSubject时,可以直接获得上次的状态,而event不行,只能事件触发时才有数据,界面监听数据时,必须一开始就有数据,大部分状态都是基于这个原因使用Rxjs管理状态
  • Rxjs可以基于数组、promise、类事件、单值等创建可观察数据
// 分流处理
 private getServerErrorSubject(): Observable<IConsoleLog> {
    return this.serverAgentService.$serverData.pipe(
      filter((data: IPackage) => (data.action === ServerActions.error)),
      map((data: IPackage<ErrorPayload>) => this.createLog(LogTypes.studioServer, ConsoleLogLevel.error, data.payload)),
    )
  }

  private getServerLogSubject(): Observable<IConsoleLog> {
      return this.serverAgentService.$serverData.pipe(
        filter((data: IPackage) => (data.action === ServerActions.log)),
        map((data: IPackage<LogPayload>) => this.createLog(LogTypes.studioServer, ConsoleLogLevel.info, data.payload))
      )
  }

  private getRunStatusSubject(): Observable<IConsoleLog> {
      return this.serverAgentService.$serverData.pipe(
        filter((data: IPackage) => (data.action === ServerActions.run)),
        map((data: IPackage<RunningPayLoad>) => {
          const { status } = data.payload
          return this.createLog(LogTypes.studioServer, ConsoleLogLevel.info, {
            text: `当前程序状态:${status}`
          })
        })
      )
  }

// 合并流的结果,汇合
  private bindObservable(): Subscription {
    this.$serverError = this.getServerErrorSubject()
    this.$serverLog = this.getServerLogSubject()
    this.$runStatus = this.getRunStatusSubject()

    this._logSubscription = merge(
      this.$serverError,
      this.$serverLog,
      this.$runStatus,
      this.$studioLogSubject,
    ).pipe(
      tap(data => this.log.debug('receive console data', data)),
      // 通过scan累计所有日志
      scan((logList, log) => {
        return [...logList, log]
      }, [])
    ).subscribe(this.$log)
  }

// 在日志窗口组件中监听
......
  useEffect(() => {
    const consoleLogService = useService(ConsoleLogService)
    const subscription = consoleLogService.$log.subscribe(list => {
      logData.current = list
      // 定时器节流
      setIntervalFlag(true)
      if (!consolePanelIsShow) {
        changeConsolePanelIsShow(true)
      }
    })

// 返回清理方法
    return () => subscription.unsubscribe()
  }, [ consolePanelIsShow, changeConsolePanelIsShow ])

状态管理

由状态驱动视图的开发理念越来越普及,复杂应用的开发基本通过管理状态来控制视图。所以在应用初始阶段就应该设计好状态包括状态之间的关联和状态与UI的关系。当然大部分情况下状态管理库已经帮我们做好这些,但是一些复杂的应用还是无法满足,这个时候就需要自己去想解决方案。

整个桌面应用的状态大致包含两部分:界面状态Service状态

  • 界面状态
    • 纯界面端的状态好说,直接由redux开道,通过Dva将界面的model分类,封装model的state、reducer、effects
  • Service状态(内部变量)
    • 我们有大量Service类内需要提供给界面的状态,这部分状态可以直接在model中包装然后提供给界面,但是有几点不太好:
      • 需要多余的样板代码去封装service的内容来生成model,然后connect到组件
      • 有些状态的功能比较专一,不需要共享,只需要一对一传给界面
      • redux内state的变化会触发更新,多余的变化不利于性能
      • 其实Dva中model的概念就包含了状态和状态关系的处理的内容,service也包含这样的能力,所以没有必要绕一层Dva去联通组件,就像Angular中的service包含了逻辑与状态处理
    • 所以我们使用Rxjs定义可观察数据,将需要的数据传递给redux或者直接传给组件,当然这样会失去一些统一性,但更有效率

所以状态到界面的传递有以下三种方法

  • redux ---> ui
  • service ---> redux ---> ui
  • service ---> ui

结合一开始的软件整体结构图,整个应用的逻辑、状态、视图还是比较清晰的。其实我们的核心还是没有变,将界面与逻辑的开发分开,通过状态连接两者,将项目的层次结构分清,便于后续的迭代开发。

下一期继续介绍可视化部分的具体实现

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant