Topppy's Blog

FE developer

React18

React 18 超全升级指南

  • ReactDOM.render替换为createRoot,影响范围:第三方组件库。
  • setState的同步异步breakChange
  • useSyncExternalStore:订阅外部数据 & 解决tear问题
  • 想深入理解的话:https://www.zhihu.com/question/502917860
  • 什么是tear(撕裂):https://github.com/reactwg/react-18/discussions/69,
  • 简言之:在fiber的分块更新时域不连续性前提下,并发存在多源修改数据的可能性,等你第二次接着渲染的时候,数据可能被人改了,同一render内两块渲染的数据不一致。

如何升级到react18

  • 关于strictMode的变化
  • 不再支持IE

配合React Conf 2021食用
Concurrent React for Library Maintainers

hooks:

文章&工具

感想

从新的hook可以窥探到,react的发展越来越不像一个 UI 库了,有了更复杂&难以理解的设计理念,甚至明明白白的告诉你:“哪些特性普通开发者不用关心,只需要使用react给你的一般API就可以,复杂的事情我们我们已经帮你处理好了”。

React的用户被分层了。一部分普通用户学会 how to use, 一部分库开发者要学会 why and how to make it right and fast。

不变的是react那个核心的设计思想(仅本人感受到的):用技术抹平人的差异性,让更多的人可以低成本的 产出 高性能的结果。这或许是一种研发的工业化?

工人分化为:点点点机器的工人和研发机器的工人。

能run起来的最粗暴的方式就是
把nodeIntegration打开,把contextIsolation关掉。

1
2
3
4
5
6
7
8
9
10
const config: BrowserWindowConstructorOptions = {
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
webSecurity: false,
preload: preloadPath,
enableRemoteModule: true,
}
}

如果装native包,务必在 app/release/下安装,并且要使用 npm 而不是yarn.

electron-react-boilerplate 使用的rebuild版本有问题,直接升级到最新版本。

未解决问题。 基于ffi-napi 多次调用win32的方法,QueryDisplayConfig,node进程会直接中断退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import log from 'electron-log'

const ffi = require('ffi-napi')
const ref = require('ref-napi') // 对接C++指针
const refStruct = require('ref-struct-napi') // 对接C++结构体
const refArray = require('ref-array-napi') // 对接C++数组

// 屏幕的四种模式

export const sdcApplay = 0x80 // 屏幕设置辅助参数

export enum DisplayMode {
internal = 1, // 仅电脑屏
clone = 2, // 复制
extend = 4, // 扩展
external = 8, // 仅第二屏幕
}

// 这两个结构体的内容太复杂,而且实际项目用不到,所以直接算一下结构体的大小填充个无用数据
const DISPLAYCONFIG_PATH_INFO = refStruct({
unuse: refArray(ref.types.int, 72 / 4),
})
const DISPLAYCONFIG_MODE_INFO = refStruct({
unuse: refArray(ref.types.int, 64 / 4),
})

const PathArrayType = refArray(ref.refType(DISPLAYCONFIG_PATH_INFO))
const ModeArrayType = refArray(ref.refType(DISPLAYCONFIG_MODE_INFO))

const libm = ffi.Library('user32', {
SetDisplayConfig: ['long', ['int', 'pointer', 'int', 'pointer', 'int']],
GetDisplayConfigBufferSizes: [
'long',
['int', ref.refType(ref.types.int), ref.refType(ref.types.int)],
],
QueryDisplayConfig: [
'long',
[
'int',
ref.refType(ref.types.int),
PathArrayType,
ref.refType(ref.types.int),
ModeArrayType,
ref.refType(ref.types.int),
],
],
})

export default {
setDisplayMode: (mode: DisplayMode) => {
log.info(
'=====================================================================setDisplayMode',
mode,
)
// eslint-disable-next-line no-bitwise
const re = libm.SetDisplayConfig(0, null, 0, null, sdcApplay | mode)
return re
},
getDisplayMode: () => {
const requiredPaths = ref.alloc(ref.types.int)
const requiredModes = ref.alloc(ref.types.int)
// 获取QueryDisplayConfig的2-5参数,不用动
const result2 = libm.GetDisplayConfigBufferSizes(
2,
requiredPaths,
requiredModes,
)
log.info(result2, requiredPaths.deref(), requiredModes.deref())

const PathArray = refArray(DISPLAYCONFIG_PATH_INFO, requiredPaths.deref())
const ModeAyyay = refArray(DISPLAYCONFIG_MODE_INFO, requiredModes.deref())
const paths = new PathArray()
const modes = new ModeAyyay()

// 查询结果存储在flags
log.info('new flags')
const flags1 = ref.alloc(ref.types.int)
// 2到5参数没有用到,参数1是常量
log.info('start get')
libm.QueryDisplayConfig(
4,
requiredPaths,
paths.ref(),
requiredModes,
modes.ref(),
flags1,
)
// log.info('save re')
// const re = flags1.deref()
log.info(
'=====================================================================getDisplayMode',
flags1.deref(),
)
return flags1.deref()
},
}

