笔记

记录工作时遇到的一些问题。

https://solstice-taxicab-e2a.notion.site/55145f0829d14b2689850dacff1a8699?v=86d673bb29664b0c9f85b462904949ad

创建自定义事件

https://developer.mozilla.org/zh-CN/docs/Web/Guide/Events/Creating_and_triggering_events

var event = new Event('build');

// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);

// Dispatch the event.
elem.dispatchEvent(event);

判断字符串起始

String.prototype.startsWith()
String.prototype.endsWith()
// 均可传入第二个参数表示起始位置

简单的任务队列

this.queue = Promise.resolve(true);

addQueue(job) {
this.queue = this.queue.then(job)
.catch((e) => {
console.error(e);
});
return this.queue;
}

// 将Promise任务添加至队列,确保上个任务完成后执行该任务

package.json 私有包处理

"project": "git+ssh://git@github.com/<username>/<proejct>#<branch/commit>",

安装mecab

brew install mecab

//安装字典
$ tar zxfv mecab-ipadic-2.7.0-XXXX.tar.gz
$ cd mecab-ipadic-2.7.0-XXXX
$ ./configure --with-charset=utf8
$ make
$ sudo make install

flex 子元素无法设置为 position sticky

flex 默认 子元素为 strech

设置该子元素 align-self: flex-start;

useEffect / useLayoutEffect 区别

https://juejin.im/post/5de38c76e51d455f9b335eff

文本一行时 居中对齐, 多行时左对齐

<div>  // 父元素设置 text-align center
<p>text</p> // display inline-block ; text-align left
</div>

React、Vue 事件 异步方法使用事件 (合成事件)

// https://zh-hans.reactjs.org/docs/events.html
onClick=(e) => {
let event = e // 需要赋值
promise().then(res => console.log(e))
}

对象扩展 条件赋值

{
foo: 'a',
...(true ? {boo: 'b'} : null)
}

React 报无限 loop

onClick={fun()}

React Hook 翻页

const [current, setCurrent] = useState(1)
useEffect(() => {
cb && cb(current)
}, [current])
onClick={() => setCurrent(prev => ++prev)}

React Hook 倒计时

useEffect 形成闭包,使用 Ref 存储上次time

或使用 time 作为监听值 触发timeout 成为 interval 来使用

字符问题

转为 Unicode, 可能带有换行 或者特殊字符 ,使用 normalize 转换

? / ?? 的使用

a?.b?.c = 'xxx'

React Hook 异步引用 state

根本原因 Hook 是向函数中传入函数,形成闭包。

所记录的的函数创建时的引用值,而无法获取更新后(内存地址变化)的值,导致运行异常。

使用 useRef 获得一个,引用类型的变量记录值的变化即可。

之前遇到的一个问题:

// stateA stateB
const initCallBack = useCallback(() => {
// do sth
}, [stateA, stateB])

// pageA
useEffect(() => {
api().then(initCallBack)
} [initCallBack])
// pageB
useEffect(() => {
api().then(initCallBack)
} [initCallBack])

有2个页面有会在载入时进行init, 后调用 initCallback 会根据 state 状态跳出,防止重复 init。

但是打log可以发现还是重复init了。 原因还是在于异步调用,虽然 dep 是注册了 initcallback 的变化,但是在执行异步方法中所使用的并不是更新后的 callback,从而使用了错误的 init 状态,导致多次 init。

解决办法是在记录 init state 的同时,使用ref或普通变量去记录且用于判断init状态即可。

drag api

dom 元素在 drag end 时,默认会有一个元素恢复原位的一个动画。

// https://stackoverflow.com/questions/32206010/disable-animation-for-drag-and-drop-chrome-safari
dom.onDragOver = (e) => e.preventDefault()

drag api 兼容

桌面端与移动端不兼容,js只有桌面端(鼠标输入)形式的 drag api.

移动端可使用 https://github.com/timruffles/mobile-drag-drop 做兼容

在既有鼠标输入又有触摸输入的设备(可触屏的win10),无法使用触摸拖拽

自定义 font 顺序加载

使用自定义字体时,如果使用的是外部资源,会出现文字由默认字体到目标字体闪烁变化的问题

// https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face
@font-face {
src: url(data:application/octet-stream;base64,xxxx)
}

字体文件转为 base64 保证加载顺序

或者

<link rel="preload" href="https://fonts.woff2" as="font" crossorigin="anonymous"/>

IOS 视频不显示封面

