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-babel
的 babelHelpers?: "runtime" | "bundled" | "inline" | "external"
属性举例,
runtime
,那么仓库需要依赖@babel/runtime
,在打包后文件顶部会写入import xxx from @babel/runtime/xxx
。(推荐包使用)bundled
,生成的代码会直接包含 helper 代码,但每份 helper 代码只会生成一次。(推荐应用使用)inline
,会在每个打包后的文件内重复的写入 helper 代码,造成冗余。(不推荐使用)external
,代码将会编译成babelHelpers["typeof"]
,将会使用全局的 helper 方法。(不推荐使用)
为你的 js、json 文件添加类型校验
// @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 的有效性。
{
"$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 中的 Performance
和 Memory
标签中加在对应文件
如何在 VSCode 中调试 ts
配置 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 持续集成
- Jenkins 安装 github 插件
- Jenkins -> 配置 -> GitHub -> 高级 -> 添加 githubservers -> 添加一个凭证
- 凭证在 github -> settings -> developer settings -> Personal access tokens -> 勾选 repo 和 admin:repo_hook
- 将 secret text 复制到凭证 (要选择 secret text)
- github 仓库 -> setting -> webhooks -> add webhook -> 输入 url
http://[ip]:[port]/github-webhook
- Jenkins pipeline -> 构建触发器 -> github hook trigger for GITScm polling
- 完
web 调起 App(Deep Linking)
平台技术方案对比
技术 | Universal Link | Android App Link | URI Scheme | Chrome Intent |
---|---|---|---|---|
平台要求 | >= iOS 9 | >= Android 6 | Chrome 1 < 25, iOS | Chrome 1 >= 25 |
未安装表现 | 打开 Web 页面 | 打开 Web 页面 | 发生错误 | 可以打开 Web 页面 |
能否不发生跳转 | 不能 | 不能 | 能 | 能 |
能否去下载页面 | 能 | 能 | 不能 | 能 |
iframe 触发 | 不支持 | 不支持 | Chrome 1 <= 18, iOS < 9 | 不支持 |
链接格式 | 正常的 URL | 正常的 URL | 自定义协议的 URL | intent 协议的 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 方案
利用 visibilityChange
、pagehide
监听,利用属性 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:​
;零宽不连字 (zero-width non-joiner,ZWNJ)
放在电子文本的两个字符之间,抑制本来会发生的连字,而是以这两个字符原本的字形来绘制。 Unicode:
U+200C
HTML:‌
;零宽连字(zero-width joiner,ZWJ)
是一个控制字符,放在某些需要复杂排版语言(如阿拉伯语、印地语)的两个字符之间,使得这两个本不会发生连字的字符产生了连字效果。 Unicode:
U+200D
HTML:‍
;左至右符号(Left-to-right mark,LRM)
是一种控制字符,用于计算机的双向文稿排版中。 Unicode:
U+200E
HTML:‎ ‎
或‎
;右至左符号(Right-to-left mark,RLM)
是一种控制字符,用于计算机的双向文稿排版中。 Unicode:
U+200F
HTML:‏ ‏
; 或‏
;字节顺序标记(byte-order mark,BOM)
常被用来当做标示文件是以UTF-8、UTF-16或UTF-32编码的标记。 Unicode:
U+FEFF
查看 Unicode 字符百科
过滤零宽空格
str.replace(/[\u200b-\u200f\uFEFF\u202a-\u202e]/g, "");