跳到主要内容

在 TypeScript 中声明全局变量

· 阅读需 1 分钟
1adybug
子虚伊人

有时我们需要声明一些全局变量或者模块,此时我们可以使用 declare 方法来实现:

.d.ts 或者 ts 文件中:

declare global {
var tip: string
}
注意
  1. .d.ts 文件中不能有 import 语句,否则它会变成模块
  2. ts 文件必须被引入,或者是入口文件

对于浏览器打包的项目,还可以添加 window 上的变量

declare global {
var tip: string

interface Window {
tip: string
}
}

window.tip = "This is a tip."
注意

必须使用 interface 来扩展声明

在 Remix 中使用 Ant Design

· 阅读需 1 分钟
1adybug
子虚伊人

Remix.js 中使用 Ant Design 会出现首次渲染样式丢失的问题,参考 Ant Design 官方的解决方案 整体导出

npm i @ant-design/static-style-extract
npm i cross-env tsx -D
{
"scripts": {
"predev": "tsx ./scripts/genAntdCss.tsx",
"prebuild": "cross-env NODE_ENV=production tsx ./scripts/genAntdCss.tsx"
},
"dependencies": {
"@ant-design/static-style-extract": "^1.0.2"
},
"devDependencies": {
"cross-env": "^7.0.3",
"tsx": "^4.15.6"
}
}
import fs from "fs"

import { extractStyle } from "@ant-design/static-style-extract"
import { ConfigProvider } from "antd"

const outputPath = "./app/antd.min.css"

const css = extractStyle(node => <ConfigProvider theme={{ token: { colorPrimary: "red" } }}>{node}</ConfigProvider>)

fs.writeFileSync(outputPath, css)
import "./antd.min.css"

如果有自定义主题的需求,只需要传递给 ConfigProvider 相应的配置即可:

const css = extractStyle(node => <ConfigProvider theme={{ token: { colorPrimary: "red" } }}>{node}</ConfigProvider>)
注意

这种办法只能是妥协之计,打包出来的 css 文件很大。具体的优化还需要官方实现

设置滚动条样式

· 阅读需 1 分钟
1adybug
子虚伊人

很多时候,我们希望设置滚动条的样式,但是设置的前提是,我们设置了滚动条的宽度:

/* 设置滚动条的整体样式 */
::-webkit-scrollbar {
/* 滚动条的宽度,纵向滚动时有效 */
width: 12px;
/* 滚动条的高度,横向滚动时有效 */
height: 12px;
}

/* 设置滚动条滑块的样式 */
::-webkit-scrollbar-thumb {
/* 滑块的颜色 */
background-color: rgba(0, 0, 0, 0.25);
/* 滑块的圆角 */
border-radius: 6px;
}

/* 当鼠标悬停在滑块上时,改变滑块的颜色 */
::-webkit-scrollbar-thumb:hover {
/* 滑块的颜色 */
background: rgba(0, 0, 0, 0.45);
}

/* 设置滚动条轨道的样式 */
::-webkit-scrollbar-track {
/* 滚动条轨道的颜色 */
background: rgba(0, 0, 0, 0.05);
/* 滚动条轨道的圆角 */
border-radius: 6px;
}

/* 滚动条角落的颜色 */
::-webkit-scrollbar-corner {
/* 滚动条角落的颜色 */
background: rgba(0, 0, 0, 0.05);
}

如何这些样式不生效,还有可能是元素设置了 scrollbar-colorscrollbar-width 属性,导致浏览器默认样式覆盖了我们的样式,所以需要将这些属性设置为 auto

闭包

· 阅读需 1 分钟
1adybug
子虚伊人

在 JavaScript 中,很多时候我们总是认为:一个变量如果无法被访问到了,那么它就会被垃圾回收,然而事实却并非如此:

function bar() {
const a = 1
const b = 2

function log() {
console.log(a)
}

return log
}

const log = bar()
console.dir(log)
提示

