對於一種事情,常常重複的話,很容易就會厭煩、以爲無趣、失去了當初的熱情。javascript
天然而然,就失去了當初的熱情,找不到成就感,甚至還懷疑,本身是否是不適合作前端,是否是應該換一份工做,是否是要轉行了? 前端工程師如何持續保持熱情(一)css
前端工程師如何持續保持熱情(二)html
一個比較常見的問題:每一次都是作差很少的活,又不是很難的那種(溫馨區)=>這種事情愈來愈多=>力不從心、壓力山大=>開始厭煩、失去熱情前端
解決方案: 作完可能須要覆盤=> 尋求更好的方案=>下次嘗試=>效率提高、保持熱情vue
上一次這種case是按時完成,此次必定要提早;上次用的是常規方法,此次要爭取優化一下;以前試了幾回是差很少的套路,此次看看能不能封裝一個公共的工具java
人家還在用手用肩膀搬磚,咱們就開直升機來搬磚、用一個自動化機器搬磚、甚至使用magic讓磚直接飄到終點。對比之下,咱們的搬磚很好玩,甚至還有點上癮。就像兩我的,一個最多隻能考100分,另外一個是必定能考100分並且還有機會提早交卷、不虛任何難度。他們的熱情、成就感、興趣徹底不是一個數量級的。超出預期與遇上預期,它們的區別無異於降維打擊了,工做效率差異其中一個小方面,就是從這裏開始的react
下面咱們也是從例子出發:git
基於antd,咱們若是想作一個下拉菜單,用的是menu組件,效果是這樣的: 程序員
那麼代碼大概就是chrome
<Menu>
<Menu.Item>
我的中心
</Menu.Item>
<Menu.Item>
用戶管理
</Menu.Item>
<Menu.Item>
退出登陸
</Menu.Item>
</Menu>
複製代碼
對於後續,通常的思路就是,之後產品叫加一個什麼item,那就在代碼裏面加一行就好。可是,需求老是善變的,用戶管理具備權限,只能由管理員打開怎麼辦?若是正在操做,退出登陸按鈕disabled怎麼實現?
因此,最開始咱們應該直接面向配置編程,經過一個配置對象生成:
class Cpn extends Component {
// ...
config = [
{
display: true,
render() {
return (
<a href="/user">我的中心</a>
)
}
},
{
display: this.props.isAdmin,
render() {
return (
<a href="/admin">用戶管理</a>
)
}
},
{
display: true,
render: () => {
return (
<Button disabled={this.props.isOperating} href="/admin">退出</Button>
)
}
},
]
renderMenu = () => {
return this.config.filter(x => x.display)
.map(({ render }, index) => (
<Menu.Item key={index}> {render(index)} </Menu.Item> )) } // ... render() { return ( <Menu> {this.renderMenu()} </Menu> ) } } // 若是是vue,這樣寫更爽 複製代碼
原本10行代碼,被改爲了幾十行,兜了一個彎回來,剛剛開始可能會以爲麻煩。也許就是由於以爲直接加3行代碼一個item很方便很舒服,而後就一直這樣下去了。直到後來,這個菜單變得很是複雜,多了不少邏輯和權限控制,那時候的render的代碼就是上百甚至幾百行的條件渲染了。甚至新來的實習生可能都會吐槽「咱們這裏有一個幾百行的render函數,全是if」。其實,一切的緣由,就是由於祖傳下來的。再追溯回去,就是最開始的時候想方便,直接寫3行代碼一個item,後面的人有樣學樣......這套代碼,最後是「也就只能經過這樣的方法實現這點效果了」
若是一開始就按照配置來寫,之後每次增長一個item,配置數組很穩定地只是多了幾行、多了一個元素,若是有更多其餘邏輯,能夠對renderMenu進行改造,通常也不會超過3行就能夠實現。接着這種navheader的菜單式組件,通常是全局存在的組件,因此會鏈接redux,讀props來條件渲染。這樣子下去,後面的人也是學着來寫,就是一套穩定的可擴展代碼了,也不怕需求怎麼變動
可能第一次並無想到要這麼寫,可是寫了一兩次需求,覆盤一下,會發現這個組件之後有複雜化的趨勢,並且都是作了一樣的事情。那麼這時候,應該要有寫配置的想法了
舒適提示:慎重考慮,別過分設計,不是全部的組件都是很複雜的
一個ui組件庫一把梭,全是各類表單增刪改查,表格渲染。這就是後臺系統類的需求,也許多數人的實習都有一段作管理後臺的經歷。你們的見解,大概就是:簡單但作得很煩、沒意思、無聊、想吐
在代碼層面上,如何精簡代碼,上一次已經講到,這是減小重複工做的一方面。從業務和架構層面上來講,管理後臺類需求也是差很少的:
接下來咱們逐個擊破(react爲例,思路也能夠做爲其餘框架的參考):
可能有權限系統
對於權限,好比會有一個role_id表示當前用戶身份,前端讀取這個id作判斷來展現組件或者能不能作某種操做
// 組件層面上的權限
if (role_id === 1) {
return <Detail /> } // 邏輯層面上的權限 if (role_id === 1) { doSth() } // 角色不少,不一樣角色不一樣ui,有不一樣的權限,難道仍是要繼續加if-else嗎? 複製代碼
這樣子咱們就有不少重複操做,逐漸進入了安穩的溫馨區。並且作起來的時候這些又不是什麼難的事情,可是事情又特別多。因而很快就以爲沒意思了,因此咱們必須改變現狀。
首先,咱們要維護一份配置表,上面寫着用戶能有什麼權限
const USER = 1;
const operationsMap = {
[USER]: [
'display-detail',
'dosth',
],
};
複製代碼
前面的操做,通過咱們權限配置表來解決:
// 組件層面上,咱們能夠用高階組件
const WithPermission = (props) => {
// 傳入角色和權限名,權限表裏面在該角色的權限下找,有沒有這個操做權限
return operationsMap[props.roleId].includes(props.action) ? props.children : null;
}
// 組件展現。不再怕不少個角色了,只須要寫一份配置能夠解決
<WithPermission action="display-detail" roleId={1}>
<Detail /> </WithPermission>
// 邏輯層面上,不重複講了,包一個函數就能夠了
複製代碼
這裏就提供一個參考的路線,更復雜的就是從這條路線發散出去的。權限這塊作起來,方案不少,也要看業務來決定架構
邏輯多是所有頁面同樣的
來到頁面 =》 請求接口 =》 渲染數據、綁定事件。後面若是有操做: 操做 =》 觸發事件 =》 邏輯執行 =》 請求接口/改變ui =》 接口返回從新渲染
所有頁面都是這樣的,可是又重複寫各類組件生命週期裏面作該作的事情,就命名、變量、少數邏輯不同。很快,又開始以爲無聊了,並且又浪費時間。此時,不咱們在業務頁面上寫重複邏輯的方法,就是咱們須要本身創造一些業務生命週期:
首次邏輯上面多加幾個業務生命週期,後面的交互也是差很少的,咱們能夠複用。只須要知道哪一種type和須要的id便可:config[type](id1, id2)。 type爲'fetch', 'update', 'delete', 'add'其中之一。對於通常的增刪改查,假設咱們有一個表格id爲tid,表格每一項的id爲iid,那麼增和查只須要tid,刪和改須要tid和iid,固定的套路。
有須要的話,可能會寫一個觸發事件後的預處理函數prefix,插入個性化邏輯來控制後面的運行。業務生命週期通常和常見的的curd操做是捆綁一塊兒的。
都是curd
對於curd的邏輯基本是如出一轍,可是這些和生命週期不同,一些業務生命週期分界線就是curd請求操做,請求接口前、請求接口後、修改前、修改後等等。首先各類重複的curd,能夠用配置解決:
// 當前頁面配置
// 頁面配置到後面應該由一個頁面來配置,作到簡單需求0開發
const config = {
fetch: '/api/getInfo',
update: '/api/updateInfo',
delete: '/api/deleteInfo',
add: '/api/addInfo',
}
// 僞代碼
// 在框架代碼上,先讀取當前頁面配置:
const currentConfig = allConfigs.find(conf => conf.id === id)
// 業務生命週期和根組件的組件生命週期結合,拉取數據
componentDidMount() {
// 業務生命週期beforeFetchData,傳入配置參數
const prefixParams = lifecircle.beforeFetchData(currentConfig.params);
fetchData({
url: currentConfig.fetch,
params: prefixParams,
}).then(res => {
// 業務生命週期afterFetchData,傳入請求的響應
lifecircle.afterFetchData(res)
// 錯誤處理生命週期,不必定觸發
}).catch(e => lifecircle.handleErrorAfterFetch(e))
}
複製代碼
這裏暫時提供一種思路,若是考慮無差異體驗、擴展性、侵入性,實現起來應該使用繼承(帶業務生命週期的基類)+高階組件(無差異體驗)+依賴注入(對組件加入新邏輯或者改寫)來實現,比較麻煩。
頁面路由能夠作成差很少
和前面說的權限差很少的處理方法,一個映射配置表解決,或者能夠直接寫在權限表裏面。當判斷到有權限,router切換頁面,不然進入兜底頁面
管理後臺系統類需求,咱們並非單純爲了完成任務把一個個需求作完,而是要把它規劃好、作好,作到擴展度和自由度很強、基本無需開發的程度。到那個時候,無形之間已經變成一個小架構師了
爲了脫離溫馨區,咱們應該作出改變,和現有的方法不同。所以,除了優雅地解決平常問題外,還要多一點思考和嘗試。也就是每一次覆盤後或者知道了常規方案後,腦暴出來的一些想法。若是想法是正確的,那麼就會帶來更好的收益。其實各類輪子,就是由於這樣而產生,爲了解決現有的問題
經過幾個例子,說明作完項目的思考、搞點事情是有必要的,是改進現狀的但願
每一次提交,你們都會用的4步:
$ git pull
$ git add .
$ git commit -m "feat: 😊今天又一個新特性"
$ git push
複製代碼
{
"script": {
"git": "git pull && git add . && git commit -m ",
"postgit": "git push"
}
}
複製代碼
使用的時候,咱們只要npm run git -- "feat: 😊今天又一個新特性"
。這裏涉及兩個點,一個是postxxx是指npm run執行某個script以後所作的命令(某個script以前的是prexxx,遵照這種命名協議便可),一個是--
會給npm命令參數列表加上參數,能夠在process.argv裏面拿到。別忘記先設置upstream哦
基於vscode
寫html的,輸入一個html選擇再回車就能夠出來一片基本結構;寫react、vue的,輸入幾個字母就能夠出來經常使用的模版;寫普通的js的,輸入經常使用的api部分字母立刻出來一坨......這都是大部分人都在用的xx snippet插件,要哪一個下載哪一個。
可是,事情老是不盡完美的,一個snippet不是適合全部人的,因此就有了各類各樣的其餘snippet插件。程序員的心態:用得不爽,立刻造一個去。這裏不詳細展開如何寫插件,詳見地址。
實際上,snippet是vscode自有的特性,你會發現每一種snippet插件都有一個snippet文件或者文件夾,這些代碼片斷都會被vscode讀取
平時咱們老是用各類xx snippets,隨着項目增長、種類繁多,咱們的插件也不知不覺開了一堆。同時跑一堆項目、開個移動端調試、開個代理、加上打開一大堆瀏覽器頁面,此時若是電腦開始卡了那就要按需開啓插件。並且插件多了,snippet也比較亂了,打幾個字母出現的選項不少了,影響效率。最終我選擇了簡約,各類其餘snippet插件都去掉,留下本身要的以及加上本身經常使用的
按下shift+command+p,選擇configure user snippets,配置全局代碼片斷文件:
{
"const": {
"scope": "javascript,typescript",
"prefix": "cs",
"body": [
"const $1 = $2;"
]
}
}
複製代碼
當插件啓用,咱們想輸出const a = 1;
,只須要輸入c、s、回車、a、tab、1就能夠完成。scope是指什麼語言下這段snippet生效,另外提一下,vscode的概念裏面jsx叫javascriptreact,tsx叫typescriptreact
我的平時喜歡寫的那些代碼片斷,都準備齊全了,讓本身的環境簡易且純粹一些。把本身經常使用的代碼片斷都收集起來,用一套適合本身的代碼片斷吧。其實能夠直接去.extension裏面改插件...
first of all,寫css想打width,但打出了wid的時候第一個提示倒是widows,是否是很不爽?因此,修改snippet解決它吧!
lodash是一個很值得學習的庫,建議對着文檔的使用方法,手寫每個函數的實現,打好基礎,教科書通常。別看表面那些簡單的case,覺得很簡單,寫完再去看源碼對一下答案,總會有你疏漏的地方。lodash總會給你帶來遺漏的js基礎知識,還會讓你看見一些坑和黑科技。
這裏講一下老生長談的深拷貝吧。簡單的狀況下,JSON.parse(JSON.stringify(target))
能夠解決。可是這個方法的缺陷有:
lodash則考慮很周全,基本全部的原生類都有一套拷貝方法,若是感興趣能夠看看一些其餘的類是如何拷貝的。
因此深拷貝說到最後,若是考慮全部的類,並無絕對意義上的深拷貝。若是從標準的json來講,只有除了function、symbol、undefined的基本數據類型能夠拷貝,對象只有普通對象和數組。可是對於前端,業務中可能會拷貝undefined、一些其餘的類。對於function,lodash都不拷貝的了,想一想也知道,不就是一樣的功能嗎,爲何要大費周章拷貝並且仍是不穩定的?因此lodash裏面能夠看見一段這樣的代碼:
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
複製代碼
最後,一種簡單的具備普適的深拷貝方案要知足:
function deepCopy(target, cache = new Set()) {
// 解決環引用
if (typeof target !== 'object' || cache.has(target)) {
return target
}
if (Array.isArray(target)) {
return target.map(t => {
cache.add(t)
return t
})
} else {
// 考慮symbol key
return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
cache.add(target[key])
res[key] = deepCopy(target[key], cache)
return res
}, target.constructor !== Object ? Object.create(target.constructor.prototype) : {}) // 保留繼承關係
}
}
複製代碼
深拷貝、數組去重、對象比較這些老生常談的話題,其實深挖一下,由於涉及到不少數據類型和類,能夠挖出不少基礎知識,或許裏面恰好就找到本身的知識漏洞了
把經常使用的那些方法都看過一次並實現一遍,積累一些經驗。這樣子,平時開發中一些經常使用工具函數也大概都瞭解了,下一次再作一樣的事情,就會瞬間完成甚至不用花時間。也能夠考慮一下給公司項目造輪子、寫公共模塊了。
前提:確認是內部平臺、或者用戶使用的瀏覽器無需考慮兼容性。這將是一個好機會,是時候表演真正的技術了!
使用Proxy減小代碼
你們都知道它是一個代理,並且作的事情比defineproperty多不少
// proxy能夠爲所欲爲命名key
const o = new Proxy({}, {
get(target, name) {
return name;
}
})
// defineproperty只能在知道key的狀況下使用。想達到proxy的效果須要函數封裝
const _o = Object.defineProperty({}, 'key', {
get(){
return 'key'
}
})
// 封裝過的
function _proxy(key){
return Object.defineProperty({}, key, {
get(){ return key; }
})
}
// defineProperty使用上多了一層函數調用:
_proxy('你仍是得先知道我是誰').你仍是得先知道我是誰;
// proxy 版本
o.你不用先知道我是誰;
複製代碼
若是對一個對象屬性進行劫持,又想返回自己的該屬性的值,proxy能夠直接作到:
var o = { a: 1 };
var proxy = new Proxy(o, {
get(target, key) { return target[key] }
})
proxy.a; // 1
複製代碼
可是definedproperty就不能靠本身作到,須要藉助外部的力量:
var o = { a: 1 };
Object.defineProperty(o, 'a', { get(){ return o.a } });
o.a; // 啊,炸了
var temp = 1;
Object.defineProperty(o, 'a', { get(){ return temp } });
o.a; // 1
複製代碼
更多proxy的使用可見這裏
使用symbol
常見的場景,好比渲染表格須要給每個item加一些新的字段,做爲輔助字段。以antd的table爲例,表格一般最後一列都是操做,增刪改什麼的,咱們能夠插入一個函數實現:
const ADD = Symbol();
data = data.map(x => ({ ...x, [ADD]: (test, record, index) => { console.log(`update the ${index}`) } }))
<Table dataSource={data} />
複製代碼
這只是一種新的實現方法,可是平時比較常見的多是增長中間輔助字段,到最後發請求的時候可能會把屬性過濾掉或者刪除掉。而symbol做爲key能夠直接stringify就發到後臺去了。
ps:想作惟一key的話,redux的action type不要用symbol了,由於redux的chrome插件有用了stringify,致使記錄下的全部的action都是未知名字。若是不使用redux的chrome插件就隨意
更多symbol的使用可見這裏
使用裝飾器(須要babel)
實際上就是o = decorator(o)的存在。用angular的人可能感覺到它的強大,能夠實現依賴注入、面向切面編程。mobx裏面也有大量使用了裝飾器
@safeunmount // 一個改造生命週期和setstate,使得組件被卸載時不會setstate的方法
@inject('user')// mobx把store的user數據傳進來,相似connect
@aftermount(fn) // 組件掛載完作某事
@report(id) // 注入上報邏輯
class C extends Component {}
複製代碼
直接結果就是,功能都是已經寫好的,想作什麼就加一行裝飾器功能代碼。實際上裝飾器咱們須要按照一套規則來寫,不能徹底修改一個類的原有屬性,只是在上面包一層。
// report上報,使用裝飾器和傳統方法對比
// 傳統方法
import reportReq ...;
class C extends Component {
componentDidmount(){
reportReq(id, 'mount')
}
componentDidupdate(){
reportReq(id, 'update')
}
}
// 裝飾器
import report ...;
@report(id)
class C extends Component {}
// 裝飾器文件
import reportReq ...;
function report(id) {
return function(target) {
const { componentDidmount, componentDidupdate } = target.prototype;
target.prototype.componentDidmount = function(...a) {
reportReq(id, 'mount');
componentDidmount.call(this, ...a);
};
target.prototype.componentDidupdate = function(...a) {
reportReq(id, 'update');
componentDidupdate.call(this, ...a);
};
}
}
複製代碼
其餘組件須要上報,傳統方法只能使用高階組件。若是還想加入aftermount,那又要再包一個高階組件。不這樣作,那就單獨到組件裏面寫邏輯。你,想寫一個單詞呢仍是想多寫若干行代碼呢?
相信大部分人都是工做佔據了大多數時間,因此讓這段漫長的時間過得愉快一點,保持熱情是一種方法。可是,通常的狀況是:事情太多=>作不完=> 進入死循環 => 逃不掉的壓力 =>失去熱情、度日如年。歸根到底,溫馨區呆的太長,逆水行舟中不進則退,直到最後力不從心。所以,打破這個循環的方法就是:
保持coding的熱情,增長工做效率,並非爲了寫更多代碼以及加更多的班。目的是讓現有工做時間充實起來,避免度日如年,保持一個良好的心態和適當的生活節奏。省下的時間,則能夠去作愛作的事情了。生活不能被代碼充滿了。
人的最終目標,總歸於星辰大海。to be continue...
關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技