Skip to main content

2021

十二月

考虑一个块列表布局

在一个卡片中,有 padding: 32px,红色框内是 content。考虑蓝色的列表布局。绿色线条是每项之间的间距。

@card-space: 32px;
@card-half-space: @card-space / 2;

.wrapper {
display: flex;
flex-wrap: wrap;
// 通过负的间距,收回多余的间距
margin: -@card-half-space;

.card {
flex: 0 0 50%;

.cardContent {
// 为每个内容设置 half space 的 margin
margin: @card-half-space;
}
}
}

Redux vs Recoil

图片引用自 https://blog.joshsoftware.com/2021/12/07/can-recoil-replace-redux-state-management-in-react/

@babel/runtime 的使用思考

@babel/runtime 是一个 helper 库。

@rollup/plugin-babelbabelHelpers?: "runtime" | "bundled" | "inline" | "external" 属性举例,

  • runtime ,那么仓库需要依赖 @babel/runtime,在打包后文件顶部会写入 import xxx from @babel/runtime/xxx。(推荐包使用)

  • bundled,生成的代码会直接包含 helper 代码,但每份 helper 代码只会生成一次。(推荐应用使用)

  • inline,会在每个打包后的文件内重复的写入 helper 代码,造成冗余。(不推荐使用)

  • external,代码将会编译成 babelHelpers["typeof"] ,将会使用全局的 helper 方法。(不推荐使用)

为你的 js、json 文件添加类型校验

intro-to-js-ts.

// @ts-check
/**
* @type {import('@remix-run/dev/config').AppConfig}
*/
module.exports = {

}

// @ts-check是为了给予类型报错提示。

但是经过实践,typescript@4.5.2 会给类型的提示,但是不会报错,但如下,对于变量申明就会报错

// @ts-check
/**
* @type {import('@remix-run/dev/config').AppConfig}
*/
const config = {
a: 1
}
module.exports = config

JSON Schma

如下,JSON 的属性 $schma 可以帮助校验 json 的有效性。

{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
}

十一月

查看加载模块的 CPU 配置文件和堆内存配置文件

node --cpu-prof --heap-prof -e "require('request')"

会生成一个.cpuprofile和一个.heapprofile 文件

然后在 Chrome DevTool 中的 PerformanceMemory 标签中加在对应文件

如何在 VSCode 中调试 ts

参考 typesctipt-debugging

配置 tsconfig.json ,确保 sourceMap: true ,确认 outDir

// tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
// other config ...
},
}

.vscode 下创建 tasks.json

// tasks.json
{
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"problemMatcher": ["$tsc"],
"group": "build",
"label": "tsc: build"
},
]
}

.vscode 下创建 launch.json

// launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build",
"outFiles": [
// 注意这里的 dist 是 tsconfig.json 配置的 outDir
"${workspaceFolder}/dist/**/*.js"
]
}
]
}

javascript Unicode

内建函数

  • String.charCodeAt()

    给定索引处的 UTF-16 代码单元

    ( 可以理解为将字符串转成 unicode 再传成 10 进制的数字)

  • String.fromCharCode()

    返回由指定的 UTF-16 代码单元序列创建的字符串

    (可以理解为代码单元转字符串)

自定义函数

function unicodeAt(str, index = 0) {
let code = str.charCodeAt(index).toString(16).toUpperCase()
while (code.length < 4) {
code = `0${code}`
}
return `\\u${code}`;
}
function toUnicode(str) {
if (!str) {
return ''
}
return Array.prototype.reduce.call(str, (pre, cur, index) => {
return `${pre}${unicodeAt(str, index)}`
}, '')
}

字符串长度

const drink = 'cafe\u0301';
console.log(drink); // => 'café'
console.log(drink.length); // => 5
console.log(drink.normalize()) // => 'café'
console.log(drink.normalize().length); // => 4
const drink = 'cafe\u0327\u0301';
console.log(drink); // => 'cafȩ́'
console.log(drink.length); // => 6
console.log(drink.normalize()); // => 'cafȩ́'
console.log(drink.normalize().length); // => 5
const smile = '😀';
const regex = /[😀-😎]/u;
const regexEscape = /[\u{1F600}-\u{1F60E}]/u;
const regexSpEscape = /[\uD83D\uDE00-\uD83D\uDE0E]/u;
console.log(regex.test(smile)); // => true
console.log(regexEscape.test(smile)); // => true
console.log(regexSpEscape.test(smile)); // => true

