普通视图

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

每日一题-有序三元组中的最大值 I🟢

2025年4月2日 00:00

给你一个下标从 0 开始的整数数组 nums

请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数,则返回 0

下标三元组 (i, j, k) 的值等于 (nums[i] - nums[j]) * nums[k]

 

示例 1:

输入:nums = [12,6,1,2,7]
输出:77
解释:下标三元组 (0, 2, 4) 的值是 (nums[0] - nums[2]) * nums[4] = 77 。
可以证明不存在值大于 77 的有序下标三元组。

示例 2:

输入:nums = [1,10,3,4,19]
输出:133
解释:下标三元组 (1, 2, 4) 的值是 (nums[1] - nums[2]) * nums[4] = 133 。
可以证明不存在值大于 133 的有序下标三元组。 

示例 3:

输入:nums = [1,2,3]
输出:0
解释:唯一的下标三元组 (0, 1, 2) 的值是一个负数,(nums[0] - nums[1]) * nums[2] = -3 。因此,答案是 0 。

 

提示:

  • 3 <= nums.length <= 100
  • 1 <= nums[i] <= 106

2873. 有序三元组中的最大值 I

作者 stormsunshine
2023年12月13日 21:05

解法一

思路和算法

直观的思路是遍历每个下标三元组 $(i, j, k)$ 计算 $(\textit{nums}[i] - \textit{nums}[j]) \times \textit{nums}[k]$ 的值,并得到最大值。

用 $\textit{maxValue}$ 表示下标三元组的最大值,初始时 $\textit{maxValue} = 0$,遍历过程中如果遇到一个下标三元组的值大于 $\textit{maxValue}$,则用该下标三元组的值更新 $\textit{maxValue}$。遍历结束之后,$\textit{maxValue}$ 即为下标三元组的最大值。

根据题目要求,如果所有下标三元组的值都是负数则返回 $0$。由于 $\textit{maxValue}$ 的初始值是 $0$,因此如果所有下标三元组的值都是负数,则 $\textit{maxValue}$ 的值不会更新,符合题目要求。

代码

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    long value = (long) (nums[i] - nums[j]) * nums[k];
                    maxValue = Math.max(maxValue, value);
                }
            }
        }
        return maxValue;
    }
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.Length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    long value = (long) (nums[i] - nums[j]) * nums[k];
                    maxValue = Math.Max(maxValue, value);
                }
            }
        }
        return maxValue;
    }
}

复杂度分析

  • 时间复杂度:$O(n^3)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要使用三层循环遍历所有的下标三元组并计算值。

  • 空间复杂度:$O(1)$。

解法二

思路和算法

下标三元组 $(i, j, k)$ 满足 $i < j < k$。当下标 $j$ 确定时,为了使下标三元组的值最大,应使 $\textit{nums}[i]$ 和 $\textit{nums}[k]$ 最大。用 $n$ 表示数组 $\textit{nums}$ 的长度,则 $1 \le j \le n - 2$,当下标 $j$ 确定时,$\textit{nums}[i]$ 应取数组 $\textit{nums}$ 的下标范围 $[0, j - 1]$ 中的最大值,$\textit{nums}[k]$ 应取数组 $\textit{nums}$ 的下标范围 $[j + 1, n - 1]$ 中的最大值,因此需要维护数组 $\textit{nums}$ 的每个前缀与后缀的最大值。

创建长度为 $n$ 的数组 $\textit{leftMax}$ 和 $\textit{rightMax}$,其中 $\textit{leftMax}[i]$ 表示数组 $\textit{nums}$ 的下标范围 $[0, i]$ 中的最大值,$\textit{rightMax}[i]$ 表示数组 $\textit{nums}$ 的下标范围 $[i, n - 1]$ 中的最大值。数组 $\textit{leftMax}$ 和 $\textit{rightMax}$ 的元素值计算如下。

  1. 当 $i = 0$ 时,$\textit{leftMax}[i] = \textit{nums}[i]$;当 $i = n - 1$ 时,$\textit{rightMax}[i] = \textit{nums}[i]$。

  2. 当 $i > 0$ 时,$\textit{leftMax}[i] = \max(\textit{leftMax}[i - 1], \textit{nums}[i])$;当 $i < n - 1$ 时,$\textit{rightMax}[i] = \max(\textit{rightMax}[i + 1], \textit{nums}[i])$。

计算得到数组 $\textit{leftMax}$ 和 $\textit{rightMax}$ 的值之后,即可计算下标三元组的最大值。对于每个 $1 \le j \le n - 2$,当下标 $j$ 固定时,下标三元组 $(i, j, k)$ 的最大值是 $(\textit{leftMax}[j - 1] - \textit{nums}[j]) \times \textit{rightMax}[j + 1]$。遍历所有可能的下标 $j$ 之后即可得到下标三元组的最大值。

特别地,当所有下标三元组的值都是负数时,返回 $0$。

代码

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.length;
        int[] leftMax = new int[n];
        leftMax[0] = nums[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], nums[i]);
        }
        int[] rightMax = new int[n];
        rightMax[n - 1] = nums[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i + 1], nums[i]);
        }
        for (int j = 1; j < n - 1; j++) {
            long value = (long) (leftMax[j - 1] - nums[j]) * (rightMax[j + 1]);
            maxValue = Math.max(maxValue, value);
        }
        return maxValue;
    }
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        long maxValue = 0;
        int n = nums.Length;
        int[] leftMax = new int[n];
        leftMax[0] = nums[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.Max(leftMax[i - 1], nums[i]);
        }
        int[] rightMax = new int[n];
        rightMax[n - 1] = nums[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = Math.Max(rightMax[i + 1], nums[i]);
        }
        for (int j = 1; j < n - 1; j++) {
            long value = (long) (leftMax[j - 1] - nums[j]) * (rightMax[j + 1]);
            maxValue = Math.Max(maxValue, value);
        }
        return maxValue;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。计算前缀最大值数组与后缀最大值数组的时间是 $O(n)$,计算三元组最大值的时间是 $O(n)$。

  • 空间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要创建两个长度为 $n$ 的数组。

解法三

思路和算法

下标三元组 $(i, j, k)$ 满足 $i < j < k$。可以从左到右遍历数组 $\textit{nums}$,对于每个下标 $k$ 计算 $\textit{nums}[i] - \textit{nums}[j]$ 的最大值,得到下标 $k$ 确定时的下标三元组的最大值。

遍历过程中需要维护已经遍历过的元素的最大差值 $\textit{maxDiff}$ 和最大元素值 $\textit{maxNum}$,初始时 $\textit{maxDiff} = \textit{maxNum} = 0$。当遍历到元素 $\textit{num}$ 时,执行如下操作。

  1. 将当前元素之前的最大差值 $\textit{maxDiff}$ 作为 $\textit{nums}[i] - \textit{nums}[j]$ 的最大值,将当前元素 $\textit{num}$ 作为 $\textit{nums}[k]$,计算下标三元组的值 $\textit{maxDiff} \times \textit{num}$,使用该下标三元组的值更新结果。

  2. 计算当前元素之前的最大元素值与当前元素值之差 $\textit{maxNum} - \textit{num}$,并更新 $\textit{maxDiff}$ 的值。

  3. 使用当前元素值更新最大元素值 $\textit{maxNum}$。

上述操作中,每次计算下标三元组的值时,$\textit{maxDiff}$ 一定是当前元素之前的两个元素 $\textit{nums}[i]$ 与 $\textit{nums}[j]$ 之差且一定有 $i < j$,因此可以确保得到正确的结果。

代码

###Java

class Solution {
    public long maximumTripletValue(int[] nums) {
        long maxValue = 0;
        int maxDiff = 0;
        int maxNum = 0;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            maxValue = Math.max(maxValue, (long) maxDiff * num);
            maxDiff = Math.max(maxDiff, maxNum - num);
            maxNum = Math.max(maxNum, num);
        }
        return maxValue;
    }
}

###C#

public class Solution {
    public long MaximumTripletValue(int[] nums) {
        long maxValue = 0;
        int maxDiff = 0;
        int maxNum = 0;
        int n = nums.Length;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            maxValue = Math.Max(maxValue, (long) maxDiff * num);
            maxDiff = Math.Max(maxDiff, maxNum - num);
            maxNum = Math.Max(maxNum, num);
        }
        return maxValue;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要遍历数组一次。

