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

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。 注意:这个插件最好只用在开发环境中。

单分派与双分派

images.png

面向过程 ——> 面向对象 : 提高代码的可复用性;
if语句 ——> 多态 : 提高代码的可维护和可扩展性(同时提高了程序的效率,因为工厂类创建对象后,调用该对象的方法就无需条件判断了)

何为分派

分派就是根据类型去选择调用哪个函数。

像C 或者 JAVA 这种静态语言支持多态,天然支持分派,静态语言我们只能在运行时自行判断,一个简单的例子如下。

function foo(value) {
    if(value instanceof Array) {
       // do something
    } else if(value instanceof Date) {
       // do something
    } else if (value instanceof Number || value instanceof String) {
      // do something
    } else {
        // do something
    }
}

这种不算复杂还好,但是如果多个参数,多种类型,这一个函数的 if 分支判断就会越来越多越来越难以维护。

我们可以借鉴一下 Python 中的解决方案,Python 通过单分派泛函数部分支持了方法重载。

单分派函数

我们可以实现一个简单的 singleDispatch 用 Map 存储注册的参数类型,通过 register 链式调用,来注册类型及其对应处理函数。

function singleDispatch() {
  const registry: Map<string, Function> = new Map();
  const res = (...args: any) => {
    const types = args.map((arg: any) => {
      const constructor = Object.getPrototypeOf(arg).constructor
      return Object.getOwnPropertyDescriptors(constructor)?.name?.value || ''
    });

    const dispatcher = registry.get(types.toString())
    if (dispatcher) return dispatcher?.(...args);
  }

  res.register = (types: string[], dispatch: Function) => {
    if (!Array.isArray(types)) throw Error(`${types} not array!`);
    if (typeof dispatch !== 'function') throw Error(`${dispatch} not function!`);

    registry.set(types.toString(), dispatch)
    return res
  }

  return res;
}

使用方法:

function test(name: number): number;
function test(name: string): string;
function test(name: number | string) : number | string{
  return singleDispatch().register(['Number'], () => {
    console.log('Number', name)
    return name
  }).register(['String'], () => {
    console.log('String', name)
    return name
  })
}

你可以在此基础上实现更高级的功能,比如泛函数,any 类型等等。

双分派(double dispatch)

分派过程就是确定一个方法调用的过程,双分派就是见人说人话,见鬼说鬼话,将静态绑定和动态绑定结合起来,也就是说根据运行时传入对象的类型确定方法调用的过程。
重载是静态绑定,多态是动态绑定(运行时进行),双分派把多态放在重载之前,以实现在运行时动态判断执行那个子类的方法。因为编译器知道this所指向的实例的类型,因此将其作为参数传入到函数中就可以实现。

class Man : public Base {
public:
    int getSpeakRes(SpeakBase& speak) {
        return speak.speakWord(this);  //编译器知道this是哪个类型实例
    }
}

class Ghost : public Base {
public:
    int getSpeakRes(SpeakBase& speak) {
        return speak.speakWord(this);
    }
}

class Speak : public SpeakBase {
public:
    int speakWord(Man& man) {
        return "Man"; 
    }
    int speakWord(Ghost& ghost) {
        return "Ghost"; 
    }
}
class Board {
    drawFigure(aFigure) {
        aFigure.getDrawnOn(this)
    }
    drawTriangle(aTriangle) {
        // ...
    }
    drawSquare(aSquare) {
        // ...
    }
}

class Triangle extends Figure {
    getDrawnOn(aDrawableSurface) {
        aDrawableSurface.drawTriangle(this)
    }
}

class Square extends Figure {
    getDrawnOn(aDrawableSurface) {
        aDrawableSurface.drawSquare(this)
    }
}


----

let board = new Board();
let triangle = new Triangle();

board.drawFigure(triangle)

参考文章:

https://www.cnblogs.com/loveis715/p/4423464.html
http://yaoyao.codes/c++/2015/04/26/cpp-double-dispatch
https://www.typescriptlang.org/docs/handbook/functions.html#overloads
https://www.mikealche.com/software-development/refactor-long-if-or-switch-blocks-with-double-dispatch-in-javascript