082019.12

Ubuntu18初体验

Ubuntu18已经发布一年多了,我一直忍者没升级,因为我挺喜欢原来的桌面风格的。但是因为它老是提示升级,我又有点强迫症,毕竟人要跟上时代,有些变化不得不跟着去适应,就像生活一样,所以,最后,我还是决定冒险升级一下。整个升级过程中有几次交互,我都点了 keep。最后升级完,重启,新的桌面系统,不得不适应一下。经过一会儿的调整之后,得到了我想要的界面,既然使用了 gnome 桌面,那就更像 windows 一些吧。

20:17:40 已有0条回复
032019.12

啥是微前端?

微服务应该很多人都听过了,大概是将一个大的复杂应用,拆分成多个独立的应用,通过 API 接口的方式串联在一起的,后端应用。微服务确实是后端的应用架构。而在前端,不知道是为了赶时髦还是什么意思,类似的概念被称为“微前端”,就是将原本一个完整的应用,通过某种架构形式,拆分为多个独立的应用,在整体层面,又可以将这些独立应用融合在一起,完成应用本身的功能。

形象的举个例子,假如说,你有一个订餐应用,现在要增加一个给外卖小哥用的独立部分,以往的做法,是在原有的基础上,新开一个菜单作为入口,进来之后是独立的模块,为了独立,尽可能和其他模块不产生耦合。在微前端的概念下,这个新的部分,则是一个完整的独立应用,它有一个入口,但是肯定不是在底部的菜单栏中,而可能是在首页的某个按钮区域内,点击进来以后,就像进入一片新天地,所有功能和外部没有任何耦合的地方,全部是自己独立的。没错,你会想到小程序,特别是支付宝首页菜单点进去的小程序。微前端就是这种架构,只不过是针对的web。

在我的直观感受中,微前端就是一个更高层面的路由和应用加载系统。路由就是要确保在已有应用基础上,两个应用可以不打架。当然你可以要求所有微前端应用的团队按照你的这个主体框架的要求来配置路由,但是对于需要复用的微前端应用而言,它不一定只在你一个框架中运行,可能在多个框架中跑,这个时候,没法只遵循你的路由规则。因此,微前端上层框架,首先要解决路由问题。其次要解决的是应用加载问题,一般而言,微前端里面的每一个微前端应用,都是异步加载的,就和小程序一样,当需要使用这个小程序的时候,才会下载下来运行。这和我们现在流行的 webpack 打包的方式完全不同,微前端框架如何去加载和渲染异步的应用,而且还要保证和路由一起工作良好。最后,除了这两大问题之外,还要解决样式冲突、状态共享、路由跳转传参等等问题。这些林林总总的问题凑到一起,OMG,又是一个新趋势……

但不管怎样,随着前端发展趋势,感觉这确实是要面临的一个问题。

12:29:00 已有0条回复
262019.11

使用 @babel/plugin-transform-runtime 的时候遇到坑。这个插件要求我们必须安装 @babel/runtime 系列,根据它的 corejs 参数,我们需要安装 @babel/runtime-corejs3 这种包,但是经过摸索之后我发现这里的 corejs 版本填写的越高,被植入的 polyfill 越多,到最后,打包出来的代码比之前大了很多。原本以为 corejs 版本越高越好。但是现在顿悟了,为了代码体积,放弃 corejs3 直接使用 @babel/runtime。之所以高版本 polyfill 加载的更多,我想是因为标准有一点改动,都会有新的 polyfill 被加入到 corejs 中,这就导致代码越来越多,最终导致原本为了减少代码量而使用的 transform-runtime 现在变成了负担。

17:29:49 已有0条回复
242019.11

占位符历险记

我是占位符,英文名placeholder,在编程领域跑龙套,开发者经常用到我,但我从来不是主角,我一直揣着一本《符号的自我修养》,并梦想有一天可以成为一个故事的主角而给开发者和用户带来快乐。我把我的故事写下来,希望在我的故事里,你可以获得一些乐趣吧。

配置