  • 空间复杂度:$O(1)$。

前缀后缀最大

作者 simidagogogo
2023年10月1日 18:38

前缀后缀最大,时间复杂度O(n),空间复杂度O(n)

###cpp

class Solution {
public:
    long long maximumTripletValue(vector<int>& nums) {
        int n = nums.size();
        
        // 记录每个元素左边最大和右边最大
        vector<pair<long long, long long>> vec(n);
        
        int left = 0, right = 0;
        for (int i = n - 1; i >= 0; --i) {
            vec[i].second = right;
            right = std::max(nums[i], right);
        }
        
        for (int i = 0; i < n; ++i) {
            vec[i].first = left;
            left = std::max(nums[i], left);
        }
        
        long long res = 0;
        for (int i = 0; i < n; ++i) {
            long long num = (vec[i].first - nums[i]) * (vec[i].second);
            if (res < num) res = num;
        }
        return res;
    }
};
昨天 — 2025年4月1日首页

JavaScript Import/Export:告别混乱,拥抱模块化!

作者 烛阴
2025年4月1日 23:28

什么是模块化?

模块化是将代码分割成独立的、可重用的部分(模块),每个模块负责特定的功能。这样做的好处包括:

  • 提高可读性:将复杂的代码分解为小块,使其更易于理解。
  • 增强可维护性:修改一个模块不会影响其他模块,降低了出错的概率。
  • 促进重用性:可以在不同项目中重用相同的模块,减少重复代码。

Export:导出模块

在JavaScript中,使用export关键字可以将模块中的变量、函数或类导出,以便在其他模块中使用。export有两种主要形式:命名导出和默认导出。

1. 命名导出

命名导出允许你导出多个变量或函数。示例如下:

// math.js
export const PI = 3.14;
export function add(x, y) {
    return x + y;
}

在上面的代码中,我们导出了一个常量PI和一个函数add。在其他模块中,可以通过相同的名称导入这些导出:

// app.js
import { PI, add } from './math.js';

console.log(PI); // 3.14
console.log(add(2, 3)); // 5

2. 默认导出

默认导出允许你导出一个单一的值或对象。示例如下:

// calculator.js
export default function multiply(x, y) {
    return x * y;
}

在其他模块中,你可以使用任意名称导入默认导出:

// app.js
import multiply from './calculator.js';

console.log(multiply(2, 3)); // 6

Import:导入模块

使用import关键字可以引入其他模块的导出。根据导出的类型,import的语法也有所不同。

1. 导入命名导出

import { PI, add } from './math.js';

2. 导入默认导出

import multiply from './calculator.js';

3. 导入所有导出

如果你想导入一个模块的所有导出,可以使用* as语法:

import * as math from './math.js';

console.log(math.PI); // 3.14
console.log(math.add(2, 3)); // 5

注意事项:

  • 文件扩展名:  在 import 语句中,通常需要指定文件的扩展名(例如 .js)。
  • 模块解析:  JavaScript 运行时环境需要能够找到并加载模块。这通常涉及到模块解析算法,它会根据配置的规则查找模块文件。
  • ES Modules vs. CommonJS:  import 和 export 是 ES Modules 的语法。CommonJS 使用 require() 和 module.exports。 现代 JavaScript 开发更倾向于使用 ES Modules。

小米汽车事故背后:耀眼的智驾,仓促的2秒钟

2025年4月1日 22:46

3月29日,安徽铜陵市发生了一起严重的交通事故。一辆小米SU7撞上水泥护栏后,发生起火,车内三名乘员不幸身亡,事故车被烧至白车身裸露。

4月1日,小米官方确认了此次事故,并公布了事故车辆当时的部分行车状态。

据小米声明,事故发生前,车辆处于NOA智能辅助驾驶状态,时速为116km/h。事发路段因施工,有路障封闭车道,改道至逆向车道行驶。NOA在行业中被称作领航辅助驾驶功能,可以自行控制车辆施行加减速,并变更车道,上下匝道。

根据小米公布的行车记录,车辆几乎在碰撞前的2秒钟,NOA系统才发出障碍物提醒。驾驶员极限接管车辆后,已经难以避免事故发生。碰撞前的最后速度仍有97km/h。

3月30日,小米成立了专项工作组,积极配合警方完成取证、调查工作。事发地铜陵市交通运输局也成立了专门的工作组,正在对事故展开调查。

本次事故主角小米SU7,是近两年中国汽车市场现象级的爆款产品。上市一年卖出18.6万辆,目前因订单积压,最长交车时间更是长达一年之久。

小米创始人雷军也逐渐成为“车圈顶流”。无论发布会、行业论坛还是线下活动,雷军的一举一动几乎都会迅速登上热搜,他的创业精神打动了大量消费者。

雷军曾公开表示,SU7早期订单中,有30%都是盲订用户。没有看过摸过车,便决定购入。是消费者对小米与雷军的信任,支撑了数量庞大的订单。

而智能驾驶也是几年汽车行业的核心发展主题之一,新能源车龙头公司比亚迪刚刚掀起智驾平权运动,引发吉利、奇瑞等企业跟随,高速领航功能已经可以装在10万元不到的汽车上,为大量用户使用。

加上理想、 小鹏、蔚来、鸿蒙智行等以智能驾驶技术为旗帜的新造车公司,今年配备高速领航以上的汽车销量,将直奔千万量级而去,直接占到中国全年乘用车销量的一半之多。

顶流公司小米,加汹涌的上智热潮驾,这场事故已然引发全民关注,也将再次绷紧车企和消费者心中的行车安全之弦。

事故现场还原:极限2秒钟

从小米公布的驾驶日志可以看到,事故发生前,这辆小米SU7的高速NOA功能开启,车速为116km/h。

系统曾对驾驶员进行轻度分心报警,以及一次手握方向盘的脱手预警。这种方向盘脱手报警后,驾驶员需重新将手握方向盘,报警才会消失。

在8分钟的正常行驶后,NOA突然发出“请注意前方有障碍”的提醒,并开始减速。

小米公布的事故车行驶日志

从系统发出障碍物提醒(22:44:24),到车辆发生碰撞(22:44:36-28),前后仅有两秒钟的时间。

在这两秒钟内,驾驶员快速接管了车辆,接管当时,车辆时速仍在100km/h以上。

驾驶员采取了向左打22度方向盘,刹车踏板开度31%。这似乎是仓促之下的应对。因为深度踩下刹车,踏板开合度一般在60%左右。转向角度过大,刹车力度不足,无法快速刹停车辆,还容易导致车辆失控,撞上周边障碍物。

有车企研发人士向36氪分析,在时速116km/h的背景下,方向盘的22度,对应的车轮转向角是巨大的。并且这辆小米SU7标准版为后驱车型,“高速急转向是很危险的行为,车辆在当下很可能失控,并开始打滑”。

事故车(图源网络)

从事故车图片看,车辆撞上水泥护栏的位置是副驾前方。

有业内人士向36氪分析,事故当时,水泥护栏位于两条车道的中界线,在驾驶员向左变道行驶途中,避让不及,车辆右前侧撞上水泥护栏,“这是一种偏置碰撞场景,现在也是车企的常规测试项目”。

小米在中保研的测试

2024年9月,中保研曾发布小米SU7的碰撞测试结果,其中便包括乘员侧正面25%的偏置碰撞测试场景(如上图所示)。测试结果表明,在64km/h的速度下遇见偏置碰撞,小米车内乘客并且受伤,获得了最优秀的G级评价。

而关于25%以及40%侧正面碰撞测试,国标要求的速度标准是56km/h。“这个事故中,车速太高了,最终碰撞的时速竟然有97km/h”,有业内人士向36氪表示,“几乎没有车能扛住这个车速下的撞击”。

车辆与水泥护栏发生碰撞后,发生了起火。从事故车照片看,车辆已被烧成灰烬。

有电池行业人士向36氪表示,小米在电池防护上的标准,比行业高不少,即便是磷酸铁锂电池,也在扩散热防护中采用了气凝胶隔热材料。不过,97km/h的高速撞击下,动力电池面临极大的变形撕裂风险。

“小米SU7标准版使用的是磷酸铁锂电池,微小碰撞下,磷酸铁锂电池只会冒烟并太不会起火。但如果内部电芯被撕裂,电解液泄漏,那么这场火便不可避免了”,业内人士向36氪表示。

“说白了,现在的电池包热防护设计,基本都是针对80公里时速以下,再高的速度,前舱的防撞要求将大幅增加,为了小概率场景, 去拉高整车成本,大部分车企都不会做。”

综合而言,小米SU7本次事故的直接原因,即车辆智能驾驶系统与驾驶员都未及时发现前方障碍物,也未留出充足时间避让。

智能驾驶,成为本次事故的重要讨论点之一。行业争相追逐的、车企推广普及的智能驾驶,似乎仍然没有能力处理此类复杂路况。

聒噪的智能驾驶,看不见的风险提示

“夜晚、高速路、纯视觉、还是修路场景,难度其实不小。”

多位智能驾驶行业人士告诉36氪,小米事故车辆的智能辅驾驶助遇到的场景,几乎是每个车企智驾团队都会遇到的挑战。

而小米事故车辆SU7标准版,搭载的只是入门级的智驾方案。

硬件上,包括1颗英伟达Orin N芯片,AI算力为84TOPS,无激光雷达,全车的智驾感知由1个毫米波雷达、11个摄像头、12个超声波雷达来实现;软件算法由小米汽车自研,具备高领航辅助驾驶等能力。

有行业人士向36氪汽车介绍,在纯视觉智驾感知环节,前几年由特斯拉带火的BEV感知(鸟瞰图)+OCC(占据网络)等技术,是行业比较常见的感知方案。

该人士表示,在高速道路上,BEV能检测距离往往就100米左右,而实际上,超过60米车就看得的不是很准了。“如果以100km/h行驶,智驾系统的反应时间基本也就3.6秒左右”。

“远距离的静止障碍物检测难度不小,有激光雷达肯定会更好,至少能更早看到障碍物。”

另一位智驾行业资深人士也表示,“如果是路况不好(比如光照、视野遮挡)的情况下,从100km/h到完全刹停是有一定技术难度的;但减速到比较低,如60km/h还是相对容易做到。”

而遗憾的是,小米公布的事故车行驶日志显示,从智驾系统发出障碍物提醒到碰撞发生,中间只有2秒时间。“智驾响应明显是偏晚了的。”

但在辅助人类驾驶过程中,即便智驾系统反应不够及时,车企往往还有另一重保证——自动紧急制动,俗称“AEB”。

“即使AEB没有刹停,100米的检测范围内,AEB是足够减速到30-40km/h的。”有行业人士对36氪表示,不过从事发时97km/h的速度来看,AEB很可能没有实际生效。

而此前,小米汽车曾公布过自身测试结果,称能在135km/h下检测前方车道静止的故障车,并成功刹停。

小米发布会截图

然而有行业人士告诉36氪,不仅小米,其他车企同行的AEB能够生效场景范围也相当有限。例如面对一些水马、桩桶、护栏等路面静态障碍物,AEB往往无法生效。

36氪也在小米汽车的用户手册中查阅发现,小米列出的前向防碰撞辅助功能无法正常工作的场景中,就包括异形障碍物,如落石、侧卧行人、水马、动物等。

有智驾行业人士告诉36氪,尽管车企列明了一长串注意事项,“但其实只是一种免责声明。而用户在实际开车时,很难想起来什么场景下AEB能够生效。”

行业中,类似的“因为AEB不起作用而导致车辆剐蹭碰撞”的用户投诉和曝光并不少。有行业人士告诉36氪,智驾第一梯队车企的纯视觉方案,日常也能收到不少投诉工单,原因就是因为剐蹭了水马等路面障碍物。

换言之,即便被端到端、大模型等各种花哨的新技术名词笼罩,智能驾驶依然在犯一些低级错误,或者暴露出难以应对复杂路况的能力边界。

智驾普及,碰上了安全暗礁。

今年以来,车企掀起了一轮又一轮的智驾平权潮流,尽管智驾功能的软件迭代多数都是期货,但硬件先行抢占用户心智,已经成为车企们“看破不说破”的共谋。

在一些用户眼中,智能驾驶的酷炫科技迎合了先锋人士、年轻人的潮流,不用智驾的人士反而成了保守派。

据青岛广播电台《正在新闻》报道,小米SU7事故中驾驶车辆的女生曾多次告诉母亲,智驾“方便、安全”。

这位母亲曾告诫女儿,现在技术不完善,不能盲目相信,自己开才放心,“我说她以后一定会后悔;她还反驳我,说有各种(证明安全)的依据。”

事实上,智能驾驶的进化,远未到用户可以真正放开手脚安睡无虞的阶段。

即便是一位从事智驾工作多年的工程师也对36氪汽车表示:从来不敢在晚上开启智驾,因为他深知智驾系统目前的能力边界。

而广泛普通用户,其实更难以分辨车企不同智驾等级、不同芯片配置带来的能力差异,以及背后潜在的安全风险。

一位工程师告诉36氪,近期有推行智驾平权的车企,其销售门店的泊车演示失败案例就有百十来起,已经对销售的成交率产生负面影响,公司大量工程师也在为大面积交付的智驾系统,进行售后补漏工作,“有些工程师的头上,挂了20000多个问题待解决。”

智能驾驶的普及,势必将惠及消费者,用技术补足人类驾驶员的环境识别缺陷,响应能力,同时让司机可以短暂缓解驾驶疲劳。

但过度营销,乃至急功近利式的进行功能比拼,势必埋下安全隐患,甚至让企业自身驶入质量暗礁。

车企有理由、也有责任帮助用户清晰好认知智驾,而非沉浸在“遥遥领先”、“第一梯队”的标签与光环中。广大用户也需警醒的是,智驾虽然火热,但依旧是辅助人类驾驶的角色,行车安全的方向盘仍然掌握在自己手里。

对于小米汽乃至所有车企而言,虽然源源不断的汽车订单将其捧上了高位,但喧闹的流量可以载舟也能覆舟,尊重汽车产品与新技术的进化节奏,加强风险把控,如履薄冰,才能在汽车行业长远发展。

vue自定义“权限控制”指令

2025年4月1日 22:44

自定义指令示例:权限控制指令 v-permission

场景说明

实现一个根据用户权限动态控制元素显示/隐藏或禁用操作的指令,支持以下功能:

