普通视图

发现新文章,点击刷新页面。
今天 — 2026年5月2日首页

每日一题-旋转数字🟡

2026年5月2日 00:00

我们称一个数 X 为好数, 如果它的每位数字逐个地被旋转 180 度后,我们仍可以得到一个有效的,且和 X 不同的数。要求每位数字都要被旋转。

如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。0, 1, 和 8 被旋转后仍然是它们自己;2 和 5 可以互相旋转成对方(在这种情况下,它们以不同的方向旋转,换句话说,2 和 5 互为镜像);6 和 9 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。

现在我们有一个正整数 N, 计算从 1 到 N 中有多少个数 X 是好数?

 

示例:

输入: 10
输出: 4
解释: 
在[1, 10]中有四个好数: 2, 5, 6, 9。
注意 1 和 10 不是好数, 因为他们在旋转之后不变。

 

提示:

  • N 的取值范围是 [1, 10000]

【宫水三叶】简单模拟题

作者 AC_OIer
2022年9月25日 08:44

模拟

利用 $n$ 的范围为 $1e4$,我们可以直接检查 $[1, n]$ 的每个数。

由于每一个位数都需要翻转,因此如果当前枚举到的数值 x 中包含非有效翻转数字(非 0125689)则必然不是好数;而在每一位均为有效数字的前提下,若当前枚举到的数值 x 中包含翻转后能够发生数值上变化的数值(2569),则为好数。

代码:

###Java

class Solution {
    public int rotatedDigits(int n) {
        int ans = 0;
        out:for (int i = 1; i <= n; i++) {
            boolean ok = false;
            int x = i;
            while (x != 0) {
                int t = x % 10;
                x /= 10;
                if (t == 2 || t == 5 || t == 6 || t == 9) ok = true;
                else if (t != 0 && t != 1 && t != 8) continue out;
            }
            if (ok) ans++;
        }
        return ans;
    }
}

###TypeScript

function rotatedDigits(n: number): number {
    let ans = 0
    out:for (let i = 1; i <= n; i++) {
        let ok = false
        let x = i
        while (x != 0) {
            const t = x % 10
            x = Math.floor(x / 10)
            if (t == 2 || t == 5 || t == 6 || t == 9) ok = true
            else if (t != 0 && t != 1 && t != 8) continue out
        }
        if (ok) ans++
    }
    return ans
};

###Python3

class Solution:
    def rotatedDigits(self, n: int) -> int:
        ans = 0
        for i in range(1, n + 1):
            ok, x = False, i
            while x != 0:
                t = x % 10
                x = x // 10
                if t == 2 or t == 5 or t == 6 or t == 9:
                    ok = True
                elif t != 0 and t != 1 and t != 8:
                    ok = False
                    break
            ans = ans + 1 if ok else ans
        return ans
  • 时间复杂度:共有 $n$ 个数需要枚举,检查一个数需要遍历其每个数字,复杂度为 $O(\log{n})$。整体复杂度为 $O(n\log{n})$
  • 空间复杂度:$O(1)$

最后

如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。

所有题解已经加入 刷题指南,欢迎 star 哦 ~

数位 DP 通用模板(Python/Java/C++/Go)

作者 endlesscheng
2022年9月25日 08:29

视频讲解,从 19:30 开始(基于题目 2376. 统计特殊整数)。
讲了数位 DP 的通用模板,以及如何使用该模板秒杀相关困难题目。
讲完题目后还讲了一些上分的训练技巧。


根据题意,好数中不能有 $3,4,7$,且至少包含 $2,5,6,9$ 中的一个。

将 $n$ 转换成字符串 $s$,定义 $f(i,\textit{hasDiff}, \textit{isLimit}, \textit{isNum})$ 表示构造从左往右第 $i$ 位及其之后数位的合法方案数,其余参数的含义为:

  • $\textit{hasDiff}$ 表示前面填的数字是否包含 $2,5,6,9$(至少一个)。
  • $\textit{isLimit}$ 表示当前是否受到了 $n$ 的约束。若为真,则第 $i$ 位填入的数字至多为 $s[i]$,否则可以是 $9$。如果在受到约束的情况下填了 $s[i]$,那么后续填入的数字仍会受到 $n$ 的约束。
  • $\textit{isNum}$ 表示 $i$ 前面的数位是否填了数字。若为假,则当前位可以跳过(不填数字),或者要填入的数字至少为 $1$;若为真,则要填入的数字可以从 $0$ 开始。

后面两个参数可适用于其它数位 DP 题目。

枚举要填入的数字,具体实现逻辑见代码。对于本题来说,由于前导零对答案无影响,$\textit{isNum}$ 可以省略。

下面代码中 Java/C++/Go 只需要记忆化 $(i,\textit{hasDiff})$ 这个状态,因为:

  1. 对于一个固定的 $(i,\textit{hasDiff})$,这个状态受到 $\textit{isLimit}$ 的约束在整个递归过程中至多会出现一次,没必要记忆化。比如 $n=1234$,当 $i=2$ 的时候,前面可以填 $10,11,12$ 等等,如果受到 $\textit{isLimit}$ 的约束,就说明前面填的是 $12$。「当 $i=2$ 的时候,前面填的是 $12$」这件事情,在整个递归中只会发生一次。
  2. 另外,如果只记忆化 $(i,\textit{hasDiff})$,$\textit{dp}$ 数组的含义就变成在不受到约束时的合法方案数,所以要在 !isLimit 成立时才去记忆化。接着上面的例子,在前面填 $12$ 的时候,下一位填的数字不能超过 $3$,因此算出来的结果是不能套用到前面填的是 $10,11$ 这些数字上面的。
DIFFS = (0, 0, 1, -1, -1, 1, 1, -1, 0, 1)

class Solution:
    def rotatedDigits(self, n: int) -> int:
        s = str(n)
        @cache
        def f(i: int, has_diff: bool, is_limit: bool) -> int:
            if i == len(s):
                return has_diff  # 只有包含 2/5/6/9 才算一个好数
            res = 0
            up = int(s[i]) if is_limit else 9
            for d in range(0, up + 1):  # 枚举要填入的数字 d
                if DIFFS[d] != -1:  # d 不是 3/4/7
                    res += f(i + 1, has_diff or DIFFS[d], is_limit and d == up)
            return res
        return f(0, False, True)
class Solution {
    private static int[] DIFFS = {0, 0, 1, -1, -1, 1, 1, -1, 0, 1};

    public int rotatedDigits(int n) {
        char[] s = Integer.toString(n).toCharArray();
        int[][] memo = new int[s.length][2];
        for (int[] row : memo) {
            Arrays.fill(row, -1);
        }
        return dfs(0, 0, true, s, memo);
    }

    private int dfs(int i, int hasDiff, boolean isLimit, char[] s, int[][] memo) {
        if (i == s.length) {
            return hasDiff; // 只有包含 2/5/6/9 才算一个好数
        }
        if (!isLimit && memo[i][hasDiff] >= 0) {
            return memo[i][hasDiff];
        }
        int res = 0;
        int up = isLimit ? s[i] - '0' : 9;
        for (int d = 0; d <= up; d++) { // 枚举要填入的数字 d
            if (DIFFS[d] != -1) { // d 不是 3/4/7
                res += dfs(i + 1, hasDiff | DIFFS[d], isLimit && d == up, s, memo);
            }
        }
        if (!isLimit) {
            memo[i][hasDiff] = res;
        }
        return res;
    }
}
int diffs[10] = {0, 0, 1, -1, -1, 1, 1, -1, 0, 1};

class Solution {
public:
    int rotatedDigits(int n) {
        string s = to_string(n);
        int m = s.size();
        vector<array<int, 2>> memo(m, {-1, -1});

        auto dfs = [&](this auto&& dfs, int i, bool has_diff, bool is_limit) -> int {
            if (i == m) {
                return has_diff; // 只有包含 2/5/6/9 才算一个好数
            }
            if (!is_limit && memo[i][has_diff] >= 0) {
                return memo[i][has_diff];
            }
            int res = 0;
            int up = is_limit ? s[i] - '0' : 9;
            for (int d = 0; d <= up; d++) { // 枚举要填入的数字 d
                if (diffs[d] != -1) { // d 不是 3/4/7
                    res += dfs(i + 1, has_diff || diffs[d], is_limit && d == up);
                }
            }
            if (!is_limit) {
                memo[i][has_diff] = res;
            }
            return res;
        };

        return dfs(0, false, true);
    }
};
var diffs = [10]int{0, 0, 1, -1, -1, 1, 1, -1, 0, 1}

