useQuery 中的状态
@tanstack/react-query 的 useQuery 分别提供了四种状态:
isPending:是否在等待数据,只要有数据存在,无论是缓存的,还是请求的,都不会是isPendingisFetching:是否在请求数据isRefetching:是否在重新请求数据isLoading:是否在首次请求数据,等同于isPending && isFetching
同一个 queryKey 的这些状态是共享的
@tanstack/react-query 的 useQuery 分别提供了四种状态:
isPending:是否在等待数据,只要有数据存在,无论是缓存的,还是请求的,都不会是 isPendingisFetching:是否在请求数据isRefetching:是否在重新请求数据isLoading:是否在首次请求数据,等同于 isPending && isFetching同一个 queryKey 的这些状态是共享的
使用 apt 命令安装很有可能失败,参考官方教程 install-from-a-package:
在列表中选择 Ubuntu 版本
转到 pool/stable/ 并选择适用的架构(amd64、 armhf、arm64 或 s390x)
下载 Docker Engine、CLI、containerd 和 Docker Compose 软件包的以下 deb 文件:
containerd.io_<version>_<arch>.debdocker-ce_<version>_<arch>.debdocker-ce-cli_<version>_<arch>.debdocker-buildx-plugin_<version>_<arch>.debdocker-compose-plugin_<version>_<arch>.deb安装 .deb 软件包。将以下示例中的路径更新为您下载 Docker 软件包的位置。
sudo dpkg -i ./containerd.io_<version>_<arch>.deb \
./docker-ce_<version>_<arch>.deb \
./docker-ce-cli_<version>_<arch>.deb \
./docker-buildx-plugin_<version>_<arch>.deb \
./docker-compose-plugin_<version>_<arch>.deb
Docker 守护进程自动启动。
通过运行 hello-world 镜像来验证安装是否成功:
sudo service docker start
sudo docker run hello-world
此命令下载测试映像并在容器中运行。容器运行时,它会打印一条确认消息并退出。
现已成功安装并启动了 Docker Engine
创建目录 /etc/docker:
sudo mkdir -p /etc/docker
创建并编辑文件 /etc/docker/daemon.json:
sudo vim /etc/docker/daemon.json
添加以下内容:
{
"registry-mirrors": ["https://docker.sunzishaokao.com", "https://hub.hxui.site", "https://docker.1ms.run"],
"exec-opts": ["native.cgroupdriver=systemd"]
}
其中 registry-mirrors 为镜像地址,根据实际情况替换。
重启 Docker 服务:
sudo systemctl daemon-reload
sudo systemctl restart docker
首先确定 Ubuntu 版本代号:
lsb_release -c
常见的版本代号:
备份原有的源文件:
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
修改源文件:
sudo vim /etc/apt/sources.list
将文件内容替换为以下内容:
# 中科大镜像源
deb https://mirrors.ustc.edu.cn/ubuntu/ noble main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ noble-updates main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ noble-backports main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ noble-security main restricted universe multiverse
其中 noble 为 Ubuntu 版本代号,根据实际情况替换。
更新源:
sudo apt update
wsl 默认使用的是 NAT 模式的网络,无法直接访问外部网络,可以通过修改 wsl 的网络设置,将其设置为 mirrored,使其能够直接访问外部网络。
在 C:\Users\用户名 目录下创建 .wslconfig 文件,内容如下:
[wsl2]
networkingMode=mirrored
在 wsl 中查看网络配置:
ifconfig
如果 ip 地址已经和主机在同一个网段,那么网络设置已经生效。
重启 wsl 使设置生效:
wsl --shutdown
代理可能需要重新设置,或者重启代理软件或者主机
如果要在 NAT 模式下使用 clash 代理,可以参考 获取 Windows 在 wsl 中的 ip 这篇文章
vi ~/.bashrc
在 ~/.bashrc 文件中添加以下内容:
export http_proxy=http://172.30.160.1:7890
export https_proxy=http://172.30.160.1:7890
export all_proxy=http://172.30.160.1:7890
export HTTP_PROXY=http://172.30.160.1:7890
export HTTPS_PROXY=http://172.30.160.1:7890
export ALL_PROXY=http://172.30.160.1:7890
重启终端,或者执行 source ~/.bashrc 使设置生效。
在发布 npm 包后,可以立即在 npm 上看到最新的版本,然而 npmmirror 等镜像站却有一定的延迟。如果想要立即同步到镜像站,可以使用以下方法:
在项目根目录创建 scripts 文件夹,创建 sync.mjs 文件
// @ts-check
import { readFile } from "fs/promises"
/**
* 将浏览器中直接复制的 headers 转换为对象
* @param {string} str 复制的 headers
* @returns {Headers} headers 对象
*/
function getHeaders(str) {
const reg = /^(.+?):$\n^(.+?)$/gm
const reg2 = new RegExp(reg.source, "m")
const headers = new Headers()
const match = str.match(reg)
if (!match) throw new Error("headers 格式错误")
Array.from(match).forEach(item => {
const match2 = item.match(reg2)
headers.set(match2[1], match2[2])
})
return headers
}
const headers = getHeaders(`accept:
*/*
accept-encoding:
gzip, deflate, br, zstd
accept-language:
zh-CN,zh;q=0.9,en;q=0.8
content-length:
0
dnt:
1
origin:
https://npmmirror.com
priority:
u=1, i
referer:
https://npmmirror.com/
sec-ch-ua:
"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile:
?0
sec-ch-ua-platform:
"Windows"
sec-fetch-dest:
empty
sec-fetch-mode:
cors
sec-fetch-site:
same-site
user-agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36`)
/**
* 同步包
* @param {string} packageName 包名
*/
function syncPackage(packageName) {
return fetch(`https://registry-direct.npmmirror.com/-/package/${packageName}/syncs`, {
headers,
referrer: "https://npmmirror.com/",
referrerPolicy: "strict-origin-when-cross-origin",
method: "PUT",
mode: "cors",
credentials: "omit",
})
}
async function main() {
const packageJson = JSON.parse(await readFile("package.json", "utf-8"))
await syncPackage(packageJson.name)
}
main()
在 package.json 中添加 sync 脚本
{
"scripts": {
"postpublish": "node scripts/sync.mjs"
}
}
gcTime 表示所有缓存数据回收时间。默认为 5 分钟。如果设置为 Infinity, 则表示缓存数据永不过期。如果存在多个 gcTime 值,则取缓存周期中的最大值。如果所有引用这个 queryKey 的 hooks 都被销毁了,那么这个 queryKey 的缓存数据也会被销毁。如果在销毁之后,又有新的 hooks 使用了这个 queryKey,分为两种情况:
gcTime 时间内,那么会直接使用缓存数据,且新的 gcTime 会加入到这个缓存周期中,即使这个 gcTime 比之前的 gcTime 小,也会取最大值gcTime 时间,那么会重新请求数据,会生成一个新的缓存周期staleTime 表示缓存数据新鲜时间。默认为 0。在该时间间隔内,认为数据是新鲜的,不会重新发请求。如果设置为 Infinity,则表示数据永远新鲜。
之前也分析过 ahooks 中的 useRequest(参考useRequest 中的 cacheTime 和 staleTime),规则复杂,心智负担严重,且在严格模式下数据不一致。@tanstack/query 中的 useQuery 明显更加优雅,且设计合理。
Ant Design 总是会给 a 元素设置默认样式,这样会导致在使用 a 元素时,样式不符合我们的预期。比如在 a 元素中设置了 color 为 red,但是在 Ant Design 中,a 元素的默认样式是 blue,这样就会导致我们的样式被覆盖。可以用以下方式去除默认样式:
a[href],
a[href]:active,
a[href]:hover {
color: inherit;
}
有的时候,某些函数类的参数类型是 () => (undefined | SomeType),如果我们使用箭头函数的话,就会出现返回类型不匹配的情况,比如:
import { useEffect } from "react"
useEffect(() => setTimeout(() => console.log("Hello"), 1000), [])
这时候,编辑器会给我们报错 不能将类型“Timeout”分配给类型“void | Destructor”,我们可以使用 {} 将 console.log("Hello") 包裹起来:
import { useEffect } from "react"
useEffect(
() =>
setTimeout(() => {
console.log("Hello")
}, 1000),
[],
)
但是这样的话,代码会变得很臃肿,我们可以使用 void 操作符来解决这个问题:
import { useEffect } from "react"
useEffect(() => void setTimeout(() => console.log("Hello"), 1000), [])
大功告成!
在 react router(或者 remix)中使用 Ant Design 时,如果不对 css 进行处理,会导致首屏样式丢失的问题。之前介绍过整体导入的解决方案 在 Remix 中使用 Ant Design,但是这种方式会导致打包出来的 css 文件很大。本文介绍一种更优雅的解决方案:在 SSR 中按需提取 Ant Design 的 css:
需要在项目中暴露 entry.client.tsx 和 entry.server.tsx,如果已经暴露了,可以跳过这一步:
npx react-router reveal
bunx react-router reveal
pnpx react-router reveal
yarn dlx react-router reveal
安装相应依赖:
npm i @ant-design/cssinjs @ant-design/static-style-extract
bun i @ant-design/cssinjs @ant-design/static-style-extract
pnpm i @ant-design/cssinjs @ant-design/static-style-extract
yarn add @ant-design/cssinjs @ant-design/static-style-extract
在 root.tsx 中放入 __ANTD_STYLE_PLACEHOLDER__:
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"
const isDev = process.env.NODE_ENV === "development"
export const Layout: FC<PropsWithChildren> = ({ children }) => (
<html lang="zh">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
{/** 如果需要在开发环境开启,去除 !isDev */}
{!isBrowser && !isDev && "__ANTD_STYLE_PLACEHOLDER__"}
</head>
<body>
<ConfigProvider
theme={{
token: {
colorPrimary: "#FF0000",
},
}}
>
{children}
</ConfigProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
)
修改 entry.client.tsx:
import { startTransition, StrictMode } from "react"
import { hydrateRoot } from "react-dom/client"
import { legacyLogicalPropertiesTransformer, StyleProvider } from "@ant-design/cssinjs"
import { HydratedRouter } from "react-router/dom"
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<StyleProvider transformers={[legacyLogicalPropertiesTransformer]} hashPriority="high">
<HydratedRouter />
</StyleProvider>
</StrictMode>,
)
})
修改 entry.server.tsx:
import { type RenderToPipeableStreamOptions, renderToPipeableStream } from "react-dom/server"
import { PassThrough } from "node:stream"
import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs"
import { createReadableStreamFromReadable } from "@react-router/node"
import { isbot } from "isbot"
import { type AppLoadContext, type EntryContext, ServerRouter } from "react-router"
const ABORT_DELAY = 5_000
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
loadContext: AppLoadContext,
) {
return new Promise((resolve, reject) => {
let shellRendered = false
const userAgent = request.headers.get("user-agent")
const fromBot = !!userAgent && isbot(userAgent)
// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
const readyOption: keyof RenderToPipeableStreamOptions = (userAgent && isbot(userAgent)) || routerContext.isSpaMode ? "onAllReady" : "onShellReady"
const cache = createCache()
const { pipe, abort } = renderToPipeableStream(
<StyleProvider cache={cache}>
<ServerRouter context={routerContext} url={request.url} abortDelay={ABORT_DELAY} />
</StyleProvider>,
{
[readyOption]() {
shellRendered = true
const body = new PassThrough({
transform(chunk, encoding, callback) {
chunk = String(chunk).replace("__ANTD_STYLE_PLACEHOLDER__", fromBot ? "" : extractStyle(cache))
callback(null, chunk)
},
})
const stream = createReadableStreamFromReadable(body)
responseHeaders.set("Content-Type", "text/html")
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
}),
)
pipe(body)
},
onShellError(error: unknown) {
reject(error)
},
onError(error: unknown) {
responseStatusCode = 500
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) console.error(error)
},
},
)
setTimeout(abort, ABORT_DELAY)
})
}
首先是在服务端的 HTML 代码中插入了 __ANTD_STYLE_PLACEHOLDER__
!isBrowser && !isDev && "__ANTD_STYLE_PLACEHOLDER__"
然后是将 antd 的样式抽取为 style 标签
const cache = createCache()
renderToPipeableStream(
<StyleProvider cache={cache}>
<ServerRouter context={routerContext} url={request.url} abortDelay={ABORT_DELAY} />
</StyleProvider>,
)
const css = extractStyle(cache)
将 __ANTD_STYLE_PLACEHOLDER__ 替换为抽取的 style 标签
chunk = String(chunk).replace("__ANTD_STYLE_PLACEHOLDER__", fromBot ? "" : extractStyle(cache))