IOS设备默认不显示 video 封面有一个有意思的方法

// https://muffinman.io/blog/hack-for-ios-safari-to-display-html-video-thumbnail/

<video>
<source src="path-to-video.mp4#t=0.001" type="video/mp4" />
</video>

IOS 文件下载

IOS中文件下载,js创建的a标签需要append到body中,不然是下载不了的!!

safari 浏览器 url hash 设置有节流限制

出现在使用某个md标题插件,记录当前的位置,过多触发 set url hash, 导致该功能被限制在 30s/次

webpack 打包时记录版本信息

// webpack config
const GitRevisionPlugin = require('git-revision-webpack-plugin')
const gitRevisionPlugin = new GitRevisionPlugin()
const LoadCommitDate = gitprocess
.execSync('git log -1 --date=format:"%Y/%m/%d %H:%M:%S" --format="%ad"')
.toString()

// 也可以用 dayjs 二次处理
// 后来也想 直接用 new Date() 记录CI打包的时间不过想想意义不太大。

export default {
plugins: [
new webpack.DefinePlugin({
'process.env.VERSION': JSON.stringify(gitRevisionPlugin.version()),
'process.env.COMMITDATE': JSON.stringify(LoadCommitDate)
})
]
}

复合组件

// https://github.com/penouc/blog/issues/7
// 调用React的顶层api React.Children 获取child, 处理 Child props。
<Stepper.Steps>
<Stepper.Step num={1} text={"Stage 1"}/>
<Stepper.Step num={2} text={"Stage 2"}/>
<Stepper.Step num={3} text={"Stage 3"}/>
<Stepper.Step num={4} text={"Stage 4"}/>
</Stepper.Steps>

组件数据 部分解耦

// 传入children为函数即可, children 渲染,部分依赖父组件,部分依赖该组件内部数据

const CustomComponent = ({children}) => {
return (
<div>
{this.children(data)}
</div>)
}

<CustomComponent>
{(internalProps) => {
return <div></div>
}}
</CustomComponent>

before / after 伪元素

input 没有伪元素!!!

PWA 更新

// https://create-react-app.dev/docs/making-a-progressive-web-app/

This means that users will end up seeing older content until they close (reloading is not enough) their existing, open tabs. See this blog post for more details about this behavior.

在资源更新的事件触发后,需要关闭tab,再重新打开才能确保资源成功更新

一种有意思设置默认参数形式

当时也不知道怎么想的, 写个这个挺高效的传参方式

const getEvents = ({page_num}={}) =>
instance.get(`/messages`, { params: {page_size: 10, page_num: 1, ...params}})

Typescript 联合类型断言

data.forEach(item => {
if (item.topic.startsWith('things/')) {
item = item as TYPEA
} else if (item.topic.startsWith('scenes/')) {
item = item as TYPEB
}
})

react function children

适合我这样的懒人

// array children 需要一个 key 哦
<div className="content">
{(() => {
lists.length > 5 && (lists[4] = <i key="icon-more" className="iconfont icon-more"/>)
return <>{lists.slice(0, 5)}</>
})()}
</div>

webthing 设备卡片 UI 模板的设计

第一版

考虑到数据与模板尽量解耦

useBaseThing 可以单独使用,用于注册事件等不需要 UI 参与的业务,

const useBaseThing = (thingDesc) => {
// propertes state
// model ref
// action callback
// model event update state
return { propertes, model, action }
}

const ThingTemplate = ({propertes, model, action, children}) => {
return (
<div>
thing xxxxxx
<div>{propertes}</div>
<button onclick={action}>action</button>
{children}
</div>
)
}

// 一个基本的 thing UI

const data = useBaseThing(thingDesc)
return (
<ThingTemplate {...data}>
<div>自定义icon等</div>
</ThingTemplate>)

第二版

第一版虽然完全分离, 但是有个问题,要使用模板就必然要先 useBaseThing 获取数据

所以考虑重构,将模板创建整合进 useBaseThing

同时一个 thing UI 中,需要自定义的部分需要包含更多的内容,如二级对话框、图标、触发按钮等,还要考虑到后期的扩展性,仅留下一个 ThingTemplate children 作为自定义元素就远远不够了。

const useBaseThing = ({
thingDesc,
templateConfig: {
CustomTemplateA,
CustomTemplateB,
CustomTemplateC
}
}) => {
// propertes state
// model ref
// action callback
// model event update state
return {
propertes,
model,
action,
baseThing: <ThingTemplate
customTemplateA={<CustomTemplateA propertes={propertes}/>}
customTemplateB={<CustomTemplateB propertes={propertes}/>}
customTemplateC={<CustomTemplateC propertes={propertes}/>}
/>
}
}