  1. 根据权限列表隐藏无权限的元素。
  2. 可选禁用元素而非隐藏(如按钮变灰)。
  3. 权限变更时自动更新元素状态。

完整代码与逐行解析

// permissionDirective.js
export default {
  // 元素挂载或更新时触发
  mounted(el, binding) {
    updateElementPermission(el, binding);
  },
  // 绑定值或参数变化时触发
  updated(el, binding) {
    updateElementPermission(el, binding);
  },
};

// 统一处理权限逻辑
function updateElementPermission(el, binding) {
  // 1. 获取必要数据
  const { value, modifiers, arg } = binding;
  const hasPermission = checkPermission(value); // 核心权限校验

  // 2. 处理无权限的情况
  if (!hasPermission) {
    // 禁用模式(通过参数指定,如 v-permission:disable)
    if (arg === 'disable') {
      el.disabled = true; // 禁用表单元素
      el.style.opacity = '0.5'; // 视觉提示
      el.title = '无权限操作'; // 鼠标悬停提示
    } 
    // 默认隐藏模式
    else {
      el.style.display = 'none'; // 直接隐藏元素
    }
  }
  // 3. 处理有权限的情况(恢复初始状态)
  else {
    // 禁用模式的恢复
    if (arg === 'disable') {
      el.disabled = false;
      el.style.opacity = '1';
      el.title = '';
    } 
    // 隐藏模式的恢复
    else {
      el.style.display = '';
    }
  }

  // 4. 支持强制显示(通过修饰符,如 v-permission.force)
  if (modifiers.force) {
    el.style.display = '';
    el.disabled = false;
  }
}

// 模拟权限校验(实际从 Vuex/Pinia 获取)
function checkPermission(requiredPermission) {
  // 实际项目中从全局状态获取用户权限
  const userPermissions = ['view', 'edit'];
  return userPermissions.includes(requiredPermission);
}

逐行代码解析

1. 指令生命周期处理

export default {
  mounted(el, binding) { // 元素挂载时初始化
    updateElementPermission(el, binding);
  },
  updated(el, binding) { // 绑定值变化时更新
    updateElementPermission(el, binding);
  },
};
  • 关键点‌:

    • mounted:元素首次插入 DOM 时校验权限。
    • updated:当指令绑定的值(如权限要求)或参数变化时重新校验。

‌**2. 统一处理函数 updateElementPermission**‌

function updateElementPermission(el, binding) {
  const { value, modifiers, arg } = binding;
  const hasPermission = checkPermission(value);
  // ...
}
  • 解构赋值‌:

    • value:指令绑定的值,如 v-permission="'edit'" 中的 'edit'
    • modifiers:修饰符对象,如 .force 对应 modifiers.force: true
    • arg:指令参数,如 :disable 对应 arg: 'disable'

3. 权限校验逻辑

function checkPermission(requiredPermission) {
  const userPermissions = ['view', 'edit'];
  return userPermissions.includes(requiredPermission);
}
  • 模拟数据‌:实际项目中需从全局状态(如 Vuex)获取用户权限列表。
  • 返回值‌:当前用户是否拥有所需权限。

4. 无权限处理 - 禁用模式

if (arg === 'disable') {
  el.disabled = true; // 适用于按钮、输入框等
  el.style.opacity = '0.5'; // 视觉提示
  el.title = '无权限操作'; // HTML 原生属性提示
}
  • 适用场景‌:允许用户看到元素但无法交互(如灰色按钮)。

  • 细节‌:

    • el.disabled 仅对表单元素有效,普通元素需用 CSS 或事件拦截。

5. 无权限处理 - 默认隐藏模式

else {
  el.style.display = 'none'; // 彻底隐藏元素
}
  • 优势‌:完全移除元素对布局的影响。
  • 注意‌:若元素含动画,可用 visibility: hidden + opacity: 0 代替。

6. 权限恢复处理

else {
  if (arg === 'disable') {
    el.disabled = false;
    el.style.opacity = '1';
    el.title = '';
  } else {
    el.style.display = '';
  }
}
  • 恢复逻辑‌:重置元素到初始状态,display: '' 会恢复为 CSS 定义的值。

7. 强制显示模式

if (modifiers.force) {
  el.style.display = '';
  el.disabled = false;
}
  • 用途‌:开发阶段临时覆盖权限控制,如 v-permission.force
  • 扩展性‌:可结合环境变量自动启用(如 process.env.NODE_ENV === 'development')。

高级使用示例

场景1:禁用无权限的删除按钮

<template>
  <button 
    v-permission:disable="'delete'"
    @click="handleDelete"
  >删除数据</button>
</template>
  • 效果‌:用户无 delete 权限时按钮变灰且提示。

场景2:动态切换权限

<template>
  <div>
    <button @click="togglePermission">切换权限</button>
    <p v-permission="requiredPermission">需要{{ requiredPermission }}权限的内容</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      requiredPermission: 'edit'
    };
  },
  methods: {
    togglePermission() {
      this.requiredPermission = this.requiredPermission === 'edit' ? 'view' : 'edit';
    }
  }
};
</script>
  • 动态响应‌:点击按钮切换所需权限,指令自动更新元素状态。

对比其他实现方案

方案 优点 缺点
自定义指令 逻辑复用,统一管理 需处理全局状态访问
组件封装 可复用带权限控制的组件 每个需控制的组件都要封装
v-if + 计算属性 简单场景直观 重复代码多,难以统一修改逻辑

总结

  • 核心价值‌:通过指令集中处理权限逻辑,避免重复代码。

  • 扩展性‌:通过参数(:disable)和修饰符(.force)支持多样化需求。

  • 响应式‌:利用 Vue 的响应式系统,权限变化时自动更新元素状态。

  • 注意事项‌:

    • 普通元素禁用需手动拦截事件(如添加 @click.stop)。
    • 实际项目需接入全局权限状态(如从 Vuex 获取用户权限)。

浏览器页面渲染机制深度解析:从构建 DOM 到 transform 高效渲染的底层逻辑

2025年4月1日 22:39

浏览器是如何渲染页面的?

当浏览器的网络线程收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列。

在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。


整个渲染流程分为多个阶段,分别是: HTML 解析、样式计算、布局、分层、绘制、分块、光栅化、画

每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入。

这样,整个渲染流程就形成了一套组织严密的生产流水线。


渲染的第一步是解析 HTML

解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。

如果主线程解析到link位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因。

如果主线程解析到script位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML。这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。

第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。


渲染的下一步是样式计算

主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式,称之为 Computed Style。

在这一过程中,很多预设值会变成绝对值,比如red会变成rgb(255,0,0);相对单位会变成绝对单位,比如em会变成px

这一步完成后,会得到一棵带有样式的 DOM 树。


接下来是布局,布局完成后会得到布局树。

布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。例如节点的宽高、相对包含块的位置。

大部分时候,DOM 树和布局树并非一一对应。

比如display:none的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。还有匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法一一对应。


下一步是分层

主线程会使用一套复杂的策略对整个布局树中进行分层。

分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。

滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可以通过will-change属性更大程度的影响分层结果。


再下一步是绘制

主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。


完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。

合成线程首先对每个图层进行分块,将其划分为更多的小区域。

它会从线程池中拿取多个线程来完成分块工作。


分块完成后,进入光栅化阶段。

合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。

GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。

光栅化的结果,就是一块一块的位图


最后一个阶段就是

合成线程拿到每个层、每个块的位图后,生成一个个「指引(quad)」信息。

指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。

变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因。

合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像。

什么是 重排(reflow)?

重排 的本质就是重新计算 layout 树。

当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。

为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 重排 是异步完成的。

也同样因为如此,当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。

浏览器在反复权衡下,最终决定获取属性立即 重排

什么是 重绘(repaint)?

重绘 的本质就是重新根据分层信息计算了绘制指令。

当改动了可见样式后,就需要重新计算,会引发 重绘。

由于元素的布局信息也属于可见样式,所以 重排 一定会引起 重绘

为什么 transform 的效率高?

因为 transform 既不会影响布局也不会影响绘制指令,它影响的只是渲染流程的最后一个绘制「draw」阶段

由于 绘制 阶段在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响 transform 的变化。

  • 如果您觉得这篇文章对您有帮助,欢迎点赞和收藏,大家的支持是我继续创作优质内容的动力🌹🌹🌹也希望您能在😉😉😉我的主页 😉😉😉找到更多对您有帮助的内容。

    • 致敬每一位赶路人

刷刷题48 (setState常规问答)

2025年4月1日 22:20

1. 为什么说setState是异步的?React如何处理多个setState调用?

  • 异步原因‌:为了优化性能,React会将多个setState合并为一次更新,减少不必要的渲染次数(批处理机制)。

  • 处理方式‌:

    // 连续调用会被合并,最终count只+1
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    

    React内部通过updater队列收集多个更新,在事件循环末尾统一处理。


2. 如何强制在setState后立即获取最新状态?

使用setState的‌回调函数‌或‌Promise封装‌:

// 方式1:回调函数
this.setState({ count: 1 }, () => console.log(this.state.count));

// 方式2:async/await(需自行封装)
const setStateAsync = (state) => new Promise(resolve => this.setState(state, resolve));
await setStateAsync({ count: 1 });

3. 哪些场景下setState会同步执行?

在React的‌控制范围之外‌的代码中会同步更新:

  • setTimeout/setInterval
  • 原生DOM事件监听器
  • Promise回调
setTimeout(() => {
  this.setState({ count: 1 }); // 同步更新
  console.log(this.state.count); // 输出1
}, 0);

4. setState(partialState, callback)this.state = ...直接修改的区别?

  • ‌**setState**‌:

    • 触发组件重新渲染
    • 遵守React的更新队列机制
    • 支持批处理优化
  • ‌**直接修改this.state**‌:

    • 不会触发渲染(状态不更新到UI)
    • 破坏React状态管理机制,导致数据不一致

5. 为什么说在setState中依赖this.state的值是危险的?如何解决?

  • 危险原因‌:异步更新可能导致获取的是过期状态

    // ❌ 错误:可能基于旧值计算
    this.setState({ count: this.state.count + 1 });
    
  • 解决方案‌:使用‌函数式更新

    // ✅ 正确:接收前一个状态
    this.setState((prevState) => ({ count: prevState.count + 1 }));
    

6. React 18中setState的批处理机制有哪些改进?

  • React 17及之前‌:仅在React事件处理函数中自动批处理

  • React 18+ ‌:

    • 所有场景(包括Promise、setTimeout等)默认自动批处理
    • 可使用flushSync强制同步更新:
    import { flushSync } from 'react-dom';
    flushSync(() => this.setState({ count: 1 })); // 立即更新
    

7. 如何避免setState导致的不必要重复渲染?

  • 浅比较优化‌:

    class Component extends React.PureComponent { // 自动浅比较props/state
      state = { list: [] };
      addItem = () => {
        // ✅ 正确:返回新数组
        this.setState(prev => ({ list: [...prev.list, newItem] }));
      }
    }
    
  • 手动控制‌:

    shouldComponentUpdate(nextProps, nextState) {
      return nextState.count !== this.state.count; // 仅当count变化时更新
    }
    

8. 在componentWillUpdate中调用setState会发生什么?

  • 引发无限循环‌:
    componentWillUpdate → setState → 重新渲染 → 再次触发componentWillUpdate
  • 解决方案‌:
    禁止在此生命周期中调用setState,改用componentDidUpdate处理副作用。

9. 为什么推荐在setState中返回不可变数据?如何正确实现?

  • 不可变性优势‌:

    • 便于React快速比较状态变化
    • 避免引用类型数据共享导致的副作用
  • 正确写法‌:

    // ✅ 对象
    this.setState({ user: { ...this.state.user, name: 'Alice' } });
    
    // ✅ 数组
    this.setState({ list: [...this.state.list, newItem] });
    

10. setState在异步操作(如AJAX请求)中可能遇到什么问题?如何解决?

  • 竞态条件(Race Condition) ‌:
    当多个异步操作返回顺序不确定时,可能导致状态覆盖

  • 解决方案‌:

    1. 使用AbortController取消过期请求
    2. 函数式更新确保基于最新状态:
    fetchData().then(data => {
      this.setState(prev => ({ 
        list: prev.filterId === currentId ? data : prev.list 
      }));
    });
    

关于前端性能优化

2025年4月1日 22:10

面试中,面试官经常问关于前端性能优化的问题,前端性能优化已成为打造卓越用户体验的关键要素。一个响应迅速、加载流畅的网页,能够极大地提升用户满意度,增加用户留存率。

渲染层面的性能优化

重绘与重排:理解渲染的底层逻辑