Typescript tuple

我尝试了在 stack overflow 第一次提出问题,关于将readonly 的数组类型转为 一一对应的对象类型

得到了一个解决方案,通过 infer 和 recursive call 来实现。

更多 tuple manipulations 参考 Handle literal arrays/tuples types in TypeScript

十月

Babel 宽松模式(loose mode)

宽松模式在 babel 中一般不推荐使用:

  • 优点:生成代码速度更快,代码简洁。
  • 缺点:代码可能不符合 ES6 标准。

九月

CSS 应该在什么时候使用什么单位(form Josh W Comeau)

  • 对于排版(typography) 通常使用 rem,有更重要的可访问性优势
  • 对于盒模型,使用像素(pixels),比 rem 更加直观
  • 对于宽高,固定大小使用固定值,如200px,相对大小使用百分比,如 25%
  • 对于颜色更喜欢 hsl

CSS 分层阴影(layered shadows)

在线创建平滑友好的分层阴影 https://shadows.brumm.af/

基础

/* 普通的 */
.box {
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2);
}

/* 创建平滑的阴影,逐渐的增加 y-offset 和 blur-radius */
.box-shadow-5 {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.12),
0 2px 2px rgba(0, 0, 0, 0.12),
0 4px 4px rgba(0, 0, 0, 0.12),
0 8px 8px rgba(0, 0, 0, 0.12),
0 16px 16px rgba(0, 0, 0, 0.12);
}

/* 当层数增加,透明度也应该随之增加 */
.box-shadow-6 {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.11),
0 2px 2px rgba(0, 0, 0, 0.11),
0 4px 4px rgba(0, 0, 0, 0.11),
0 8px 8px rgba(0, 0, 0,0.11),
0 16px 16px rgba(0, 0, 0, 0.11),
0 32px 32px rgba(0, 0, 0, 0.11);
}

通过逐渐控制透明度来变得尖锐或发散

/* 尖锐的,逐渐增大透明度 */
.box-shadow-sharp {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25),
0 2px 2px rgba(0, 0, 0, 0.20),
0 4px 4px rgba(0, 0, 0, 0.15),
0 8px 8px rgba(0 ,0, 0, 0.10),
0 16px 16px rgba(0, 0, 0, 0.05);
}

/* 发散的,逐渐减少透明度 */
.box-shadow-diffuse {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.08),
0 2px 2px rgba(0, 0, 0, 0.12),
0 4px 4px rgba(0, 0, 0, 0.16),
0 8px 8px rgba(0, 0, 0, 0.20);
}

通过更快的增加 blur-radius 来显得更加柔和,梦幻

.box-shadow-dreamy {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07),
0 2px 4px rgba(0, 0, 0, 0.07),
0 4px 8px rgba(0, 0, 0, 0.07),
0 8px 16px rgba(0, 0, 0, 0.07),
0 16px 32px rgba(0, 0, 0, 0.07),
0 32px 64px rgba(0, 0, 0, 0.07);
}

多态组件的类型定义

import { ComponentsPropsWithoutRef, ElemntType, ReactNode } from 'react'

type MyButtonProps<T extends ElemntType> = {
as?: T
children: ReactNode
}

const Button = <T extends ElemntType = 'button'>({
as,
children,
...props
}: MyButtonProps<T> &
Omit<ComponentsPropsWithoutRef<T>, keyof MyButtonProps<T>>) => {
const Component = as || 'button'

return <Component {...props}>{children}</Component>
}

export default Button

React 中命令式插入的组件,context 问题

命令式插入的到 root 的组件,ReactDOM.render 会动态创建新的 React 实例,插入的 context 和页面的 context 不相同,所以无法获取当前页面根节点配置的 context。

八月

简单无脑的黑暗模式实现

@media (prefers-color-scheme: dark) {
html {
filter: invert(1) hue-rotate(180deg);
}
html img {
filter: invert(1) hue-rotate(180deg);
}
}

html {
transition: color 0.3s, background-color 0.3s;
}

七月

fetch abort

通过 AbortController 接口中止一个或多个请求

const controller = new AbortController()

// 点击 abort 接口
abortBtn.addEventListener('click', function() {
controller.abort();
})

fetch('url', { signal: controller.signal })
.then(response => {
console.log(response)
})
.catch(err => {
console.log(err) // AbortError
})

React 中的实践

