作为一个开发人员, 工作中需要经常进行 host 切换, 快速在不同环境中进行开发测试, 阿里内部有个 iHost, 用起来简单顺手, 可惜并没有开放, 离开后没有找到一款更好的用的 host 切换工具, 索性自己写一个. 项目已经开源
代码地址 https://github.com/nanjixiong218/MyHost , 欢迎吐槽.
所用到的技术
因为是桌面应用, 作为一个前端开发, 目前的首选自然是 eletron, 前端框架选择 react 技术栈.
项目基于 electron-react0-boilerplate https://github.com/chentsulin/electron-react-boilerplate 生成, 用到技术包括:
- react 16.x
- redux 4.x
- redux-thunk 2.x
- react-router 4.x
- ant-design 3.x
- reselect 3.x
- redux-actions 3.x
- webpack 4.x
- node-sass 4.x
主要逻辑和演示
整个应用的难度其实不大, 主要就是生成一些条目, 基于条目的激活状态对 host 文件进行读写, 基本操作如下:
功能设计
三个 tab 页
host 设置页: 进行 host 条目的增删改查
当前生效的 host: 查看系统当前生效的 host
原始 host: 启动应用后会备份一份原始 host 稿件, 用于还原
host 设置
能够创建一个 host 条目, 编写对应的 host
可以对 host 进行增删改查
同时可以创建一个 host 分组进行统一管理
点击左侧的 checkbox, 编辑激活状态的 host 设置都会实时改变系统 host, 不过在编辑时设置了一个 2 秒的延迟, 避免在不断的输入过程中频繁读写文件
整个应用功能就是这么简单, 一个关键点就是权限问题, 要操作 host 文件需要改变 / private/etc/host 文件的和 / private/etc 目录的权限. 所以在应用启动时会弹出输入电脑用户密码的弹窗进行授权.
记录几个主要技术使用
redux-actions 和 bindActionCreators
基于 redux 的项目有一个经常被诟病的地方在于 action, reducer 的繁琐, 模板代码太多. https://github.com/redux-utilities/redux-actions 是一个不错的工具用来解决此问题, 具体用法请自行学习. 不过值得注意的一点是, redux 的 bindActionCreators 不支持对象嵌套的 actionCreator, 例如
- const actionsCreators = {
- add: function() {
- xx
- },
- change: {
- changeTitle: function() {
- xx
- },
- changeDate: function() {
- xx
- }
- }
- }
- const { add, change } = bindActionCreators(actionsCreator, dispatch)
复制代码
这里 change 中的 actionCreators 是不会绑定成功的, 由于 redux-actions 生成的 actionCreators 是可以嵌套, 所以原始的 bindActionCreators 和 redux-actions 配合起来很不舒服. 项目中稍微改写了一下 https://github.com/nanjixiong218/MyHost/blob/master/app/utils/bindActionCreators.js 使其支持嵌套.
https://github.com/reduxjs/reselect
对于一些基于计算性的数据, 同时把原始数据和计算数据存储在 store 上不是一个好的方案, 每次更新要进行双重更新, 很容易产遗漏, redux 不同于 mobx, mobx 存在 computed 装饰器用于解决计算属性问题. 这个时候 https://github.com/reduxjs/reselect 就派上了用场, 不仅解决了计算性数据问题, 它更大的价值在于能够减少重复计算, 每次 rerender 对于大量数据的计算可以在入参不变的情况下不重复计算. 不过问题就是会多出一层 selectors 层, 可能也会让某些人抓狂吧, 嘎嘎~~
state 持久化存储
为了每次打开 App 的时候保持之前的状态, 整个 store 层需要存储下来, 因此借助 https://github.com/electron-userland/electron-json-storage 写了一个简单 redux 中间件:
- import storage from 'electron-json-storage';
- // 同步 store 到某个文件的持久化存储中间件, 基于 electron-json-storage 做存储
- const storageState = store => dispatch => action => {
- dispatch(action);
- const nextState = store.getState();
- const { menuTree, checkedKeys } = nextState.host.menus;
- const { systemHost } = nextState.host.systemHost;
- const defaultDataPath = storage.getDefaultDataPath();
- const dataPath = storage.getDataPath();
- console.log('paths:', defaultDataPath, dataPath);
- storage.set('HostState-xu', nextState); // 会创建一个 HostState-xu.json 的文件, 尽可能保证唯一性, 不与其他应用冲突
- };
- export default storageState;
复制代码
每一次 action 改变都同步保持 state.
https://github.com/chentsulin/electron-react-boilerplate 带来的坑点
flow 和 eslint
Flow https://github.com/facebook/flow 和一些 https://github.com/eslint/eslint 规则会在前期带来很多困扰和额外的工作, 所以可以自行斟酌修改一些规则, 不过对于 Flow 和 eslint 个人还是建议多多使用的, 会减少很多后期的小问题.
但是项目中的 eslint-import 插件在结合 webpack 设置 alias 的时候会出现问题, 同样对于测试用例, webpack 的 alias 配置也不会生效.
几个处理关键点:
.eslintrc 需要改成. eslintrc.js
.eslintrc.js 中引用 path 需要 require, 不可以 import
改成. eslintrc.js 在 vscode 中会出现 Eslint server 挂掉的问题, 主要是因为项目中
webpack.config.render.dev.js
中自己进行了环境变量的检测
CheckNodeEnv('development')
, 注释掉此行代码.
https://github.com/chentsulin/electron-react-boilerplate 在不断地更新, 最新版的 eslint 配置可能已经和我当时不一致了, 一些处理经验也有可能过时了, 列出一些参考 issue: #620 https://github.com/chentsulin/electron-react-boilerplate/issues/620 ,#1321 https://github.com/chentsulin/electron-react-boilerplate/issues/1321 , #1509 https://github.com/chentsulin/electron-react-boilerplate/issues/1509
图片的引用
引用图片的路径在打包后总是有问题, 官方建议在 render 进程中通过 require 引入图片, 走一遍 loader 生成 base64 的图片, 直接通过 path.join 和 __dirname 生成路径在开发环境没问题, 但是打包后会报错.
然而, 在主进程中要使用图片同样存在问题, 上诉方案并不生效, 目前尚未没有找到解决方案, 已经提了相关 https://github.com/chentsulin/electron-react-boilerplate/issues/1753
貌似 electron 的静态资源路径在开发和生成环境是会有些问题, 这里还需要进一步研究下, 有了解的同学可以指教下.
shell 的 exec
本想使用 https://github.com/shelljs/shelljs 做一些命令处理, 但是发现其有 electron 兼容性问题 electron-compatibility https://github.com/shelljs/shelljs/wiki/Electron-compatibility
因为需求不复杂, 直接用 child_process.exec 进行处理.
一个诡异的打包问题
由于图片引入问题, 我会经常打包验证, 回来发现一个问题, 如果上一次的打包出现问题, 修改后再次进行打包, 发现程序仍然启动不了, 这个时候需要断网启动才会成功. 尚不了解什么原因.
阶段小结
第一次写一个完整的 electron 项目, 用于解决实际需求, 还是挺有意思的, 后续项目还要做一些测试, 持续集成, 自动更新的处理, 目前经验还不足, 各位社区的基友, 请多指教指教, 一起学习!
来源: https://juejin.im/post/5b90ceaa6fb9a05d212e7475