有一次,我到了一场叫“配置”的剧场里,导演在找一个跑龙套的,因为在这场剧里面,开发者不能直接把密钥写在代码里面,但是在运行时的时候,又要求使用这个密钥,密钥只会呆在独立的服务器里面。导演拿它没有办法,这个时候我毛遂自荐,上去说到:你把我放在本来要写密钥的地方,等构建的时候,再把密钥从密钥服务器里面拉下来,让密钥过来替我。导演拍了我的脑袋,说,你小子还真行。以上构建机,跑了大概10分钟,导演就让我去领盒饭了。

xargs

我灰溜溜串场到命令行的时候,遇到了 xargs,它让我过去帮忙。

ls /xx | xargs -t -I{}  cp {} /tmp/{}

moment.js

我签约了 moment.js,经常在它的剧里面跑龙套,例如:

'YYYY-MM-DD HH:mm:ss'

插值

在我25岁的时候,终于熬出了头,在一类叫“插值”的戏里,几乎每一场戏都有我,虽然每次出场我都被化妆师重新化妆。

<div>{{ text }}</div>
<div>{{ dog }}</div>

保持替换

真正让我在演艺界找到自己价值的,是在一场叫保持替换的电影里。在这场电影里,一个字符串中的某些字符要被替换,但是具有特殊样子的字符不会被替换。我不是这些特殊样子的字符,我在它们的变化过程中扮演了极其具有技术性的一环。

DD\D 要被替换为 44D, 因为最后一个D前面有一个\,所以这个D不能被替换掉。

刚开始导演没打算用我,直接使用正则 ?<! 就能实现。后来发现,浏览器不支持,于是不得不请出我这个大神。一开始,我给出了非常简单的方案:

先把 \D 改为 ___4___,等所有的 D 都被替换为 4 之后,又把 ___4___ 替换为 D,这样就可以了。

但是,这样不行,因为用户可能在字符串里面本身就包含了 ___4___ 这个字符串。我请出来深山里面的 unicode 来帮忙:

先把 \D 替换为 '\uddd1', \d 替换为 '\uddd0' 这种形式,这样,这些字符就是用户很难输入的了,几乎不会输入这种字符。后面再把这些字符给替换回来。

这样,我完美装逼,导演给我颁了一个最佳技术奖。

未完待续。。。

我的故事还在继续,你有你的故事么,跟我分享一下呗

17:32:29 已有0条回复

iOS Safari浏览器上overflow: scroll元素无法滑动bug解决方法整理

222019.11

一篇文章发布了两年了,阅读量仍然很飙,可见好文章不会过时,大家都知道我是写前端,区块链我一窍不通啊老铁

14:26:00 已有0条回复
212019.11

真正说服我使用 react hooks 的,是 react blog 里面提到的,使用 class component 打包时无法对 class 的属性方法进行优化,而使用 hooks 则可以将代码压缩到极致。这一点让我瞬间服软了,没有无论别人说的多么花哨,不足官方一句话让我释然。有人会说 hooks 是趋势是未来啥的,但是,实际上,hooks 比 class 复杂和难理解多了,而且随着业务逻辑的增长,一个 function 里面将会写出越来越多的代码,越来越复杂,让你无法维护。而实际上,在运行时,两则的差别不是特别大,都没有性能上的优劣。但是,唯独代码压缩这一点打动了我,真的。

23:19:48 已有0条回复

JS 如何获取高精度的时间戳

在js项目中,有的时候,我们希望获取比Date.now()更高精度的时间戳。这篇文章简单介绍了js时间精度的来龙去脉,这里就不再赘述了。现在,我们来介绍一下在浏览器中如何获取更高精度的时间戳。

在浏览器中有一个 performance.now() 的接口,它表达了从页面加载到执行该语句之间的时间间隔,是一个衡量值。页面加载结束时间通过 performance.timing.navigationStart 获取,两个值相加,就可以得到执行 performance.now() 的具体值,该值比 Date.now() 精度要高。

js中获取高精度时间戳

你可以看到,它的结果和 Date.now() 结果是一致的,只是精度更高,经过四舍五入之后是相等的(忽略js执行时间带来的微小误差)。

但是,在js中有一个不好的事实,浮点数运算会带来不可预料的误差。