func rotatedDigits(n int) int {
s := strconv.Itoa(n)
m := len(s)
memo := make([][2]int, m)
for i := range memo {
memo[i] = [2]int{-1, -1}
}
var dfs func(int, int, bool) int
dfs = func(i, isDiff int, isLimit bool) (res int) {
if i == m {
return isDiff // 只有包含 2/5/6/9 才算一个好数
}
if !isLimit {
p := &memo[i][isDiff]
if *p >= 0 {
return *p
}
defer func() { *p = res }()
}
up := 9
if isLimit {
up = int(s[i] - '0')
}
for d := 0; d <= up; d++ { // 枚举要填入的数字 d
if diffs[d] != -1 { // d 不是 3/4/7
res += dfs(i+1, isDiff|diffs[d], isLimit && d == up)
}
}
return
}
return dfs(0, 0, true)
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mD)$,其中 $m=\mathcal{O}(\log n),\ D=10$。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题状态个数等于 $\mathcal{O}(m)$,单个状态的计算时间为 $\mathcal{O}(D)$,所以动态规划的时间复杂度为 $\mathcal{O}(mD)$。
  • 空间复杂度:$\mathcal{O}(m)$。

专题训练

见下面动态规划题单的「十、数位 DP」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

788. 旋转数字:动态规划

作者 tuotuoli
2019年7月12日 01:09

动归数组d,d[i]表示数字i的状态。

d[i]对应有三个值,1是好数,0是普数,-1是坏数。

好数是旋转后不同的数比如(2,5,6,9),普数是旋转以后相同的数如(0,1,8),坏数是旋转后不能成立的数如(3,4,7),这里前十个数的好坏情况通过切片给预处理了。

某数字i末位或末位以外的数字为坏数,数字i就肯定是坏数,例如(897,389,941)等等。

如果数字i不是坏数,那只要末位或末位以外的数字存在好数,那数字i就肯定是好数,如(120,886,509)等等就是好数,如(808,880)就是普数,程序这里计算用了按位或"|"。

如果数字i的结果d[i]是1是好数,那总答案就加1。

主要是末位以外的数字在计算该数字前肯定计算过了,所以可以动归。

