个人开源项目 | 面试总结

介绍项目

  • 豆瓣FM桌面版:https://github.com/abigaleypc/musicer
  • 该项目主要是一款电豆瓣FM的桌面应用,我将豆瓣FMweb端 (http://douban.fm/) 的功能移植到桌面应用中。该应用使用跨平台框架可兼容Mac和Windows。
  • 架构思路:前后端的架构和选型分析;前后端分离与接入层接口封装,前端与接入层规范对接;数据状态管理;登录态保持与抓包过程。
  • 功能模块:主要模块有当前频道,歌词滚动,微信分享,切换频道,点赞,删除歌词,用户登录等功能。前端主要用了react去组件化各个模块,包括组件之间的数据流通。

    • 当前歌曲,歌词滚动,微信分享这几个模块都需要知道当前歌曲的ID,我会将当前歌曲的一些必要信息存在一个state里面比如songID。切换歌曲模块主要触发songID更改的一个action,当歌曲被更改时,刚上面提到的内容接收到新的状态也会被更新。其中歌词滚动这一模块需要不断监听当前时间并做滚动处理,我的做法类似节流的方式,每隔0.3s就做一个更新,主要是从歌词一开始设定当前时间为0,豆瓣FM获取到的歌曲它是有带每句歌词对应的时间,我是将我们设定的时间不停累加,插在处于对应歌词之间的地方,如果0.3s之后还在当前两句歌词范围内就不滚动。

      面试过程中提到歌词滚动的优化,可在每获取一句歌词时再读取一次下一句歌词的时间,再将settimeout的时间戳设为两句歌词的间隔时间。

    • 点赞和删除歌曲会检测当前登录态,如果当前未登录,就会直接跳转到登录界面。如有已经登录,就会带上登录态去做一些请求

    • 登录态,在调用豆瓣FM的所有接口我都没在前端直接调用,而是做了一个NODE的接入层,获取或者修改信息都是通过node接入层再到了豆瓣FM官网。

流程图

状态管理

豆瓣FM桌面版采用redux做状态管理。Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

三个原则

  • 单一数据源
    整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
  • State 是只读的
    惟一改变 state 的方法就是触发 action:action 是一个用于描述已发生事件的普通对象。视图和网络请求都不能直接修改 state,它们只能表达想要修改的意图。所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。 Action 是普通对象,可以被日志打印,后期调试或测试时回放出来。
  • 使用纯函数reducers来执行修改
    为了描述 action 如何改变 state tree ,需要编写 reducers,reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始可以只有一个 reducer,随着应用变大,可以拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。

数据流

  • store:是保存所有数据的地方,你可以把它看成一个容器。
  • state:Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
  • action: State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
  • reducer: Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

redux

参考: 阮一峰redux教程

Redux分析

redux可以让构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用)
简单清量(只有2kB)且没有任何依赖
由 Flux 演变而来,但受 Elm 的启发,避开了 Flux 的复杂性
做复杂应用和庞大系统时优秀的扩展能力。
可以用 action 追溯应用的每一次修改 (因此才有强大的开发工具。如录制用户会话并回放所有 action 来重现它)
单一的state tree:让同构应用开发变得非常容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。调试也变得非常容易

需要Redux
  • 多交互、多数据源
  • 可以用 action 追溯应用的每一次修改
  • 在React中我们要传递状态特别麻烦,特别是遇到非父子组件的。但redux提供给我们的是全局的状态,所有状态集合store,通知作用的action和执行官reducer。这样很好地减少数据耦合,我们可以站在一个地方观览所有数据。
  • redux把流程规范了,统一渲染根节点虽然对代码管理上规范了一些,只要有需要显示数据的组件,当相关数据更新时都会自动进行更新
抛弃Redux
  • 操作多个数据流,包括异步流时较繁琐
  • 一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。
  • 当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断。
  • 每次添加新状态都需要在store,action,reducer添加新行为,这让代码看起来非常冗余,似乎写了特别多重复性代码,如果追求代码简洁,redux会让你泪奔~~o(>_<)o ~~
  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

项目中如何使用redux?

  • React和Redux分工
    React-Redux将所有组件分为了UI组件和容器组件。
    UI组件不带任何逻辑,只负责渲染,所有的数据都通过this.props提供。(由React负责)
    容器组件负责数据管理和业务逻辑处理。(由Redux负责)
  • Provider
    react-redux提供了Provider组件,用于保存store给子组件中connect使用。
    将它包裹在根组件的最外层,它会将store传递给容器组件。

  • connect()
    connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])的作用是生成容器组件,容器组件将UI组件包装了起来,所有的数据都存放在容器组件上。

  • mapStateToProps(state)
    该方法的作用是:组件将监听store的任何变化,返回需要传递给子组件的state,返回的值将作为属性绑定在容器组件上。之后UI组件就可以通过this.props去读取数据了。

  • mapDispatchToProps(dispatch)
    该方法的作用是:将逻辑处理事件传递给子组件,返回一个dispatchProps对象,该对象是action和dispatch的一个组合。返回的对象将被绑定在UI组件上,返回结果类似如下结构:

    1
    2
    3
    {
    addItem: (text) => dispatch(action)
    }
  • mergeProps(): [mergeProps(stateProps, dispatchProps, ownProps): props] (Function), 如果指定了这个参数,mapStateToProps()与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。

参考: React-Redux学习

登录态 与 接入层

  • 用户未登录的情况下 输入用户名密码

    接入层模仿浏览器(User-Agent)访问豆瓣FM,并拦截它的重定向,模仿浏览器再次访问多个重定向的地址,获取到证明个人身份的多个值和token,加密写入本地文件夹。返回用户登录成功(本地存下token+用户名 + 个人信息)

  • 第二次进入时,访问后端判断token是否失效,未失效就采用前端的个人信息
    失效了或者无token 则登录态为未登录

参考:搭建音乐播放器桌面应用–前端篇 , 扮演服务层角色 – NodeJs

采用Electron

  • 跨平台: 兼容Mac, Windows 和 Linux
  • 浏览器兼容: PC端的前端攻城狮经常需要考虑浏览器的兼容性,而当我们把呈现在页面上的内容转移到应用上时,我们可以直接根据Chrome浏览器编程。
  • 快速上手: 这可以直接省去大量学习成本,只要搭建好基础框架,就可以像写页面一样去写应用。