跳到主要内容

获取 windows 在 wsl 中的 ip

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

有时我们需要在 wsl 中与 windows 进行通信,比如设置代理之类,可以通过以下方式获取到 windows 的 ip:

在终端中输入:

ipconfig

带有 vEthernet (WSL (Hyper-V firewall)) 字段的适配器便是 wsl 所在的网络,ip 地址便是 windows 的 ip。

当然,有时候我可能会发现在 wsl 中无法访问到 windows 的服务,大概率是防火墙的问题,可以尝试关闭防火墙,或者新增一个入站规则,允许 wsl 访问 windows 的端口。

  1. 打开 Windows Defender
  2. 点击 防火墙和网络保护
  3. 点击 高级设置
  4. 点击 入站规则
  5. 点击 新建规则
  6. 选择 端口
  7. 选择 特定本地端口
  8. 输入 windows 的 ip 和端口
  9. 点击 确定

这样就可以在 wsl 中访问到 windows 的 ip 了。

在 linux 中向 exec 传递 env

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

在 linux 中使用 exec 执行环境命令并传递 env 参数时时,发现很多命令都丢失了:

import { exec } from "child_process"

// 正常
exec("node -v")

// 报错,提示找不到 node 命令
exec("node -v", {
env: {
NODE_ENV: "production",
NAME: "Tom",
},
})

但是在 windows 中测试,发现正常。求助万能的 ChatGPT ,得知,在 linux 中,如果传递了 env 参数,所有的环境变量都会被覆盖,所以正确的做法是:

exec("node -v", {
env: {
...process.env,
NODE_ENV: "production",
NAME: "Tom",
},
})

正常,搞定。

Antd Design 的 Form 中自定义受控组件的使用

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

自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:

  1. 提供受控属性 value 或其它与 valuePropName 的值同名的属性。

  2. 提供 onChange 事件或 trigger 的值同名的事件。

  3. 转发 ref 或者传递 id 属性到 dom 以支持 scrollToField 方法。

import { ChangeEvent, FC, forwardRef } from "react"

import { Button, Form, Input } from "antd"
import { useForm } from "antd/es/form/Form"
import FormItem from "antd/es/form/FormItem"
import { useInputState } from "soda-hooks"

type Info = {
name?: string
id?: string
}

type InfoItemProps = {
value?: Info
onChange?: (value: Info) => void
}

const InfoItem = forwardRef<HTMLDivElement, InfoItemProps>((props, ref) => {
const { value, onChange } = props

// 推荐使用 soda-hooks 的 useInputState
const [name, setName] = useInputState(value?.name)

const [id, setId] = useInputState(value?.id)

function onNameChange(e: ChangeEvent<HTMLInputElement>) {
setName(e.target.value)
// 将变化之后的 value 传递给外部,优先级为 state < props < 最新的变化的 value
onChange?.({ id, ...value, name: e.target.value })
}

function onIdChange(e: ChangeEvent<HTMLInputElement>) {
setId(e.target.value)
onChange?.({ name, ...value, id: e.target.value })
}

return (
<div ref={ref}>
<Input value={name} onChange={onNameChange} />
<Input value={id} onChange={onIdChange} />
</div>
)
})

type FormData = {
info: Info
}

const App: FC = () => {
const [form] = useForm<FormData>()

return (
<Form<FormData> form={form} onFinish={console.dir}>
<FormItem<FormData> name="info">
<InfoItem />
</FormItem>
<FormItem<FormData>>
<Button onClick={() => form.setFieldsValue({ info: undefined })}>Reset</Button>
</FormItem>
<FormItem<FormData>>
<Button htmlType="submit">Submit</Button>
</FormItem>
</Form>
)
}

export default App
提示
  1. onChange 是会一直变化的,所以需要获取到最新值
  2. onChangeform.setFieldsValue 一样,都是同步的

Ant Design 中 FormList 的使用

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