class Solution:
    def rotatedDigits(self, N: int) -> int:
        ans = 0
        d = [0, 0, 1, -1, -1, 1, 1, -1, 0, 1] + [0] * (N - 9)
        for i in range(N + 1):
            if d[i // 10] == -1 or d[i % 10] == -1:
                d[i] = -1
            elif d[i // 10] == 1 or d[i % 10] == 1:
                d[i] = 1
                ans += 1
        return ans
class Solution:
    def rotatedDigits(self, N: int) -> int:
        ans, d = 0, [0, 0, 1, -1, -1, 1, 1, -1, 0, 1] + [0] * (N - 9)
        for i in range(N + 1):
            d[i] = -1 in (d[i // 10], d[i % 10]) and -1 or d[i // 10] | d[i % 10]
            ans += d[i] == 1
        return ans
func rotatedDigits(N int) int {
    ans, d := 0, make([]int, N + 1)
    copy(d, []int{0, 0, 1, -1, -1, 1, 1, -1, 0, 1})
    for i := 0; i <= N; i++ {
        if d[i / 10] == -1 || d[i % 10] == -1 {
            d[i] = -1
        } else if d[i] = d[i / 10] | d[i % 10]; d[i] == 1 {
            ans++
        }
    }
    return ans
}
impl Solution {
    pub fn rotated_digits(n: i32) -> i32 {
        let mut d: Vec<i32> = vec![0, 0, 1, -1, -1, 1, 1, -1, 0, 1];
        d.extend(vec![0; (n - 9).max(0) as usize]);
        let mut ans = 0;
        for i in 0..=n as usize {
            d[i] = if d[i / 10] == -1 || d[i % 10] == -1 {-1} else {d[i / 10] | d[i % 10]};
            ans += if d[i] == 1 {1} else {0};
        }
        ans
    }
}
var rotatedDigits = function(N) {
    let ans = 0
    const d = [0, 0, 1, -1, -1, 1, 1, -1, 0, 1].concat(Array(Math.max(0, N - 9)).fill(0))
    for (let i = 0; i <= N; ++i) {
        if (d[Math.floor(i / 10)] == -1 || d[i % 10] == -1) {
            d[i] = -1
        } else if (d[Math.floor(i / 10)] == 1 || d[i % 10] == 1) {
            d[i] = 1
            ++ans
        }
    }
    return ans
};
function rotatedDigits(N: number): number {
    let ans: number = 0;
    let d: number[] = [0, 0, 1, -1, -1, 1, 1, -1, 0, 1, ...Array(Math.max(N - 9, 0)).fill(0)];
    for (let i of Array.from({length: N + 1}, (_, k) => k)) {
        let [j, k] = [Math.trunc(i / 10), i % 10];
        d[i] = (d[j] == -1 || d[k] == -1) && -1 || d[j] | d[k];
        ans += Number(d[i] == 1);
    }
    return ans
};

pyimage.png
goimage.png
rsimage.png
jsimage.png
tsimage.png

昨天 — 2026年5月1日首页

Netcat Cheatsheet

Basic Syntax

Core nc command forms.

Command Description
nc host port Open a TCP connection
nc -u host port Open a UDP connection
nc -l port Listen on a local port
nc -h Show help and options
man nc Read the local Netcat manual

Connect and Listen

Create simple client and server connections.

Command Description
nc example.com 80 Connect to TCP port 80
nc -l 5555 Listen on port 5555
nc server.example.com 5555 Connect to a listening host
nc -v host 22 Connect with verbose output
nc -n 192.168.1.10 22 Skip DNS lookups

Port Checks

Check whether TCP ports are open.

Command Description
nc -z -v host 22 Check one TCP port
nc -z -v host 20-80 Check a port range
nc -z -v host 80 443 Check selected ports
nc -z -w 3 host 443 Check with a timeout
nmap host Use Nmap for deeper scans

UDP

Use UDP instead of TCP.

Command Description
nc -u host 53 Connect to a UDP service
nc -u -l 5555 Listen for UDP datagrams
nc -z -v -u host 53 Check a UDP port
nc -u -w 3 host 123 UDP check with timeout
nc -u 192.168.1.10 5555 Send text to a UDP listener

File Transfers

Send files between two hosts.

Command Description
nc -l 5555 > file Receive a file
nc host 5555 < file Send a file
nc -l 5555 | tar xzvf - Receive and extract a directory
tar czvf - dir | nc host 5555 Archive and send a directory
nc -w 5 host 5555 < file Send with a timeout

HTTP and Raw Requests

Send plain text requests to network services.

Command Description
printf "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | nc example.com 80 Send an HTTP request
echo "QUIT" | nc mail.example.com 25 Send a simple SMTP command
echo "PING" | nc host 5555 Send text to a listener
nc host 80 Type a request manually
curl http://example.com Use curl for HTTP work

Timeouts and Persistent Listeners

Control how long Netcat waits and whether it keeps listening.

Command Description
nc -w 5 host 80 Timeout after 5 seconds
nc -k -l 5555 Keep listening after disconnect
nc -v -w 3 host 22 Verbose connection with timeout
nc -l 8080 < index.html Serve one file on port 8080
while true; do nc -l 8080 < index.html; done Serve the file repeatedly

Troubleshooting

Quick checks for common Netcat issues.

Issue Check
nc: command not found Install netcat-openbsd, netcat-traditional, or nmap-ncat
Connection refused Confirm the remote service is running and listening on that port
Command hangs Add -w to set a timeout
UDP result is unclear UDP has no TCP-style handshake, use Nmap for reliable scans
Option behaves differently Run man nc and check the Netcat implementation on that system

Related Guides

Use these guides for broader networking workflows.

Guide Description
Netcat Command in Linux Full Netcat guide with examples
Nmap Command in Linux Scan hosts and ports in more detail
curl cheatsheet Work with HTTP requests and APIs
tcpdump cheatsheet Capture and inspect network packets
ss Command in Linux Inspect sockets and listening services

追觅造车,从“火箭”开始

2026年5月1日 18:26

4月27日,旧金山艺术宫,这座美国百年历史地标迎来了一场科技发布会。

追觅科技以“DREAME NEXT”为主题的全球发布会周在此开幕,展台上那台红色超跑的尾部,两个固体火箭推进器格外醒目。这便是追觅“星空计划”重磅打造的Nebula NEXT 01 JET Edition,业内称其为“火箭车”。 

从车企研发负责人到供应链从业者再到行业分析师,目光都聚焦于此。 

这款火箭车搭载专属定制双固体火箭助推系统,可实现150毫秒瞬时响应,最大推力100千牛,与波音737单台发动机的推力接近,车辆零百加速仅需0.9秒。 

同时亮相的还有旗舰级激光雷达DHX1、首款可量产全固态电池、全线控智能底盘等一系列“星空十二全栈技术体系”。 

然而,比参数更值得关注的是这场发布会的性质。与其说这款火箭车是追觅的最新成果展示,倒不如说追觅是以这款火箭车为载体,开启了一场面向全球的技术预演。 

这背后,是追觅一以贯之的理念:先有技术,后有产品。 

追觅造车,早有预兆

当下,中国汽车产业正面临一场升级:如今的中国新能源汽车在制造能力与智能化水平上已走在世界前列,但激烈角逐仍多局限于国内市场,尚未在全球高端舞台形成真正突破。 

而全球高端新能源市场,眼下仍是一片蓝海。传统超豪华品牌电动化步伐谨慎,新兴品牌也尚未建立起稳固的标杆。这正是追觅用技术试图切入的空白。 

但汽车终究是高度复杂的产业。想要登顶全球市场,出色的软硬件实力只是入场券,更关键的是能否为行业开辟全新的发展路径,带来更新的技术与产品定义。 

追觅火箭车的出现,正是在尝试回应这一更深层的命题。 

自追觅创立以来,便始终坚持着一个底层信条:核心技术是一切的根本。创始人俞浩在创立公司之时,便明确了“产品必须要具备一定技术壁垒”的理念,公司创立后的第一个项目,便是做比肩国际最高水平的10万转速数字马达。 

有意思的是,智能清洁设备中的高速马达与新能源汽车所搭载的电驱动马达,在核心逻辑上高度相通:两者均需实现电能的高效转化与动力的精准调控,同时必须满足高负荷工况下的运行稳定性等严苛量产要求。  

而动力性能一直是高端市场的图腾和标志,也是技术研发的痛点和难点。 

相较燃油车的发动机,新能源汽车的驱动电机技术难度以及功能虽然已经大幅下降,例如10万元级的产品,其零百加速都有可能进入8秒甚至6秒。但实际上,超高端车的动力性能仍有不小的挑战。 

驱动电机的研发难点,在于如何超高速下的材料物理极限,以及运行的稳定性和效率。 

而对电动马达的研发已经长达十年的追觅,不仅在气动、电磁、驱动、电力电子等关键技术领域有所突破,更是在动平衡、减震、降噪、热设计等量产痛点上积累了相当的经验。在国际厂商的电动马达转速仍普遍停留在12.5万转时,追觅已将这一数字突破至20万转。 

更重要的是,追觅对核心技术的投入远不止于高速马达这一个领域。在智能算法、仿生机械臂、全域智能芯片等诸多前沿领域,追觅早已持续布局。 

技术的共通性与多年耕耘的厚积薄发,让追觅“跨界造车”早已注定。 

追觅的筹码,不止技术

回望百年汽车工业,每个时代的行业标杆,从来都是重构整车核心概念、开辟全新发展赛道的创新先行者。如奔驰以初代机械架构奠定现代汽车根基,特斯拉凭智能座舱极简理念定义新能源造车逻辑。 

整车核心概念,是一款车的灵魂,更是品牌立足行业的核心底气与发展上限。 

行业内具备超高速驱动电机研发能力的企业,远不止追觅一家。然而,追觅却是少数能将技术真正推向突破,并成功应用于智能汽车这一复杂场景的企业,这其中的关键不仅在于持续投入,更在于对技术本质的深刻理解,以及对市场趋势的精准洞察。 

追觅火箭车的真正价值,在于它跳出了固有的行业范式。百公里加速的极限难道只有2秒?驱动方式是否只能在燃油与纯电之间二选一?在电动马达与火箭助推技术均有布局的追觅看来,或许有更多新的可能。 

而这也是当下行业技术创新的核心:融合与突破。汽车是典型的重资产行业,确保产品从设计到量产顺利落地,一直以来都是中国车企面临的现实挑战。也因此,大量精力被生产制造环节所占据,往往挤占了企业在核心技术上的持续投入。 

随着行业制造能力整体提升,焦点正重新回归到核心技术本身。只有通过技术突破,才能为行业打开新的增长空间。 

追觅扮演的正是这样的角色。作为消费电子领域的长期参与者,它将自身对技术的理解与对市场的洞察相融合,精准切入汽车产业的升级节点。 

当然,仅有愿景远远不够。想要真正造车,工程实现能力是不可或缺的一课。追觅的跨界实践,正是在验证这条从技术到产品、从洞察到实现的全链路。 

在智能感知层面,追觅星空计划与全球顶尖核心合作伙伴联合开发推出超高线激光雷达DHX1,基于其6D全彩千线激光雷达平台,可同步感知三维空间坐标XYZ与物体RGB色彩信息,直接生成原生彩色点云。 

DHX1支持最高4320线全彩4K超高清感知,最远测距可达600米,10%反射率下测距仍达400米,可清晰识别300米内的水马、280米内的小动物等细小目标。无论是红绿灯、车道线,还是各类微小目标,都能实现精准识别。 

底盘操控层面,全线控与AI协同的智能底盘,则打破了传统机械操控局限,让“火箭车”得以兼顾极限加速与行车稳定;动力续航层面,全固态电池搭配一体化封装技术,“火箭车”更是解决了当前新能源汽车的能源痛点。 

理念、技术与工程能力,从这三个维度上,追觅正努力构建高端新能源车所需的核心技术闭环。这不仅是一家企业的进取方向,也契合着中国汽车产业向上突破的整体路径。 

中国新能源产业的第三次跃迁

中国新能源汽车产业历经十余年高速发展,造车新势力早已完成两轮迭代升级。 

第一代造车新势力以特斯拉、蔚来、理想、小鹏为核心代表,解决了新能源汽车从概念到规模化量产的挑战。 

第二代造车新势力则以鸿蒙智行为典型代表,其核心发展逻辑不在于亲自下场造车、布局整车重资产,而是依托自身科技研发的优势,聚焦智能座舱、智能驾驶、车载生态等核心领域深度赋能车企,让新能源汽车从平价走向高端。 

追觅星空计划和火箭车的问世,正式标志着第三代造车新势力登上行业舞台。 

经过多年深耕积淀,中国已经形成全球最完备、技术最领先、配套最高效的新能源汽车全产业链体系,上游核心零部件、中游整车制造、下游智能生态配套均已实现自主可控,产业硬实力已经具备冲击全球高端赛道的基础条件。但进军全球高端市场,更需要在原创技术、品牌塑造与全球运营有所突破。 

追觅代表的,正是这样一种“第三种力量”:它既延续了前两代技术攻坚的核心优势,又避开了重资产内卷与纯软件赋能现实情况。凭借多年在消费电子与智能硬件领域的积累,掌握了电驱动、AI 感知、智能算法、多品类硬件集成的全链条能力,搭建起独有的人车家一体技术生态。 

从这个角度来看,火箭车更像是追觅对外展示技术成果的一项里程碑,它向外界传达的信息很简单:追觅造车,并非从零开始,其根基是追觅多年以来的深厚技术储备,也是其先有技术,后有产品的理念体现。 

从清华工场到火箭车,从10万转马达到20万转,从扫地机器人的激光雷达到汽车的超高清4K感知,追觅用时间证明:想要真正能够在一个行业里有所创新的人,从不跟随既定的规则,它们会选择以底层技术的持续突破,重新定义赛道本身。 

现在的中国汽车产业,正在进入一个由技术生态驱动的新阶段。而追觅,已经站在了这场变革的前沿。 

封面来源 | 企业

苹果现金战略重大转向

2026年5月1日 17:58
苹果首席财务官凯文・帕雷在分析师电话会上透露,公司正式放弃长期奉行的净现金中性现金管理策略。帕雷并未直白解释战略转向原因,仅用官方套话含糊带过。但此次调整意味着,苹果未来或将加大现金储备、留存更多现金流。(新浪财经)

React 性能优化精讲

作者 Wect
2026年5月1日 17:53

在日常 React 项目开发中,绝大多数开发者都会陷入一个核心误区:默认 React 框架本身高性能,业务项目就一定流畅无卡顿。但在真实企业级项目落地中,我们频繁遇到各类性能问题:首屏白屏耗时久、页面滚动帧率暴跌、表单输入响应延迟、应用长期运行越用越卡、偶发全局白屏崩溃等。

究其本质:React 仅封装了高效的底层视图更新机制,并不会自动优化业务代码。框架解决了原生 JS 频繁操作 DOM 的低效问题,但项目中出现的无效重渲染、重复计算、资源冗余、主线程阻塞、内存泄漏等核心性能问题,全部源于业务代码不规范、状态设计不合理、工程配置不完善。

本文将从浏览器底层渲染原理、React 核心更新机制、组件级精准优化、大数据场景专项优化、首屏全链路工程优化、应用稳定性治理、React18 高阶并发调度、状态架构源头优化八个核心维度,由浅入深、层层递进,结合通俗解读与专业原理,搭建一套闭环、可落地、成体系的 React 性能优化方案。全文逻辑严谨、流程清晰、案例完整可复用,既适合开发者深度学习沉淀技术笔记,也可直接用于团队技术分享、项目性能复盘与架构优化落地。

一、底层基石:前端性能优化的本质逻辑

所有前端页面的性能问题,最终都指向浏览器主线程。浏览器的 JS 解析执行、DOM 节点操作、CSS 样式计算、页面布局绘制、交互事件响应全部依赖主线程,且主线程为单线程串行执行,同一时间仅能处理一项任务。一旦主线程被耗时任务长时间阻塞,页面就会出现卡顿、输入延迟、点击无响应、卡死、白屏等问题。

因此,前端性能优化的终极本质可归纳为四条核心准则,所有 React 优化方案均围绕这四点展开:

  1. 减少无效 JS 计算:规避重复执行、冗余计算、无意义逻辑执行,降低 JS 执行耗时;

  2. 减少冗余 DOM 更新:最小化真实 DOM 操作频次,减少浏览器重排、重绘开销;

  3. 精简网络资源:压缩资源体积、减少请求次数、优化加载策略,极速首屏渲染;

  4. 规避主线程阻塞:拆分耗时任务、优先级调度任务,保障用户交互高优先级执行。

想要做好 React 性能优化,不能只靠 API 堆砌,必须先吃透浏览器渲染底层逻辑与 React 视图更新机制,从根源理解性能瓶颈的产生原因。

1.1 浏览器完整渲染流水线(核心性能理论)

浏览器从接收前端代码到最终页面可视化展示,遵循一套固定、不可逆的渲染流水线,任意环节耗时过长都会直接影响用户体验,完整流程如下:

解析HTML生成DOM树 → 解析CSS生成CSSOM树 → 合成渲染树 → 布局(重排)计算元素尺寸位置 → 绘制(重绘)像素填充 → 图层合成 → 页面最终展示

在整条流水线中,**重排(Reflow)重绘(Repaint)**是影响页面性能的两大核心概念,必须精准区分:

  • 重排(Reflow,回流):当元素的布局尺寸、位置、层级、盒模型属性发生变更时,浏览器需要重新计算页面所有相关元素的布局信息,触发完整渲染流水线。重排开销极高,是页面卡顿的核心元凶

  • 重绘(Repaint):仅元素颜色、背景色、透明度、阴影等纯样式属性变更,不改变页面布局结构,无需重新计算元素位置尺寸。开销远低于重排,但高频、大批量重绘依然会造成页面掉帧卡顿。

而 React 虚拟 DOM + Diff 算法的核心价值,正是精准对比视图差异,只推送最小粒度的 DOM 更新补丁,最大限度减少真实 DOM 操作,从源头降低浏览器重排、重绘的性能开销。

1.2 React 视图更新完整链路

React 采用经典的数据驱动视图设计思想,摒弃原生手动操作 DOM 的模式,通过状态变更自动触发视图更新。其完整更新流程分为协调阶段提交阶段两大核心阶段,两个阶段的执行特性完全不同,也是性能优化的关键切入点。

完整更新链路流程

State / Props / Context 状态变更 → 对应组件标记为待更新状态 → 进入协调阶段(生成新虚拟DOM、新旧虚拟DOM Diff 比对、计算最小更新补丁) → 进入提交阶段(批量操作真实 DOM、触发浏览器渲染流水线) → 完成页面视图更新

两大阶段核心差异

  • 协调阶段(内存计算):纯 JS 内存运算,不涉及任何真实 DOM 操作,运行开销极低。React18 及以上版本支持任务暂停、中断、优先级插队,调度灵活性大幅提升。

  • 提交阶段(DOM 操作):执行真实 DOM 增删改操作,触发浏览器重排重绘,开销极大。该阶段为同步执行、不可中断,是 React 项目绝大多数性能瓶颈的核心场景

1.3 React 原生机制的四大天然性能缺陷

很多人误以为 React 框架自带极致性能,实则不然。React 为了兼顾通用性、灵活性与开发体验,底层设计天然存在性能冗余,这也是我们需要手动做业务层优化的根本原因:

  1. 无条件递归更新:父组件触发重渲染时,默认会递归触发整棵子组件树重渲染,与子组件自身数据是否变更无关,产生大量无效渲染。

  2. 无自动缓存机制:函数组件每次重渲染都是一次全新的函数执行,内部定义的函数、对象、数组都会生成全新内存引用,极易触发不必要的更新。

  3. 浅比较局限性:Diff 算法、所有 React 缓存 API 均采用浅比较策略,无法识别嵌套对象、深层数组的属性变更,容易出现更新失效或过度更新问题。

  4. 同步阻塞渲染(React18 前):旧版本 React 渲染任务一旦启动必须执行完毕,无法中断,大数据渲染、复杂视图更新会直接阻塞主线程,造成交互卡顿。

二、组件层核心优化:彻底解决无效重渲染问题

无效重渲染是 React 项目最普遍、性价比最高、优先级最靠前的优化点。所谓无效重渲染,即组件的状态、props、依赖无任何有效变更,但组件依然重复执行渲染逻辑、参与 Diff 比对,白白消耗主线程资源,长期累积就会造成页面卡顿。

2.1 无效重渲染三大核心根源

经过大量企业项目复盘,99% 的组件无效重渲染均来自以下三种场景,其中后两种为高频隐形坑:

  1. 自身状态更新:组件内部 state、context 发生有效变更触发渲染,属于合理渲染,无需优化;

  2. 父组件渲染传导:父组件任意状态更新,无论子组件 props、数据是否变动,子组件都会无条件跟随重渲染,是最核心的无效渲染场景

  3. 引用地址陷阱:组件内联定义函数、对象、数组,每次组件渲染都会生成全新内存引用,浅比较机制会判定数据更新,强制触发子组件重渲染,隐蔽性极强。

2.2 优化前置原则:先测速,后优化

性能优化最大的误区是盲目堆砌 memo、useMemo、useCallback。所有缓存 API 都存在内存开销和代码复杂度成本,滥用、错用不仅无法提升性能,反而会造成内存冗余、代码可读性下降,引发负优化

企业级标准优化流程:先定位瓶颈、再精准优化、最后验证效果,杜绝无意义优化。

React DevTools Profiler 性能排查完整流程

  1. 安装官方 React 开发者工具插件,切换至 Profiler 性能面板;

  2. 开启录制按钮,复现页面卡顿、频繁更新、输入延迟等问题场景;

  3. 停止录制,查看组件渲染耗时、渲染次数、更新链路;

  4. 通过 Why did this render 功能精准定位更新诱因:自身状态更新/父级传导/Props 引用变更;

  5. 根据定位结果做靶向优化,实现精准降本提效。

2.3 三大记忆化 API 深度实战(完整缓存体系)

React 提供三套互补的记忆化 API,形成「组件渲染+计算逻辑+函数引用」的完整缓存体系,核心原理统一:依赖不变,复用上次执行结果,跳过无效计算与渲染

2.3.1 React.memo|组件级渲染缓存

React.memo 是官方高阶组件(HOC),专门用于缓存函数组件渲染结果。它会对组件 Props 执行浅比较,若 Props 无任何变更,直接复用上次渲染结果,跳过本次重渲染和 Diff 比对,从根源拦截无效渲染。

适用场景:纯展示组件、无内部状态组件、被父组件高频带动更新的通用 UI 组件、列表项组件;

不适用场景:高频动态变更组件、渲染耗时极短的小型组件(缓存开销大于优化收益)。

// 基础用法:纯组件浅比较缓存
const UserCard = React.memo(({ name, avatar }) => {
  return (
    <div className="card">
      <img src={avatar} alt="用户头像" />
      <p className="name">{name}</p>
    </div>
  )
})

// 进阶用法:复杂嵌套Props自定义比对,解决浅比较失效问题
// 仅核心业务ID一致,判定组件无需更新,精准规避无效渲染
const CustomMemoComp = React.memo(Component, (prevProps, nextProps) => {
  return prevProps.info.id === nextProps.info.id
})

2.3.2 useMemo|计算值与引用数据缓存

useMemo 用于缓存组件内部的耗时计算逻辑对象、数组等引用类型数据。当依赖项不变时,不会重复执行计算逻辑,同时稳定数据的内存引用地址,配合 React.memo 可彻底杜绝因引用变更导致的无效重渲染。

核心两大作用:1. 避免耗时筛选、计算、遍历逻辑重复执行;2. 稳定引用类型数据地址,补齐 memo 缓存能力。

const ListPage = ({ originList = [] }) => {
  // 仅原始列表数据变化时,重新执行筛选计算,否则复用缓存结果
  const validList = useMemo(() => {
    return originList.filter(item => item.status === 1 && item.isValid)
  }, [originList])

  // 稳定数据引用,子组件memo生效,杜绝无效渲染
  return <List data={validList} />
}

2.3.3 useCallback|函数引用固化

函数组件每次重渲染,内联函数都会被重新创建,生成全新内存引用。即便函数逻辑完全不变,引用地址变更也会让 memo 缓存失效,触发子组件重渲染。

useCallback 的核心作用是固化函数引用地址,依赖不变时,函数地址永久不变,完美配合 memo 实现组件缓存。

关键注意点:useCallback 必须配合 React.memo 使用,单独使用无任何渲染优化效果。

const ListPage = () => {
  const [count, setCount] = useState(0)

  // 依赖为空数组,组件生命周期内函数引用永久固定
  const handleItemClick = useCallback((id) => {
    console.log('点击列表条目:', id)
  }, [])

  // 子组件ListItem搭配memo即可实现缓存生效
  return <ListItem onClick={handleItemClick} />
}

2.4 高频优化误区深度解析(避坑核心)

大量开发者优化无效、越优化越卡,本质是踩中了缓存 API 的使用误区,四大高频坑点务必规避:

  1. 过度缓存:对简单组件、极简计算逻辑使用 memo、useMemo,缓存的内存开销、代码维护成本大于优化收益,造成负优化;

  2. API 单独使用:仅写 useCallback/useMemo 但不配合 memo,无法拦截组件重渲染,优化完全失效;

  3. 浅比较局限忽略:嵌套对象、深层数组的属性变更,浅比较无法识别,会出现「数据更新、视图不更新」的隐性 bug;

  4. 依赖项不规范:随意省略、篡改 Hook 依赖项,导致缓存数据陈旧,出现视图与数据不一致的业务问题。

三、场景化实战优化:大数据长列表卡顿终极解决方案

在后台管理系统、内容信息流、数据大屏、日志列表等业务场景中,长列表滚动卡顿是最典型的性能瓶颈。当单页数据量超过 500 条时,全量 DOM 渲染会直接导致首屏加载缓慢、滚动帧率暴跌、页面卡死,传统分页、懒加载仅能缓解问题,无法根治。

3.1 长列表卡顿底层核心原理

  1. DOM 节点过载:DOM 节点的解析、挂载、样式计算、渲染成本极高,上千个 DOM 节点会瞬间耗尽主线程资源,造成首屏渲染阻塞;

  2. 高频重排重绘:滚动过程中,海量列表项持续更新位置、样式,高频触发浏览器渲染流水线,持续阻塞主线程,导致滚动掉帧;

  3. 内存持续累积:非可视区域的列表项常驻 DOM 树,不会自动销毁,长期滚动会持续累积内存,出现「页面越滑越卡」的现象。

3.2 终极方案:虚拟滚动原理详解

虚拟滚动是解决长列表卡顿的行业最优方案,核心思想:放弃全量 DOM 渲染,仅渲染用户可视区域内的 DOM 节点,让页面常驻 DOM 数量始终维持在 20-50 个,从根源解决 DOM 过载、渲染卡顿问题。

虚拟滚动完整执行流程(文字流程图解):

  1. 定义外层固定高度容器,锁定列表可视区域范围;

  2. 设定单条列表项固定/动态尺寸,计算可视区域可容纳的最大条目数;

  3. 监听页面滚动事件,实时获取滚动偏移量;

  4. 根据偏移量、单条尺寸、可视高度,精准计算当前需要渲染的数据区间;

  5. 仅渲染区间内的少量 DOM 节点,通过 transform 位移模拟完整列表的滚动高度;

  6. 滚动过程中实时更新渲染区间,复用 DOM 节点,实现无缝滚动。

3.3 技术选型与完整实战代码

业界主流两大成熟方案,可根据业务场景选型:

  • react-window:轻量、高性能、体积小,适配绝大多数常规长列表场景,优先推荐;

  • react-virtualized:功能全面,支持复杂表头、不定高、分组列表,适配重度复杂业务。

import { FixedSizeList } from 'react-window'

// 定高虚拟列表完整实战Demo,适配绝大多数大数据列表场景
const BigDataList = ({ dataList = [] }) => {
  return (
    <FixedSizeList
      height={500}     // 列表可视区域高度
      width="100%"     // 列表自适应宽度
      itemCount={dataList.length} // 数据源总条数
      itemSize={50}    // 单条列表固定高度
    >
      {({ index, style }) => {
        // 实时获取当前渲染条目数据
        const item = dataList[index] || {}
        return (
          <div style={style} className="list-item">
            {item.title}
          </div>
        )
      }}
    </FixedSizeList>
  )
}

3.4 组合优化与避坑细则

虚拟滚动可解决滚动卡顿,搭配以下优化可实现极致体验:

  1. 分页+虚拟滚动组合:接口分页控制单次加载数据量,减少首屏渲染压力,滚动触底懒加载增量数据,适配无限滚动场景;

  2. 不定高列表适配:不规则列表使用 VariableSizeList 动态计算条目高度,避免滚动错位、空白问题;

  3. 简化列表节点:列表条目避免嵌套重型组件、复杂计算、高频动画,降低单条 DOM 渲染耗时;

  4. 关闭滚动监听冗余逻辑:滚动过程中禁止执行耗时计算、接口请求,仅保留视图更新逻辑。

四、首屏性能优化:从打包到传输全链路提速

首屏加载速度直接决定用户留存率与产品体验核心指标。React 项目默认会将所有业务代码、第三方依赖打包为单一 bundle 文件,随着项目迭代,代码量和依赖持续膨胀,会出现首屏白屏时间长、资源加载慢、LCP(最大内容绘制)指标不达标、首屏交互延迟等问题。

首屏优化核心思路:拆包减量、按需加载、资源压缩、加速传输,确保首屏仅加载核心必需资源,非核心资源延迟加载。

4.1 代码分割与懒加载(最高收益优化)

基于 ES6 动态 import 语法,Webpack 可自动实现代码块分割,搭配 React 官方的 lazy + Suspense 实现路由级、组件级按需加载,是中大型 React 项目首屏优化的必备方案,优化收益最高。

4.1.1 路由级懒加载(核心优化)

路由页面是天然的按需加载单元,非当前路由无需在首屏加载,可最大程度压缩首屏包体积。

import { lazy, Suspense } from 'react'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'

// 核心首页、高频页面常驻首屏,保证基础体验
import Home from './pages/Home'
// 非核心路由、低频页面懒加载,首屏不加载
const About = lazy(() => import('./pages/About'))
const UserCenter = lazy(() => import('./pages/UserCenter'))

// 优雅加载兜底,避免首屏白屏,提升用户感知
const PageLoading = () => <div className="loading">页面加载中,请稍候...</div>

const App = () => {
  return (
    <Router>
      {/* 懒加载页面统一兜底 */}
      <Suspense fallback={<PageLoading />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/user" element={<UserCenter />} />
        </Routes>
      </Suspense>
    </Router>
  )
}

4.1.2 组件级懒加载(精细化优化)

针对弹窗、抽屉、富文本编辑器、数据图表、Excel 导出等非首屏、重型、触发式使用的组件,实现按需加载,进一步精简首屏资源体积。

4.2 工程化打包深度优化

基于 Webpack/Vite 工程配置,从打包层面全方位精简代码、优化资源:

  1. 开启 Tree-Shaking 摇树优化:项目统一使用 ES6 Module 模块化规范,生产环境自动剔除未引用的死代码、冗余依赖、无效逻辑;

  2. 资源压缩处理:生产环境开启 JS、CSS、HTML 代码压缩,去除注释、空格、冗余代码,关闭 sourceMap 减少打包体积;

  3. 第三方依赖拆分:将 React、ReactDOM、UI 组件库、Axios 等稳定不常更新的依赖单独拆包,利用浏览器长效缓存,避免每次迭代重复加载;

  4. 服务端传输压缩:服务器开启 Gzip、Brotli 压缩,资源传输体积可缩减 60% 以上,大幅提升加载速度;

  5. 静态资源 CDN 托管:图片、静态资源、第三方库全部托管至 CDN,利用 CDN 就近加速能力,规避服务器带宽限制。

4.3 静态资源精细化优化

  1. 图片懒加载:非可视区域图片统一开启 loading="lazy",延迟加载,减少首屏资源请求量;

  2. 图片格式升级:使用 WebP、AVIF 高压缩率格式替代 PNG、JPG,同等清晰度下体积减半;

  3. 图标轻量化:小尺寸图标统一使用 IconFont 字体图标或 SVG 图标,替代图片图标,减少网络请求次数与资源体积。

五、稳定性优化:异常容错与内存泄漏治理

真正高性能的企业级应用,不仅要加载快、交互流畅,更要长期稳定运行。很多项目短期使用流畅,长时间运行后出现内存飙升、页面卡顿、偶发白屏、崩溃等问题,核心原因是缺少异常容错兜底和内存泄漏治理。

5.1 错误边界:隔离局部异常,杜绝整页白屏

React 中任意子组件出现渲染报错、生命周期报错,错误会逐层向上冒泡,最终导致整个应用白屏崩溃。**错误边界(Error Boundary)**可捕获子组件渲染异常,隔离错误范围,展示降级 UI,保障应用主体可用。

注意:错误边界仅类组件支持,可捕获渲染、生命周期、构造函数错误,无法捕获异步请求、定时器、事件回调中的错误

import React from 'react'

class ErrorBoundary extends React.Component {
  state = { hasError: false, errorMsg: '' }

  // 捕获错误,更新状态触发降级渲染
  static getDerivedStateFromError(error) {
    return { hasError: true, errorMsg: error.message }
  }

  // 收集错误信息,用于日志上报
  componentDidCatch(error, errorInfo) {
    console.error('组件渲染异常:', error, errorInfo)
    // 可对接前端监控平台,实现异常自动上报
  }

  render() {
    // 异常降级展示
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h3>模块加载异常</h3>
          <p>{this.state.errorMsg}</p>
          <button onClick={() => window.location.reload()}>刷新重试</button>
        </div>
      )
    }
    // 无异常则正常渲染子组件
    return this.props.children
  }
}

5.2 内存泄漏根治方案

页面长期运行卡顿、内存占用持续升高、页面越用越卡,核心原因是:组件卸载后,副作用逻辑未彻底销毁。前端高频内存泄漏场景:定时器、全局事件监听、未取消的异步请求、WebSocket 订阅、全局变量挂载。

统一解决方案:在 useEffect 清理函数中,批量销毁所有副作用,彻底杜绝内存泄漏。

import { useEffect, useState } from 'react'

const DemoComponent = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    // 开启定时器
    const timer = setInterval(() => setCount(prev => prev + 1), 1000)
    // 绑定全局滚动监听
    const handleScroll = () => console.log('滚动监听')
    window.addEventListener('scroll', handleScroll)
    // 异步请求中断控制器
    const abortController = new AbortController()

    // 组件卸载时统一清理所有副作用
    return () => {
      clearInterval(timer) // 清空定时器
      window.removeEventListener('scroll', handleScroll) // 移除事件监听
      abortController.abort() // 取消未完成请求
    }
  }, [])

  return <div>计数器:{count}</div>
}