import React, { useRef, useEffect } from 'react'
const () => {
const controller = useRef(new AbortController())

useEffectOnce(() => {
Promise.all([
fetch('url1', { signal: controller.current.signal }),
fetch('url2', { signal: controller.current.signal }),
]).then(() => {
// do somthing
})

return () => {
// 在页面卸载时(请求可能还在继续),执行 abort,
controller.current.abort()
}
})

return (
// ...
)
}

position: fixed 失效

父元素设置了非 none 的 transform,会导致最元素的 fixed 失效

  • 非 none 的transform 会创建一个堆叠上下文(Stacking Context)和包含块(Containing Block)
  • 由于堆叠上下文的创建,该元素会影响其子元素的固定定位。设置了 position:fixed 的子元素将不会基于 viewport 定位,而是基于这个父元素

更多参考 CSS3 transform对普通元素的N多渲染影响

六月

JS 的数值表示方式

const max = Number.MAX_VALUE
const min = Number.MIN_VALUE

const a = 35 // 十进制
const b = 123e3 // 科学计数法
// 进制
const c = 0o377 // 0o 八进制
const d = 0xff // 0x 十六进制
const e = 0b11 // 0b 二进制

// 特殊值
-0 === +0 // true >>> Object.is 为 false
0 === -0 // true >>> Object.is 为 false
0 === +0 // true >>> Object.is 为 true

NaN === NaN // false
Object.is(NaN, NaN) // true
0 / 0 // NaN

// 无穷
1 / -0 // -Infinity
-1 / -0 // Infinity

React Spring Cheatsheet

测试用例 AAA 模式

AAA模式:编排(Arrange),执行(Act),断言(Assert)。

describe("<Input />", () => {
it("It should keep a $ in front of the input", () => {
// Arrange
const utils = render(<CostInput />)
const input = utils.getByLabelText('cost-input')
// Act
fireEvent.change(input, { target: { value: '23' } })
// Assert
expect(input.value).toBe('$23')
});
});

jest mock console.log

let originalLog

beforeEach(() => {
originalLog = global.console.log
global.console.log = jest.fn()
})

afterEach(() => {
global.console.log = originalLog
})

五月

后端返回字符带有 \n 的 css 处理

div {
white-space: pre-line;
}

white-space: pre-line

连续的空白符会被合并。在遇到换行符或者 <br />元素,或者需要为了填充「行框盒子(line boxes)」时会换行。

四月

onerror 跨域问题 Script error.

当加载自不同域的脚本中发生语法错误时,为避免信息泄露(参见bug 363897),语法错误的细节将不会报告,而代之简单的 "Script error."

解决方案

script 添加 corssorigin="anonymous"

<script src="<https://xxx.com/xxx.js>" crossorigin="anonymous"></script>

此时非同域的资源加载,浏览器会报 No 'Access-Control-Allow-Origin' header is present on the requested resource

然后需要配置 cdn

Access-Control-Allow-Origin: *

三月

Jenkins + GitHub 持续集成

  1. Jenkins 安装 github 插件
  2. Jenkins -> 配置 -> GitHub -> 高级 -> 添加 githubservers -> 添加一个凭证
    • 凭证在 github -> settings -> developer settings -> Personal access tokens -> 勾选 repo 和 admin:repo_hook
    • 将 secret text 复制到凭证 (要选择 secret text)
  3. github 仓库 -> setting -> webhooks -> add webhook -> 输入 url http://[ip]:[port]/github-webhook
  4. Jenkins pipeline -> 构建触发器 -> github hook trigger for GITScm polling

web 调起 App(Deep Linking)

平台技术方案对比

技术Universal LinkAndroid App LinkURI SchemeChrome Intent
平台要求>= iOS 9>= Android 6Chrome 1 < 25, iOSChrome 1 >= 25
未安装表现打开 Web 页面打开 Web 页面发生错误可以打开 Web 页面
能否不发生跳转不能不能
能否去下载页面不能
iframe 触发不支持不支持Chrome 1 <= 18, iOS < 9不支持
链接格式正常的 URL正常的 URL自定义协议的 URLintent 协议的 URL
  • URI Scheme

appname://any.com/path?query=value

最原始的一种调起方式。

缺点:命名可能冲突、调起失败会直接发生错误会提示 URL 无效

优点:适用于在特定使用场景,即调起失败后仍旧可以继续使用当前页面

  • Universal Link

未安装会跳转指定页面,例如 iTunes