使用 console.dir 可以避免控制台只打印变量的字符串形式

可以看到,闭包如下:

01

但是如果我们再创建一个函数 log2,并且引用了变量 b

function bar() {
const a = 1
const b = 2

function log() {
console.log(a)
}

function log2() {
console.log(b)
}

return log
}

const log = bar()
console.dir(log)

此时再查看闭包:

02

可以得出结论:只要产生了闭包,而上下文中的其他变量又被其他函数所引用了,即使引用的函数无法被访问,被引用的变量也不会被回收

HTML 元素 props

· 阅读需 1 分钟
1adybug
子虚伊人

很多时候我们会有元素标签上显示的属性来获取元素的 props

import { HTMLAttributes } from "react"

type MyDivProps = HTMLAttributes<HTMLDivElement>

但其实,React 内置更为方便的泛型工具 ComponentProps

import { ComponentProps } from "react"

type MyDivProps = ComponentProps<"div">

HTMLAttributes<HTMLDivElement>ComponentProps<"div"> 的区别在于,后者包含了 refkey 属性

前端守则

· 阅读需 3 分钟
1adybug
子虚伊人

技术栈

类型要求
编辑器Visual Studio Code
浏览器Chrome
语言TypeScript
UI 库React
组件库Ant Design / Deepsea Components
脚手架Rsbuild / Next
路由React Router
包管理器Yarn
时间处理Day.js
CSSTailwind / Emotion
hooksahooks
状态共享ahooks / Soda Hooks / React Soda
网络请求fetch
数据验证Zod
数据库管理Prisma
移动端Capacitor
移动端组件库Ionic
代码格式化Prettier

要求

  1. 所有 React 组件必须使用以下形式书写:

    const Component: FC = () => <div></div>

    export default Component

    // 有参数组件
    import { FC } from "react"

    export type ComponentProps = {}

    const Component: FC<ComponentProps> = props => {
    const {} = props

    return <div></div>
    }

    export default Component
  2. 全局共享状态优先使用 ahooks 实现,其次 React Soda

    import { useRequest } from "ahooks"

    export function useCount() {
    return useRequest(() => Promise.resolve(Math.random()), {
    cacheKey: "abc",
    })
    }
    注意

    ahooksStrictMode 下有 Bug,需要关闭严格模式

    import createStore from "react-soda"

    export const useCount = createStore(0)
  3. CSS 样式优先使用 Tailwind 实现,其次 Emotion 或者行内样式实现。

    注意
    • Tailwind 不支持动态属性,如 `bg-${color}`
    • 尽量不要在 Emotion 中书写动态样式,Emotion 生成的样式不会被自动清除。如果有动态需求,可以使用 CSS 变量 var(--name) 搭配行内样式实现
  4. 对于 Ant Design 的样式修改尽量通过修改主题来实现

  5. 对于表单查询的参数使用 Soda Hooks 中的 useQueryState 来存储和控制

    import { useQueryState } from "soda-hooks"

    const [query, setQuery] = useQueryState({
    keys: ["name", "unit"],
    parse: {
    age: value => (value ? Number(value) : undefined),
    },
    })
  6. 在项目根目录配置 Prettier

    // prettier.config.cjs
    module.exports = {
    // tailwind 格式化插件
    plugins: ["prettier-plugin-tailwindcss"],
    semi: false,
    tabWidth: 4,
    arrowParens: "avoid",
    printWidth: 800,
    trailingComma: "none",
    }
  7. 命令规范

在开发环境中使用 https

· 阅读需 1 分钟
1adybug
子虚伊人

现在很多环境下的网络请求要求必须是 https 请求,但是在开发或者内网环境下,无法使用 CA 机构颁发的证书,这时候可以使用 OpenSSL 提供的自签证书功能:

安装

在 windows 系统下,可以使用 winget 来安装第三方分发版本:

winget search openssl
# 选择一个版本较新的分发版本
winget install FireDaemon.OpenSSL

重启终端