六、React18高阶优化:并发渲染与任务优先级调度

React18 版本最核心的底层升级就是并发渲染模式(Concurrent Mode),彻底解决了旧版本同步阻塞渲染的痛点。通过任务优先级分级调度,让高优先级的用户交互任务优先执行,低优先级的视图更新任务可中断、可插队,极致提升用户交互流畅度。

6.1 并发模式核心原理

React18 之前,所有渲染任务均为同步、不可中断,一旦渲染任务开始,必须执行完毕才能响应其他交互。遇到大数据渲染、复杂视图更新时,主线程被长时间阻塞,直接造成输入延迟、点击卡顿、页面无响应。

React18 并发模式将任务分为两大优先级:

  • 高优先级任务:用户输入、按钮点击、弹窗开关、手势交互等即时用户操作,优先执行,绝不阻塞;

  • 低优先级任务:数据筛选、列表渲染、视图更新、状态同步等非即时操作,支持暂停、中断、恢复、插队。

6.2 useTransition 实战落地

useTransition 是 React18 核心高阶 Hook,用于手动标记非紧急低优先级任务,避免繁重的数据处理、视图更新阻塞用户实时交互,完美解决搜索输入、筛选、排序等场景的卡顿问题。

import { useState, useTransition } from 'react'

const SearchPage = () => {
  const [keyword, setKeyword] = useState('')
  const [list, setList] = useState([])
  // 开启过渡任务,isPending标记低优先级任务执行状态
  const [isPending, startTransition] = useTransition()

  const handleInputChange = (e) => {
    const value = e.target.value
    // 高优先级:实时更新输入框内容,保证输入丝滑无延迟
    setKeyword(value)
    // 低优先级:将数据筛选、列表更新纳入过渡任务,可被中断
    startTransition(() => {
      // 模拟大数据筛选、复杂计算逻辑
      const filterResult = mockFilterData(value)
      setList(filterResult)
    })
  }

  return (
    <>
      <input value={keyword} onChange={handleInputChange} placeholder="关键词搜索" />
      {/* 低优先级任务执行中展示加载状态,优化用户感知 */}
      {isPending ? <div>数据筛选中...</div> : <List data={list} />}
    </>
  )
}

