首页
Search
1
解决visual studio code (vscode)安装时没有选择安装路径问题
138 阅读
2
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
131 阅读
3
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
107 阅读
4
如何在 Clash for Windows 上配置服务
77 阅读
5
Uniapp如何引入自定义样式文件
75 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,061
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
624
篇与
的结果
2024-08-24
轻松写测试:编写 JSX 直接生成 DOM 元素
首先我们以一个基本的 HTML 作为例子,这是我们的目标: Test Title Test Paragraph Test Link End DOM APIs创建 DOM 元素最直接的方式是通过 DOM APIs。const root = document.createElement('div') root.id = 'root' const title = document.createElement('h1') title.textContent = 'Test Title' root.appendChild(title) const p = document.createElement('p') root.appendChild(p) p.appendChild(document.createTextNode('Test Paragraph ')) const a = document.createElement('a') a.href = 'https://blog.crimx.com' a.textContent = 'Test Link' p.appendChild(a) p.appendChild(document.createTextNode(' End')) document.body.appendChild(root)优点 速度快。 缺点 编写较繁琐。 不直观。即便使用 document fragment 可以方便离线操作 DOM,光看代码还是不容易看出目标 HTML 是什么样子。 HTML 字符串另一个最常见的方式是编写 HTML 字符串,通过 innerHTML 解析生成 DOM 元素。const root = document.createElement('div') root.id = 'root' root.innerHTML = `Test Title Test Paragraph Test Link End` document.body.appendChild(root)优点 容易编写。 直观。 缺点 需要解析字符串,所以会有性能损失,但这个差别非常小(一般最多只有几毫秒),对于测试来说完全可以接受。 不好查错。字符串不容易 lint,出现错误需要人眼排查。 JSX这个问题其实正是 React 团队创造 JSX 语法糖的原因。利用 JSX 我们可以在 JS 文件中编写 Markup,然后转换成相应的 JavaScript 代码。默认情况下,JSX 生成的渲染函数是 React.createElement(component, props, ...children)。然而 JSX 只是语法糖,各种 JSX(TSX) 编译器都提供了选项来自定义生成的函数名。我们完全可以换成直接生成 DOM 的库。如 @babel/plugin-transform-react-jsx 提供了 pragma 等选项;TypeScript 可以在 tsconfig.json 中配置 jsxFactory。Babel 例子中配合了 deku 生成 DOM,这个库有点老,写测试也不需要虚拟 DOM 来操作,建议使用更轻量的 tsx-dom(也支持 JSX)。document.body.appendChild( Test TitleTest ParagraphTest LinkEnd )优点 容易编写。 直观。 可以使用 JSX 相关的 lint 规则。 缺点 需要搭建编译器。虽然如今项目十个有九个都是打包的,但有的脚手架需要 eject 才让配置,这点需要考虑。 总结如果测试需要大量编写 DOM 元素,使用 JSX 的方式无疑是最方便可靠的;如果只是少量几个测试,可以用 innerHTML 的方式凑合用;一遍不建议使用 DOM APIs ,因为测试首要考虑的应该是直观。
2024年08月24日
4 阅读
0 评论
0 点赞
2024-08-24
Arch Linux 使用 iptables 管理网络
GUI?面对这个需求其实第一反应是找找有没有带 GUI 的管理工具。很可惜,在 Linux 下进行应用层的网络管理似乎不容易,目前只找到一个工具 Douane,但是看 issue 貌似不是很稳定,且一个软件拆成几个仓库,编译过程十分繁琐。iptables后来了解到 iptables 这个内置的包过滤工具,发现配置起来简单多了。其解决方案是新建一个用户组,让 iptables 默认拦截扔掉所有包,但除了这个用户组中的程序。 新建用户组 internet sudo addgroup internet 保存以下脚本并运行 #!/bin/sh # 清理所有规则 sudo iptables -F # 只允许 internet 组 sudo iptables -A OUTPUT -p all -m owner --gid-owner internet -j ACCEPT # 允许本地连接 sudo iptables -A OUTPUT -p tcp -d 127.0.0.1 -j ACCEPT sudo iptables -A OUTPUT -p tcp -d 192.168.0.1/24 -j ACCEPT # 拒绝所有包 sudo iptables -A OUTPUT -j REJECT # 以 internet 为主要用户组打开新的 shell sudo -g internet -s 现在只有在新 shell 中打开的程序才可以访问网络。 如果最后的切换提示没有权限,执行 sudo visudo 最后添加 crimx ALL=(ALL:internet) ALL,代表用户 crimx 允许在 internet 组执行任意命令。crimx 换成你的用户名即可。 要恢复正常网络访问,只需重启或者恢复默认 sudo iptables-restore /etc/iptables/empty.rules
2024年08月24日
4 阅读
0 评论
0 点赞
2024-08-24
真·复用组件 - React hooks 结合 RxJS 封装异步逻辑
一个时代的结束在 React 开发中,过去一个惯例是按组件是否维护自身 state 区分为 Dumb/Pure/Presentational 和 Smart/Stateful/Container 。这并不是因为这么写更好,而是因为过去 React 中使用 state 的话必须绑定到 class 组件中,这使到 state 非常难以剥离复用和测试,故如此区分其实是一种无奈的曲线之举。这样的情况在 React hooks 出现后终于得到了改变。如果你有封装过 custom hooks 就会意识到,hooks 逻辑是独立于组件而存在的。如官方的例子import React, { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }这使到逻辑非常容易被复用和测试,所以正如 Dan Abramov 也建议不必再教条式地对组件做旧有的区分。有了 hooks 我们可以大胆地复用功能更丰富的组件。Hooks 与异步逻辑因为 hooks 是生活在函数组件中,它们很可能会被反复调用许多遍,所以一般我们的逻辑都是多包了一层函数,也叫 Thunk,这使到我们的逻辑可以延迟到恰当的时候执行。React 提供了 useEffect 和 useLayoutEffect 来让我们存放异步的逻辑。但一般来说,我们仅用 useEffect 来实现一些简单的异步逻辑,如一次性的初始化获取数据,添加外部事件监听,等等。而一些稍微复杂的逻辑我们一般是放到上层的状态管理仓库中处理,如 Redux 。这是因为复杂的异步往往会涉及到不同时间点的状态,我们往往需要用很多中间变量去维护。function useAPI(keyword) { const [result, setResult] = useState(null) useEffect(() => { let isStale = false fetchAPI(keyword).then(result => { if (!isStale) { setResult(result) } }) return () => { isStale = true } }, [keyword]) return result }如这个例子中,因为 Promise 不能取消,我们需要一个中间变量 isStale 来取消已经过时的结果,以避免早先的查询因为网络延迟问题而覆盖了后面的结果。这只是个简单的例子,对于更复杂的,按过去的思路,将这类逻辑放到 Redux 中处理是一种符合习惯的解决方法。但如果这些状态不被其它组件所共用,那么我们其实是在用一种两边不讨好的方式在开发:一来引入了额外的全局状态和仓库连接步骤,二来这个组件又不能方便地被独立复用。我们需要一种更省事的方式来封装异步逻辑。RxJS:??有人叫我?异步处理是存在已久的问题,业内早已有许多成熟的解决方案,RxJS 便是其中之一。但如果你没有接触过 RxJS 或者是新手,在搜集资料的时候可能会发现一种两极分化的情况:一部分人在惊叹赞美,一部分人在极力劝退。这是因为响应式编程用了一种常理以外的角度观察世界,也是笔者在《理解 RxJS 》中提到的,一种上帝的四维视野:逻辑不再存在于时间之中,而是在时间之外。我们不需要维护什么中间状态,每一个时间点上的状态我们都可以直接得到。当然代码还是在我们的四维世界里执行的。RxJS 有点像电子游戏,只会渲染你需要的部分。比如我们告诉 RxJS 需要时间点 1 和 4 的状态,那么到时间点 4 的时候,我们就有了 1 和 4 的状态,不需要的状态就被丢弃了,但在我们看来,它们都还在,就像游戏中我们看不见的其它场景。对于部分人来说这可能比较难接受,就像有人晕车、有人晕船,觉得不适有的人会选择再适应一下,有的人会更换其它适合自己的方式,有的人会劝大家不要坐车坐船,见仁见智。在笔者看来,这是一种正确的异步处理方式。虽然有初期的学习成本,但这个是一次性的。从 RxJS 的角度看,时间点上的状态可以像数组一样处理,这带来了极大的便利。React 中使用 RxJS唠叨了一番,那么如何在 React 中使用 RxJS 呢?首先我们已经有了相当成熟的 redux-observable,这是类似于 redux-saga 的管理方式。不一样的是 redux-saga generator 的使用方式是自用的,离开了这个框架没什么移植性,而 redux-observable 使用的 RxJS 是通用的,与这框架无关。但两者的入门成本也不一样,具体对比可以参考这里。对于 hooks 中使用 RxJS 的,目前有几个。reactjs-hooks-rxjs 是一个对订阅组件外部 Observable 的简单封装。rxjs-hooks 提供了两个 API 转换 Observable,可以与 React 的 props, state 和事件交互。在使用过程中发现两个 API 设计得过于复杂,不仅使用起来不方便,由于 hooks 不能可选且顺序必须固定的特性,复杂的接口代表了一些没用到的资源会存在空转状态。最后因为一个无法解决的 issue 笔者不得不弃用而重新设计一个轮子 observable-hooks。这个超小的库是一个全方位的解决方案,通过简化每个 API 的职责解决了空转的问题并提高了性能。React 与 RxJS 交接的地方都交给 hooks 处理,这保持了 Observable 的纯净性,允许逻辑像 Epic 一样分离测试,所以如果项目本身就用了 redux-observable 的话会非常方便。一个简单的例子,检测用户的输入状态,停下来一秒后复原。import React from 'react' import { useObservableState } from 'observable-hooks' import { timer } from 'rxjs' import { switchMap, mapTo, startWith } from 'rxjs/operators' const App = () => { const [isTyping, updateIsTyping] = useObservableState( event$ => event$.pipe( switchMap(() => timer(1000).pipe( mapTo(false), startWith(true) ) ) ), false ) return ( {isTyping ? 'Good you are typing.' : 'Why stop typing?'} ) }可以看到异步逻辑是纯净的,能够被剥离出来进行复用或测试。useObservableState 是一个简单封装避免了初始化触发额外的 setState ,核心的三个 API 是 use-observable 从变量变化到 Observable ,以及对各种 Observables 进行各种处理(merge, concat...)。 use-observable-callback 从事件回调到 Observable 。 use-subscription 从 Observable 回到外部。 通过这三个 API 的组合就可以达到 React 和 RxJS 的无缝交接,利用 Thunk 保证了 Observable 只会被创建一遍,每次都返回同样的变量。更多的例子:Pomodoro Timer ExampleTypeahead Example最后现在 observable-hooks 已经非常稳定,文档测试齐全,在「沙拉查词 7」中已经大量使用实现复杂的组件动效,效果非常不错。如果你像我一样喜欢 React 和 RxJS 的话强烈建议试一试!
2024年08月24日
11 阅读
0 评论
0 点赞
2024-08-24
配置 SplitChunksPlugin 减少 Webpack 打包体积
为什么要手动优化在优化之前我们必须先问一个问题,需不需要手动优化?过早的优化是魔鬼,许多情况下 Webpack 的自动分块已经可以满足。手动优化一般是在项目组件变得庞大之后,我们希望根据业务对部分依赖作特殊的处理,从而大幅度减少打包体积。CommonsChunkPlugin 的局限在过去 Webpack 提供了 CommonsChunkPlugin 来配置拆分和组合块(chunks),如今这个插件已被淘汰,取而代之的是更具拓展性的 SplitChunksPlugin 。两者有什么不同?从名字我们就可以看出些线索。CommonsChunkPlugin 顾名思义,其目的是提出公用的块放在一起,而 SplitChunksPlugin 则更倾向于如何把块切分出去。在 CommonsChunkPlugin 中,如果需要手动优化,提取多个公共块需要通过多次 new CommonsChunkPlugin 来分别配置。这里就有一个问题,如果一个模块存在多个公共块中该如何处理?答案是每个公共块都会存一份。因为这里每个配置都是独立的,很难再进一步统筹优化。SplitChunksPlugin 一站式配置SplitChunksPlugin 通过引入 cacheGroups 来解决这个问题。在 SplitChunksPlugin 中,手动切分多个公共块不再需要多次 new SplitChunksPlugin,而是统一在 cacheGroups 域下配置。module.exports = { //... optimization: { splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, name: 'vendor', chunks: 'all', } } } } };cacheGroups 默认会继承 optimization 的部分配置,而通过 priority 和 reuseExistingChunk 我们解决了复用的问题。模块可视化细调之前先安装 webpack-bundle-analyzer 直观地查看模块与块。沙拉查词的优化首先这是沙拉查词优化前的样子,使用了默认的 optimization 配置,打包后 5.63MB(包含一个 PDF 浏览器)。第一眼可以非常明显地观察到一个巨型包 @ant-design/icons/lib,这是 antd 3.9 引入的坑爹改变,目前没有很好的解决方式,所以这里我们手动给 antd 全家桶割一个块。因为只有三个地方用到,我们不希望其它入口加载时也引入这个块,所以要声明这次分割影响到的块,cacheGroups 中提供了 test 来匹配。同时因为这三个地方都是沙拉查词中独立的页面,一般需要用户手动访问才会加载,故这里索性将 node_modules 中三者公共的模块都切出来。neutrino.config .optimization .merge({ splitChunks: { cacheGroups: { antd: { test: /[\\/]node_modules[\\/]/, name: 'antd', chunks: ({ name }) => /^(notebook|options|history)$/.test(name), reuseExistingChunk: true } } }, })这里我配置了 reuseExistingChunk 是期望如果 node_modules 中有的模块在其它地方已经分割了,那么尽可能复用,因为如前面所述这三个页面只有用户需要时才访问,所以没有关系。效果如图,打包后 3.74MB(包含一个 PDF 浏览器)。减少了 1.89MB 的体积!接下来可以看到还有几个大块,这个主要来自于沙拉查词的查词面板,包括 React 系的模块以及各个词典的模块。它们有一个特点是必然在一起出现,所以我们给它们割一个块。neutrino.config .optimization .merge({ splitChunks: { cacheGroups: { dictpanel: { test: /([\\/]src[\\/]content[\\/])|([\\/]components[\\/]dictionaries[\\/])|([\\/]node_modules[\\/]react)/, name: 'dictpanel', chunks: ({ name }) => !/^(selection|audio-control|background)$/.test(name) }, antd: { test: /[\\/]node_modules[\\/]/, name: 'antd', chunks: ({ name }) => /^(notebook|options|history)$/.test(name), reuseExistingChunk: true } } }, })其中 src/content/ 下放的是词典面板的组件,components/dictionaries/ 下是各个词典的模块,最后匹配了所有 react 开头的模块。同时我们隔离了用不到词典面板的几个入口,避免因为用到其中的一两个模块而引入整个分割出来的块。效果如图,打包后 3.44MB(包含一个 PDF 浏览器)。相比原来减少了 2.19MB 的体积!这里我觉得已经差不多了,再往下压意义不大。可见通过简单的配置我们减少了 2.19MB 体积,还是比较划算的。最后当然 SplitChunksPlugin 能做的远不止这些,遇到问题可以多翻文档找灵感。祝大家国庆节快乐!
2024年08月24日
5 阅读
0 评论
0 点赞
2024-08-24
利用 Webpack API 获取资源清单
为什么如今几乎每个 Webpack 打包的项目都会用到 HTML Webpack Plugin。这个插件可以生成 HTML 文件带上打好的包。这在我实现一个浏览器扩展脚手架时提供了灵感。每个扩展都会有一个清单文件,里面列举了这个扩展需要加载的各种资源。{ "background": { "scripts": ["jquery.js", "my-background.js"], }, "content_scripts": [ { "matches": ["*://blog.crimx.com/*"], "js": ["common.js", "my-content.js"], "css": ["my-content.css"] } ], // ... }通常这些是手写上去的,但如果结合 Webpack 流程,我设想是能不能像 HTML Webpack Plugin 一样自动生成这些配置。如此便可发挥 Webpack 自动拆分块以及添加哈希的优势。Plugin基本的 Webpack Plugin 十分简单,在 constructor 处理配置,暴露 apply 方法实现逻辑。class WexExtManifestPlugin { constructor (options) { this.options = options } apply (compiler) { } }Tapable在 Webpack 中,API 通过 hook 勾上 Tapable 来挂载回调。不同的 Tapable 子类用于不同种类的回调。我们这里使用 Promise 处理异步回调。class WexExtManifestPlugin { constructor (options) { this.options = options } apply (compiler) { compiler.hooks.done.tapPromise('WexExtManifestPlugin',async ({ compilation }) => {}) } }CompilationCompilation 是 Webpack 最重要的 API 之一,通过 entrypoints 我们可以获得每个包的 entry 和 name ,通过 entry.getFiles() 可以获取该入口下所有文件,通过 name 可以定位到相应包名,从配置中获取其它信息。class WexExtManifestPlugin { constructor (options, neutrinoOpts) { this.options = options } apply (compiler) { compiler.hooks.done.tapPromise( 'WexExtManifestPlugin', async ({ compilation }) => { compilation.entrypoints.forEach((entry, name) => {const files = entry.getFiles().map(file => file.replace(/\.(css|js)\?.*$/, '.$1'))}) } ) } }完整的实现在这里。通过获取资源清单,脚手架可以利用 Webpack 实现复杂的优化;同时复用 Neutrino 的配置,扩展的资源配置统一到 Neutrino 入口中,不再需要手动维护。
2024年08月24日
5 阅读
0 评论
0 点赞
1
...
65
66
67
...
125