生成证书

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout your-key.pem -out your-cert.pem

使用

import { readFileSync } from "fs"
import { createServer } from "https"

import express from "express"

const app = express()

const server = createServer(
{
key: readFileSync("your-key.pem"),
cert: readFileSync("your-cert.pem"),
},
app,
)

server.listen(3000)

在 Node.js 中使用 fetch 请求接口可能会报错:

TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11730:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
cause: Error: self-signed certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1674:34)
at TLSSocket.emit (node:events:514:28)
at TLSSocket._finishInit (node:_tls_wrap:1085:8)
at ssl.onhandshakedone (node:_tls_wrap:871:12) {
code: 'DEPTH_ZERO_SELF_SIGNED_CERT'
}
}

指定环境变量 NODE_TLS_REJECT_UNAUTHORIZED='0' 即可

npx cross-env NODE_TLS_REJECT_UNAUTHORIZED='0' node index.js

在 leaflet 中使用百度地图瓦片图

· 阅读需 1 分钟
1adybug
子虚伊人

安装

安装 leaflet, proj4, proj4leaflet

npm i leaflet proj4 proj4leaflet
npm i @types/leaflet @types/proj4 @types/proj4leaflet -D

使用

import L from "leaflet"

import "leaflet/dist/leaflet.css"
import "proj4leaflet"

import "./style.css"

const resolutions = Array(20)
.fill(0)
.map((_, i) => Math.pow(2, 18 - i))

const baiduCrs = new L.Proj.CRS(
"EPSG:900913",
"+proj=merc +a=6378206 +b=6356584.314245179 +lat_ts=0.0 +lon_0=0.0 +x_0=0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs",
{
resolutions,
origin: [0, 0],
bounds: L.bounds([20037508.342789244, 0], [-20037508.342789244, 20037508.342789244]),
},
)

const map = new L.Map(document.getElementById("app")!, {
center: [33.60478, 119.05186],
zoom: 19,
crs: baiduCrs,
minZoom: 3,
// 必须设置最大缩放为 19
maxZoom: 19,
})

L.tileLayer("/wanda-baidu/{z}/{x}/{y}.png", {
tms: true,
attribution: 'Map data &copy; <a href="https://map.baidu.com/">百度地图</a>',
minZoom: 3,
// 必须设置最大缩放为 19
maxZoom: 19,
}).addTo(map)

L.marker([33.60503, 119.045273]).addTo(map)

Node.js 中 child_process 模块中的 exec 和 spawn

· 阅读需 4 分钟
1adybug
子虚伊人

Node.js 的 child_process 模块允许你从 Node.js 应用程序内部运行其他程序和命令。它是一个非常强大的功能,可以用来执行操作系统级别的任务,例如启动一个新的进程来处理 CPU 密集型工作或者运行系统命令。

child_process 模块是 Node.js 中一个强大的模块,它被用于各种场景,如:

  • 在后台运行定时任务 (比如,备份数据库)
  • 利用多核 CPU 优势来提高应用性能
  • 运行系统命令,进行文件操作
  • 实现基本的并行处理

运用这个模块可以帮助你的 Node.js 应用与操作系统更紧密地交互,从而执行一些复杂的任务。不过,也需要注意,错误地使用 child_process 模块可能会导致安全问题,比如如果用户输入没有得到恰当的处理,就可能会引发命令注入攻击。因此,在使用它时必须小心谨慎。

这里有几个主要的函数和类,你可以通过 child_process 模块使用:

exec

exec 用于执行一个命令并且将结果以回调函数的形式返回。它适合用于那些产生少量输出的情况

const { exec } = require("child_process")

exec("ls", (error, stdout, stderr) => {
if (error) {
console.error(`执行的错误: ${error}`)
return
}

console.log(`stdout: ${stdout}`)
console.error(`stderr: ${stderr}`)
})

在这个例子中,我们使用了 exec 函数来运行 ls 命令,这个命令会列出当前文件夹中的所有文件。如果执行成功,它的输出会被打印到控制台。