七、源头架构优化:状态设计决定性能上限

经过大量项目复盘得出结论:80% 的 React 性能问题,根源不是不会用缓存 API,而是状态架构设计不合理。混乱的状态定义、冗余的状态存储、不合理的状态层级,会从源头产生大量无效渲染和重复计算。优秀的状态设计,可以无需堆砌优化代码,从根源规避性能问题,是性价比最高的底层优化。

7.1 状态设计四大黄金原则

  1. 状态最小化:不存储可通过现有数据计算的冗余状态,仅存储核心原始数据,减少状态更新频次;

  2. 状态下沉:局部状态定义在最小使用单元组件中,避免顶层状态更新带动整棵组件树联动渲染;

  3. 状态扁平化:摒弃嵌套对象式 State,扁平化存储状态,减少无效深层属性变更导致的引用变化;

  4. 高低频拆分:高频更新状态(输入框、滚动位置)与低频更新状态(用户信息、配置数据)拆分管理,避免高频状态带动低频视图更新。

7.2 正反案例对比(规范落地)

// ❌ 错误示范:嵌套冗余、顶层聚合、包含静态数据、高低频混杂
const [userInfo, setUserInfo] = useState({ name: '', age: 0, token: '' })

// ✅ 正确示范:扁平化、按需拆分、剥离静态数据、高低频分离
const [name, setName] = useState('')
const [age, setAge] = useState(0)
// 静态/全局数据抽离至状态管理库,不占用组件本地state
const { token } = useUserStore()

