前段時間發過一篇文章,在酷家樂作面試官的日子,得到了不少小夥伴們的點贊和閱讀,這大概是我第一次較爲正式的在技術社區發文章,因此仍是挺驚訝也挺開心你們願意花時間看我寫的文章。css
其實做者本人的文筆很爛,是個嚴重偏科的人,高考語文不及格,並且離及格線還很遠,小時候最怕寫做文之類的,以前不太喜歡寫文章,但仍是以爲無論什麼行業,訓練總結和概括能力是有必要的,過一段時間作了一件事情以後,回過頭來總結一下,有助於本身看到這段時間成長了什麼,還有待提高的能力,有什麼能夠分享給其餘人的,一些錯誤的理解甚至還能被糾正,其實挺好。前端
我在酷家樂是用戶平臺業務組的一枚小前端,以前被挺多小夥伴誤覺得是大佬,其實感受仍是沾了公司和工具平臺大佬們的光,哈哈。。其實咱們組作的業務很簡單,就是產品運營導向的導流展現性質的網站,好比:酷家樂主站,m 站,桌面客戶端,算是比較傳統的 toC web 業務,包括一些移動 h5 和基於 electron 的本地客戶端軟件的一些業務,業務上沒有什麼難點也沒有亮點,強行要說的話可能對樣式和體驗的要求會高一些(=。=)vue
因此若是光作業務沒有思考(技術或業務)的話,作了幾年各方面能力仍是原地踏步一點也不奇怪,(確定要有小夥伴吐槽,面試造火箭進去擰螺絲了)其實成長徹底是看我的,公司平臺只是給你機會,你要抓不住的話也沒有人能夠幫你帶你,持續努力真的很重要,誰都喜歡和能力強的人作隊友,老闆也喜歡那種點一下方向就能作好全部事情的人,因此我其實工做了快 4 年在技術上一直都沒有敢懈怠過。react
上面說了咱們的業務自己沒有什麼技術壁壘,但技術債卻不少,基礎設施比較落後,我 17 年中剛來的時候這個業務組的技術棧仍是 ftl + jquery 的形式,開發前端須要把 jetty 起起來。。。我相信每一個公司都有這種技術債,只不過我司在互聯網工程界當時算是落後其餘公司一大截,不只僅是咱們組,其餘前端組甚至後端的技術棧也是同樣老舊,但這種局面也必定是有客觀緣由的,多是資金有限,改造陳本巨大,業務多人少,有體系化改造經驗的人也少等等。jquery
因此,當時我進來就是負責和個人老闆「胖胖」(比我早來一個月多點)一塊兒改造用戶平臺前端組的技術架構和基礎設施,然而起初那半年,我真正能花在技術上的時間真的不多,由於業務不等人,但咱們仍然不得不「在高速公路上給飛馳的汽車換輪胎」,一邊忍受極低的開發效率,幾乎每一個需求過來都是硬編碼,還時不時要清理前人留下的坑,一邊利用晚上和週末的時間作技術方案、技術任務(然而那段時間我家裏也是最忙的時候,結婚老婆懷孕都趕在了一塊兒,公司和醫院就是個人家)。web
第一步咱們就開始嘗試作先後端分離,固然咱們須要真正的「前端基架組」的大佬們幫咱們作掉一些事情,好比服務端渲染層,這層在前端和後端之間。而純前端部分就得靠咱們本身了,當時個人老闆「胖胖」給了我兩件事情,一件是前端工程化,一件是前端架構,後來我其實並無選擇的機會,由於工程化被交給了另外一個更年輕的同事,因此天然我就負責起了更偏「業務」的前端架構。面試
說是說前端架構,但不少「後端思惟」的人認爲「架構師」並非這種業務上的架構,真正的架構師視角應該是系統級的,包括了不少應用,並且前端能有啥架構?算法
我表示不太能認同,我本身就是計算機軟件工程畢業的,js 自己就是面向對象的語言,實際上隨着如今前端業務的複雜性提高,前端不過是在走後端的老路,之前後端碰到的問題,在前端領域也碰到了,沒有設計的前端代碼在這個年代顯然是站不住腳的,猶如 10 年前一次性「網頁腳本」般的存在,難不成咱們一套系統用了不到半年一年就要重作遷移一次?vuex
因此我就開始嘗試在我老闆「胖胖」搭建的 pug + jquery 的純前端項目下開始作代碼遷移(這時候咱們的工程化也是同步進行的,實際上整個前端項目的開發體驗好了太多了,有 mock,有 dev,有 build,也不用起後端服務),這個項目當時繼續用 jquery 其實也是爲了好遷移,畢竟新的工程化工具開發效率體驗要好不少,因此不少遺留代碼只是整理了一下就遷移過去了(過程並無想的那麼輕鬆),而且新的需求採用了新的架構去寫,去掉了早期繼承式的代碼風格,採用了工廠模式和麪向切面的思想,跟 react 同樣把對應的生命週期鉤子拋出來,開發效率相比之前要高了不少,也更加容易閱讀和拆分。docker
工廠模式和麪向切面的寫法
但遷移這種活其實須要的並非技術,而是耐心和細心,能夠說是吃力不討好吧,至少當時我是這麼認爲的。
直到遇到一些交互比較多的需求,須要頻繁的作 reRender,這時候老闆搭建的架構就出現了瓶頸,對於 reRender 沒有很好的支持,也比較麻煩,因此我就突發奇想,把 react 中數據驅動視圖的概念帶到了當前 pug + jquery 的體系下,取名叫 store,讓開發者聚焦在數據的更改上,數據改變自動觸發視圖的 reRender,還開發了一個簡易版的 router,能夠根據連接定位到對應的視圖模塊,改變路由也能自動切換綁定好的視圖,雖然這些都是很小的技術方案,也就一百來行代碼,但給我後面的工做其實作了一些鋪墊。
引入 store 後的寫法
引入 router 後的寫法
除了前端架構,我還作過一段時間前端性能分析的項目,以虛擬小組(相似興趣小組)的方式運做,我是組長,用空餘時間帶幾我的一塊兒來作一些技術項目,主要是利用 puppeteer 來離線對一些頁面進行性能跑分並給出對應的優化建議(跟監控不是一回事),不事後來這個項目因爲你們的業務時間不可控,拖了比較久被砍了。這個經歷對我仍是挺傷的,雖然有客觀緣由,但也意識到本身的組織能力是個短板。
客觀的說,17 年下半年這段時間,若是不是當時擔子比較重,還真的有離職的想法,感受本身在作別人不肯意作的「雜活」,若是更功利有的選的話,我想我不會選擇這條路,由於跟我以前的經驗徹底不搭邊,我以前作的比較深刻的是爬蟲和數據庫相關的內部系統,雖然有過心理準備,但也沒想到是這種狀況,因此若是不想把時間浪費在這種我認爲「能夠節省的時間」上,那就只能「先苦後甜」了。
當時其實和個人老闆「胖胖」也溝經過,我很真實地告訴了他個人想法和目前遇到的狀況,他也客觀的說過作架構作框架的人在任何一家公司其實都差很少(老闆之前在淘寶三年也作過相似個人事情),很難拿到好的結果,述職的時候都會比較難說,並且設計上的東西很難說你的就是好的,又很難有量化指標,但這種事情又很重要不能不作,只說不想讓作這樣事情的人再吃虧,會盡力幫我爭取一個好的結果(至此,我以爲我老闆真的很好,他不是那種掌控型的領導,是會給建議的領導,而且對每一個下屬的心理、成長都很關心,若是我沒記錯的話,業務團隊裏面咱們組的離職率應該是最低的,近兩年時間內我的緣由離職的僅有 1 人,而實際上,咱們組的業務屬性應該離職率是最高的才比較正常,這就很能體現他在管理上的一些才能了)。
機會之因此稱之爲機會,就是並非每一個人都有的,對於大部分互聯網公司,其實對社招都並非很友好,當下就是須要一些熟練工消化業務,並且這些熟練工自己還不能太差,要 cover 一個或者多個項目的開發和維護,還要帶來一些經驗輸出給團隊的其餘成員,至於特別培養什麼的基本就別奢求了,可遇不可求,對社招的要求只會更高,但手上的事情其實很難作得超出預期。
怎麼樣?上面的話聽起來就像一個 loser 在抱怨,客觀的現象講出來其實就沒意思了,否則還能怎麼樣呢?但我認爲優秀的人即便不須要鋪墊一個完美開局,也應該把當下的每一件事作到你本身的極致,我來酷家樂學到最多的就是適應、沒有機會本身創造機會。
因此 18 年上這半年,是我搞事搞的最多的半年,結果也確實這半年的績效是我最好的一次。這半年由於業務上比較輕鬆,因此作技術項目的時間會比較寬裕。這個階段,個人 OKR 其實有部分是我本身定的,個人老闆只是幫我衡量一下可不能夠作,或者商量一下怎麼樣的形式去作。
這半年我作了公司內部相似 gio 的全埋點採集 SDK,這個需求實際上是來自於咱們組的產品,因此能爭取到一些業務上的時間來換取作技術項目的時間,實現我就不細說了,主要仍是借鑑淘寶 spm 的一些作法。雖然這個項目後來被大數據組接走了,不過對我來講,我以爲還挺有意思的,這個項目也讓我第一次意識到,產品們也是能夠溝通的,即便是業務組,只要對業務有價值的項目其實也是能夠作的,只不過要考慮一下 ROI。
有了全埋點系統還不夠,產品們但願能對一個需求的各類形態作實驗,針對不一樣的用戶,不一樣的灰度,看哪一種方式帶來的數據增加和回報更好,因此咱們又開發了一個分桶實驗管理系統來支持,這個系統主要責任方是後端,因此前端只是打輔助的角色,開發週期也比較短,不過由於沒有產品交互,因此都是本身在想什麼樣的展示形式比較合理。
另外還參加了基架組的一小部分細碎工做,不過也沒幫上什麼大忙,本身卻是系統學習了 docker 和 k8s 相關的知識。
上面也說了,幾個項目不是被接走、被砍、就是主要責任方不是本身,以爲仍是達不到我對本身的要求。
而後這時候有個契機是,公司但願統一使用 react 技術棧,雖然 react 在 C 端業務開發效率並不算很高,但統一的好處實際上是大於壞處的,尤爲咱們不少組之間的組件有多是通用的,不少其餘組已經在使用 react 了,長遠來看是值得切換的。
藉着這個機會,我本身提出想開發一款狀態管理框架,當時比較流行的是 redux 和 mobx,redux 的好處是可預測,比較穩定,但從一些已有的項目和我本身的經驗來看,開發和維護體驗並很差,心智負擔不少,我但願有更加簡潔和可追溯的 API,對 TS 的支持也但願更好。mobx 的問題是難測試,難調試,有不少 magic,老項目裏面的一堆 reducer 也很難遷移,多人協做也會致使一些公用 store 逐漸變成「胖球」模型,雖然使用上更加受到 vuer 的青睞。
爲什麼不能結合一下他們的優勢呢?當時就想本身開發一個狀態管理框架,統一一下公司內部的各類框架,再加入一些經常使用的功能和擴展插槽,融入一些業務架構的理念,附上一個最佳實踐,既達到了收攏、規範效果,又能夠及時添加功能、應對各類場景。
因此就嘗試開始寫這個狀態管理框架,我取名叫 sticky。在寫這篇文章以前,我也看到過社區不少人都折騰過狀態管理框架,還有不少人去對標 redux、mobx、dva 等,其實這是有緣由的,由於狀態管理在 react 生態是最偏「業務架構」屬性的,因此可玩性會很強。
我以爲,我的開發者其實很難和一個團隊一個社區去抗衡,各類各樣的框架其實理念都差很少,不少人都想到一塊去了,對於普通的框架開發者,框架只是在設計上的抉擇不一樣,不存在什麼特別厲害的「黑科技」,因此,框架「統1、舒服」遠遠比「優雅、黑科技」要重要的多,好像 vue 的初衷就是這樣吧,當時尤大大也只是以爲市面上的框架總有用的不舒服的地方,因此才本身開發了一個。
因爲框架還在開源審覈流程中,暫時還沒向外推廣,我能夠先稍微介紹一些 sticky 框架的特色。因爲咱們的業務屬性不存在相似後端的 model,因此我借鑑了 DDD 中的領域模型的概念,我把下面這樣的 class 稱之爲一個領域模型:
import { state, mutation, effect, computed, reducer } from 'sticky';
import { fetchColumnList } from '../api';
class DesignColumnDomain {
@state() columnList = []; // 須要狀態管理的屬性
@state() pageSize = 18;
@state() totalPage = 0;
@state() current = 1;
@state('localStorage', 1000) status = 1; // 在 localStorage 中也存一份,並設置過時時間
@computed // 計算屬性,屬性沒變化會緩存計算值,不會重複計算
get computedVal() {
return this.pageSize + this.totalPage;
}
@mutation // 相似 mobx、vuex 的 mutation、action 寫法,只作同步操做
columnListLoaded = ({ columnList, totalPage }) => {
if (columnList) {
this.columnList = columnList;
this.totalPage = totalPage;
}
}
@reducer // redux 的 reducer 寫法
updateColumnListPage = (state, page) => {
return Immutable.fromJS(state).setIn(['current'], page).toJSON();
}
@effect // 支持異步操做,提供更新語法糖
async fetchColumnListFromRemote({ page, tagId }) {
this.$merge(
this.updateColumnListPage,
this.columnListLoaded,
); // 合併多個同步操做
const { columnList, totalPage } = await fetchColumnList({
page,
num: this.pageSize,
tagId,
});
this.columnListLoaded({
columnList,
totalPage,
});
// 提供簡單的單屬性更新的語法糖
this.$update({
columnList,
totalPage,
});
/** * 這個 effect 之因此不讓直接經過 this 賦值就是爲了防止寫出過多過程式,沒法複用的代碼 * 但願隔離流程和狀態更新操做,職責分離,這點我認爲 redux 的形式更好 **/
}
}
// 讓用戶本身去實例化的好處是靈活性高,即便不用 ts,vscode 也會自動提示這個 model 中的屬性和方法
// 若是用工廠模式去幫助產生實例,ts 下類型的推導是正確的沒什麼問題,但 js 就沒法享受這個好處了
export default new DesignColumnDomain();
複製代碼
下面這個 class 其實也只是一個普通的 class,它的做用是用來分發流程,我把它叫作 processor,注意:在上面的 domain class 中是不建議互相引用的,全部的方法都只操做本模塊的狀態,若是要結合起來作一些計算,應該提高到 processor class 中來作,processor class 也能夠作成複用的,它描述的是一組操做流程:
import { getQuery, updateQuery } from '@util/url-tool';
import $column from '../@domain/column';
import $tag from '../@domain/tag';
class ListProcessor {
@computed
get computedVal() {
return this.$column.pageSize + this.$tag.currentTagId;
}
async changePage(page) {
$column.updateColumnListPage(page);
updateQuery({
page,
});
$column.fetchColumnListFromRemote({
page,
tagId: $tag.currentTagId
});
}
async changeTag(id) {
$column.updateColumnListPage(1);
$tag.updateCurrentTagId(id);
updateQuery({
page: 1,
tagid: id
});
$column.fetchColumnListFromRemote({
page: 1,
tagId: id
});
}
async initLayoutState() {
const { page = 1, tagid = '' } = getQuery();
$column.updateColumnListPage(page);
$tag.updateCurrentTagId(tagid);
$tag.fetchTagsFromRemote();
$column.fetchColumnListFromRemote({ page, tagId: tagid });
}
// 未來的一些想法,想補足一些競態的功能
@effect('takeLatest')
async processorFuture() {
return this.$pipe(
this.$merge(
$column.updateColumnListPage(page),
$tag.updateCurrentTagId(tagid),
),
//this.$call($tag.fetchTagsFromRemote()),
this.$cancel($column.current === 0, [
$tag.fetchTagsFromRemote()
]),
this.$update({
computedVal,
})
);
}
}
export default new ListProcessor();
複製代碼
怎麼使用,sticky 把一些細節都給隱藏起來了,整個初始化只須要一條代碼就搞定了:
Sticky.render(<Layout />, '#app'); 複製代碼
jsx 或 tsx 中又該如何使用呢?
import React from 'react';
import { stick } from 'sticky';
import {
Breadcrumb,
Radio,
Pagination,
Skeleton,
} from 'penrose';
import './index.scss';
import Columns from './columns';
import $list from '../../@processor/list';
import $tag from '../../@domain/tag';
import $column from '../../@domain/column';
@stick() // 標記這是一個須要被自動觸發更新的組件
export default class List extends React.Component {
componentDidMount() {
// 直接觸發 processor class 中的流程
$list.initLayoutState();
}
render() {
// 模板中直接使用 class 中的屬性或方法
return (
<>
<Breadcrumb styleName="bread-crumb">
<Breadcrumb.Item href="/design">
優秀設計
</Breadcrumb.Item>
<Breadcrumb.Item>
設計專欄
</Breadcrumb.Item>
</Breadcrumb>
<Radio.Group value={$tag.currentTagId} onChange={$list.changeTag}>
<Radio value="">熱門推薦</Radio>
<For
each="item"
index="idx"
of={$tag.tags}
>
<Radio key={idx} value={item.id}>{item.tagName}</Radio>
</For>
</Radio.Group>
<Skeleton
styleName="column-list"
when={$column.columnList.length > 0}
render={<Columns showTag={$tag.currentTagId === ''} data={$column.columnList} />}
/>
<Pagination
current={$column.current}
defaultPageSize={$column.pageSize}
totalPage={$column.totalPage}
onChange={$list.changePage}
hideOnSinglePage={true}
/>
</>
);
}
}
複製代碼
看到這裏應該是有一些疑問,爲何直接用 es module 來取代像 mobx 那樣的 store key 的形式掛載到 props 上,這樣主要仍是但願編輯器能在 js 下直接就有提示,若是經過 props 來傳遞,一方面會斷開引用,造成了聲明的心智負擔,另外一方面也會與父組件傳過來的 props 混在一塊兒。
這樣作有缺陷嗎?固然有,import domain 的語句會和其餘 import 語句混在一塊兒,貌似還不太符合 react 組件設計原則,但其實普通的基礎組件咱們不會用狀態管理,業務組件用到狀態管理,大部分狀況下使用者並不想關心你的 domain class 裏有什麼東西,只關心傳進去的 props 參數而已,因此只是數據源從哪裏獲取的問題,外部獲取用 forceUpdate(), 走 props 的話能夠直接用 setState(),因爲每一個 domain class 自帶隱形的命名空間,更不存在衝突的狀況,因此也不須要 redux 那種一一映射的麻煩寫法。
其餘的一些功能:
// 全局配置開關
Sticky.config({
// devTool: true,
middleware: {
logger: false,
effect: true,
}
});
// 使用一個自定義中間件,中間件形式和 redux 的同樣
Sticky.use(middleware);
複製代碼
sticky 也支持時間旅行,自帶 logger 和 effect 中間件,基本算是抹平了 redux 和 mobx 的差別性吧,至於合理性我以爲見仁見智,反正公司內部已經小範圍在嘗試使用了,後面可能會進入維護期並開源出來。
這個半年作的事情比較多,成長的速度也是最快的,比較精讀的框架源碼應該也有 5 個以上,也爲下半年的晉升答辯潤色了很多,我司新人入職必需要滿 1 年才能進行晉升,因此我算是剛解凍就晉升了,雖然挺險的,我答辯的時候由於演講者註釋沒調出來腦子一片空白全程脫稿裸講,講得特別爛,感謝工具和算法的幾個大佬最終給過了。。。不過整個過程感受仍是挺嚴格的,被懟的很慘,確實仍是有挺多人被卡掉了。
每一年的下半年業務老是不少,因此這半年其實也沒什麼時間作技術項目,主要的精力都花在了 react 化改造和幾個大型業務的任務拆分和技術方案制定上。
react 化改造主要包括了不斷的優化 sticky 框架,搭建新項目新倉庫,並跑通整個開發構建部署流程,老的頁面或活動頁咱們仍然採用 jquery 開發和維護,新的主頻道頁緩慢遷移到了 react 技術棧,除了這些,還和 UED 合做,建設了公司內部的 C 端通用 react 組件庫,初期我也貢獻了比較多的代碼,開發效率相比之前要高了不少,整個過程算是我一直在協調和跟進的,這半年在技術上的組織能力要比之前更遊刃有餘了,但其實技術上的亮點可能不如上半年。
並且這半年在業務上的溝通合做仍是出了點問題,由於一些緣由,在會議中直接和產品暴露了情緒,其實顯得本身挺不專業的,我並非不懂這個道理,我工做那麼久其實也是第一次那麼生氣,氣到要直接懟回去,生氣實際上是由於對方先把激烈的言辭甩了給你,還對你的辛苦不屑一顧,本質實際上是由於她不理解你或者不想理解你,「我理解你的難處,可是對不起,我就要明天上線」,「視覺週末加班出稿子,但仍是沒有一次性到位,致使前端延期,但我仍是明天就要上線」等等,毫無人情味和委婉的表達,並且我也不可能把鍋所有甩給視覺,我不是一個喜歡推卸責任或者主動攻擊別人的人,生活中我就是這種性格,但要太過度了,可能寧願丟了工做也要跟你幹到底的典型三傻之獅子座脾氣。。。當時就以爲同事也是人,大家合起夥來懟弱勢羣體實在是有點太尷尬了,況且人家沒犯什麼錯誤,職場再爾虞我詐也稍微在意一下對方的感覺吧,說話徹底不先考慮一下合不合適說。
結果最後仍是我被老闆和平臺前端大老闆給約談了,他們也沒有跟我上綱上線,也客觀分析了事情的狀況和個人問題,事情過了以後其實想一想也沒什麼大不了的事情,對方是個說話比較衝的人,但也不是那種不能夠溝通的,因此其實跟當時業務壓力大也有關係,忽然就點着了。
這半年基本上功過抵消了,因此就拿了個很普通的績效,算是戾氣最重的一段時間,有一個多月也是常常工做帶到家裏作,每天作到凌晨一二點,還在和視覺互相回企業微信的消息。由於晉升了,因此其實好績效變得更加難拿了,對於資深來講不作點有影響力的事情其實很難拿好績效。
今年上半年給本身定的目標是開源 sticky 框架,而且把前端物料平臺作起來。對應給本身的要求是獨立負責一整個比較大型的技術項目並更好的支撐業務拿到好的結果,而且把運營和推動事情的軟技能給提高起來,無論結果怎麼樣,這是個人方向。
回過頭來,感受本身作了不少東西,又感受什麼也沒作,沒什麼拿得出手的亮點項目,因此我以爲後面可能得更聚焦一些,只作好 1-2 件事,雖然這在業務組真的很難,但只要有點時間作技術,我以爲就已經很幸福了,剩下的就得看管理層和公司層面的用人方式了。
不過說真的,以個人背景,可能到哪都只能寫業務,我相信大部分人跟我是同樣的,因此其實寫業務也沒什麼很差,寫簡單的業務也沒什麼很差,重要的是業務給你帶來了什麼,你給業務帶來了什麼,不排斥但也不懈怠,業務寫的不咋樣的人怎麼放心給你作技術呢?
若是你以爲你的成長比平臺慢,那你應該抓點緊了,若是你的成長已經遠遠超過了平臺,換個平臺也不必定是件壞事,但不要由於一點困難就輕易的逃避,一件事情還沒作好就想着作別的事情,有時候要正視本身的問題,不要老是怪別人怪平臺。
以上所有,是我本身的見解,不表明公司不表明任何。
咱們還有不少 HC,若是你有興趣加入一塊兒作建設,個人郵箱:feifan@qunhemail.com