需要服务端给出 apple-app-site-association 以验证 App 的 URL 绑定

  • Android App Link

类似 universal link,需要服务端给出 assetlinks.json 以验证 App 的 URL 绑定

  • Chrome Intent

如果已安装,Chrome 会不询问用户直接调起 App。如果未安装,Chrome 会跳转至 S.browser_fallback_url

intent:
HOST/URI-path // Optional host
#Intent;
package=[string];
action=[string];
category=[string];
component=[string];
scheme=[string];
S.browser_fallback_url=[encoded_full_url];
end;

唤起方式

  • iframe
  • a 标签
  • window.location

checkOpen 方案

利用 visibilityChangepagehide 监听,利用属性 document.hidden

let hidden: Hidden;

function getSupportedProperty(): void {
if (typeof document.hidden !== 'undefined') {
// Opera 12.10 and Firefox 18 and later support
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
}

getSupportedProperty();

/**
* 判断页面是否隐藏(进入后台)
*/
function isPageHidden(): boolean {
if (typeof hidden === 'undefined') return false;
return document[hidden] as boolean;
}
/**
* 检测是否唤端成功
* @param cb - 唤端失败回调函数
* @param timeout
*/
export function checkOpen(failure: () => void, timeout: number): void {
const timer = setTimeout(() => {
const pageHidden = isPageHidden();
if (!pageHidden) {
failure();
}
}, timeout);

if (typeof visibilityChange !== 'undefined') {
document.addEventListener(visibilityChange, () => {
clearTimeout(timer);
});
} else {
window.addEventListener('pagehide', () => {
clearTimeout(timer);
});
}
}

二月

Chrome 调试技巧

  • Elements > styles 标签

    :hov 可以强制开启微类

    .cls 可以修改激活禁用类名

  • Elements > Computed 标签

    选中节点,可以看到节点最终的生效的样式

  • Network > Online 选择

    弱网调试

控制台

常用方法

  • copy(a) 可以快速完整的拷贝 a 的值,不论是复杂对象还是什么
  • document.designMode = "on" 或者 document.body.contentEditable="true" 开启网页可视化编辑

Performance

Source

debug工具栏

  • 暂停(继续)

  • 单步跳过

  • 进入函数

  • 跳出函数

  • 单步执行

  • 激活(关闭)所有断点

  • 代码执行异常处自动断点

    • 需要勾选“Pause On Caught Exceptions”

watch: 变量监控,可以添加删除需要监听的变量

Call Stack: 函数调用栈,根据调用栈可以非常方便检索到项目中何处主动“递进”调用了该函数

Scope: 作用域,当前断点函数所有属性的值

Breakpoints: 断点列表

XHR Breakpoints: 调试 XHR

DOM Breakpoints: DOM 断点,在 Elements 中右键选择 break on

Global Breakpoints: 全局监听

Coverage

代码使用率统计

Rendering

监控页面重绘重排时变化的区域进行高亮处理。

Laters

显示地查看 DOM 层关系

一月

零宽空格(zero-width space

  • 零宽空格(zero-width space, ZWSP)

    用于可能需要换行处。 Unicode: U+200B HTML: &#8203;

  • 零宽不连字 (zero-width non-joiner,ZWNJ)

    放在电子文本的两个字符之间,抑制本来会发生的连字,而是以这两个字符原本的字形来绘制。 Unicode: U+200C HTML: &#8204;

  • 零宽连字(zero-width joiner,ZWJ)

    是一个控制字符,放在某些需要复杂排版语言(如阿拉伯语、印地语)的两个字符之间,使得这两个本不会发生连字的字符产生了连字效果。 Unicode: U+200D HTML: &#8205;

  • 左至右符号(Left-to-right mark,LRM)

    是一种控制字符,用于计算机的双向文稿排版中。 Unicode: U+200E HTML: &lrm; &#x200E;&#8206;

  • 右至左符号(Right-to-left mark,RLM)

    是一种控制字符,用于计算机的双向文稿排版中。 Unicode: U+200F HTML: &rlm; &#x200F; 或 &#8207;

  • 字节顺序标记(byte-order mark,BOM)

    常被用来当做标示文件是以UTF-8、UTF-16或UTF-32编码的标记。 Unicode: U+FEFF

查看 Unicode 字符百科

过滤零宽空格

str.replace(/[\u200b-\u200f\uFEFF\u202a-\u202e]/g, "");