八、工程落地规范与全文总结

8.1 企业项目优化优先级(收益从高到低)

在实际项目优化中,无需盲目全量优化,可按照以下优先级落地,低成本获取最高性能收益:

  1. 代码分割+工程打包优化:首屏加载速度提升最明显,用户感知最强;

  2. 状态架构优化+无效重渲染优化:根治日常交互卡顿,从源头减少性能消耗;

  3. 大数据列表虚拟滚动优化:解决特定场景重度卡顿问题,刚需优化;

  4. 内存泄漏治理+错误边界兜底:保障应用长期稳定运行,规避远期性能劣化;

  5. React18 并发渲染优化:极致优化交互体验,解决输入、筛选等高频场景卡顿;

  6. 静态资源精细化优化:低成本、高收益,全方位辅助提效。

8.2 核心优化准则

所有 React 性能优化必须遵循核心准则:先测速、后优化,先源头、后补丁,按需优化、拒绝过度。绝不以牺牲代码可读性、可维护性、可扩展性为代价,换取微小的性能提升,避免过度优化导致的工程负债。

8.3 全文总结

本文从浏览器底层渲染原理出发,完整覆盖了 React 视图更新机制、组件级缓存优化、大数据场景专项优化、首屏全链路工程优化、应用稳定性治理、React18 高阶并发调度、状态架构源头优化八大模块,搭建了一套从底层原理到业务落地、从短期提速到长期稳定的闭环性能优化体系。