mac需要动态不引入win32的native包(会报错)

需要安装node-gyp构建环境(开发机 & 打包机)

https://github.com/nodejs/node-gyp#on-windows
python 3.9 安装 + 环境变量(要选给all users 安装)
npm config set python “C:\Programs\Python\Python39\python.exe”
npm install windows-build-tools -g

卡住解决:https://www.jianshu.com/p/e2f12fab2b78

参考资料文档:

如何实现外界屏幕模式切换
https://blog.csdn.net/wangyunman/article/details/103080818
调用Windows API:SetDisplayConfig
调用displayswitch.exe 可以实现,但是会有一个切换的系统级UI变化,https://renenyffenegger.ch/notes/Windows/dirs/Windows/System32/DisplaySwitch_exe#:~:text=DisplaySwitch.exe%20can%20be%20used,the%20windows%2Bp%20keyboard%20shortcut.

在 Electron 下调用 Win32 API 的经历

node 如何执行 windows API
https://github.com/waitingsong/node-win32-api

外部函数接口 FFI —— 虚拟机中重要但不起眼的组件
https://zhuanlan.zhihu.com/p/32134367
node.js + Electron 调用 Windows API 踩坑日记
https://blog.csdn.net/qq_21487663/article/details/111099822
electron怎么调用windows的api?

问题:

  1. electron 安装native包报错
  2. win32-api没有这个方法

https://juejin.cn/post/6854573212341108749#heading-9

https://blog.csdn.net/YW_yang/article/details/117434122

https://blog.csdn.net/weixin_40450855/article/details/109318361?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link&utm_relevant_index=4

https://www.cnblogs.com/silenzio/p/11639960.html

锁版本, 统一 编译需要的C++版本,python版本

现象:
无法使用location的api刷新 or 替换页面url。

原因:
第三方页面使用了beforeunload事件,正常浏览器会弹窗用户确认,并阻塞直到用户完成操作。electron内默认没有弹窗,但是正常阻塞了,所以需要额外处理。

参考:https://github.com/mrdulin/blog/issues/38

这个问题是他们库的实现上缺少功能,目前只能自己再包一层promise,手动监听一下 downloadItem的updated事件,如果state是injected,就reject

1
2
3
4
5
item.on('updated', (event, state) => {
if (state === 'interrupted') {
reject(new Error('download failed'))
}
})

背景

  1. 两个进程的cookie不共享,因此需要在渲染进程发送
  2. 主进程通知渲染进程发送,并等待请求响应结果,再做下一步处理。

实现

由于IPC都是EventEmmiter模式的,因此我们需要用Promise包装一下 IPC的通知。

主进程发送消息给渲染进程

单向通知,不关心接收方的响应

1
2
3
4
5
6
7
8
9
/**
* 主进程向渲染进程发送消息
* @param channel 频道
* @param msg 消息数据
*/

const sendToRenderer = (channel: string, msg: any) => {
// 封装webcontent的send方法
}

主进程发送消息给渲染进程,并等待渲染进程的响应

双向通信,等待响应数据好继续执行任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 主进程向渲染进程发送消息,并等待渲染进程的响应
* @param channel 频道
* @param msg 消息
* @return Pomise<any>
*/

export const callRenderer = (channel: string, msg: any) => new Promise((resolve, reject) => {
const onData = (e: any, result: any) => {
resolve(result)
}

const { sendChannel, dataChannel } = getIPCChannels(channel)

// 监听渲染进程的响应
ipcMain.once(dataChannel, onData)

const mainWindow = BrowserWindow.getFocusedWindow()
if (mainWindow && mainWindow.webContents) {
// 发送消息
mainWindow.webContents.send(sendChannel, msg)
} else {
// 取消监听
ipcMain.removeListener(dataChannel, onData)
}
})

