原文地址:https://zhuanlan.zhihu.com/p/...,欢迎转载 :-)html
其实对于这个专栏的订阅用户感到很是抱歉,已经停更好久了,也没啥特别的理由就是懒 orz!不对,画风不能这样开头,是这样的,我以为我应该用 React 去作点儿什么,写文章可以清晰个人思路,让我和别人有交流,可是并无实际作产品那么性感,因而我决定要用 React 来作一些产品出来,因而就有了 “氢” http://origingroup.tech 相信我在这里对氢的介绍以及实现技术介绍也能对你们有帮助。前端
因此这篇文章我主要想介绍一些作这个东西的思路、想法和一些技术实现以及用到的一些工具。react
这个名字听着就很奇怪,为何是个 H 元素做为名字,对,是这样的,先无论氢是什么东西,个人想法是我要用 React 去作一些产品出来那总得取个名字吧,“一些产品” 意味着我得费好多经历去想产品名称,好了,不如就按照元素周期表的顺序取名字吧,因而“氢”这个名字就这样定了。 :- ) (我都以为我是天才了,hhhhhh ☞ )linux
好了,说正经的,其实作氢这个东西早有预谋,好久好久之前我作了一些产品,而后夭折了,eee...... 因而如今我决定把它们捡起来(感受哪里不对。。)!webpack
其中的一个夭折的是 “一个针对我的的项目管理工具”,本想提供 saas 的服务,结果被我改来改去最后tm很长时间没空理它不玩了,因而最终被改到如今的 “针对我的的笔记,任务,待办离线管理工具” ,因而就有了这个叫氢的家伙(固然以前给它取的名字不叫这个。。)web
提供 workflowy 功能的强大列表,可以将平常的琐碎的事情,用极致简单的交互去作增删改查chrome
核心目标基于任务管理来打造,因此参考了不少任务管理工具的 UX 以及本身的想法-结合 scrum 模型,让它在任务管理上也能简单极致(最开始核心目标太多,致使产品几乎难产,其实最后的氢是一个删减吧,这也让我懂得了作产品须要克制)redux
最后我须要有一个 powerful 的编辑器,由于平时记录事情的时候总的写点儿什么呀,因而任务详情被打形成了一个 wysiwyg 编辑器,而且支持用 markdown 语法,能够直接粘贴 markdown 生成样式,后面还会提供导出 markdown 功能windows
固然得漂亮浏览器
好了,说了辣么多,先上几个图,毕竟有句名言叫-meitushuogejb
第一张图就是我说的 列表 ,具体怎么好用可能还得本身体验过才知道,所见即所得,回车就建立一个任务,tab 一下任务就变为子任务,ctrl + command + up/down
如你所见,上面的这个东西是一个 Desktop app ,具体实现方式是:
Electron + React
最开始的时候只是针对 web 版本的,因此技术全是围绕 React 来,后来决定该为 Desktop app ,固然第一选择是 electron,过程当中也是遇到了很多坑的地方,下面分几个方便来分享一下
React 技术栈的选择
项目结构与 Webpack 打包编译
Electron 相关的使用细节
如何用 Electron 作 i18n
React 技术栈选择
基本的技术使用和我在本专栏中提到的无差,具体为:
ES6 + JSX + Less :做为基本的语言层选择,这套路基本仍是很经常使用的了
Ant.design: 使用 ant.design 做为基础库,这里感谢玉伯大大团队的贡献
Redux + Redux-Saga:如今熟悉了 redux 和 redux-saga 事后很难再改成其余的方式,由于感受这种配套已经很极致了,在处理数据流转已经 UI 交互的时候,redux-saga 几乎是个 magic 的工具
draft.js + draft-plugin-js: 在编辑器的选择上使用了 Facebook 的 draft.js , 若是你愿意详细了解其设计和架构的话也会以为这也是个伟大的项目,同时经过 draft-plugin-js 能够很快的将编辑器功能组件化,很容易经过 hook 定制本身的编辑器,氢中的编辑器有两个,第一个是列表项每一个都是一个编辑器实例,任务详情,定制化的一个富文本编辑器
Immutable.js: 不可变数据,这对于列表来讲真是过重要了,React 若是不进行优化的在特殊状况下会有严重的性能问题,氢种的列表就是这种特殊状况,一编辑某一个任务,处理很差会卡,会抖动。所以我在作列表的时候就决定由 immutable 重构了整个项目,同时列表的数据结构也是作了特殊的设计和优化的。(好比一个小问题,上下移动,如何肯定顺序呢?)
React-intl:氢作了基本的国际化,也就支持英文,固然如今应该有不少语法错误还没检查,使用的是 React-intl。
React-vitualized:这个项目也是为了作性能优化用的,不过如今的版本由于优化了数据结构不须要了
项目的目录结构设计和 webpack 打包编译才是头痛的问题,当 webpack 赶上 electron,各类环境不一致致使的奇怪 bug 我是不会跟别人说的,☝️我的默默的承受。。。
先上个项目结构的图
好长的目录,其中关键的地方是 containers 的设计,项目中 containers 目前的设计是一个 cotainer 包含
reducer.js
sagas.js
index.jsx
styles.less
components
固然图中的项目结构中有专门的 reducers 目录和 sagas 目录,这是由于以前的设计没改,后面的 reducer ,style,sagas 都是组件自包含的。
感谢这个项目帮我解决了很多坑 chentsulin/electron-react-boilerplate 正常状况下的 Electron ,React 项目基本就按照这个 boilerplate 来就好了,不过我本身在打包上作了不少自定义的修改,因此才会出现不少 webpack 的问题。
其中的一个优化点是,不使用 dll ,不管 production 仍是 dev 都会使用 vendors.js ,而这个 vendors 是预先打包压缩好了的,因此每次打包实际都是只打包了业务代码。具体方式是看图就知道了
经过定义 externals 让打包过程忽略这些模块的打包,使用 external 方式引用
专门打包 vendor 的webpack 配置,必定要注意 libraryTarget
这里不想列举太多细节,说说其中的一个,初始化 loading 加载,由于 electron 每次打开都几乎是打开一个浏览器,在执行大的js 上也会花不少时间,因此会出现1s到2s的停顿,显得应用很卡。
氢中解决这个问题的方式使用一个专门负责 loading 的 window ,这个页面极其简单,很快就能够加载出来了,而后这个时候打开主要的 window,当这个window 加载完了再显示出来,再关闭负责 loading 的 window, 下面我直接贴出 main.js 的代码,有须要的能够拿走
app.on('ready', createWindow); function createWindow () { locale = app.getLocale(); landingWindow = new BrowserWindow({ show: false, frame: false, width: 490, height: 400 }) landingWindow.once("show", () => { // Create the browser window. mainWindow = new BrowserWindow({ width: 1000, height: 740, titleBarStyle: 'hidden', icon: `file://${__dirname}/assets/imgs/logo.png`, show: false }) mainWindow.once("show", () => { landingWindow.hide() landingWindow.close() landingWindow.removeAllListeners(); mainWindow.show() landingWindow = null }) mainWindow.webContents.on('did-finish-load', () => { if (!mainWindow) { throw new Error('"mainWindow" is not defined'); } mainWindow.show(); mainWindow.focus(); }); // Emitted when the window is closed. mainWindow.on('closed', function () { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow.removeAllListeners(); mainWindow = null; }) // and load the index.html of the app. mainWindow.loadURL(`file://${__dirname}/app.html`); const menuBuilder = new MenuBuilder(mainWindow); menuBuilder.buildMenu(locale); }) landingWindow.loadURL(`file://${__dirname}/landing.html`) landingWindow.once('ready-to-show', () => { landingWindow.show() }) }
在作国际化版本的时候有两种状况
main.js 主进程的国际化
window 内 renderer 进程的国际化
在 main.js 能够经过
locale = app.getLocale();
来获取当前系统的语言,不过须要注意的是,获取的地方必定要在 app.on('ready') 的注册函数中获取,否则默认会一直取到 “en-US”
在 window 中, 这就真的是前端的天下了
locale = navigator.language
和在 chrome 中无差,能够如上获取语言,而后再经过设置到 react-intl 的 provider 中,接下来就是琐碎的翻译工做了。。。
氢仍是花了我很多时间的,目前没打算 open source, 积累到了必定程度应该会开源,最后说一说作这个东西的收获:
设计和 UX 是很重要的
克制,在作产品上,在设计上必定要克制
linux 哲学,让产品只完成一个功能,不要想太多,想太多了什么都作不成
哪怕仍是个浑身 bug 的东西,也要尽快的推出来,否则你都找不到理由继续作下去