React 性能优化的本质可概括为四句话:减少无效渲染、减少重复计算、减少资源体积、减少主线程阻塞

真正的高阶性能优化,不是熟练堆砌各类缓存 API,而是吃透底层运行机制,在项目开发初期就规避性能隐患,让 React 应用实现高速加载、流畅交互、长期稳定的极致体验。

美银:全球超大规模云计算企业2027年AI资本开支或达1万亿美元

2026年5月1日 17:39
美银证券发布半导体行业研究报告,分析师Vivek Arya团队在四大美国科技巨头——谷歌、微软、亚马逊、Meta相继公布2026年第一季度财报并更新资本支出展望后,将2026年全球超大规模云计算企业资本支出预测上调至超过8000亿美元,同比增长67%,并预计2027年将进一步突破1万亿美元,同比增长约25%。(财联社)

苹果美股盘前涨近3%

2026年5月1日 17:00
苹果美股盘前涨近3%。消息面上,苹果第二财季营收1111.8亿美元,高于市场预期;并批准一项至多1000亿美元的股票回购计划,提高派息至每股0.27美元。(界面)

美股总市值首次突破75万亿美元 创历史新高

2026年5月1日 16:40
数据显示,截至上个交易日,美股总市值首次突破75万亿美元,比年初增长3万亿美元,创历史新高。总市值排名前三的公司分别为英伟达(4.85万亿美元)、谷歌(4.66万亿美元)、苹果(3.98万亿美元)。(财联社)

GitHeron:把网页标注写到 GitHub

作者 yukang
2026年5月1日 21:08

最近写了一个 Chrome 插件,叫 GitHeron。它想解决的问题很简单:

Web highlights and clippings, synced to GitHub as Markdown.

我一直使用 Hypothesis 来同步冲浪记录,然后使用一个 Obsidian 插件来同步到我的知识库。但 hypothesis 的浏览器插件体验不佳,时不时需要登录,选中文字打算做备注时又偶尔无法激活,我就想自己写个插件来解决这个问题。

GitHeron 的思路是直接使用 Github token 访问私有 repo,通过 API 把数据写入仓库。GitHub 当然不是传统意义上的数据库。但对个人工具来说,它已经提供了很多“数据库”才有的能力:同步、历史、权限、备份、API、跨设备访问。更重要的是,这些都完全是由自己控制的。

网页备注和高亮

GitHeron 最核心的功能是网页标注。

在网页上选中一段文字,按下快捷键 (默认 Ctrl+E),就可以打开 note 编辑框。写完之后,这段文字会在页面上变成高亮。下次再打开同一个页面,GitHeron 会自动把之前的高亮恢复出来。

这件事听起来不复杂,但体验上很重要。很多阅读笔记工具只能把内容保存走,却不能在原网页上重新建立上下文。GitHeron 更在意的是“回到现场”:当你再次打开这篇文章时,能立刻看到自己上次为什么停在这里。

写 note 时也可以加 tags,最近使用过的 tags 会出现在输入框附近:

保存整篇网页

除了高亮,GitHeron 还可以保存当前网页的主要内容。

按下快捷键 (默认 Ctrl + O) 后,它会提取页面正文,转换成 Markdown,然后保存到仓库中的 Clippings 目录。这里保存的是 main content,不是整个网页 HTML,所以导航栏、广告、推荐列表这些内容会尽量被过滤掉。

网页剪藏部分使用 Defuddle 来提取 main content,再转换成 Markdown。它不能保证所有网页都完美,但比直接保存整个 DOM 更接近“我真正想留下来的文章内容”。

这个功能更接近 Obsidian Web Clipper:遇到一篇值得完整保存的文章,不需要复制粘贴,也不需要手工整理格式,直接让它进入自己的 Markdown 仓库。

使用体验

在 Settings 填入一个 Github repo 地址,私有的或者公开的都行,然后去 Github token 页面生成一个 token 含有写入这个 repo 权限的 token,填入插件的配置里即可。

默认有两个快捷键:

  • Ctrl+E:给当前选中文字添加 note;
  • Ctrl+O:保存当前网页正文。

如果喜欢鼠标操作,也可以开启选中文字后的悬浮按钮;如果不喜欢它打扰阅读,可以在 settings 里关掉,只使用快捷键。

同步 GitHub 时也有两种模式。普通模式会等 GitHub 写入完成再结束;后台同步模式则会先更新页面状态,把任务放到后台慢慢同步。网络失败或 GitHub 返回错误时,可以在 settings 的 tasks 里看到最近任务,并进行 retry。

写 note 应该是一个很轻的动作,不应该因为网络慢而打断阅读节奏。

技术方案

GitHeron 是一个 Chrome MV3 插件,主要由 content script 和 service worker 组成。

content script 负责页面里的交互:选区、高亮、快捷键、弹框和正文提取。为了避免被网页自身样式影响,弹框和面板都放在 Shadow DOM 里。

service worker 负责设置、后台任务和 GitHub API。写入仓库时使用 GitHub 的 Git Data API 来生成 commit,这样一次保存可以同时更新 Markdown 内容和用于恢复高亮的辅助数据。

这里有一个取舍:Markdown 文件应该尽量保持可读,不应该塞进大段元数据。所以 GitHeron 会把可读内容写进 .md,把用于定位高亮的 selector 信息放到旁边的 JSON 文件里。这样仓库里既有人能直接读的笔记,也有插件重新打开网页时需要的结构化数据。

小结

GitHeron 是一个很个人化的工具,它的目标不是做一个复杂的标注系统,而是让“读到有用内容”到“进入自己的知识库”之间少一点摩擦。

对了,我最近还 Vibe coding 了另一个小插件 auto tabs,也是解决我日常的具体问题的。在 AI 时代,稍微有点编程经验的人都会把自己的工作流优化到极致。

一天上线 + 零返工:我如何给复杂前端需求建立“安全感”

作者 hpoenixf
2026年5月1日 16:25

一天上线一个高不确定性需求:状态矩阵 + E2E 让前端交付不返工

最近用一个工作日上线了一个"容易反复改"的前端需求,过程几乎没有返工。

说真的,这次上线给我一种很少见的感觉:我对这段逻辑有安全感。不是那种"大概没问题"的心虚。

需求本身不复杂,但很典型:AI 流式回答过程中,根据"思考步骤"和"正文"的返回情况动态切换 UI。

难点不在 UI,在时序不确定性。


一个看着简单、写起来容易错的需求

