海屿文章列表

infer 关键字

infer 可以在 extends 的条件语句中推断待推断的类型,你可以理解成一个类型方程中的未知数。

  • 只能出现在有条件类型的 extends 子语句中;
  • 出现 infer 声明,会引入一个待推断的类型变量;
  • 推断的类型变量可以在有条件类型的 true 分支中被引用;
  • 允许出现多个同类型变量的 infer

用例

推断返回值

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type func = () => number;
type variable = string;
type funcReturnType = ReturnType<func>; // funcReturnType 类型为 number
type varReturnType = ReturnType<variable>; // varReturnType 类型为 string

解包

type Unpacked<T> = T extends (infer R)[] ? R : T;

type idType = Unpacked<Ids>; // idType 类型为 number
type nameType = Unpacked<Names>; // nameType 类型为string

T extends (infer R)[] ? R : T的意思是,如果T是某个待推断类型的数组,则返回推断的类型,否则返回T\

推断联合类型

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T10 = Foo<{ a: string; b: string }>; // T10类型为 string
type T11 = Foo<{ a: string; b: number }>; // T11类型为 string | number

https://jkchao.github.io/typescript-book-chinese/tips/infer.html
https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md

加入购物车 🛒 抛物线动画

计算二次函数参数

采用两点式计算二次函数参数
以屏幕坐下为坐标轴原点,对称轴用起始点元素的横坐标减去屏幕宽度的八分之一。

targetX 与X轴的一个交点,购物车在左下角所以就用的是目标点的横坐标。

/**
 * 计算二次函数参数
 * @param depart
 * @param targetX
 * @returns {[number, *, *]}
 */
const quadraticCurve = (depart, targetX) => {
  const k = depart.x - window.innerWidth / 8
  const x2 = targetX + 2 * (k - targetX)
  const a = depart.y / ((depart.x - targetX) * (depart.x - x2))

  return [a, targetX, x2]
}

执行动画

departEle 是圆点飞出的起始元素
className 是圆点的类名
frame 是帧数

/**
 * 执行动画
 * @param departEle
 * @param targetX
 * @param className
 * @param frame
 */
export const quadratic = (
  departEle,
  targetX = 0,
  className = 'float-icon',
  frame = 25
) => {
  const { right, bottom } = departEle.getBoundingClientRect()
  const [a, x1, x2] = quadraticCurve(
    { x: right, y: window.innerHeight - bottom },
    targetX
  )

  let currentFrame = 0
  const interval = right / frame
  const childNode = document.createElement('div')
  childNode.classList.add(className)
  document.getElementsByTagName('body')[0].appendChild(childNode)
  setTimeout(() => {
    childNode.remove()
  }, 1000)

  const animation = () => {
    if (currentFrame >= frame) return
    currentFrame += 1
    const x = right - interval * currentFrame
    childNode.style.left = `${x}px`
    childNode.style.bottom = `${Math.min(
      a * (x - x1) * (x - x2) - window.pageYOffset,
      window.innerHeight
    )}px`
    window.requestAnimationFrame(() => animation())
  }
  animation()
}

效果

Dec-17-2020 16-52-57.gif

Node 连接 🔗 Codis

Codis

const Zookeeper = require('zookeeper-cluster-client')
const Redis = require('ioredis')
const EventEmitter = require('events')

class Codis extends EventEmitter {
  constructor(options) {
    super(options)
    const {serverUrl, path, password ,db =0 } = options
    this._zkAddr = serverUrl
    this._proxyPath = path
    this._password = password
    this._db = db
    this._zk = Zookeeper.createClient(this._zkAddr)
    this._connPoolIndex = -1
    this._connPool = []
    this._initFromZK()
  }

  _getConnectInfo(addr) {
    if (typeof addr !== 'string' || addr.indexOf(':') < 0) return
    const [host, port] = addr.split(':')
    return {host, port}
  }

  _initFromZK() {
    this._connPool = []
    this._zk.connect()

    this._zk.once('connected', async () => {
      console.log('zk connected')
      const proxyNameList = await this._zk.getChildren(this._proxyPath, this._watcher)

      for (let proxy of proxyNameList) {
        const proxyInfo = await this._zk.getData(`${this._proxyPath}/${proxy}`, this._watcher)
        console.log(proxyInfo.toString())
        const data = JSON.parse(proxyInfo.toString())
        if (data['state'] === 'online') {
          const {port, host} = this._getConnectInfo(data['addr'])
          this._connPool.push(new Redis(port, host, {password: this._password, db: this._db}))
        }
      }
      this.emit('ready', this._getProxy())
    })
  }