渲染进程响应主进程调用

与 callRenderer 配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 响应Main进程调用并返回数据
* @param channel 频道
* @param callback 回调, (data:any) => Promise<any>
*/
export const answerMain = (
channel: string,
callback: (data: any) => Promise<any>
) => {
const { sendChannel, dataChannel } = getIPCChannels(channel)
const listener = async (event: any, data: any) => {
try {
log.info('answerMain params:', data)
const res = await callback(data)
log.info('answerMain data', res)
ipcRenderer.send(dataChannel, res)
} catch (error) {
log.info('answerMain error:')
log.error(error)
ipcRenderer.send(dataChannel, null)
}
}

ipcRenderer.on(sendChannel, listener)
}

使用 case: 主进程调用渲染进程发送请求

1
2
3
4
5
6
7
8
// main
const data: any = await callRenderer('api-report', deviceInfo)

// render
answerMain('api-report', aync () => {
await doSomething()
return data
})

参考:https://github.com/sindresorhus/electron-better-ipc

nwb

  • 2年没更新
  • 构建结果只babel,不webpack
  • 配置项少,开箱即用,灵活度低

Neutrino

  • React 官方推荐最新
  • 配置多 & 灵活
  • 构建结果走webpack

HaveDone

  • Neutrino 默认production构建会单拆css文件出来
  • css Module的配置需要改动(个人需要)
  • 如何加postcssLoader,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
      style: {
    // 这个默认配置是 /\.module.css$/, 导致无法识别.css后缀的为css module文件
    modulesTest: /\.css$/,
    // 这个默认在production下开启,会拆分出单独的css文件,导致样式失效
    extract: false,
    loaders: [
    // Define loaders as objects. Note: loaders must be specified in reverse order.
    // ie: for the loaders below the actual execution order would be:
    // input file -> sass-loader -> postcss-loader -> css-loader -> style-loader/mini-css-extract-plugin
    {
    loader: 'postcss-loader',
    options: {
    plugins: [
    require('postcss-flexbugs-fixes'),
    require('postcss-preset-env')({
    autoprefixer: {
    flexbox: 'no-2009',
    },
    stage: 3,
    }),
    require('postcss-normalize')(),
    ],
    },
    },
    ],
    },
    }),
  • 使用自定义eslint,建立自定义的eslintrc.js文件,并配置
    1
    useEslintrc: true,

需求

邮箱账号输入下拉建议列表的item上有click事件,我们期望是邮箱账号input在blur的时候隐藏下拉。点击下拉的item,输入框内的值变为item的值。

问题 1,如果直接隐藏,就无法点击下拉元素。

因此网上一个常见的解决方案是在blur的回调中延时隐藏下拉。(侧面证明blur事件是先于click事件触发的)

问题2, 在延迟隐藏下拉的条件下,部分浏览器依旧不触发item的click事件

浏览器触发blur事件和click事件的顺序是,先blur,后click。可能某些浏览器不再触发blur之后的事件。

我们需要在触发blur前知道item被点击了。mousedown事件可以办到,为什么呢?

去这个codepen体验一下点击事件和blur事件的触发顺序;

  • button mousedown is fired.
  • input blur is fired.
  • button mouseup is fired.
  • button click is fired.

参考

我在别的网站也遇到过这种现象,就是一个限制了比如10长度的input标签,假设我想输入“中文输入法”,才5个字符是啊,然而当我使用拼音> zhongwensh就再也无法输入下一个字符了。

我在使用react来写受控input组件也会出现这种现象,这显然不是我们想要的。那么如何才可以只让中文输入法最后空格确认中文的时候才判断字符长度是否符合要求呢?

那就是

不要用onchange方法!
不要用onchange方法!
不要用onchange方法!

input的maxlength属性!
input的maxlength属性!
input的maxlength属性!

直接在input的元素上设置maxlength=。10, 就可以实现限制长度的功能,并且不会干扰中文输入法的拼音字符长度

报错信息:
chromedriver installation failed error with http(s) request: error: read econnreset
有可能是wall的问题。试试替换yarn镜像源

1
yarn config set registry https://registry.npm.taobao.org

再试一次 ok了。
但是第二次之后就不行了。
怀疑是这个包有问题。于是删掉了这个包试一下。
https://github.com/yoowinsu/blog/issues/64

0%