const ThingTemplate = ({
propertes,
model,
action,
customTemplateA,
customTemplateB,
customTemplateC
}) => {
return (
<div>
thing xxxxxx
<div>{propertes}</div>
<button onclick={action}>action</button>
{customTemplateA}
{customTemplateB}
{customTemplateC}
</div>
)
}

// 创建一个基本的 thing UI
// 这里最好配合 typescript 增加类型提示
const CustomTemplateA = (data) => <></>
const CustomTemplateB = (data) => <></>
const CustomTemplateC = (data) => <></>

const {baseThing} = useBaseThing({thingDesc, templateConfig: {
CustomTemplateA,
CustomTemplateB,
CustomTemplateC
}})

return baseThing // thing JSX.Element

// 创建一个无 UI thing
const {modal} = useBaseThing({thingDesc})
modal.on('event', () => {})

thing UI 也通过 useBaseThing 创建,可以根据 templateConfig,设置 UI 是否创建

第三版

目前实际开发下来第二版在扩展性上基本没有什么问题,但感觉还是有一些繁琐了

要说有问题,就是 ThingTemplate 是在 useBaseThing 内部创建,内部的插槽模板是写在外部,传入 useBaseThing, 写起来有一点违和

但是这些 Component 都是依赖 useBaseThing 内部的 state 数据,又没有办法,只能在这个上下文创建。

也许应该还有更加高效的组合方式

input 输入法问题

https://developer.mozilla.org/zh-CN/docs/Web/Events/compositionstart

https://juejin.cn/post/6844903950634713096

text input onchange 事件获取的 value,会包含输入法输入的内容

使用 onCompositionStart 记录当前是否为输入法状态,控制onchange执行

react v17

https://reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing

组件卸载时 useEffect 的 return 会异步执行,也就是在完全卸载后执行,dom元素事件监听的卸载需要在useLayoutEffect处理

总结就是 useEffect -> 加载 -> useLayoutEffect -> useLayoutEffect -> 卸载 -> useEffect

Electron index.html 加载本地(static)静态 js

旧版本可以这么用

<script src="./static/xxx.js"></script>

后来为了适配CI升了下版本,过不了打包流程,只能使用一个歪门邪道处理

<script id="syncload">
function loadScriptAsync(url) {
var head = document.getElementsByTagName("head")[0];
var s = document.createElement("script");
s.src = url;
let referenceNode = document.querySelector('script#syncload')
referenceNode.parentNode.insertBefore(s, referenceNode.nextSibling);
}
loadScriptAsync('./static/xxx.js')
</script>

屏幕 / Canvs录制

https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder

https://developer.mozilla.org/zh-CN/docs/Web/API/Screen_Capture_API/Using_Screen_Capture

元素滚动检测 API

IntersectionObserver

createDocumentFragment

可以理解为 React 中的 Fragment, 一个无父节点的元素

https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createDocumentFragment

DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。

因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。

Webpack 记录版本 plugin

https://github.com/LLK/scratch-www/blob/develop/webpack.config.js#L31

网页水印

https://www.zhangxinxu.com/wordpress/2020/10/text-as-css-background-image/

使用文本 svg 作为元素背景

可以实现类似 before/after 伪元素的效果

网页自动检测黑暗模式

  1. 使用 CSS 媒体查询可以解决
@media (prefers-color-scheme: dark) {
.day.dark-scheme { background: #333; color: white; }
.night.dark-scheme { background: black; color: #ddd; }

html {
filter: invert(1) hue-rotate(.5turn);
}
}

@media (prefers-color-scheme: light) {
.day.light-scheme { background: white; color: #555; }
.night.light-scheme { background: #eee; color: black; }
}

https://developer.mozilla.org/zh-CN/docs/Web/CSS/@media/prefers-color-scheme

https://www.zhangxinxu.com/wordpress/2020/11/css-mix-blend-mode-filter-dark-theme/

  1. 使用 JS
    window.matchMedia(“(prefers-color-scheme: dark)”).matches;

Module not found: Error: Can’t resolve ‘fs’

处理一些包

在Web中使用的出错

webpack config:

```JSON
{
node: {
fs: 'empty'
}
}

// Webpack >= 5
module.exports = {
...
resolve: {
fallback: {
"fs": false
},
}
}