场景:

某个 AI 对话模式下,如果没有"思考步骤",先展示等待态;有了就切换。

但实际跑起来:

  • 正文可能先到,步骤后到
  • 步骤可能先到,正文后到
  • 中间几秒到十几秒的空窗
  • 不同对话模式的逻辑还不一样

写个简单的 if 会怎样?

一边还在显示"等待灵感",另一边正文已经开始滚动了。这种 UI 上线后就是反复改的开始。


先让 AI 找现状,不要直接改

这个需求一开始容易误判。

我最初以为是:没有思考步骤时显示金句,有了之后金句和步骤都显示。后来跟产品确认才知道正确逻辑是:没步骤时显示金句,有步骤后只显示步骤。再往后又发现一个遗漏:如果已经有正文了,即使还没步骤,也不能继续显示金句。

三轮理解修正,才算把需求搞清楚。

这里 AI 的价值不是"直接给答案",而是快速把相关文件串起来。它帮我定位到几个关键文件:展示思考状态的组件、消息列表的渲染入口、全局 UI 状态管理、聊天服务和流式处理逻辑。

最关键的发现是,等待态组件和 Markdown 正文是并列渲染的:

{showProgress && <MessageProgress2 />}
{showMD && <MdRender text={handledContent} />}

只看等待态组件本身,很容易漏掉"金句 + 正文同屏"的问题。得从渲染入口一层层往下看才能发现。

到这一步我意识到:直接写代码大概率改了又改。它不是 UI 问题,是状态问题。


用状态矩阵把需求说清楚

我没有继续讨论"什么时候显示等待态",而是把所有状态列出来:

场景 chatType 是否有步骤 是否有正文 期望
1 agent 显示等待态(gif + 金句)
2 agent 显示正文,隐藏金句
3 agent 无/有 显示步骤,不显示金句
4 非 agent 任意 任意 保持原逻辑

这一步把讨论从"感觉对不对"变成了"每个状态怎么渲染"。

而且我们确实在这里抓到了一个错误:我一开始把正文判断放在了外层条件上,导致非 agent 场景被误伤。后来改成只作用在 agent 分支里。


用 E2E 锁住最容易出错的状态

没写很多测试,只覆盖了三个关键场景:

  1. agent + 无正文 + 无步骤 → 金句出现
  2. agent + 有正文 + 无步骤 → 金句消失
  3. agent + 有步骤 → 步骤树出现,金句消失

测试重点不是 UI 细节,而是:状态有没有切换正确。

为了让测试稳定,我加了几个选择器:

data-testid="progress-agent-quote"   // 金句容器
data-testid="progress-quote-text"    // 金句文本
data-testid="progress-analyzing"     // 分析中状态
data-testid="progress-tree"          // 步骤树

这些不是在测实现细节,而是稳定定位几个用户可见状态。

跑完之后我就知道了一件事:以后谁改这段逻辑,这几个状态不会被改坏。第一层安全感就是这样来的。


做 Demo,把时序问题变成可见的

E2E 能证明逻辑,但不适合肉眼看过程。尤其这个需求的重点是"数据从没有到有"的动态变化。

所以我做了一个 Demo 模式,按 375px 移动端视口打开浏览器,演示状态变化:

  1. 正文先到 → 金句消失 → 再到步骤
  2. 步骤先到 → 金句消失 → 再到正文
  3. 两者交错 → 步骤 → 正文 → 子步骤 → 完成态

页面会自动推进状态,每个 case 停留十秒左右,底部有倒计时。这个比截图有用,因为它能暴露"切换瞬间"有没有怪异 UI。

所有人可以"看到"状态变化,不用靠想象。而且是在后端还没准备好之前就把交互问题确认掉了——正文先到怎么办?步骤先到怎么办?loading 什么时候消失?这些如果等到联调才讨论,基本必返工。


一个取舍:不用 mock 网络,直接驱动 Store

一开始考虑过 mock SSE、模拟流式接口。但成本高,而且这次的核心不是网络层,是 UI 状态。

所以我选了一个更直接的方式:直接用脚本驱动 Store 状态。组件完全不变,只是数据来源变了。

这个方案的好处:不依赖后端、状态完全可控、每次演示一致、各种顺序都能模拟。本质是把"时间问题"转成"状态问题"。


测试 hook 的取舍

为了快速做 E2E 和 demo,我在开发模式下加了一个 hook,让 Playwright 可以直接 dispatch Redux 状态。优点是快、稳定、可控。缺点也明显:即使只在 dev 生效,它还是侵入了主入口。

后来讨论了三个方案:

  1. Playwright route mock SSE —— 最接近真实链路,但动态演示要处理本地 mock server、HTTPS、CORS 等问题,太重
  2. 单独 debug page —— 干净,但会新增一套页面
  3. 把 hook 抽到独立 dev-only 文件 —— 保留可控性,主入口侵入降到最低

最后选了方案 3。hook 逻辑放在独立的 dev 文件里,主入口只保留一行动态 import。方便以后整体删掉或替换。


结果

时间线:

  • 前一天下班:需求下达
  • 晚上(1~2 小时):完成状态建模 + 测试 + demo
  • 第二天 10 点:用 demo 和产品确认所有交互
  • 下午 4 点:联调完成
  • 下午 6 点前:上线

这次真正节省时间的不是写代码快,而是避免了后面的返工。状态在一开始就说清楚了,交互在 demo 阶段就确认了,测试锁住了关键逻辑。联调之后,前端几乎不需要再改。


代价

写 demo 需要额外时间,加了测试需要维护,测试 hook 有一定侵入性。

但跟"上线前反复改 UI + 心里不踏实"比,我觉得值得。


最大的收获

这次让我确认了一件事:在需求模糊、状态复杂、时序不确定的情况下,先确认状态和行为再写代码,其实是更快的路径。

AI 在这里面最有用的地方不是"替我写代码",而是帮我压缩探索时间。一个需求如果直接改,很容易只改一个组件,漏掉渲染入口里并列显示的问题。

而把需求变成状态表之后,几个关键问题自然就浮出来了:正文来了怎么办?不同对话模式是否一样?loading 结束后怎么办?simple 模式要不要动?

这些问题一列出来,代码就好写很多。


最后

这次需求很小,但很典型:状态多、时序乱、容易误解、容易反复改。

用的方法也不复杂:先让 AI 搜,不要先让 AI 改;用状态矩阵说清需求;用 E2E 锁关键状态,不覆盖所有细节;用 Demo 提前确认交互,有争议就跑一遍。

结果:一天上线,几乎零返工。最重要的是,有安全感。


异步 UI 的问题,本质是状态问题。先把状态说清楚,再写代码,才是最快的方式。

云深处科技完成IPO辅导

2026年5月1日 15:54
据证监会官网IPO辅导公示系统显示,杭州云深处科技股份有限公司(简称“云深处”)及辅导券商中信建投向浙江证监局提交《辅导工作完成报告》。去年12月23日,云深处科技正式启动上市辅导。(财联社)

小鹏集团4月共交付新车31011台

2026年5月1日 15:30
.36氪获悉,5月1日,小鹏集团公布交付数据,2026年4月共交付新车31,011台。根据小鹏汽车此前披露,2025年4月共交付新车35,045台。此外,“五一”期间,小鹏集团旗下多款车型推出0首付+限时0息购车方案,最长可享3年0息,至高贴息30,900元,有效期至2026年5月31日。

苹果称印度反垄断机构越权,双方争端愈演愈烈

2026年5月1日 15:16
文件显示,苹果指控印度竞争监管机构越权,强迫这家美国科技公司在涉及iPhone应用市场的反垄断案中提交财务数据,同时苹果对相关处罚法规提出质疑。媒体查阅了苹果4月24日提交给印度法院的一份非公开文件,这是该公司与印度调查人员就该案日益激化的对峙的最新迹象。苹果表示,该案可能使其面临高达380亿美元的罚款。(新浪财经)

界面财联社入股小红书关联公司

2026年5月1日 14:55
近日,小红书关联公司行吟信息科技(上海)有限公司注册资本从1980万元增至2000万元,新增上海界面财联社科技股份有限公司为股东,持股1%。据股权穿透显示,该公司现由毛文超、瞿芳及上述新增股东共同持股。(证券时报)
❌
❌