  _getProxy() {
    this._connPoolIndex += 1
    const len = this._connPool.length
    if (this._connPoolIndex >= len) this._connPoolIndex = 0
    if (len !== 0) return this._connPool[this._connPoolIndex]
  }

  _watcher(event) {
    const {type, state, path} = event
    console.log(`type: ${type} || state: ${state}`)
    if (type === 'SESSION' && state === 'CONNECTING') {

    } else if (type === 'SESSION' && state === 'EXPIRED_SESSION') {
      this._zk.close()
    } else if (['CREATED', 'DELETED', 'CHANGED', 'CHILD'].includes(type) && state === 'CONNECTED') {
      this._initFromZK()
    } else {
      console.error("zookeeper connection state changed but not implemented: event:%s state:%s path:%s" % (type, state, path))
    }
  }

  getResource() {
    return this._getProxy()
  }

  close() {
    try {
      this._zk.close()
    } catch (err) {
      console.error(err)
    }
  }
}

module.exports = Codis

Electron 封装 web 应用

articleocw-5783f68fee8ae.png

webview 标签

使用 webview 标签,首先需要开启权限。

webPreferences: {webviewTag: true, nodeIntegration: true, webSecurity: false}

webview 常用属性

  • nodeintegration 整合node,并且拥有可以使用系统底层的资源,例如 require 和 process
  • disablewebsecurity 禁用web安全机制
<webview id="webview" src="https://google.com/"
         disablewebsecurity style="display:flex; width:100%; height:100vh"/>

注入脚本

const webview = document.querySelector('#webview')
const preloadFile = 'file://' + require('path').resolve('../static/preload.js');
webview.setAttribute('preload', preloadFile);

preload.js
将 ipcRenderer 注入到 web 应用全局,这样就可以与 electron 主进程通信了。

const {ipcRenderer} = require('electron')
window.ipcRenderer = ipcRenderer

webpack 配置

preload 文件不会自动打包进去,需要配置 webpack 复制进去。

new CopyWebpackPlugin({
        patterns: [{ from: path.resolve(__dirname, 'src', 'static'), to: 'static' }],
    }),

参考文章

https://gist.github.com/bbudd/2a246a718b7757584950b4ed98109115

Webpack 打包含动态加载的类库

原文:https://scarletsky.github.io/2019/02/19/webpack-bundling-libraries-with-dynamic-imports/#%E6%96%B9%E6%A1%88%E4%BA%8C-%E7%94%B1%E7%B1%BB%E5%BA%93%E5%A4%84%E7%90%86

把含有加载依赖的部分分离到另一个文件中。

// runtime.js
module.exports = {
    onLoadDeps: function() {
        return Promise.all([
            import('my-lib/dist/text-encoding'),
            import('my-lib/dist/other-dep.js'),
            import('my-lib/dist/another-dep.js'),
        ]);
    }
}

然后修改 webpack 配置:

module.exports = {
    output: { ... },
    module: {
        noParse: /src\/runtime/,
    },
    plugins: [ ... ] 
}

这样,webpack 在处理 my-lib.js 的时候会把 runtime.js 加载进来,但不会去解析它。

如果应用层引用了这个类库,那么 webpack 打包应用的时候就会处理类库中的 import(),这样就和应用层平时的动态加载一样了,上面的问题也就解决了。 最后剩下一个问题,那就是在开发环境下,我们也需要测试 runtime.js,但这时候它是 import('my-lib/dist/xxx') 的,这个肯定会报 Error: Cannot find module 的错误。 这时候可以像方案一那样,用 import(importPrefix + '/text-encoding') 的方式来解决,也可以利用 NormalModuleReplacementPlugin 来解决。

// webpack.dev.js
module.exports = {
    // ...
    plugins: [
        new webpack.NormalModuleReplacementPlugin(/my-lib\/dist\/(.*)/, function (resource) {
            resource.request = resource.request.replace(/my-lib\/dist/, '../src/deps')
        }),
    ]
}

这个插件可以改变重定向资源,上面的配置是把 my-lib/dist/* 里面的资源都重定向到 ../src/deps。 更多详细的用法,可以参考下官方文档 NormalModuleReplacementPlugin。 注意:这个插件最好只用在开发环境中。