前端工程師如何持續保持熱情(二)

對於一種事情,常常重複的話,很容易就會厭煩、以爲無趣、失去了當初的熱情。javascript

  • 作不完的業務需求,日復一日,就以爲工做乏味、都是體力活;
  • c端作多了,就以爲業務邏輯沒有挑戰性,沒意思,設計要求苛刻,特別煩;
  • b端作多了,就以爲每天寫平臺,每天對着無味的數據,沒機會玩一下炫酷的特效;
  • 技術建設作多了,看着本身作的東西都膩了;
  • 研究一些花哨的東西,又對工做內容沒有什麼意義;
  • 想用一下最新技術,然而項目歷史緣由又望洋興嘆......

天然而然,就失去了當初的熱情,找不到成就感,甚至還懷疑,本身是否是不適合作前端,是否是應該換一份工做,是否是要轉行了? 前端工程師如何持續保持熱情(一)css

前端工程師如何持續保持熱情(二)html

不要留在溫馨區過久

一個比較常見的問題:每一次都是作差很少的活,又不是很難的那種(溫馨區)=>這種事情愈來愈多=>力不從心、壓力山大=>開始厭煩、失去熱情前端

解決方案: 作完可能須要覆盤=> 尋求更好的方案=>下次嘗試=>效率提高、保持熱情vue

上一次這種case是按時完成,此次必定要提早;上次用的是常規方法,此次要爭取優化一下;以前試了幾回是差很少的套路,此次看看能不能封裝一個公共的工具java

基礎鋪墊——一樣都是搬磚,但要優雅地搬磚

人家還在用手用肩膀搬磚,咱們就開直升機來搬磚、用一個自動化機器搬磚、甚至使用magic讓磚直接飄到終點。對比之下,咱們的搬磚很好玩,甚至還有點上癮。就像兩我的,一個最多隻能考100分,另外一個是必定能考100分並且還有機會提早交卷、不虛任何難度。他們的熱情、成就感、興趣徹底不是一個數量級的。超出預期與遇上預期,它們的區別無異於降維打擊了,工做效率差異其中一個小方面,就是從這裏開始的react

下面咱們也是從例子出發:git

eg1:navHeader的菜單

基於antd,咱們若是想作一個下拉菜單,用的是menu組件,效果是這樣的: 程序員

image

那麼代碼大概就是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來條件渲染。這樣子下去,後面的人也是學着來寫,就是一套穩定的可擴展代碼了,也不怕需求怎麼變動

可能第一次並無想到要這麼寫,可是寫了一兩次需求,覆盤一下,會發現這個組件之後有複雜化的趨勢,並且都是作了一樣的事情。那麼這時候,應該要有寫配置的想法了

舒適提示:慎重考慮,別過分設計,不是全部的組件都是很複雜的

eg2: 「無聊」的後臺管理系統?

一個ui組件庫一把梭,全是各類表單增刪改查,表格渲染。這就是後臺系統類的需求,也許多數人的實習都有一段作管理後臺的經歷。你們的見解,大概就是:簡單但作得很煩、沒意思、無聊、想吐

在代碼層面上,如何精簡代碼,上一次已經講到,這是減小重複工做的一方面。從業務和架構層面上來講,管理後臺類需求也是差很少的:

  1. 可能有權限系統
  2. 大部分頁面邏輯多是同樣的,以表格展現,彈窗增長和修改,勾選刪除(或者多選刪除)
  3. 都是curd,甚至連curd的接口都差很少、頁面差很少徹底就是數據庫每一條數據的直接體現
  4. 頁面路由能夠作成差很少,用id也好,用文字也好,前端會選擇xx-router做爲解決方案

接下來咱們逐個擊破(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 =》 接口返回從新渲染

所有頁面都是這樣的,可是又重複寫各類組件生命週期裏面作該作的事情,就命名、變量、少數邏輯不同基本如出一轍的代碼。很快,又開始以爲無聊了,並且又浪費時間。此時,不咱們在業務頁面上寫重複邏輯的方法,就是咱們須要本身創造一些業務生命週期:

  • 來到頁面: 在頁面render函數return以前加一些處理,如權限攔截、表格框架
  • 請求接口:請求以前增長一個prefix,適配數據和個性化邏輯
  • 渲染數據:請求完成後,在渲染以前增長一個生命週期處理,如適配邏輯、預處理邏輯

首次邏輯上面多加幾個業務生命週期,後面的交互也是差很少的,咱們能夠複用。只須要知道哪一種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切換頁面,不然進入兜底頁面

管理後臺系統類需求,咱們並非單純爲了完成任務把一個個需求作完,而是要把它規劃好、作好,作到擴展度和自由度很強、基本無需開發的程度。到那個時候,無形之間已經變成一個小架構師了

想盡辦法搞一點事情

爲了脫離溫馨區,咱們應該作出改變,和現有的方法不同。所以,除了優雅地解決平常問題外,還要多一點思考和嘗試。也就是每一次覆盤後或者知道了常規方案後,腦暴出來的一些想法。若是想法是正確的,那麼就會帶來更好的收益。其實各類輪子,就是由於這樣而產生,爲了解決現有的問題

經過幾個例子,說明作完項目的思考、搞點事情是有必要的,是改進現狀的但願

簡化git經常使用4步曲

每一次提交,你們都會用的4步:

$ git pull
$ git add .
$ git commit -m "feat: 😊今天又一個新特性"
$ git push
複製代碼
  • 😢:「每天搬磚,每天輸這重複的代碼,閉着眼、用腳均可以打出來了,有時候還打錯字,真煩。不想幹了,我想靜靜,別問我靜靜是誰」
  • 👧:「你的成就感呢?這就沒意思了?同是一個辦公室,爲何我沒有感受到沒意思啊,反而愈來愈有趣」
  • 😢:「怎麼作到的,到底是什麼,男人見了沉默,女人見了流淚」
  • 👧:「近來研究了npm script,發現咱們平時每天作的一樣的事情,均可以整合起來一個命令解決,好比你說的git」
{
  "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哦

  • 😢:「啊,你爲何如此優秀」
  • 👧:「不說了,我要繼續把git hook集成到裏面,而後直接遠程部署,執行dist命令......作到一個npm run git實現一條龍服務。還有不少事情要作,天天進步一點點,我愛工做。今天又是元氣滿滿的一天哦」

代碼片斷

基於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))能夠解決。可是這個方法的缺陷有:

  1. JSON.stringify中undefined、函數、以及其餘大部分複雜的對象的值爲的key會被去掉
  2. JSON.parse中遇到undefined會報錯
  3. 環引用報錯
  4. 失去繼承關係
  5. symbol作key被跳過

lodash則考慮很周全,基本全部的原生類都有一套拷貝方法,若是感興趣能夠看看一些其餘的類是如何拷貝的。

因此深拷貝說到最後,若是考慮全部的類,並無絕對意義上的深拷貝。若是從標準的json來講,只有除了function、symbol、undefined的基本數據類型能夠拷貝,對象只有普通對象和數組。可是對於前端,業務中可能會拷貝undefined、一些其餘的類。對於function,lodash都不拷貝的了,想一想也知道,不就是一樣的功能嗎,爲何要大費周章拷貝並且仍是不穩定的?因此lodash裏面能夠看見一段這樣的代碼:

if (isFunc || !cloneableTags[tag]) {
    return object ? value : {}
}
複製代碼

最後,一種簡單的具備普適的深拷貝方案要知足:

  • 兼容環引用
  • 兼容symbol做爲key
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多不少

它們的set、get對比
// 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...

關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技

相關文章
相關標籤/搜索