原文地址:https://zhuanlan.zhihu.com/p/...,歡迎轉載 :-)
? 關於
其實對於這個專欄的訂閲用户感到非常抱歉,已經停更很久了,也沒啥特別的理由就是懶 orz!不對,畫風不能這樣開頭,是這樣的,我覺得我應該用 React 去做點兒什麼,寫文章能夠清晰我的思路,讓我和別人有交流,但是並沒有實際做產品那麼性感,於是我決定要用 React 來做一些產品出來,於是就有了 “氫” http://origingroup.tech 相信我在這裏對氫的介紹以及實現技術介紹也能對大家有幫助。
所以這篇文章我主要想介紹一些做這個東西的思路、想法和一些技術實現以及用到的一些工具。
? “氫” 是什麼鬼
這個名字聽着就很奇怪,為什麼是個 H 元素作為名字,對,是這樣的,先不管氫是什麼東西,我的想法是我要用 React 去做一些產品出來那總得取個名字吧,“一些產品” 意味着我得費好多經歷去想產品名稱,好了,不如就按照元素週期表的順序取名字吧,於是“氫”這個名字就這樣定了。 :- ) (我都覺得我是天才了,hhhhhh ☞ )
好了,説正經的,其實做氫這個東西早有預謀,很久很久以前我做了一些產品,然後夭折了,eee...... 於是現在我決定把它們撿起來(感覺哪裏不對。。)!
其中的一個夭折的是 “一個針對個人的項目管理工具”,本想提供 saas 的服務,結果被我改來改去最後tm很長時間沒空理它不玩了,於是最終被改到現在的 “針對個人的筆記,任務,待辦離線管理工具” ,於是就有了這個叫氫的傢伙(當然之前給它取的名字不叫這個。。)
? 那氫提供的核心功能有哪些呢?
-
提供 workflowy 功能的強大列表,能夠將日常的瑣碎的事情,用極致簡單的交互去做增刪改查
-
核心目標基於任務管理來打造,所以參考了很多任務管理工具的 UX 以及自己的想法-結合 scrum 模型,讓它在任務管理上也能簡單極致(最開始核心目標太多,導致產品幾乎難產,其實最後的氫是一個刪減吧,這也讓我懂得了做產品需要剋制)
-
最後我需要有一個 powerful 的編輯器,因為平時記錄事情的時候總的寫點兒什麼呀,於是任務詳情被打造成了一個 wysiwyg 編輯器,並且支持用 markdown 語法,可以直接粘貼 markdown 生成樣式,後面還會提供導出 markdown 功能
-
當然得漂亮
好了,説了辣麼多,先上幾個圖,畢竟有句名言叫-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 打包編譯才是頭痛的問題,當 webpack 遇上 electron,各種環境不一致導致的奇怪 bug 我是不會跟別人説的,☝️個人默默的承受。。。
先上個項目結構的圖
好長的目錄,其中關鍵的地方是 containers 的設計,項目中 containers 目前的設計是一個 cotainer 包含
-
reducer.js
-
sagas.js
-
index.jsx
-
styles.less
-
components
當然圖中的項目結構中有專門的 reducers 目錄和 sagas 目錄,這是因為之前的設計沒改,後面的 reducer ,style,sagas 都是組件自包含的。
説説 webpack 的打包
感謝這個項目幫我解決了不少坑 chentsulin/electron-react-boilerplate 正常情況下的 Electron ,React 項目基本就按照這個 boilerplate 來就行了,不過我自己在打包上做了很多自定義的修改,所以才會出現很多 webpack 的問題。
其中的一個優化點是,不使用 dll ,無論 production 還是 dev 都會使用 vendors.js ,而這個 vendors 是預先打包壓縮好了的,所以每次打包實際都是隻打包了業務代碼。具體方式是看圖就知道了
通過定義 externals 讓打包過程忽略這些模塊的打包,使用 external 方式引用
專門打包 vendor 的webpack 配置,一定要注意 libraryTarget
Electron 相關的使用細節
這裏不想列舉太多細節,説説其中的一個,初始化 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()
})
}
如何用 Electron 做 i18n
在做國際化版本的時候有兩種情況
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 的東西,也要儘快的推出來,不然你都找不到理由繼續做下去