前端工程师如何持续保持热情(二)

对于一种事情,常常重复的话,很容易就会厌烦、以为无趣、失去了当初的热情。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...

关注公众号《不同的前端》,以不同的视角学习前端,快速成长,一块儿把玩最新的技术、探索各类黑科技

相关文章
相关标签/搜索