有时候在我们的表单中可能存在动态增删的情况,比如需要登记报名的人员,人员的数量是不确定的,每个人员都要登记姓名和身份证号,这便是动态表单。

简单的动态表单

import { FC, Fragment } from "react"

import { Button, Form, Input } from "antd"
import FormItem from "antd/es/form/FormItem"
import FormList from "antd/es/form/FormList"

const App: FC = () => (
<Form onFinish={console.dir}>
<FormList name="hobbies" initialValue={["钓鱼"]}>
{(fields, { add }) => (
<Fragment>
{fields.map(({ key, name }) => (
<FormItem key={key} name={name}>
<Input />
</FormItem>
))}
<Button onClick={() => add()}>Add</Button>
</Fragment>
)}
</FormList>
<Button htmlType="submit">Submit</Button>
</Form>
)

export default App

在这个表单中,hobbies 字段是动态的,使用起来也非常简单,但是有以下注意点:

  1. FormList 不接受泛型参数,直接给它传递动态的字段名即可,这里是 hobbies

  2. FormListinitialValue 就是初始值,如果,你希望初始时就有一个输入框呈现,可以传入 [undefined]

  3. initialValue 的每一项都会传递给 FormItem,继而传递给内部的表单元素。所以,必须传递相应类型的值,比如 Input 组件可以接受的初始值是 string | undefined,那 initialValue 可以接受的类型就是 (string | undefined)[],而 RangePicker 可以接受的初始值是 [Dayjs | null, Dayjs | null] | undefined,那 initialValue 可以接受的类型就是 ([Dayjs | null, Dayjs | null] | undefined)[]

  4. FormList 接受一个函数作为 children。函数的有三个参数:fieldsoperationmeta

  5. fields 是一个由 field 组成的数组,用于渲染动态表单的每一个子元素。

  6. field 有两个属性 keyname,都是用于直接传递给 FormItem。注意不能直接使用 {...field} 的形式。因为 React 组件的 key 属性必须显式地声明,也就是必须是 key={key} 的形式。

  7. opertion 有三个属性 addremovemove 属性。

  8. add 是一个用于添加表单数量的方法,接受两个参数 defaultValueinsertIndexdefaultValue 会传递给新增的 FormItem。与第 3 点类似,初始值的类型必须匹配。insertIndex 用于设置插入的位置。不能使用 <Button onClick={add}>Add</Button> 的形式,因为 onClick 是传递参数 MouseEvent 的,只能使用 <Button onClick={() => add()}>Add</Button>

  9. remove 是一个用于删除某个表单的方法,接受一个参数 index,可以是 number 也可以是 number[],对应 field 中的 name

  10. move 是一个用于移动表单的方法,接受两个参数 fromto,都对应 field 中的 name

嵌套的动态表单

import { FC, Fragment } from "react"

import { Button, Form, Input } from "antd"
import FormItem from "antd/es/form/FormItem"
import FormList from "antd/es/form/FormList"

const App: FC = () => (
<Form onFinish={console.dir}>
<FormList name="persons" initialValue={[{ name: "Tom", age: "18" }]}>
{(fields, { add }) => (
<Fragment>
{fields.map(({ key, name }) => (
<Fragment key={key}>
<FormItem name={[name, "name"]} label="姓名">
<Input />
</FormItem>
<FormItem name={[name, "age"]} label="年龄">
<Input />
</FormItem>
</Fragment>
))}
<Button onClick={() => add()}>Add</Button>
</Fragment>
)}
</FormList>
<Button htmlType="submit">Submit</Button>
</Form>
)

export default App