重绘和重排是渲染过程中的两个关键概念。重绘指的是元素外观的改变,如背景颜色、文字颜色的调整,但不涉及布局的变动。由于不影响布局,重绘的代价相对较低。而重排则是指元素布局的改变,包括位置、大小的调整,以及元素的隐藏或显示等。一旦发生重排,浏览器需要重新计算布局,这可能会影响到其他元素的位置和大小。需要注意的是,重排必定会引发重绘,而重绘不一定会导致重排。

以下代码示例展示了如何通过批量修改 DOM 来减少重排和重绘的次数:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <div id="myDiv">Hello, World!</div>
  <script>
    const div = document.getElementById('myDiv');
    // 欠佳做法:多次修改 DOM 会触发多次重排
    // div.style.color = 'red';
    // div.style.fontSize = '20px';
    // div.style.padding = '10px';

    // 优化做法:批量修改 DOM
    div.style.cssText = 'color: red; font-size: 20px; padding: 10px;';
  </script>
</body>

</html>

文档碎片:高效构建 DOM 的秘密武器

文档碎片是一种轻量级的 DOM 容器,它允许我们在内存中构建 DOM 结构,最后一次性将其插入到文档中。这样做可以显著减少重排和重绘的次数,提高渲染性能。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <ul id="myList"></ul>
  <script>
    const list = document.getElementById('myList');
    const fragment = document.createDocumentFragment();
    for (let i = 0; i < 10; i++) {
      const li = document.createElement('li');
      li.textContent = `Item ${i}`;
      fragment.appendChild(li);
    }
    list.appendChild(fragment);
  </script>
</body>

</html>

资源加载优化

图片懒加载:节省首屏加载资源的良方

图片懒加载是一种有效的资源加载优化策略,它可以避免在首屏加载时一次性加载所有图片,从而减少资源消耗,提高页面加载速度。我们可以使用 getBoundingClientRect() 方法或 IntersectionObserver API 来实现图片懒加载。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <img class="lazy" data-src="https://picsum.photos/200/300" alt="Lazy Image">
  <script>
    const lazyImages = document.querySelectorAll('.lazy');
    const observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.classList.remove('lazy');
          observer.unobserve(img);
        }
      });
    });
    lazyImages.forEach(img => {
      observer.observe(img);
    });
  </script>
</body>

</html>

路由懒加载:按需加载代码,提升首屏速度

在单页面应用中,路由懒加载是一项重要的优化技术。它可以将不同路由的代码分割成独立的文件,只有当用户访问该路由时,才会加载相应的代码。这样可以显著减少首屏加载时间,提高用户体验。

javascript

// Vue 路由懒加载示例
const routes = [
  {
    path: '/about',
    component: () => import('./views/About.vue')
  }
];

资源预加载与 DNS 预解析:提前布局,加速资源加载

使用 <link rel="preload"> 可以预加载重要的资源,而 <link rel="dns-prefetch"> 则可以预解析 DNS,提前建立与服务器的连接,从而提高资源加载速度。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <link rel="preload" href="styles.css" as="style">
  <link rel="dns-prefetch" href="//example.com">
</head>

<body>
  <!-- 页面内容 -->
</body>

</html>

JS 执行优化

防抖与节流:控制函数执行频率的利器

防抖和节流是两种常用的优化技术,它们可以限制函数的执行频率,减少不必要的计算和资源消耗。在处理高频事件(如滚动、窗口大小改变等)时,这两种技术尤为有用。

// 防抖函数
function debounce(func, delay) {
  let timer;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

// 节流函数
function throttle(func, limit) {
  let inThrottle;
  return function () {
    const context = this;
    const args = arguments;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

Web Worker:释放主线程,提升计算性能

Web Worker 是 HTML5 的一项重要特性,它允许我们在后台线程中执行 JavaScript 代码,从而避免阻塞主线程。这对于处理复杂的计算任务非常有用,可以显著提升页面的响应性能。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <button id="startWorker">Start Worker</button>
  <script>
    const startWorkerButton = document.getElementById('startWorker');
    startWorkerButton.addEventListener('click', () => {
      if (typeof Worker !== 'undefined') {
        const worker = new Worker('worker.js');
        worker.onmessage = function (event) {
          console.log('Received message from worker:', event.data);
        };
        worker.postMessage('Start calculation');
      } else {
        console.log('Web Workers are not supported in this browser.');
      }
    });
  </script>
</body>

</html>
// worker.js
onmessage = function (event) {
  if (event.data === 'Start calculation') {
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += i;
    }
    postMessage(result);
  }
};

框架层面优化

React 的 useMemo 和 useCallback:缓存计算结果,避免重复渲染

在 React 中,useMemo 和 useCallback 是两个非常实用的钩子函数。useMemo 可以缓存计算结果,避免在每次渲染时都进行重复计算;useCallback 则可以缓存函数,避免在每次渲染时都创建新的函数实例。

import React, { useMemo, useCallback } from 'react';

function App() {
  const [count, setCount] = React.useState(0);

  const expensiveCalculation = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
      sum += i;
    }
    return sum;
  }, []);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Expensive Calculation: {expensiveCalculation}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App;

使用 key 优化列表渲染:帮助框架高效识别元素变化

在 React 或 Vue 中,使用 key 可以帮助框架快速识别哪些元素发生了变化,从而优化列表的渲染性能。key 应该是唯一的,并且尽量保持稳定。

import React from 'react';