单独执行 performance.now() 可以得到精度更高(小数点后面9+位,纳秒)的时间戳,但是,两个值相加之后,就只剩下毫秒数值。虽然这个精度其实已经够我们用了,但是我们如果非得要得到最高精度的时间戳,可以先将两个数值字符串化,在利用我写的来进行相加。

10:03:38 已有0条回复
112019.11

前端获取IP

在前端js代码中获取用户的IP地址:

export function getUserIP() {
  return new Promise((resolve, reject) => {
    const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
    const pc = new RTCPeerConnection({
      iceServers: []
    })
    const noop = function() {}
    const localIPs = {}
    const ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g
    const iterateIP = (ip) => {
      if (!localIPs[ip]) {
        resolve(ip)
      }
      localIPs[ip] = true
    }

    //create a bogus data channel
    pc.createDataChannel('')
    // create offer and set local description
    try {
      pc.createOffer(function(sdp) {
        sdp.sdp.split('\n').forEach(function(line) {
          if (line.indexOf('candidate') < 0) {
            return
          }
          line.match(ipRegex).forEach(iterateIP)
        })
        pc.setLocalDescription(sdp, noop, noop)
      }, function(sdp) {
        reject()
      })
    }
    catch (err) {
      reject(err)
    }
    //listen for candidate events
    pc.onicecandidate = function(ice) {
      if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) {
        return
      }
      ice.candidate.candidate.match(ipRegex).forEach(iterateIP)
    }
  })
}

需要用到webRTC,如果不支持该接口,那就无法获取。

16:54:19 已有0条回复
072019.11

react诡异的input无法输入问题,createPortal来解决

这写Modal的时候遇到一个诡异的react问题,即input无法输入。不能说资深,但是对于自认为熟谙套路的react开发者,检查了好几遍,调试了好一会儿,还是解决不了。

就在我快要放弃的时候,我突然想到,是不是所在组件树的上层组件在处理children的时候对input做了处理?按理不该啊。

于是我对嵌套的上一层组件内部实现进行了检查,最后发现,果然是在Modal的实现中,使用了ReactDOM.render来更新渲染,从而实现延时的动画效果。果然是一大堆坑。

在实现Modal时,为了实现过渡动画,要采用一个特殊的办法,当DOM挂载好之后,不能立即显示,而是要通过一个时间来过渡。由于element.style的操作会合并,一次性更新,所以当DOM卸载时,直接就将Modal干掉了。因此,Modal组件内部通过state维护了两个状态display和visible。display用于控制DOM的挂载卸载,visible用于控制显示隐藏。特别是在关闭Modal过程中,要先visible=false,等过渡时间结束之后才能display=false。

遇到这种情况,通过ReactDOM.render来更新DOM的操作就会导致input无法输入。因为render函数在做diff的时候,render是全量diff,在input没有key的情况下,ReactDOM无法确认是否其value要使用对应值。甚至在其所在树的上层,就已经把它干掉了。

通过createPortal可以解决这个问题。

ReactDOM.createPortal用以创建一个React虚拟树,该虚拟树被挂载在原虚拟树中,但是,在渲染到DOM中时,却被挂载在createPortal的第二个参数上。

这就出现了一种不常见的现象:virtual dom和真实的DOM并非以相同的结构存在。让我们来用代码演示一下。

class MyComponent extends Component {
  constructor(props) {
    super(props)
    this.el = document.createElement('div')
  }
  componentDidMount() {
    document.body.appendChild(this.el)
  }
  componentWillUnmount() {
    document.body.removeChild(this.el)
  }
  render() {
    return (
      <div className="root">
        {ReactDOM.createPortal(<div className="child">child</div>, this.el)}
        <div>xxx</div>
      </div>
    )
  }
}

上面这段代码,虽然在虚拟树中,.child是.root的第一个子节点,但是在真正的DOM中,.child被挂载在body下的一个空div中,这个空的div就是我们在constructor中创建的那一个。

而通过createPortal创建的虚拟树本身是属于react的元素树的一个节点,因此,在实际更新的时候,完全遵循react的虚拟树逻辑,也就避免了文章开头提到的问题。

22:51:04 已有0条回复