有时候,我们动态的表单可能不止一项属性,可能存在多个属性,属性中可能还有多个属性,这时便是嵌套的动态表单,这与简单的动态表单有以下几点不同:

  1. name 属性无法直接使用,必须是使用 [name, "age"] 这种路径数组的形式

  2. initialValue 也不再是 valueType[] 的形式,而是 { name: valueType }[] 的形式

  3. FormList 也可以再次嵌套 FormList

    <FormList name="persons">
    {fields =>
    fields.map(({ name }) => (
    <FormItem label="爱好">
    <FormList name={[name, "hobbies"]} initialValue={["钓鱼"]}>
    {(fields, { add }) => (
    <Fragment>
    {fields.map(({ key, name }) => (
    <FormItem key={key} name={name}>
    <Input />
    </FormItem>
    ))}
    <Button onClick={() => add()}>Add</Button>
    </Fragment>
    )}
    </FormList>
    </FormItem>
    ))
    }
    </FormList>

在 nginx 中使用 https

· 阅读需 1 分钟
1adybug
子虚伊人
http {
server {
# 网站端口
listen 443 ssl;
# 网站域名
server_name urdomain.com;

# 证书地址
ssl_certificate /etc/letsencrypt/live/urdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/urdomain.com/privkey.pem;

location / {

#本地服务地址
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

Ant Design 的 Form 中 requiredMark 的使用

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

之前一直都是使用 css 来实现必填组件的 * 号的隐藏,没想到 Ant Design 官方提供了修改的方法:

import React, { FC, Fragment, useState } from "react"

import { InfoCircleOutlined } from "@ant-design/icons"
import { Button, Form, Input, Radio, Tag } from "antd"
import { useForm } from "antd/es/form/Form"
import FormItem from "antd/es/form/FormItem"

type RequiredMark = boolean | "optional" | "customize"

const customizeRequiredMark = (label: React.ReactNode, { required }: { required: boolean }) => (
<Fragment>
{required ? <Tag color="error">Required</Tag> : <Tag color="warning">optional</Tag>}
{label}
</Fragment>
)

const App: FC = () => {
const [form] = useForm()
const [requiredMark, setRequiredMarkType] = useState<RequiredMark>("optional")

const onRequiredTypeChange = ({ requiredMarkValue }: { requiredMarkValue: RequiredMark }) => {
setRequiredMarkType(requiredMarkValue)
}

return (
<Form
form={form}
layout="vertical"
initialValues={{ requiredMarkValue: requiredMark }}
onValuesChange={onRequiredTypeChange}
requiredMark={requiredMark === "customize" ? customizeRequiredMark : requiredMark}
>
<FormItem label="Required Mark" name="requiredMarkValue">
<Radio.Group>
<Radio.Button value>Default</Radio.Button>
<Radio.Button value="optional">Optional</Radio.Button>
<Radio.Button value={false}>Hidden</Radio.Button>
<Radio.Button value="customize">Customize</Radio.Button>
</Radio.Group>
</FormItem>
<FormItem label="Field A" required tooltip="This is a required field">
<Input placeholder="input placeholder" />
</FormItem>
<FormItem
label="Field B"
tooltip={{
title: "Tooltip with customize icon",
icon: <InfoCircleOutlined />,
}}
>
<Input placeholder="input placeholder" />
</FormItem>
<FormItem>
<Button type="primary">Submit</Button>
</FormItem>
</Form>
)
}

export default App

requiredMark 有四种可以传递的值:

  1. true 默认值

  2. false 不显示是否必填

  3. "optional" 在可选表单项的 label 后面添加 (可选)

  4. (label: ReactNode, { required }: { required: boolean }) => ReactNode 自定义渲染函数

使用正则表达式替换字符串

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

当我们使用正则字符串替换字符串时,replace 函数的第二个表达式可以传入一个函数,函数的参数含义为:

/**
* 第一个参数为匹配到的字符串
* 从第二个参数开始为匹配到的所有分组
* 倒数第二个参数为匹配到的字符串的序列号
* 最后一个参数为原始字符串
*/
function replacer(match: string, arg1: string, arg2: string, ...args: string[], index: number, str: string): string {}

在 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