调用方法:exec(command[, options][, callback])

参数解释:

  1. command (必须): 你想要执行的命令字符串。
  2. options (可选): 一个对象,可以用来定制操作的各种设置,例如:
    • cwd:指定子进程的当前工作目录。
    • env:环境变量键值对。
    • encoding:输出的编码。
    • timeout:超时时间,过了这个时间子进程会被杀掉。
    • shell:要使用的 shell,如果不指定,默认在 UNIX 上是 /bin/sh,在 Windows 上是 cmd.exe
  3. callback (可选): 当进程终止或有错误发生时调用的回调函数,其参数包括:
    • error:错误对象或者 null
    • stdout:子进程的标准输出。
    • stderr:子进程的标准错误输出。

execFile

在计算机中,进程(Process)是正在运行的程序的实例。Node.js 提供了一个 child_process 模块,使得它可以从 Node.js 程序中创建和控制子进程。execFile 函数是这个模块中的一个非常有用的方法,用于创建一个新的子进程来执行一个指定的程序文件,并且可以传递参数给这个程序

调用方法:execFile(file[, args][, options][, callback])

参数解释:

  1. file (必须):这是你想要执行的可执行文件的名称或者路径。如果这个文件在系统的 PATH 环境变量里定义的目录中,你可以直接提供文件名;否则,你需要提供完整的路径。

  2. args (可选):这是一个可选参数,是一个数组,包含所有你想要传递给程序的命令行参数。

  3. options (可选):同 exec 中的 options

  4. callback (可选):同 exec 中的 callback

spawn

exec 相比,spawn 会返回一个流(Stream),这使得它更适用于需要处理大量数据的情况。

const { spawn } = require("child_process")

const child = spawn("find", ["."])

child.stdout.on("data", data => {
console.log(`stdout: ${data}`)
})

child.stderr.on("data", data => {
console.error(`stderr: ${data}`)
})

child.on("close", code => {
console.log(`子进程退出码:${code}`)
})

fork

这个函数是特别为 Node.js 模块设计的。它允许你创建一个 Node.js 进程,并运行一个模块。这对于在后台执行一个任务特别有用,而不必担心阻塞主事件循环。

const { fork } = require("child_process")

const child = fork("some-module.js")

child.on("message", message => {
console.log("收到消息:", message)
})

child.send({ hello: "world" })

在这个例子中,fork 创建了一个新的 Node.js 进程来运行 some-module.js 文件。父进程通过 .send() 方法发送消息给子进程,并通过监听 message 事件来接受子进程发送的消息。

...todo

检查依赖中的包

· 阅读需 2 分钟
1adybug
子虚伊人

有时候,项目中可能会存在某个隐藏的依赖,又或者存在同一个依赖的多个版本,这时候我们可以通过以下命令来检查依赖来源:

# 推荐:npm
npm ls @types/react
# yarn
yarn why @types/react

当项目中存在多个版本的 @types/react 时,可能或报错:

不能用作 JSX 组件, 不是有效的 JSX 元素

这时我们可以在 package.json 中配置 resolutions 统一 @types/react 版本:

{
"resolutions": {
"@types/react": "^18.2.79"
}
}

以下是实际遇到的案例,在启动某项目时,总是报以下错误:

错误

去 github 一搜,发现是项目中存在多个版本 string-width 依赖(实际上只会安装最新的依赖)的问题,使用 npm ls string-width 查看:

之前

将依赖项 string-width 版本改为 ^4

{
"resolutions": {
"string-width": "^4"
}
}

再使用 npm ls string-width 查看:

之前

问题解决了。

注意
  1. 尽量使用多个版本中最低 major 版本的依赖,新版本可能是移除了某些特性
  2. 解决这一个依赖的版本问题后,再次启动项目,可能还会报错,不要慌张,仔细看一下报错信息,有可能是另一个依赖的版本问题