function List() {
  const items = [1, 2, 3, 4, 5];
  return (
    <ul>
      {items.map(item => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

export default List;

缓存策略

本地存储与会话存储:浏览器中的数据缓存方案

localStorage 和 sessionStorage 是浏览器提供的两种本地存储机制,它们可以用于在浏览器中存储数据,避免重复请求服务器。localStorage 中的数据会一直保留,直到手动清除;而 sessionStorage 中的数据会在会话结束时自动清除。

// 存储数据
localStorage.setItem('username', 'JohnDoe');

// 获取数据
const username = localStorage.getItem('username');
console.log(username);

强缓存与协商缓存:减轻服务器压力,加速页面加载

通过设置 HTTP 响应头,我们可以实现强缓存和协商缓存。强缓存可以让浏览器直接使用本地缓存的资源,而无需向服务器发送请求;协商缓存则需要向服务器发送一个请求,询问服务器该资源是否有更新,如果没有更新,则使用本地缓存。

// Node.js 示例
const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  const filePath = path.join(__dirname, 'public', req.url);
  fs.stat(filePath, (err, stats) => {
    if (err) {
      res.statusCode = 404;
      res.end('File not found');
    } else {
      const lastModified = new Date(stats.mtime).toUTCString();
      const ifModifiedSince = req.headers['if-modified-since'];
      if (ifModifiedSince === lastModified) {
        res.statusCode = 304;
        res.end();
      } else {
        res.setHeader('Last-Modified', lastModified);
        res.setHeader('Cache-Control', 'max-age=3600'); // 强缓存 1 小时
        fs.createReadStream(filePath).pipe(res);
      }
    }
  });
});

server.listen(3000, () => {
  console.log('Server is running on port 3000');
});

网络优化

CDN 加速:借助分布式网络,提升资源加载速度

CDN(内容分发网络)可以将静态资源分发到离用户最近的节点,从而减少网络延迟,提高资源加载速度。使用 CDN 可以显著提升页面的响应性能,尤其是对于全球范围内的用户。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
</head>

<body>
  <!-- 页面内容 -->
</body>

</html>

Gzip 压缩:减少传输数据大小,提高网络效率

在服务器端开启 Gzip 压缩可以将传输的数据进行压缩,从而减少数据的大小,提高网络传输效率。这对于提升页面加载速度和节省带宽都非常有帮助。

// Node.js 示例
const express = require('express');
const compression = require('compression');
const app = express();

app.use(compression());

app.get('/', (req, res) => {
  res.send('Hello, World!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

HTTP/2 多路复用:高效传输数据,提升大文件处理能力

HTTP/2 的多路复用特性允许在一个连接上同时传输多个请求和响应,这大大提高了大文件上传和下载的速度。与 HTTP/1.1 相比,HTTP/2 可以更高效地利用网络资源。

// Node.js 示例
const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.crt')
});

server.on('stream', (stream, headers) => {
  stream.respond({
    'content-type': 'text/html',
    ':status': 200
  });
  stream.end('<h1>Hello, HTTP/2!</h1>');
});

server.listen(8443, () => {
  console.log('Server is running on port 8443');
});

首屏优化

SSR(服务器端渲染):提前生成 HTML,加速首屏显示

SSR 可以在服务器端生成 HTML 内容,然后将其发送给浏览器。这样可以减少首屏加载时间,提高搜索引擎优化(SEO)效果。对于需要快速显示内容的页面,SSR 是一个非常有效的优化方案。

// Node.js + React SSR 示例
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const App = require('./App');

const app = express();

app.get('/', (req, res) => {
  const html = ReactDOMServer.renderToString(<App />);
  const page = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>SSR Example</title>
    </head>
    <body>
      <div id="root">${html}</div>
      <script src="client.js"></script>
    </body>
    </html>
  `;
  res.send(page);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

骨架屏:营造快速加载的视觉体验

骨架屏是一种在页面加载时显示的占位布局,它可以让用户感觉页面正在快速加载,从而提高用户体验。骨架屏通常使用简单的图形和动画来模拟页面的结构。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <style>
    .skeleton {
      background-color: #f0f0f0;
      animation: skeleton-loading 1s infinite alternate;
    }

    @keyframes skeleton-loading {
      0% {
        opacity: 0.6;
      }

      100% {
        opacity: 1;
      }
    }
  </style>
</head>

<body>
  <div class="skeleton" style="width: 200px; height: 20px;"></div>
  <div class="skeleton" style="width: 150px; height: 20px; margin-top: 10px;"></div>
  <script>
    // 模拟数据加载
    setTimeout(() => {
      const skeletons = document.querySelectorAll('.skeleton');
      skeletons.forEach(skeleton => {
        skeleton.style.display = 'none';
      });
      // 显示真实内容
    }, 2000);
  </script>
</body>

</html>

首屏数据预加载:提前推送数据,减少用户等待

使用 HTTP/2 的 Server Push 可以在服务器端主动推送首屏所需的数据,这样可以减少用户的等待时间,提高页面的响应速度。

// Node.js 示例
const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.crt')
});

server.on('stream', (stream, headers) => {
  stream.pushStream({ ':path': '/styles.css' }, (pushStream) => {
    pushStream.respondWithFile('styles.css', {
      'content-type': 'text/css'
    });
  });
  stream.respond({
    'content-type': 'text/html',
    ':status': 200
  });
  stream.end('<html><head><link rel="stylesheet" href="styles.css"></head><body><h1>Hello, HTTP/2!</h1></body></html>');
});

server.listen(8443, () => {
  console.log('Server is running on port 8443');
});

监控与分析

Performance API:精准测量页面性能指标

使用 Performance API 可以测量页面的各种性能指标,如加载时间、渲染时间等。通过分析这些指标,我们可以找出页面性能的瓶颈,从而进行针对性的优化。

window.addEventListener('load', () => {
  const performanceData = window.performance.timing;
  const loadTime = performanceData.loadEventEnd - performanceData.navigationStart;
  console.log(`Page loaded in ${loadTime} milliseconds`);
});

如果以上性能关于性能优化对于你有帮助的话,请给作者点个赞吧

屏幕截图 2025-01-01 124104.png

「React」React Router v7 framework qiankun window is not defined

作者 Ada_疯丫头
2025年4月1日 21:48

前言

官网如是说:创建一个 React 项目,我们推荐从一个框架开始。

我“照本宣科”,咔咔一顿操作重构了用户登录、管理中心...一切看起来很顺利,就连 DOM 的 ts + ESLint 校验也尽可能妥善处理了。

鉴于管理中心需要分流的子业务系统比较多,也便于后续团队成员加入,我很自然的想到用微前端架构。

其实在这之前,已经采用 monorepo 架构尝试了一下 —— 当前人手有限,一个仓库梭哈所有项目,开发效率没得说,但是稍微考虑后面项目体量会随着需求量增加而增加,未来要加入的小伙伴能力梯度等因素,多少觉得不是很保险,比如:项目权限控制、项目稳定性...这也是上面提到的“重构”的来由。

所以借由这个档口,果断迟痛不如早痛,换方案!

多个子业务系统,统一入口,再加上有一个分流入口,很明显这些内容归属于主应用范畴。很快,主应用已就绪。

所谓“万事开头难”,第一步我已经换着花样走了两遍了,接下来就是依葫芦画瓢搭建子应用了。

npx create-react-router@latest

嗯,一口气出来了2个子应用。

至此,物料已就位,是时候引入微应用相关了。考虑之前公司有项目有用过 qiankun,再对比下其他微前端框架,嗯,适当控制下学习量,此次选了认识但之前没机会深入了解的 qiankun。

过程

综上,截至目前涉及到的技术有 React Router v7(因为是框架,内置 vite 打包构建工具及其他), qiankun.

第一步:主应用本地启动起来...子应用 A, B 本地跑起来...✌一切顺利。 第二步:主应用安装 qiankun,为注册子应用做准备... ✌一切顺利。

pnpm add --save qiankun

第三步:主应用使用 qiankun 提供的 API 开始注册子应用...

import { registerMicroApps, start } from "qiankun";

Ops!!! 主应用报错了 —— window is not defined.

image.png

第一反应,上官网找相关说明、GitHub issue 输入关键信息找同伴、AI 求助、百度拆盲盒...

  1. React Router v7 默认 ssr 渲染,那就关掉呗

    // react-router.config.ts
    export default {
      // Config options...
      // Server-side render by default, to enable SPA mode set this to `false`
      ssr: false,
    } satisfies Config;
    

    无效。

    GitHub 如是说,为了更快的渲染效率,即使 ssr: false,也会先在 Node 服务解析部分内容。

    好巧不巧,import 在内。

  2. 看到有类似情况,应该有可以借鉴的思路吧,进去看看...

    image.png

    喔,useEffect 缓冲下...可我这里是直接 import 就触发报错了的。

    哈,看题主动态导入有解决,我也来试试

    useEffect(() => {
        if(typeof window !== undefined){
          const { registerMicroApps,start } = require('qiankun');
          registerMicroApps([]);
          start();
        }    
    }, []);
    

    不好,window 是不报 not defined 了,变 require is not defined 了。但是动态导入直觉可行,React Router v7 应该是另外的写法,再试试 💪(总比现在换 Next.js 再来一次重构要快吧...)

尾声

React Router v7 中的动态导入:通过动态导入的方式延迟加载 qiankun 模块,直到确定是在浏览器环境中运行时再进行导入。这种方式可以避免在服务端渲染阶段就尝试访问 window 对象。例如:

useEffect(() => {
 import('qiankun').then(({ registerMicroApps, start }) => {
   // 在这里注册和启动你的微应用
 });
}, []);

此法亲测可行,记录如此。🖊

当然,应该还有其他可行方案。鄙人着急琢磨主应用与子应用的通信,其他方案就交由有兴趣的小伙伴去探索来分享了。😀

通过 llms.txt 引导 AI 高效使用网站内容

2025年4月1日 19:00
作为示例,本站也开始提供 llms.txt 和 llms-full.txt 的支持,可以参看下面的链接获取相关文件。 llms.txt llms-full.txt 什么是 llms.txt 大型语言模型(LLMs)是截止至训练日期时的人类知识的总集。而如果想要精确地解决更加实时的问题(比如在进行代码生成、研究辅助等任务中),我们可以通过搜索最新知识,依赖网络信息,来极大提升模型的准确性。然而,标准的 HTML 内容通常包含导航元素、JavaScript、CSS 和其他对于 LLMs 而言非必要的信息。这些冗余信息会在对话中占据 LLMs 有限的上下文窗口,也会干扰和降低处理效率。此外,LLMs 直接抓取和解析完整的 HTML 页面效率也很低下。为了应对这些挑战,llms.txt 应运而生,它是一个正在讨论的标准(参见 llms-txt.org),旨在为 LLMs 提供一个简洁、专业的网站内容概述,以单一且易于访问的 Markdown 文件格式呈现。llms.txt 就像一个网站的“指南”,引导 AI 系统找到站点上的关键信息,并以易于阅读和分析的结构化格式...

SU7事故后,20年老司机谈安全驾驶

2025年4月1日 19:42

本文来自微信公众号:判官老司机,作者:判官老司机,题图来自:视觉中国


根据媒体报道,3月29日夜,3名女大学生从湖北自驾小米SU7到安徽参加考试,于德上高速枞阳至祁门路段发生事故,两人当场身亡,一人送医后抢救无效去世。


在此哀悼三位逝者,愿类似悲剧不再重演。


现在事故还在调查中,但我想先跟各位司机,尤其是新手司机,好好聊聊安全驾驶这个事。


声明,以下内容与本次事故无关,不存在任何对本次事故原因和责任划分的判断、推测、评价。


我是2004年10月份考取的B1驾照,当时遇到的教练都非常严厉,动不动把学员骂到飞起。


但我从一开始开车就是很保守的。我在大学期间,春节回老家路上,就目睹过一次非常惨烈的事故现场。死者状况之惨烈,家人坐在地上欲哭无泪,给我造成很大的心理冲击。


不知道现在驾校培训什么样,那时候我们上交规课,都要看好久的事故现场无码视频,驾校宣传栏里专门有一处,挂满视觉冲击力极强的死者照片。


培训教师说,害怕就对了。给大家看这些,就是为了让大家对开车这件事心存敬畏。


那会的汽车也没什么高级安全配置,ABS防抱死、ESP车身电子稳定系统都不是标配,有任何问题就靠车身强度、主副驾安全气囊和安全带来保命。


对了,当时不系安全带不扣分,酒驾也不入刑,有些地方的驾照甚至可以直接花钱办。


不过当年的驾驶环境倒也没特别危险,一方面当时的机动车数量不多,另一方面也没有快递、外卖、电动自行车这类交通参与者。


另外,当时的购车价格门槛较高,车辆性能不高,操控难度大(手动档为主),驾驶员有充分的时间慢慢锻炼升级,人菜就慢点开,其他老司机也不会为难你。


我开过各种古早车型,包括四个前进档十字排列的桑塔纳,没有换档同步器的小面包,油离配合十分熟练,现在给我个手动档车我也能开,就像骑自行车,一旦学会就忘不掉了。


但我仍然很怂,我知道自己开的是个一吨多的、快速移动的金属机器,撞坏了还能修,报废了还能再买,而我自己和其他人都是凡胎肉身,只有一条命。


我前后换过六辆油车,开过的车型不计其数。新能源车很好,配置很丰富,充电比加油便宜,这些我都知道。


但我每次试驾新能源车的时候,在销售迫不及待让我开启辅助驾驶的时候,我都回答,谢谢,我想自己开,辅助驾驶功能我完全无所谓,能跟车就行。


对,我知道目前的辅助驾驶已经无限接近L3,可以在无人工干预的情况下应对大多数路况。


我也知道现在的车前后左右长满了雷达和摄像头,判断车距不需要靠经验,看屏幕听语音提示就行。


可是目前的新司机,在他们拿下驾照,喜提新车后,有多少人是会自己老老实实先开个十万公里,再逐步让辅助驾驶这类高科技介入呢?


我觉得一万公里可能都难,毕竟辅助驾驶就摆在那里,按一下就能开启,很少有人能抵挡诱惑吧。


于是新司机的成长曲线越来越平缓,反正辅助驾驶、智能泊车能应对日常各种需要。


但是突发情况需要司机接管的时候,这些司机是否有足够的经验、能力、反应速度来应对?


我不知道,希望他们有吧。


我为什么强调新司机应该有个十万公里自主驾驶的过程,因为这个过程是锻炼你对车速、车距、路况、突发状况的经验和处理的。


很多突发状况的处理是反直觉的,比如什么情况下应该一脚踩死刹车,什么情况下应该一脚油门踩到底,什么情况下应该迅速打方向盘。


你需要在一秒内通过眼看耳听手动脚踩完成判断和动作,这是一种经过锻炼才能获得的技能,很多情况下是违背人的本能反应的。


上边说的仅是技能类,更重要的是心理层面。


新司机都要经历一个“怂”-“逐渐胆大”-“放飞自我”-“遇到/看到事故”-“又怂”-“逐渐胆大”-“遇到事故”-……-“已老实”的过程。这个过程一般需要在十万公里的自主驾驶中完成。


近几年我在网上看各种事故视频,有很多司机遇到紧急状况不是踩刹车,而是“啊啊啊啊啊啊”。据我所知,目前还没有声控刹车这种技术。


我很怀疑这些司机并没有老老实实开着车出新手村,而是被辅助驾驶抱出新手村的。


有人可能会认为,拿到驾照说明就会开车了。


要知道,驾校教你的是通过考试,具备驾驶的资格,而开车这件事的学习曲线漫长而陡峭,具备资格并不代表你就会开车了。


车企在这个过程中起了什么作用,大家这几年也看到了。


对辅助驾驶能力的宣传无所不用其极,甚至某些车企高管,早些年还演示开启辅助驾驶然后吃便当。


如果真的有天真的司机照做,不但能吃便当,也有机会领便当。


更麻烦的是,现在新能源车的马力一个比一个大,提速一个比一个快,价格越来越低。


以前百万豪车的动力水平,现在被20万级别的车轻松拿捏。


廉价的动力被新司机低门槛获取,然后开启辅助驾驶到处溜达,这就是近几年的现状。


我常说汽车是和平年代普通人能掌握的威力最大的大杀器,何况现在加了电池的新能源车,自重还上升了。


学过初中物理的都知道,更快、更大质量的物体意味着什么。


每次一出事,就有人说菜刀是无辜的,责任在使用菜刀的人。


但是,我们是不是可以避免把菜刀给到幼儿园孩子?


或者,当菜刀已经进化到机关枪的时候,普通人还应该随意购买和使用吗?


按照目前我国的法律法规,辅助驾驶出了问题,责任仍然是司机承担。


任何司机主观上都没有发生事故的意愿,只是很多司机没有意识到,自己的客观能力,能够驾驭什么动力的车辆,或者自己是否已经具备了处理紧急状况的能力。


我只是老司机,不是专业司机,不教大家具体如何开车。我只是开车久了,见得多了,真心为当下汽车产品快速发展与驾驶人技能迅速蜕化的矛盾,感到担心。


最后两个建议,希望新手司机能听进去:


  • 胆小不丢人、怕死不丢人,不敬畏驾驶才丢人。


  • 取得驾照后,前两三年、五万公里,尽可能自己驾驶、泊车。


本文来自微信公众号:判官老司机,作者:判官老司机

下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动

一份没有项目展示的简历,是怎样在面试里输掉的?开源项目或许是你的救命稻草 😭😭😭

作者 Moment
2025年4月1日 21:22

我们公司目前在进行社招全栈开发岗位的招聘,面试过程中,我们特别看重的一点是:如果面试者没有 GitHub 项目或无法提供线上项目链接,那么通常会对简历产生较大影响。因此,我认为参与开源项目是一个非常有意义的举措,不仅能够显著提升个人技术实力,还能有效增强简历的亮点,为求职加分。

目前我们正在运行着两个新的开源项目,它们分别是

  1. file-zenith:多媒体文件处理网站

  2. 面试导航:一个有深度且知识面广的八股文面试网站。

项目地址:

  1. file-zenith

  2. 面试导航

面试导航

为什么没有看到开源代码?因为项目有很多图片,需要用云存储来存储,但是现在所以的 key 都是存放在前端的,所以不好直接把项目代码开源处理,如果有需要的话可以开源非线上版本的代码。

面试导航致力于构建一个全面且深入的前端与后端技术面试知识库,涵盖了当前技术栈的核心内容,旨在为技术学习者提供一个系统化、专业化的学习平台。网站的内容涵盖了最新的技术领域,包括 JavaScript、TypeScript、Node.js、浏览器原理、计算机网络、React 源码、前端工程化、性能优化以及前端监控等多个前沿话题。

与市场上大多数面试题库不同,本网站的特点在于不仅提供基础的面试题目,还深入挖掘每个技术点的原理,帮助用户全面理解技术背后的核心概念。每一个知识点不仅包含对其本身的深入分析,还能通过关联与拓展,帮助学习者从一个问题出发,掌握与之相关的其他面试问题,从而形成一个完整的知识体系。

我们项目的特点主要有以下几个方面:

  • 深入原理:我们不仅提供技术的基础知识,还专注于每个知识点背后的原理解析,帮助用户深刻理解技术细节。

  • 知识体系化:每个技术点都是体系化学习的一部分,学习者可以通过一个个知识点的学习,逐步掌握前后端的技术要点,构建完整的技术框架。

  • 广泛的技术覆盖:包括 JavaScript、TypeScript、Node.js、浏览器原理、计算机网络、React 源码、前端工程化、性能优化及前端监控等热门技术。

  • 面试题拓展:每个知识点不仅局限于单一面试题,还能通过对相关问题的拓展,帮助学习者准备更广泛的面试场景。

  • 针对市场需求:填补现有面试网站内容简略的空白,提供更为深入和系统的技术资源,以帮助学习者在激烈的面试竞争中脱颖而出。

项目目标:

  • 帮助学习者全面掌握前端与后端的最新技术,构建扎实的知识体系。

  • 提供深入的技术解析,使学习者能够在面试过程中清晰地表达技术原理和解决方案。

  • 丰富面试题库,从实际面试场景出发,帮助学习者应对多样化的面试问题。

  • 填补市面上技术内容过于简单的空白,提供更高质量的技术学习平台。

通过本网站,用户将能够系统地掌握当前主流技术栈的精髓,提升自己的技术能力,并为面试做好充分的准备。

20250401205353转存失败,建议直接上传图片文件

20250401205418

如何参与内容贡献?

我们欢迎所有对技术学习和分享充满热情的开发者参与我们的内容建设!无论你是学生、刚入职场的开发者,还是经验丰富的技术大咖,只要你愿意分享自己的技术知识和经验,都可以加入我们的社区,共同推动项目发展。

内容贡献方式:

  1. 撰写技术内容:你可以分享技术文章、面试题解析、学习笔记、项目经验等。我们会将优质内容发布在平台上,帮助更多用户学习和成长。

  2. 参与开源项目:我们有一些开源项目正在进行中,欢迎大家参与其中,为社区提供更多实际案例和实战经验。

  3. 提供反馈与建议:如果你在使用平台时有任何建议或发现内容中的问题,欢迎主动与我们联系,帮助我们不断完善平台。

如何参与?

  • 添加微信:扫描下方二维码或直接添加微信号 yunmz777,备注“内容贡献”,我们会第一时间与你取得联系。

  • 加入技术交流群:在群内不仅可以讨论技术问题,还能及时获取我们的项目动态和参与机会。

贡献者的专属福利:

  • 后续可以优先体验我们的独家技术服务,获取前沿技术的深度学习资料和指导。

  • 享受一些平台提供的商业合作机会,参与我们的商业项目,共同探索更多可能性。

  • 有机会成为我们的核心成员,参与平台发展决策,获取更多专业提升和资源支持。

让我们一起打造一个开放、友好、充满技术热情的学习社区,期待你的加入!😊

file-zenith

这是一个基于 Next.jsTailwind CSSTypeScript 构建的现代化文件处理平台。用户可以在网页上直接进行常见的文件处理操作,提升工作效率。平台支持多种文件操作,确保高效、安全地处理各类文件,支持的操作包括但不限于:

  • 文件压缩:支持压缩图片、PDF 文件等,减小文件大小,便于传输和存储。

  • 文件格式转换:提供多种文件格式之间的转换,例如将 PNG 图片转换为 JPG、DOCX 转换为 PDF 等。

  • 批量处理:支持批量上传与处理,节省时间和精力。

  • 文件合并与分割:如合并多个 PDF 文件或将一个大型文件分割成多个小文件。

  • 安全性保证:平台采用最新的加密技术,确保用户文件的安全性与隐私保护。

  • 持续更新功能:平台将定期更新新的功能,满足用户日益增长的需求,如文字识别、视频压缩等。

无论是日常办公、开发还是个人使用,本工具都能为你提供便捷的文件处理方案,帮助提升工作效率,节省宝贵的时间。

20250401210151

🛠 技术栈

  • Next.js - 服务端渲染与静态生成支持,确保页面的高效加载与 SEO 优化

  • Tailwind CSS - 高效的原子级 CSS 框架,快速构建响应式与美观的界面

  • TypeScript - 类型安全的 JavaScript 超集,提升代码质量与开发效率

  • NestJS - 强大的 Node.js 框架,用于构建高效的服务器端应用,提供更复杂的文件处理逻辑和 API 支持

  • Sharp - 高效的图像处理库,用于图像压缩、格式转换和处理

  • FFmpeg - 强大的多媒体处理工具,用于视频、音频格式转换与处理

🚀 功能特色

  • 🌐 无需安装,在线即可完成文件处理,无需额外配置或下载任何软件。

  • ⚡️ 高性能文件操作,响应快速,文件处理过程顺畅,不浪费任何时间。

  • 🔐 本地处理:所有文件操作均在用户浏览器中完成,避免上传与下载的安全风险。

  • 📱 响应式设计,无论是桌面端还是移动端,均能流畅操作,确保多设备兼容。

  • 🧩 模块化设计,便于功能拓展与维护,可持续增加新的文件处理工具和功能。

  • 🛡️ 隐私保护,平台不会存储用户上传的文件,文件一旦处理完成,即刻删除,确保用户数据隐私。

  • 🔄 CI/CD 自动化:使用 GitHub Actions 实现自动化测试、代码质量分析和预览部署,确保代码质量和快速迭代。每个 PR 自动部署预览环境,SonarQube 实时代码分析保障质量标准。

20250401210224

在提交代码阶段就能看到预览效果方便 code review,这些 ci 还是挺有意思的......

测试目录结构与说明

该项目包含以下测试类型:

tests/
├── unit/                     # 单元测试目录
│   ├── README.md             # 单元测试文档
│   ├── components/           # 组件单元测试
│   │   └── ThemeButton.test.tsx # 主题按钮测试
│   ├── hooks/                # React Hooks测试
│   │   └── use-mobile.test.tsx # 移动设备检测Hook测试
│   ├── utils/                # 工具函数测试
│   │   └── cn.test.ts        # 样式工具函数测试
│   ├── coverage/             # 单元测试覆盖率报告
│   └── reports/              # 单元测试HTML报告
│
├── integration/              # 集成测试目录
│   ├── README.md             # 集成测试文档
│   ├── components/           # 组件集成测试
│   │   └── Header-ThemeButton.test.tsx # 头部与主题按钮集成测试
│   ├── pages/                # 页面集成测试
│   │   └── HomePage.test.tsx # 首页集成测试
│   └── utils/                # 测试工具
│       └── test-providers.tsx # 测试上下文提供器
│
├── e2e/                      # 端到端测试目录
│   ├── README.md             # 端到端测试文档
│   ├── home.spec.ts          # 首页端到端测试
│   ├── home-po.spec.ts       # 使用页面对象模式的首页测试
│   ├── page-objects/         # 页面对象目录
│   │   └── HomePage.ts       # 首页页面对象
│   ├── reports/              # 测试报告输出目录
│   │   └── index.html        # HTML测试报告
│   ├── results/              # 测试结果目录
│   │   └── .last-run.json    # 最后运行记录
│   └── utils/
│       └── test-helpers.ts   # 测试辅助函数
│
└── coverage/                 # 整体测试覆盖率报告

测试类型说明

单元测试 (Unit Tests)

单元测试关注于测试应用程序的最小可测试单元,通常是单个函数、组件或类。这些测试是隔离的,不依赖于其他部分的功能。

  • 技术栈: Vitest, React Testing Library
  • 运行命令: pnpm test:unit
  • 覆盖率报告: pnpm test:coverage
集成测试 (Integration Tests)

集成测试检验多个单元如何一起工作,测试组件之间的交互或数据流。

  • 技术栈: Vitest, React Testing Library
  • 运行命令: pnpm test:integration
端到端测试 (E2E Tests)

端到端测试模拟真实用户行为,在实际的浏览器环境中测试整个应用程序流程。

  • 技术栈: Playwright
  • 运行命令:
    • 运行测试: pnpm test:e2e
    • UI 模式: pnpm test:e2e:ui
    • 调试模式: pnpm test:e2e:debug
    • 查看报告: pnpm test:e2e:report

测试覆盖率

项目目前的测试覆盖率如下(通过 pnpm test:coverage 查看详细报告):

  • 语句覆盖率 (Statements): 34.85%
  • 分支覆盖率 (Branches): 76.47%
  • 函数覆盖率 (Functions): 39.13%
  • 行覆盖率 (Lines): 34.85%

关键组件如 Header.tsx (98.46%)、ThemeButton.tsx (100%)、use-mobile.tsx (100%) 和 cn.ts (100%) 已有高覆盖率。

💡 如何贡献

我们欢迎所有形式的贡献,无论是功能建议、代码贡献还是问题反馈。以下是参与项目的方式:

贡献流程

  1. 提交 Issue

    • 发现 bug?有新功能想法?请先创建一个 Issue
    • 清晰描述问题或建议,附上必要的截图和复现步骤
  2. 认领任务

    • 浏览现有 Issue,找到感兴趣的任务
    • 在 Issue 下留言认领,表明你将处理此问题
  3. 开发

    • Fork 项目仓库到你的账号
    • 创建特性分支 git checkout -b feature/your-feature-name
    • 进行开发并提交更改
  4. 提交 PR

    • 完成开发后,提交 Pull Request 到主仓库
    • PR 描述中关联相关 Issue(例如 "Fixes #123")
    • 等待代码审查和合并

开发指南

  • 确保遵循项目的代码风格和最佳实践
  • 添加适当的测试覆盖你的更改
  • 更新文档以反映你的贡献

参与贡献不仅能帮助改进产品,也是提升个人技能的绝佳机会。我们期待你的创意和贡献!

📞 联系我们

如果你有任何问题、建议或合作意向,欢迎通过以下方式联系我们:

  • 微信yunmz777

  • GitHub Issues:在项目 Issues 页面提交问题或建议

我们非常重视用户反馈,并致力于不断改进产品体验。无论是技术讨论、功能需求还是使用咨询,都欢迎随时联系!

总结

最后发布一则照片广告,组内急招实习生,有没有想看看快手这边的机会的,vue 技术栈,有微前端经历更佳,可以帮忙看看简历和指导一下:

20250401205703

如果想要其他公司的或者其他岗位的可以留言,如果有相关的都可以帮到你。

除了这些项目之外,我们还有两个其他的开源项目:

如果你想参与进来开发或者想进群学习,可以添加我微信 yunmz777,后面还会有很多需求,等这个项目完成之后还会有很多新的并且很有趣的开源项目等着你。

鸿海:印度子公司从苹果取得机器设备一批,交易金额约3225.8万美元

2025年4月1日 20:57
4月1日晚间,鸿海公告,子公司Foxconn Hon Hai TechnologyIndia Mega Development Private Limited,从2024年10月25日迄今向苹果(Apple)取得机器设备一批,交易金额约3225.8万美元,主要为营运需求。同日公告,其子公司Ingrasys(Singapore)Pte. Ltd.对越南Fulian Precision Technology Component增资2340万美元,主要目的为长期投资。(界面)
❌
❌