普通视图

发现新文章,点击刷新页面。
今天 — 2025年7月2日掘金 前端

Vue修饰符

2025年7月2日 17:15

一、修饰符的本质与分类

定义:Vue 修饰符是添加在指令后缀的特殊符号,用于修改指令的行为,简化常见场景的代码逻辑。
分类

  1. 事件修饰符:优化事件处理(如 @click.prevent);
  2. 表单修饰符:处理表单输入(如 v-model.trim);
  3. 按键修饰符:监听特定按键(如 @keyup.enter);
  4. 系统修饰符:处理键盘/鼠标特殊操作(如 @click.ctrl);
  5. 组件修饰符:优化组件通信(如 .syncv-model 的修饰符)。

二、事件修饰符(最常用类别)

1. 核心事件修饰符
修饰符 作用 等价代码
.prevent 阻止事件默认行为(如 a 标签跳转) event.preventDefault()
.stop 阻止事件冒泡 event.stopPropagation()
.capture 使用事件捕获阶段(而非冒泡阶段) addEventListener('click', handler, { capture: true })
.self 仅当事件发生在元素自身时触发 if (event.target === event.currentTarget) {...}
.once 事件只触发一次 event.addEventListener('click', handler, { once: true })
2. 示例与最佳实践
<!-- 阻止表单提交默认行为 -->
<form @submit.prevent="handleSubmit">...</form>

<!-- 阻止事件冒泡(如弹窗蒙层点击) -->
<div class="modal" @click.stop="closeModal">
  <div class="content" @click="doNotClose">...</div>
</div>

<!-- 事件只触发一次(如初始化按钮) -->
<button @click.once="initSystem">初始化系统</button>

三、表单修饰符(v-model 增强)

1. 常用表单修饰符
  • .trim:自动过滤输入首尾空格
    <input v-model.trim="username" />
    
  • .number:将输入转为数字(否则为字符串)
    <input v-model.number="age" type="number" />
    
  • .lazy:失去焦点时更新(而非实时更新)
    <!-- 适用于文本区域等不需要实时更新的场景 -->
    <textarea v-model.lazy="article"></textarea>
    
2. 修饰符组合使用
<!-- 输入数字,过滤空格,失去焦点时更新 -->
<input v-model.number.trim.lazy="price" />

四、按键修饰符与系统修饰符

1. 按键修饰符
  • .enter.tab.delete(捕获删除键)等
    <input @keyup.enter="search" />
    
  • 自定义按键修饰符
    // main.js
    Vue.config.keyCodes.f1 = 112; // 定义F1键
    
    <input @keyup.f1="showHelp" />
    
2. 系统修饰符(配合键盘/鼠标按键)
  • .ctrl.shift.alt.meta(Windows键/Command键)
    <button @click.ctrl="copyText">Ctrl+点击复制</button>
    
  • .left.middle.right(鼠标按键)
    <div @click.right="openMenu">右键打开菜单</div>
    

五、组件修饰符(高级场景)

1. .sync 修饰符(Vue 2)
  • 作用:简化父子组件双向绑定,是 v-model 的语法糖
    <!-- 父组件 -->
    <ChildComponent :title.sync="pageTitle" />
    
    <!-- 等价于 -->
    <ChildComponent 
      :title="pageTitle" 
      @update:title="pageTitle = $event" 
    />
    
2. v-model 的修饰符(Vue 2 与 Vue 3)
  • .number.trim.lazy(同表单修饰符)
  • Vue 3 新增 .modelValue 自定义绑定
    <!-- 子组件 -->
    <script setup>
    defineProps({
      modelValue: {
        type: String,
        default: ''
      }
    });
    
    defineEmits(['update:modelValue']);
    </script>
    
    <!-- 父组件 -->
    <ChildComponent v-model="message" />
    

六、问题

1. 问:.prevent 和 .stop 有什么区别?请举例说明。
    • .prevent:阻止事件默认行为(如链接跳转、表单提交);
    • .stop:阻止事件冒泡(如父元素和子元素都有点击事件时,避免同时触发);
    • 示例
      <!-- 阻止链接默认跳转 -->
      <a href="#" @click.prevent="goToPage">点击跳转</a>
      
      <!-- 阻止事件冒泡(子元素点击不触发父元素事件) -->
      <div @click="parentClick">
        <button @click.stop="childClick">点击我</button>
      </div>
      
2. 问:v-model.number 有什么作用?为什么需要它?
    • 作用:将输入值转为数字类型(否则输入框值始终为字符串);
    • 场景:当输入框需要数值计算时(如年龄、价格),避免类型错误:
      <input v-model.number="age" type="number" />
      <button @click="age += 1">+1</button>
      
3. 问:.once 修饰符的实现原理是什么?
    • 本质:为事件添加 { once: true } 选项,使事件监听器只执行一次;
    • Vue 源码中,事件修饰符会被编译为对应原生 API 的参数,.once 对应:
      element.addEventListener('click', handler, { once: true });
      

七、修饰符的底层实现原理

  1. 编译阶段处理
    Vue 模板中的修饰符会被编译为对应的 DOM 事件处理逻辑,例如:

    @click.prevent.stop="handler"
    

    会被编译为:

    event.preventDefault();
    event.stopPropagation();
    handler.call(this, event);
    
  2. 性能优势
    修饰符直接在 DOM 事件层处理,避免 JavaScript 逻辑层的额外判断,提升执行效率。

八、总结

事件修饰符,防默认(.prevent),阻冒泡(.stop),
捕获阶段用 .capture,自身触发加 .self,只一次用 .once;
表单修饰符,.trim 去空格,.number 转数字,.lazy 失焦更;
按键修饰符,.enter .delete,系统修饰符,ctrl shift 配;
组件修饰符,.sync 双向绑,v-model 加修饰,表单更流畅。

Typescript入门MCP Server开发

作者 挖啊挖
2025年7月2日 17:14

前言

大家好!随着大语言模型(LLM)和 AI Agent 技术的飞速发展,如何让 AI 模型与我们现实世界的工具和服务进行交互,成为了一个越来越重要的话题。模型上下文协议(Model-Context Protocol, MCP) 就是为此而生的一个开放标准,它旨在为 AI 模型与各种工具(Tools)和资源(Resources)之间提供一个通用的、标准化的通信桥梁。

简单来说,MCP 就像是为 AI Agent 准备的“万能插座”,无论你用的是什么模型,只要你的工具遵循 MCP 协议,就能轻松地被 AI Agent 发现、理解和调用。

今天,我们将通过一个非常简单但实用的例子,手把手教你如何使用官方的 TypeScript SDK (@modelcontextprotocol/sdk) 构建一个 MCP 服务。这个服务将对外暴露一个“查询天气”的工具,可以根据输入的城市编码(adcode)从高德地图 API 获取实时的天气信息。

让我们开始吧!

准备

1. 项目设置

# 创建项目
mkdir mcp-weather && cd mcp-weather
npm init -y

# 安装依赖
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node # 开发依赖

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

2. 代码

types.ts

/**
 * 高德天气API返回的天气信息接口
 * @interface WeatherInfo
 */
export interface WeatherInfo {
  /**
   * 实况天气数据列表
   */
  lives: {
    /**
     * 省份名
     */
    province: string;
    /**
     * 城市名
     */
    city: string;
    /**
     * 区域编码
     */
    adcode: string;
    /**
     * 天气现象(汉字描述)
     */
    weather: string;
    /**
     * 实时气温,单位:摄氏度
     */
    temperature: string;
    /**
     * 风向描述
     */
    winddirection: string;
    /**
     * 风力级别,单位:级
     */
    windpower: string;
    /**
     * 空气湿度
     */
    humidity: string;
    /**
     * 数据发布的时间
     */
    reporttime: string;
  }[];
}

export type WeatherLive = WeatherInfo["lives"][0];

index.ts

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod"; // 导入zod用于参数验证和类型定义
import type { WeatherInfo, WeatherLive } from "./types.js";

const AMAP_API_BASE = "https://restapi.amap.com/v3/weather/weatherInfo?";
//! 高德地图API密钥(需要在高德开放平台申请)
const AMAP_API_KEY = "";

/**
 * 创建MCP服务器实例
 * MCP (Model Context Protocol) 是一个标准化协议,用于AI模型与外部工具的通信
 */
const server = new McpServer({
  name: "weather",
  version: "1.0.0",
  capabilities: {
    resources: {},
    tools: {},
  },
});

async function getWeatherLive(adcode: string): Promise<WeatherLive | null> {
  try {
    const url = `${AMAP_API_BASE}key=${AMAP_API_KEY}&city=${adcode}`;
    const res = await fetch(url);
    const data = (await res.json()) as WeatherInfo;
    return data.lives[0];
  } catch (error) {
    console.error("Failed to fetch weather data:", error);
    return null;
  }
}
function formatWeatherLive(weatherLive: WeatherLive) {
  return [
    `省份名:${weatherLive.province}`,
    `城市名:${weatherLive.city}`,
    `区域编码:${weatherLive.adcode}`,
    `天气现象(汉字描述):${weatherLive.weather}`,
    `实时气温,单位:摄氏度:${weatherLive.temperature}`,
    `风向描述:${weatherLive.winddirection}`,
    `风力级别,单位:级:${weatherLive.windpower}`,
    `空气湿度:${weatherLive.humidity}`,
    `数据发布的时间:${weatherLive.reporttime}`,
  ].join("\n");
}
/**
 * 注册MCP工具:获取天气信息
 * 这是MCP服务器的核心功能,定义了一个可被AI模型调用的工具
 */
server.tool(
  "get-weather", // 工具名称,AI模型会通过这个名称调用工具
  "输入城市的 adcode,查询天气", // 工具描述,帮助AI理解工具用途
  {
    // 定义工具参数的验证模式
    adcode: z.string().describe("城市的 adcode"),
  },
  // 工具的具体实现函数
  async ({ adcode }) => {
    const weatherLive = await getWeatherLive(adcode);
    if (!weatherLive) {
      return {
        content: [
          {
            type: "text",
            text: "不能获取天气信息",
          },
        ],
      };
    }

    const formattedLive = formatWeatherLive(weatherLive);
    return {
      content: [
        {
          type: "text",
          text: formattedLive,
        },
      ],
    };
  }
);

/**
 * 主函数:启动MCP服务器
 */
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.log("Weather MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

3. 编译

npx tsc

4. 运行

这里使用 Trae 示例

4.1 配置 MCP

image.png

4.2 选择手动添加

image.png

4.3 复制粘贴进打开的窗口
{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": [
          // 替换成编译后index.js的绝对路径 
        "C:\\Users\\XX\\XXX\\mcp-weather\\build\\index.js"
      ]
    }
  }
}
4.4 检查连接是否正确

image.png

4.5 开始测试

image.png

4.6 测试正确

image.png

附录: 完整的项目代码

你可以在一个 Git 仓库中找到本文的所有代码,方便你克隆和运行。 (github.com/heappynd/mc…)

参考文档

modelcontextprotocol.io/introductio…

基于 Baidu JSAPI Three 的三维围墙可视化 Demo

作者 星使bling
2025年7月2日 17:10

基于 Baidu JSAPI Three 的三维围墙可视化 Demo

项目简介

本项目演示如何在 React 环境下,结合 @baidumap/mapv-three 和 Three.js,实现北京市及其各区行政区轮廓的三维墙体可视化,并支持底图切换(Bing影像、百度矢量、无底图)。

效果截图

image1.png

image2.png

image3.png


环境准备

  • Node.js
  • VSCode / Cursor / ...

依赖安装

在 wall 目录下执行:

# 安装核心依赖
npm install --save @baidumap/mapv-three three@0.158.0 react react-dom

# 安装开发与构建相关依赖
npm install --save-dev webpack webpack-cli copy-webpack-plugin html-webpack-plugin @babel/core @babel/preset-env @babel/preset-react babel-loader

数据与资源说明

  • 行政区数据位于 data/geojson/ 目录下:
    • polygon.geojson:北京市整体轮廓
    • polygon_districts.geojson:北京市各区轮廓
  • 所有数据和静态资源会在构建时自动拷贝到 dist/mapvthree/assets/ 下,无需手动操作。
  • 其他数据可在阿里云平台下载。

目录结构

wall/
├── dist/                    # 构建输出目录
│   ├── main.js
│   ├── index.html
│   └── mapvthree/assets/    # 静态资源与数据
│       └── geojson/
├── data/
│   └── geojson/             # GeoJSON 数据
│       ├── polygon.geojson
│       └── polygon_districts.geojson
├── image/                   # 效果截图
│   ├── image1.png
│   └── image2.png
├── node_modules/
├── src/
│   ├── Demo.jsx             # 主功能组件
│   └── index.js             # 入口文件
├── webpack.config.js        # 构建配置
├── package.json
└── README.md

构建与运行

# 开发模式打包
npx webpack

# 生成的文件在 dist/ 目录下
# 用浏览器打开 dist/index.html 即可预览 Demo 效果

核心代码说明

入口文件 src/index.js

import React from 'react';
import { createRoot } from 'react-dom/client';
import Demo from './Demo';

const root = createRoot(document.getElementById('container'));
root.render(<Demo />);

主组件 src/Demo.jsx

  • 初始化三维地图引擎
  • 加载北京市及各区 GeoJSON 数据,绘制三维墙体
  • 支持底图切换(右上角下拉框)
  • 墙体统一颜色,带动画
  • 代码片段如下:
import React, { useRef, useEffect, useState } from 'react';
import * as mapvthree from '@baidumap/mapv-three';
import * as THREE from 'three';

// provider 选项
const PROVIDERS = [
    { label: 'Bing影像', value: 'bing' },
    { label: '百度矢量', value: 'baidu' },
    { label: '无底图', value: 'none' },
];

const WALL_COLOR = '#0DCCFF';

const Demo = () => {
    const ref = useRef();
    const [provider, setProvider] = useState('bing');
    const engineRef = useRef();

    useEffect(() => {
        mapvthree.BaiduMapConfig.ak = '您的AK';

        // provider 实例
        let providerInstance = null;
        if (provider === 'bing') {
            providerInstance = new mapvthree.BingImageryTileProvider();
        } else if (provider === 'baidu') {
            providerInstance = new mapvthree.BaiduVectorTileProvider();
        } else {
            providerInstance = null;
        }

        // 创建引擎
        const engine = new mapvthree.Engine(ref.current, {
            map: {
                provider: providerInstance,
                projection: 'ECEF',
                center: [116.41439809344809, 40.08866668808574],
                pitch: 0, 
                range: 100000, 
            },
            rendering: {
                sky: null,
                enableAnimationLoop: true,
            },
        });
        engineRef.current = engine;
        engine.rendering.bloom.enabled = true;

        // 1. 绘制北京市总轮廓
        fetch('mapvthree/assets/geojson/polygon.geojson')
            .then(rs => rs.json())
            .then(rs => {
                const data = mapvthree.geojsonUtils.convertPolygon2LineString(rs);
                const wall = engine.add(
                    new mapvthree.Wall({
                        height: 5000,
                        color: WALL_COLOR,
                        enableAnimation: true,
                        animationTailType: 3,
                        animationSpeed: 1,
                        opacity: 0.5,
                    })
                );
                wall.dataSource = mapvthree.GeoJSONDataSource.fromGeoJSON(data);
            });

        // 2. 绘制各区轮廓
        fetch('mapvthree/assets/geojson/polygon_districts.geojson')
            .then(rs => rs.json())
            .then(rs => {
                if (rs && rs.features) {
                    rs.features.forEach((feature) => {
                        const data = mapvthree.geojsonUtils.convertPolygon2LineString({
                            type: 'FeatureCollection',
                            features: [feature],
                        });
                        const wall = engine.add(
                            new mapvthree.Wall({
                                height: 5000,
                                color: WALL_COLOR,
                                enableAnimation: true,
                                animationTailType: 3,
                                animationSpeed: 1,
                                opacity: 0.5,
                            })
                        );
                        wall.dataSource = mapvthree.GeoJSONDataSource.fromGeoJSON(data);
                    });
                }
            });

        // 地图点击事件
        engine.map.addEventListener('click', e => {
            console.log('点击坐标:', e.point);
        });

        return () => {
            engine.dispose();
        };
    }, [provider]);

    // provider 切换控件(右上角)
    return (
        <div ref={ref} style={{ width: '100vw', height: '100vh', position: 'fixed', left: 0, top: 0 }}>
            <div style={{ position: 'absolute', zIndex: 10, right: 20, top: 20, background: 'rgba(255,255,255,0.9)', padding: 8, borderRadius: 4 }}>
                <label>底图选择:</label>
                <select value={provider} onChange={e => setProvider(e.target.value)}>
                    {PROVIDERS.map(opt => (
                        <option key={opt.value} value={opt.value}>{opt.label}</option>
                    ))}
                </select>
            </div>
        </div>
    );
};

export default Demo;

数据与构建细节

  • GeoJSON 数据:已包含北京市及各区真实行政区划数据,无需手动下载。
  • 构建自动拷贝webpack.config.js 配置了 CopyWebpackPlugin,会自动将 node_modules/@baidumap/mapv-three/dist/assetsdata/ 下的所有内容拷贝到 dist/mapvthree/assets/,确保数据和贴图可用。
  • 静态资源:地图引擎所需的贴图、worker、模型等均自动拷贝,无需手动干预。

注意事项

  • AK 配置:请在 src/Demo.jsx 中将 mapvthree.BaiduMapConfig.ak 替换为你自己的百度地图开发者密钥(AK)。
  • 数据来源:GeoJSON 行政区数据来源于阿里云开放平台,真实可靠。
  • 如需自定义墙体颜色/高度/动画,可直接修改 Demo.jsx 中的 Wall 配置。

参考资料

Figma 的加载速度是怎么靠 Rust 提上去的

2025年7月2日 16:59

本文首发公众号 猩猩程序员 欢迎关注

本文来自 www.figma.com/blog/suppor…

使用 Rust 内存优化提升文件加载速度

Raghav Anand,Figma 软件工程师
Figma 工程团队|质量与性能

🎨 插图作者:Jose Flores
两个色彩斑斓的碗——一个混乱而溢出,另一个整齐堆叠——盛满了几何形状的水果和蔬菜,象征内存使用的混乱与优化。


内存效率对于良好的用户体验至关重要。为了保持文件的快速加载和高性能,Figma 团队持续在寻找优化点——以下是我们在 Rust 中的一些优化实践。


文件加载背后的内存结构

Figma 的多人协作系统负责加载文件、同步多个协作者的更新,以及定期保存文件状态的快照。为了让实时协作在复杂的 Figma 文件结构图上高效运作,我们会将大量文件内容加载到内存中。

随着 Figma 的不断发展,我们在寻找能够高效扩展且不牺牲用户体验的方法。引入动态页面加载后,我们服务器端需要解码的文件数量激增了 30%。为应对这一新增负载,我们在 Rust 中探索了多项性能优化,取得了更快的加载速度与更高的内存效率。


更小、更高效的内存映射结构

一个 Figma 文件本质上是由一系列节点组成的集合。每个节点代表一个对象,例如三角形、方块、画板等;每个节点可以看作是一组属性的集合,例如颜色、形状、父节点等等。

在服务器端,我们将节点表示为一个键值映射结构:Map<属性名(u16), 属性值(u64指针)>,其中 u16u64 分别是键值的比特宽度。

这个映射结构位于文件加载的关键路径上,任何在此处的加速都能直接带动整体文件加载速度的提升。同时,内存分析也表明这个 map 占据了单个文件内存使用的 60% 以上(考虑到它只是存元数据,这结果让我们颇为惊讶)。

我们原先使用 Rust 默认的 BTreeMap 实现,因为我们的序列化协议需要有序迭代。但仔细研究后我们意识到:由于键的范围非常小,我们其实并不需要通用 map 的所有特性。

键空间非常有限

Figma 文件的字段(属性)定义在 schema 中,而该 schema 的演进速度与人类速度一致——非常慢。当前 schema 字段数量少于 200 个,并且这些字段呈现聚集状态。例如,只有评论节点才会使用“评论相关字段”,其他节点不会用到。

我们在实际文件上统计后发现:每个节点的属性平均只有大约 60 个。

使用更简单的 Vec 结构

基于此发现,我们将 BTreeMap 替换为更简单、内存效率更高的结构:排好序的扁平向量(vector)

原结构如下:

BTreeMap<u16, pointer>{
    0: 1,       // node_id
    1: FRAME,   // 类型
    2: null,    // parent_id
    ... (x, y, w, h 等)
}

优化后结构为:

Vec<(u16, pointer)>{
    (0, 1),
    (1, FRAME),
    (2, null),
    ... (x, y, w, h)
}

理论上,使用向量结构的操作复杂度如下表:

操作 BTreeMap Vec
插入 O(log n) O(n) 最坏
查找 O(log n) O(n)
修改 O(log n) O(n)

看起来似乎向量更慢。但实际上,由于计算机在处理线性、小量内存时非常高效(恰好适配我们的数据结构),所以实际文件反序列化速度更快。而且对于大型文件,内存使用下降了近 25% ,在生产环境中取得了显著收益。


用位填充(bit stuffing)进一步节省内存

虽然我们部署了上述优化,但我们还研究了一项尚未上线的额外优化。

我们再次审视 Map<u16, pointer>,提出问题:指针究竟是什么?

通常,指针是 64 位数据,用于告诉 CPU 数据的起始地址。64 位可表示 2⁶⁴ 字节,也就是 18 EB(前所未有的量级)。实际上,x86 架构通常只使用其中的 48 位来寻址。

因此,指针的高 16 位通常是空的,可以用作其他用途:

[0_u16, pointer_u48]

恰好,我们的 Map<u16, pointer> 中需要额外的 16 位存储字段 ID。因此,我们可以将字段 ID 与指针打包进一个 64 位整数中!

优化后的数据结构如下:

Vec<u64>{
    [0_u16 << 48 | ptr_u48],
    [1_u16 << 48 | FRAME_ptr_u48],
    ...
}

我们通过位操作(bitwise operations)从中提取字段 ID 和实际指针。这种结构虽然更复杂,需要精细管理生命周期(例如处理引用计数、避免内存泄漏),但它确实带来了进一步的提升:

  • 性能略有提升
  • RSS(常驻内存)下降约 5%

虽然我们原本期待节省 20%(因为只用 64 位而不是两个值),但实际 RSS 受操作系统和内存分配器的影响较大,不能简单地从分配量推导。

最终,我们决定不将此优化投入生产,原因很简单:收益不足以抵消潜在的稳定性风险。但如果未来需要,我们仍可启用这一策略。


成果:数字在下降!

使用 vector 代替 map 后,Rust 中的 Figma 文件加载速度显著提升:

  • 在 P99 文件加载延迟中,反序列化时间缩短了 20%
  • 整体多人协作系统内存成本下降了 20%

我们始终致力于让 Figma 的多人协作技术变得更高效、更易维护、更有性能。如果你对这样的优化感兴趣,欢迎加入我们

本文首发公众号 猩猩程序员 欢迎关注

前端写JS经常用到的事件函数,持续更新中

作者 琦遇
2025年7月2日 16:45

前端写JS经常用到的事件函数,以下是博主经常用到事件函数:

1. 防抖 (Debounce)

场景:防止函数在短时间内被高频触发,常用于输入框搜索、窗口大小调整等。

代码

function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用示例
const debouncedSearch = debounce(() => console.log('Searching...'), 500);
window.addEventListener('input', debouncedSearch);

2. 节流 (Throttle)

场景:保证函数在一定时间间隔内只执行一次,常用于监听滚动事件、拖拽事件。

代码

function throttle(fn, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime > delay) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// 使用示例
const throttledScroll = throttle(() => console.log('Scrolling...'), 1000);
window.addEventListener('scroll', throttledScroll);

3. 数组去重 (Unique Array)

场景:移除数组中的重复元素。

代码

function setArray(arr) {
  return [...new Set(arr)];
}

// 使用示例
console.log(setArray([1, 1, 'b', 'b', 3, 3])); // 输出 [1, "a", 3]

4. 生成随机字符串 (Generate Random String)

场景:生成一个指定长度的随机字符串,常用作唯一ID。

代码

function randomString(length = 8) {
  return Math.random().toString(36).substring(2, 2 + length);
}
// 使用示例
console.log(randomString(10)); // 输出类似 "7j2k9n4p1q"

5. 首字母大写 (Capitalize)

场景:将字符串的第一个字母转换为大写。

代码

function capitalize([first, ...rest]) {
  return first.toUpperCase() + rest.join('');
}

// 使用示例
console.log(capitalize('helloWorld')); // 输出 "HelloWorld"

6. 复制到剪贴板 (Copy to Clipboard)

场景:将文本内容异步复制到用户剪贴板。

代码

async function copyToClipboard(text) {
  if (!navigator.clipboard) {
    console.error('Clipboard API not available');
    return Promise.reject('Clipboard API not available');
  }
  try {
    await navigator.clipboard.writeText(text);
    console.log('Copied to clipboard');
    return Promise.resolve();
  } catch (err) {
    console.error('Failed to copy: ', err);
    return Promise.reject(err);
  }
}

// 使用示例
// copyToClipboard('Hello, Gemini!');

以上就是我为你精选的6个事件函数,你可以将它们收藏起来,根据自己的项目需求进行修改和扩展,小编还会持续更新文档

Map、Set和WeakMap、WeakSet粗略讲解。

作者 1君110356
2025年7月2日 16:41

1.Map(映射)

是什么:

  • 键值对集合,键可以是任何对象(对象、原始值)
  • 保持插入顺序

特点:

const map = new Map();
map.set('name', 'John'); // 键是字符串
map.set(1, 'number key');// 键是数字
const objKey = {};
map.set(objKey, 'object key'); // 键是对象

常用方法:

基本操作方法:

1.set(key, value)-向Map中添加或更新键值对
map.set('name', 'Alice');
map.set(1, 'number key');
2.get(key)-获取指定键对应的值,如果不存在则返回undefined
map.get('name'); // 'Alice'
map.get('nonexistent'); // undefined
3.has(key)-检查Map中是否存在指定的键
map.has('name'); // true
map.has('age'); // false
4.delete(key)-删除对应的键值对,返回布尔值表示是否删除成功
map.delete('name'); // true
map.delete('age'); // false
5.clear()-清空所有键值对
map.clear();

遍历方法:

6.keys()-返回Map中所有键的迭代器
for (let key of map.keys()) {
    console.log(key);
}
7.values()-返回Map中所有值的迭代器
for (let value of map.values()) {
    console.log(value);
}
8.entries()-返回Map中所有键值对的迭代器
for (let [key, value] of map.entries()) {
    console.log(key, value)
}
// 等同于
for (let [key, value] of map) {
    console.log(key, value)
}
9.forEach(callBackFn, [thisArg])-对Map中每个键值对执行回调函数
map.forEach((value, key)) => {
    console.log(key, value)
}

属性

10.size-获取Map中键值对的数量
consoe.log(map.size);

与Object的区别:

  • Map的键可以是任意类型,Object的键只能是String和Symbol
  • Map有size属性直接获取元素数量
  • Map保持插入顺序
  • 性能更好(频繁增删的场景)

2.Set(集合)

是什么:

  • 值唯一的集合(类似数组,但成员唯一)
  • 保持插入顺序

特点:

const set = new Set();
set.add(1);
set.add('text');
set.add({}); // 对象引用独立
set.add(1); // 不会重复添加

基本操作方法

1.add(value)-向Set中添加一个新值,返回Set对象本身(支持链式调用)
set.add(1);
set.add(2).add(3); // 链式调用
2.has(value)-检查Set中是否存在指定的值
set.has(1); // true
set.has(4); // false
3.delete(value)-删除指定的值,返回布尔值表示是否删除成功
set.delete(1); // true
set.delete(4); // false
4.clear()-清空set中所有的值
set.clear();

遍历方法

5.keys()-返回set中所有值的迭代器(与values()相同)
for (let item of set.keys()) {
    console.log(item);
}
6.values()-返回所有值的迭代器
7.entries()-与values()和keys()相同,为了和Map保持一致
8.forEach(callBackFn, [thisArg])-对Set中每个值执行回调函数
set.forEach(value) => {
    console.log(value);
}

属性

size-获取Set中值的数量(属性,不是方法)

Set的特殊用途

[...new Set(array)] // 数组去重
new Set([...setA, ...setB]) // 并集
new Set([...setA]).filter(x => setB.hax(x))) // 交集
new Set([...setA]).filter(x => !setB.has(x))) // 差集

3.WeakMap(弱映射)

是什么:

  • 键必须是对象的弱引用Map
  • 不可遍历(没有size, keys()方法)

特点:

const wm = new WeakMap();
let obj = {};
vm.set(obj, 'private data');
obj = null; // 当obj被GC回收时,WeakMap中的条目自动移除

要点:

  • 典型用途:存储私有数据、DOM节点元数据

与Map的关键区别:

  • 键是弱引用(不阻止垃圾回收)
  • 不可枚举
  • 没有size属性

4.WeakSet(弱集合)

是什么:

  • 值必须是对象的弱引用Set
  • 不可遍历

特点:

const ws = new WeakSet();
let obj = {};
ws.add(obj);
console.log(ws.has(obj)); // true
obj = null; // 当obj被GC回收时,自动从WeakSet中移除

要点:

  • 典型用途:标记对象,如已经用过的对象

与Set的关键区别

  • 值必须是对象
  • 弱引用,不阻止垃圾回收
  • 不可枚举

5.常见问题

1.Map和Object如何选择?

  • 需要非字符串键或保持插入顺序时使用Map
  • 只需要简单键值对且键是字符串时用Object

2.WeakMap为什么不能遍历?

  • 因为弱引用特性,随时可能被垃圾回收机制处理,遍历结果不靠谱

3. Set如何实现数组去重?

  • const newArray = [...new Set(array)]

4.为什么WeakMap/WeakSet的值/键必须是对象?

  • 因为只有对象才能被垃圾回收机制管理,原始值不受GC的影响

Tailwind CSS 方案与 CSS 模块化知识点讲解

作者 钢蛋
2025年7月2日 16:39

📖 前言

本文档详细讲解我们项目中使用的 Tailwind CSS 方案和 CSS 模块化架构,通过图表、代码示例和实际案例,帮助开发者深入理解现代化的 CSS 架构设计。


1. 🎯 Tailwind CSS 核心理念

1.1 什么是实用程序优先(Utility-First)?

传统 CSS 开发方式是组件优先,而 Tailwind 采用实用程序优先的理念:

📊 传统方式 vs Tailwind 方式对比

传统组件优先方式:

/* styles.css */
.card {
  background: white;
  border-radius: 8px;
  padding: 24px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.card-title {
  font-size: 24px;
  font-weight: bold;
  margin-bottom: 16px;
}

.button-primary {
  background: #3b82f6;
  color: white;
  padding: 12px 24px;
  border-radius: 6px;
  border: none;
}
<!-- HTML -->
<div class="card">
  <h2 class="card-title">标题</h2>
  <button class="button-primary">按钮</button>
</div>

Tailwind 实用程序优先方式:

<!-- 直接在 HTML 中使用原子级类 -->
<div class="bg-white rounded-lg p-6 shadow-md">
  <h2 class="text-2xl font-bold mb-4">标题</h2>
  <button class="bg-blue-500 text-white px-6 py-3 rounded-md">按钮</button>
</div>

1.2 Tailwind 工作原理

graph TD
    A[编写 HTML + Tailwind 类] --> B[Tailwind 扫描文件]
    B --> C[生成对应的 CSS]
    C --> D[PurgeCSS 移除未使用样式]
    D --> E[输出最小化 CSS 文件]
    
    F[传统方式] --> G[编写自定义 CSS]
    G --> H[手动管理样式文件]
    H --> I[难以优化文件大小]

1.3 核心优势

传统方式 Tailwind 方式 优势
需要想类名 使用预定义类 🚀 开发效率高
CSS 文件越来越大 自动清理未使用样式 📦 包体积小
样式分散难维护 样式就在元素上 🔧 易于维护
设计不统一 设计令牌约束 🎨 设计一致性

2. 🏗️ 项目架构设计

2.1 整体架构图

graph TB
    subgraph "前端应用"
        A[React 组件]
        B[Tailwind 类名]
        C[自定义组件库]
    end
    
    subgraph "样式系统"
        D[design-tokens.css<br/>设计令牌]
        E[globals.css<br/>全局样式] 
        F[layout.css<br/>布局样式]
        G[tailwind.config.js<br/>配置文件]
    end
    
    subgraph "构建输出"
        H[优化后的 CSS]
        I[JavaScript Bundle]
    end
    
    A --> B
    B --> G
    C --> D
    D --> E
    E --> F
    G --> H
    A --> I

2.2 文件组织结构

frontend/src/
├── styles/                    # 样式文件夹
│   ├── design-tokens.css     # 🎨 设计令牌(颜色、间距等)
│   ├── globals.css           # 🌍 全局样式(重置、基础样式)
│   └── layout.css            # 📐 布局相关样式
├── components/               # 组件文件夹
│   └── UI/                   # UI 组件库
│       ├── Button.tsx        # 按钮组件
│       ├── Input.tsx         # 输入框组件
│       └── Icons/            # 图标系统
└── tailwind.config.js        # ⚙️ Tailwind 配置

2.3 样式导入链

flowchart LR
    A[main.tsx] --> B[globals.css]
    B --> C[design-tokens.css]
    B --> D[layout.css]
    
    E[tailwind.config.js] --> F[Tailwind 处理器]
    F --> G[生成工具类]
    
    C --> H[CSS 变量]
    D --> H
    G --> H
    H --> I[最终样式]

具体导入流程:

// 1️⃣ main.tsx - 应用入口
import './styles/globals.css'

// 2️⃣ globals.css - 全局样式文件
@import 'design-tokens.css';  /* 导入设计令牌 */
@import 'layout.css';         /* 导入布局样式 */

/* 全局基础样式 */
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: var(--font-family-sans);
  line-height: 1.6;
  color: var(--color-text-primary);
}

3. 🎨 设计令牌系统

3.1 设计令牌的作用

graph TD
    A[设计稿] --> B[提取设计令牌]
    B --> C[CSS 变量定义]
    C --> D[Tailwind 配置]
    C --> E[组件样式]
    D --> F[工具类生成]
    E --> F
    F --> G[一致的 UI 表现]

3.2 设计令牌定义

/* src/styles/design-tokens.css */
:root {
  /* 🎨 颜色系统 - 主色调 */
  --color-primary: #3b82f6;      /* 蓝色 */
  --color-primary-dark: #2563eb;  /* 深蓝 */
  --color-primary-light: #93c5fd; /* 浅蓝 */
  
  /* 🎨 颜色系统 - 功能色 */
  --color-success: #10b981;      /* 成功 - 绿色 */
  --color-warning: #f59e0b;      /* 警告 - 黄色 */
  --color-error: #ef4444;        /* 错误 - 红色 */
  --color-info: #06b6d4;         /* 信息 - 青色 */
  
  /* 🎨 颜色系统 - 中性色 */
  --color-gray-50: #f9fafb;
  --color-gray-100: #f3f4f6;
  --color-gray-500: #6b7280;
  --color-gray-900: #111827;
  
  /* 📏 间距系统 */
  --spacing-xs: 0.25rem;    /* 4px */
  --spacing-sm: 0.5rem;     /* 8px */
  --spacing-md: 1rem;       /* 16px */
  --spacing-lg: 1.5rem;     /* 24px */
  --spacing-xl: 2rem;       /* 32px */
  --spacing-2xl: 3rem;      /* 48px */
  
  /* 🔤 字体系统 */
  --font-size-xs: 0.75rem;      /* 12px */
  --font-size-sm: 0.875rem;     /* 14px */
  --font-size-base: 1rem;       /* 16px */
  --font-size-lg: 1.125rem;     /* 18px */
  --font-size-xl: 1.25rem;      /* 20px */
  --font-size-2xl: 1.5rem;      /* 24px */
  --font-size-3xl: 1.875rem;    /* 30px */
  
  /* 🌟 阴影系统 */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
  --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
  
  /* ⭕ 圆角系统 */
  --radius-sm: 0.25rem;     /* 4px */
  --radius-md: 0.375rem;    /* 6px */
  --radius-lg: 0.5rem;      /* 8px */
  --radius-xl: 0.75rem;     /* 12px */
  --radius-2xl: 1rem;       /* 16px */
  --radius-full: 9999px;    /* 圆形 */
}

3.3 Tailwind 配置集成

// tailwind.config.js
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      // 🎨 将 CSS 变量映射到 Tailwind 类
      colors: {
        primary: {
          DEFAULT: 'var(--color-primary)',
          dark: 'var(--color-primary-dark)',
          light: 'var(--color-primary-light)',
        },
        success: 'var(--color-success)',
        warning: 'var(--color-warning)',
        error: 'var(--color-error)',
        info: 'var(--color-info)',
      },
      
      // 📏 间距映射
      spacing: {
        'xs': 'var(--spacing-xs)',
        'sm': 'var(--spacing-sm)',
        'md': 'var(--spacing-md)',
        'lg': 'var(--spacing-lg)',
        'xl': 'var(--spacing-xl)',
        '2xl': 'var(--spacing-2xl)',
      },
      
      // 🌟 自定义动画
      animation: {
        'pulse-glow': 'pulse-glow 2s ease-in-out infinite',
        'slide-shine': 'slide-shine 2s infinite',
        'fade-in': 'fade-in 0.3s ease-out',
        'slide-up': 'slide-up 0.3s ease-out',
      },
      
      // 🎬 动画关键帧
      keyframes: {
        'pulse-glow': {
          '0%, 100%': { 
            boxShadow: '0 0 5px rgba(59, 130, 246, 0.5)' 
          },
          '50%': { 
            boxShadow: '0 0 20px rgba(59, 130, 246, 0.8)' 
          }
        },
        'slide-shine': {
          '0%': { transform: 'translateX(-100%)' },
          '100%': { transform: 'translateX(100%)' }
        },
        'fade-in': {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' }
        },
        'slide-up': {
          '0%': { transform: 'translateY(20px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' }
        }
      }
    },
  },
  plugins: [],
}

4. 🧩 组件复用架构

4.1 组件复用前后对比

graph LR
    subgraph "优化前"
        A1[原生 button] 
        A2[内联样式]
        A3[重复代码]
        A4[难以维护]
    end
    
    subgraph "优化后"
        B1[Button 组件]
        B2[Tailwind 类]
        B3[设计令牌]
        B4[易于维护]
    end
    
    A1 --> B1
    A2 --> B2
    A3 --> B3
    A4 --> B4

4.2 Button 组件的演进

❌ 优化前 - 原生实现:

<!-- 每个按钮都要重复写样式 -->
<button style="background: #3b82f6; color: white; padding: 12px 24px; border-radius: 6px; border: none;">
  主要按钮
</button>

<button style="background: #6b7280; color: white; padding: 12px 24px; border-radius: 6px; border: none;">
  次要按钮
</button>

<button style="background: #ef4444; color: white; padding: 12px 24px; border-radius: 6px; border: none;">
  危险按钮
</button>

✅ 优化后 - 组件化实现:

// components/UI/Button.tsx
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
  size?: 'sm' | 'md' | 'lg'
  disabled?: boolean
  loading?: boolean
  children: React.ReactNode
  onClick?: () => void
  className?: string
}

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'md',
  disabled = false,
  loading = false,
  children,
  onClick,
  className = ''
}) => {
  // 🎯 基础样式 - 所有按钮共用
  const baseClasses = [
    'inline-flex items-center justify-center',
    'font-medium rounded-lg',
    'transition-all duration-200',
    'focus:outline-none focus:ring-2 focus:ring-offset-2',
    'disabled:opacity-50 disabled:cursor-not-allowed',
    'relative overflow-hidden'
  ].join(' ')
  
  // 🎨 变体样式 - 不同类型按钮
  const variantClasses = {
    primary: [
      'bg-primary text-white',
      'hover:bg-primary-dark',
      'focus:ring-primary/50',
      'shadow-md hover:shadow-lg'
    ].join(' '),
    
    secondary: [
      'bg-gray-100 text-gray-900',
      'hover:bg-gray-200',
      'focus:ring-gray-500/50',
      'border border-gray-300'
    ].join(' '),
    
    danger: [
      'bg-error text-white',
      'hover:bg-red-600',
      'focus:ring-error/50',
      'shadow-md hover:shadow-lg'
    ].join(' '),
    
    ghost: [
      'bg-transparent text-primary',
      'hover:bg-primary/10',
      'focus:ring-primary/50'
    ].join(' ')
  }
  
  // 📏 尺寸样式
  const sizeClasses = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg'
  }
  
  return (
    <button
      className={`
        ${baseClasses} 
        ${variantClasses[variant]} 
        ${sizeClasses[size]} 
        ${className}
      `}
      disabled={disabled || loading}
      onClick={onClick}
    >
      {loading && <LoadingSpinner />}
      {children}
    </button>
  )
}

🚀 使用效果:

// 现在使用起来非常简洁
<Button variant="primary" size="lg">主要按钮</Button>
<Button variant="secondary">次要按钮</Button>
<Button variant="danger" size="sm">危险按钮</Button>
<Button variant="ghost" loading>加载中...</Button>

4.3 复用率对比数据

指标 优化前 优化后 提升
代码复用率 20% 85% 📈 +325%
样式一致性 60% 95% 📈 +58%
维护效率 30% 90% 📈 +200%
开发速度 基准 快3倍 🚀 +300%

5. 📱 响应式设计实现

5.1 断点系统

graph LR
    A[xs: 475px] --> B[sm: 640px]
    B --> C[md: 768px] 
    C --> D[lg: 1024px]
    D --> E[xl: 1280px]
    E --> F[2xl: 1536px]
    
    A1[手机] -.-> A
    B1[大屏手机] -.-> B
    C1[平板] -.-> C
    D1[小屏笔记本] -.-> D
    E1[桌面] -.-> E
    F1[大屏桌面] -.-> F

5.2 响应式网格示例

// 响应式卡片网格
<div className="
  grid 
  grid-cols-1          /* 手机:1列 */
  sm:grid-cols-2       /* 大屏手机:2列 */
  md:grid-cols-3       /* 平板:3列 */
  lg:grid-cols-4       /* 笔记本:4列 */
  xl:grid-cols-5       /* 桌面:5列 */
  gap-4                /* 间距 */
  p-4                  /* 内边距 */
">
  {cards.map(card => (
    <Card key={card.id} {...card} />
  ))}
</div>

// 响应式文字大小
<h1 className="
  text-2xl             /* 基础:24px */
  sm:text-3xl          /* 大屏手机:30px */
  md:text-4xl          /* 平板:36px */
  lg:text-5xl          /* 笔记本:48px */
  xl:text-6xl          /* 桌面:60px */
  font-bold 
  text-center
">
  响应式标题
</h1>

6. ⚡ 性能优化策略

6.1 构建优化流程

flowchart TD
    A[源代码] --> B[Tailwind 扫描]
    B --> C{使用的类}
    C -->|已使用| D[保留]
    C -->|未使用| E[移除]
    D --> F[CSS 压缩]
    E --> F
    F --> G[最终 CSS 文件]
    
    H[原始大小<br/>~3MB] --> I[PurgeCSS<br/>处理]
    I --> J[最终大小<br/>~15KB]
    
    style H fill:#ffcccc
    style J fill:#ccffcc

6.2 文件大小对比

构建阶段 文件大小 说明
Tailwind 完整版 ~3MB 包含所有工具类
PurgeCSS 处理后 ~15KB 只保留使用的类
Gzip 压缩后 ~5KB 进一步压缩

📊 优化效果:文件大小减少 99.8%!

6.3 性能监控命令

# 分析构建产物大小
npm run build
npm run analyze

# 检查未使用的 CSS
npm run css-unused

# 性能测试
npm run perf-test

7. 🎯 最佳实践指南

7.1 类名组织原则

// ✅ 好的实践 - 按功能分组
const cardClasses = [
  // 🎨 外观
  'bg-white border border-gray-200 rounded-lg shadow-md',
  // 📏 尺寸和间距  
  'p-6 m-4 w-full max-w-sm',
  // 🎭 交互状态
  'hover:shadow-lg hover:scale-105',
  // 🎬 动画
  'transition-all duration-200 ease-in-out'
].join(' ')

// ❌ 避免的实践 - 类名过长难读
<div className="bg-white border border-gray-200 rounded-lg shadow-md p-6 m-4 w-full max-w-sm hover:shadow-lg hover:scale-105 transition-all duration-200 ease-in-out">

7.2 组件抽象策略

graph TD
    A[识别重复模式] --> B[提取公共样式]
    B --> C[创建基础组件]
    C --> D[添加变体支持]
    D --> E[集成设计令牌]
    E --> F[完善类型定义]
    F --> G[编写使用文档]

7.3 团队协作规范

// 🎯 推荐的组件结构
interface ComponentProps {
  // 必需属性
  children: React.ReactNode
  
  // 可选的外观属性
  variant?: 'primary' | 'secondary'
  size?: 'sm' | 'md' | 'lg'
  
  // 可选的行为属性
  disabled?: boolean
  loading?: boolean
  
  // 扩展属性
  className?: string
  onClick?: () => void
}

export const Component: React.FC<ComponentProps> = (props) => {
  // 1️⃣ 解构 props
  const { 
    children, 
    variant = 'primary', 
    size = 'md',
    className = '',
    ...rest 
  } = props
  
  // 2️⃣ 计算样式类
  const classes = computeClasses(variant, size, className)
  
  // 3️⃣ 渲染组件
  return (
    <div className={classes} {...rest}>
      {children}
    </div>
  )
}

8. 🚀 项目实战案例

8.1 设置页面重构前后

📊 重构前的问题:

pie title 代码质量分析
    "重复代码" : 45
    "内联样式" : 30  
    "不一致设计" : 15
    "维护困难" : 10

🎯 重构后的改进:

pie title 优化后效果
    "组件复用" : 60
    "设计系统" : 25
    "类型安全" : 10
    "易于维护" : 5

8.2 实际改造对比

❌ 改造前:

<!-- 大量重复的内联样式 -->
<div style="background: white; padding: 24px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
  <h2 style="font-size: 20px; margin-bottom: 16px; font-weight: bold;">API 配置</h2>
  <button style="background: #3b82f6; color: white; padding: 12px 24px; border: none; border-radius: 6px;">
    添加配置
  </button>
</div>

✅ 改造后:

// 使用组件化 + Tailwind
<Card className="p-6 shadow-md">
  <Card.Title>API 配置</Card.Title>
  <Button 
    variant="primary"
    className="bg-gradient-to-r from-blue-500 to-indigo-600 hover:scale-105"
  >
    <Icon name="add" size="sm" className="mr-2" />
    添加配置
  </Button>
</Card>

8.3 优化成果数据

优化指标 改造前 改造后 提升
🚀 开发效率 基准 +200% 快3倍
📦 代码复用 25% 85% +240%
🔧 维护成本 -70%
🎨 设计一致性 60% 95% +58%

9. 💡 常见问题解答

9.1 类名太多怎么办?

问题: Tailwind 类名很长,影响可读性?

解决方案:

// 方法1: 使用变量存储
const buttonStyles = [
  'bg-gradient-to-r from-blue-500 to-indigo-600',
  'text-white px-6 py-3 rounded-lg',
  'hover:scale-105 transition-transform',
  'shadow-lg hover:shadow-xl'
].join(' ')

// 方法2: 创建工具函数
const cn = (...classes: string[]) => classes.filter(Boolean).join(' ')

// 方法3: 抽象为组件
<GradientButton>点击我</GradientButton>

9.2 如何调试 Tailwind 样式?

// 开发环境显示当前断点
const BreakpointIndicator = () => (
  <div className="fixed top-4 left-4 z-50 bg-black text-white p-2 rounded text-xs">
    <div className="sm:hidden">XS</div>
    <div className="hidden sm:block md:hidden">SM</div>
    <div className="hidden md:block lg:hidden">MD</div>
    <div className="hidden lg:block xl:hidden">LG</div>
    <div className="hidden xl:block 2xl:hidden">XL</div>
    <div className="hidden 2xl:block">2XL</div>
  </div>
)

9.3 性能会有问题吗?

答案:不会!

  • 构建时优化:PurgeCSS 自动移除未使用样式
  • 运行时高效:CSS 类切换比 JavaScript 样式快
  • 缓存友好:样式文件可以长期缓存
  • 体积极小:最终 CSS 通常只有几 KB

10. 📈 总结与展望

10.1 技术收益总览

radar
    title 技术提升雷达图
    options:
      "开发效率": 90
      "代码质量": 85
      "维护成本": 80
      "用户体验": 88
      "团队协作": 82
      "性能表现": 90

10.2 关键成果

🎯 技术指标 📊 提升幅度 💡 具体表现
开发效率 +200% 无需编写自定义 CSS,快速构建 UI
代码复用 +300% 组件化 + 原子类,高度复用
维护成本 -70% 统一设计系统,易于修改
包体积 -95% PurgeCSS 优化,生产包极小
设计一致性 +150% 设计令牌约束,视觉统一

10.3 未来优化方向

timeline
    title 技术演进规划
    
    现阶段 : 基础架构搭建
           : Tailwind + 组件库
           : 设计令牌系统
    
    下个季度 : 进阶功能开发
             : 暗色主题支持
             : 更多动画效果
             : 移动端优化
    
    未来规划 : 生态完善
             : 设计系统文档
             : 自动化测试
             : 性能监控

🎉 结语

通过 Tailwind CSS + CSS 模块化 的方案,我们成功构建了一套:

  • 🚀 高效的开发体验
  • 🎨 一致的设计系统
  • 🔧 易维护的代码架构
  • 高性能的用户体验

这套方案不仅解决了传统 CSS 开发的痛点,还为团队协作和项目维护提供了坚实的基础。

记住:好的架构不是一蹴而就的,需要在实践中不断优化和完善! 💪


📝 文档版本:v1.0 | 📅 更新时间:2025年 | 👥 适用团队:前端开发团队

🧐🧐三小时完成 100+小工具项目实现与部署?不!只需要两小时!!!😢😢

2025年7月2日 16:36

利用 cursor三小时完成 100+小工具项目实现部署?不!只需要两小时

PixPin_2025-07-02_15-50-48.gif

两小时开发完成

十点刚看到文章 image.png

下午三点就开发部署完成

image.png

1.创建提示词(需求文档)

Prompt当然是让 AI 去写啦

一败涂地的国产 AI(提示词书写)

模版

  • 我想做个100+小工具的网站集合,结合AI编程实现,请帮我写一份这样的提示词

优化

  • github.com/langgptai/L… 学习LangGPT仓库,创建一个做个100+小工具的网站集合,结合AI编程实现,请帮我写一份这样的提示词

给个提示词仓库学习也写不好......

cursor 创建提示词

  • github.com/langgptai/L… 学习LangGPT仓库,创建一个做个100+小工具的网站集合,结合AI编程实现,请帮我写一份这样的提示词
你是一名资深全栈工程师和产品设计师,精通 TypeScript、React/Next.js、TailwindCSS、shadcn/ui 等现代前端技术。请帮我自动生成一个“100+实用小工具网站集合”项目,要求如下:

【项目目标】
- 打造一个纯前端的多功能小工具集合网站,所有逻辑仅在浏览器端运行,无需后端和数据库。
- 每个小工具都可独立访问和使用,支持响应式、移动端友好、深浅色切换。

【技术栈】
- Next.js 14 + TypeScript
- TailwindCSS + shadcn/ui
- Vite Bundler(Next 14 默认)
- 仅前端,无服务端依赖

【功能与结构】
- 首页以卡片网格(BentoGrid)方式展示所有工具,支持分类筛选和搜索
- 工具类型涵盖:文本处理、颜色/设计、图片/多媒体、日期/时间、数学/单位、编码/加密、Web/DevTools、随机/生成器、文件/文档、数据/可视化等
- 每个工具页面可独立运行,无整页刷新
- 每个工具都实现最小可用功能,不留 TODO 占位
- 组件全部使用函数式写法,附英文+简明中文注释
- 提供统一的 ESLint / Prettier 配置
- 自动生成 README.md,包含快速启动与贡献指南(中/EN)

【交付要求】
1. 先输出项目文件树(file-tree diff),创建完整目录与 100+ 组件文件
2. 分块输出全部代码:核心配置文件、框架文件及 100+ 工具组件实现
3. 自动添加脚本:npm run dev、npm run build、npm run lint
4. 如输出过长被截断,请在同一会话输入 #continue 续写,直至全部完成
5. 生成完毕即视为任务完成,除非我发出新指令,请勿额外解释或提问

【UI/UX】
- 首页卡片网格+BentoGrid风格,支持分类Tab和搜索
- 工具卡片等宽等高,风格统一,移动端友好
- 支持深浅色切换
- 交互流畅,界面美观现代

请严格按上述要求,自动生成项目结构和全部代码。每个工具都要有最小可用功能实现。输出时只给代码或文件树,不要多余解释。
缺点

具体工具无举出,后期 AI 幻觉严重

借鉴他人

### System(系统指令)
你是一名 **10 年以上经验** 的全栈工程师兼产品设计师,精通 **TypeScript、React/Next.js、TailwindCSS、shadcn/ui、Vite** 及现代组件架构。  
作为 **Cursor IDE 内的自动化编码代理** 工作:

- 可直接访问文件系统,创建 / 编辑 / 删除文件,并执行 **“Install dependencies”** 等操作。  
- 必须在 **一次对话** 内完成全部任务,除非我明确要求暂停。  
- 除非被请求说明,否则始终输出 **有效代码块****文件树差异**;不要给出纯文字解释。  
- 交付的代码需结构清晰、可运行,并附 **英文 + 简明中文** 注释。

---

### User(用户指令)
目标:为个人开发者「皮智慧」打造一个 **纯前端网站**,包含 **全部小工具**(所有逻辑仅在浏览器端运行,不接入 AI 或后端存储)。

#### 技术栈
- **Next.js 14 + TypeScript**  
- **TailwindCSS + shadcn/ui**(支持浅/深色切换)  
- **Vite** Bundler(Next 14 默认)  
- 无任何服务器端或数据库依赖

#### UI / UX 要求
- 首页以卡片网格方式列出所有工具,支持响应式与浅/深色切换  
- 每个工具页面需可独立运行,无整页刷新  
- 移动端友好,所有交互均流畅

#### 实现要求
- **100 个工具全部实现最小可用功能**,不得留 TODO 占位  
- 组件均使用 **函数式写法**,并附 **JSDoc + 简明中文** 注释  
- 提供统一的 ESLint / Prettier 配置  
- 生成 `README.md`,包含快速启动与贡献指南(中 / EN)

#### 交付顺序
1. 输出 **项目文件树差异**(file-tree diff),创建完整目录与 100 个组件文件  
2. 按需分块输出 **全部代码**:核心配置文件、框架文件及 100 个工具组件实现  
3. 自动添加脚本:`npm run dev``npm run build``npm run lint`  
4. 如输出过长被截断,请在同一会话输入 **`#continue`** 续写,直至全部完成  
5. 生成完毕即视为任务完成;除非我发出新指令,请勿额外解释或提问

#### UI/UX 要求
- 首页以卡片网格方式列出所有工具,支持响应式与浅/深色切换
- 每个工具页面需可独立运行,无整页刷新
- 移动端友好,所有交互均流畅
---

#### 📦  Tools List(slug | 英文名 | 一句话功能)

**文本处理**  
1. `word-count` | Word Count | 实时统计文本字数  
2. `markdown-preview` | Markdown Preview | MD→HTML 预览  
3.  `diff-viewer` | Text Diff | 文本差异对比  

**颜色 / 设计**  
1.  `color-picker` | Color Picker | 取色并复制十六进制  
2.  `tailwind-cheatsheet` | Tailwind Lookup | 类名速查  


**日期 / 时间**  
1.  `countdown-timer` | Countdown | 倒计时  


**数学 / 单位**  
1.  `random-number` | RNG | 随机数生成  

**编码 / 加密**  
1.  `qr-generator` | QR Maker | 二维码生成  

**Web / DevTools**  
1.  `json-to-ts` | JSON→TS Interface  

**随机 / 生成器**  
1.  `quote-generator` | Quote Gen | 随机名言  
2.  `lottery-picker` | Lottery Pick | 抽奖器  

**文件 / 文档**  
1.  `text-to-pdf` | Text→PDF  
---

> **执行规则**  
> - 按“交付顺序”完成;如输出过长,使用 `#continue` 续写。  
> - 未收到新指令前,请勿额外解释或提问。

2.cursor 启动!!!😤😤

image.png

image.png

没做(没错),项目完成了!!!

pnpm i
npm run dev

出错了?

image.png

改变风格

image.png

功能分类

image.png

项目完成

image.png

部署上线

Railway对比git page

  • 可以部署前端静态页面,后端服务,数据库等
  • 国内访问不需要翻墙
  • 免费额度有限制

github 部署

image.png

image.png

railway 部署

  • github账号 登录 railway
  • 选择想要部署的项目
  • 现在部署 git 提交版本 image.png
  • 报错扔给 AI 修改

image.png

  • 部署成功

image.png

线上预览

切换到settings页面,有个Networking部分,可以生成一个railway自带的域名

欢迎访问:tools-production-5d8d.up.railway.app/

参考:cursor 三小时实现 100+工具

AI助力快速引入外部组件到TinyEngine低代码引擎

2025年7月2日 16:36

本文由羽毛笔记作者观默原创。

背景:低代码时代的开发挑战

TinyEngine作为一款优秀的低代码平台,以其强大的功能和快速迭代能力赢得了众多开发者的青睐。它让开发者能够通过可视化界面快速构建应用,大大提升了开发效率。

然而,就像一座美丽的花园需要更多花卉品种来装点一样,TinyEngine也面临着组件生态的挑战:官方提供的组件虽然精心设计,但数量有限,难以满足企业级项目的多样化需求。更具挑战性的是,要将私有组件库接入TinyEngine,必须先为每个组件生成对应的schema文件——这就像为每朵花制作专属的标签卡片。

从痛点到机遇:AI时代的解决方案

最近,我们公司面临着将私有组件库接入TinyEngine的需求。如果采用传统的人工方式为每个组件编写schema文件,不仅耗时费力,更重要的是缺乏那种让人兴奋的技术创新感。想象一下,如果有几十个组件需要处理,那将是一个令人望而却步的重复性工作。

这让我开始思考:在AI技术日新月异的今天,是否有更优雅的解决方案?

传统方案 VS AI方案

在AI时代之前,我可能会选择**Babel + AST(抽象语法树)**的技术路线:

  • 解析Vue组件源码
  • 遍历AST节点
  • 提取props、events、slots信息
  • 手工编写转换逻辑

这种方法虽然可行,但就像用显微镜逐个检查细胞一样繁琐。而现在,我们有了更强大的工具——AI大语言模型

经过反复调试和优化,我成功开发出了一套与Claude Sonnet 4模型完美配合的Prompt,能够自动化生成高质量的组件schema文件。这就像拥有了一位经验丰富的架构师助手,能够理解组件的每一个细节并生成标准化的配置文件。

schema 生成 Prompt:

注意:group/category/npm 这三个字段请根据实际情况调整,不要照抄!

# 任务:Vue 组件 Schema 生成器

你是一位精通 Vue 3 Composition API、TypeScript 及低代码平台组件集成的资深架构师。你的任务是接收一个 Vue 组件的源代码及相关项目文件,然后生成一个完全符合指定规则、高度精确且信息丰富的 JSON Schema 文件,用于驱动低代码平台。

你的输出必须是一个**完整的、格式正确的 JSON 对象**,不包含任何额外的解释性文字。

---

### 前置检查:验证输入信息

在开始生成 Schema 之前,你必须首先验证是否已收到所有必需的信息。

**以下项目是必需的:**

1.  **`组件源代码`**: 目标 `.vue` 文件的完整内容。
2.  **`package.json`**: 项目的 `package.json` 文件完整内容。

**可选的组件元数据***   `中文名称 (zh_CN)`: 如果未提供,从组件的 `defineComponent({ name: '...' })` 中提取 `name` 值。
*   `图标 (icon)`: 如果未提供,从组件的 `defineComponent({ name: '...' })` 中提取 `name` 值。
*   `描述 (description)`
*   `标签 (tags)`
*   `关键词 (keywords)`
*   `文档链接 (doc_url)`

**如果必需信息缺失,请不要继续。** 你的回应应该是一个清晰的请求,明确指出用户需要提供哪些缺失的具体内容。

只有在确认所有必需输入都已提供后,才能继续执行下面的生成步骤。

---

### 第一步:分析输入 (假设已通过前置检查)

你将收到以下输入:

1.  **`组件源代码`**: 一个完整的 Vue 组件 (`.vue`) 的文本内容。
2.  **`package.json`**: 项目的 `package.json` 文件内容。
3.  **`组件元数据` (可选)**:
    *   `中文名称 (zh_CN)`
    *   `图标 (icon)`
    *   `描述 (description)`
    *   `标签 (tags)`
    *   `关键词 (keywords)`
    *   `文档链接 (doc_url)`

---

### 第二步:执行生成规则

请严格按照以下规则,一步步构建最终的 JSON 对象:

#### 1. **顶层字段填充**

*   `component`: 从组件的 `defineComponent({ name: '...' })` 中提取 `name` 值。
*   **`name.zh_CN`**: 
    - 如果 `组件元数据` 中提供了 `中文名称`,则使用该值
    - 否则根据 `component` 名称进行智能推断和翻译:
      - `Button` -> `"按钮"`
      - `LoginInfo` -> `"登录信息"`
      - `SixAxisRobot` -> `"六轴机械臂"`
      - `Chamber` -> `"腔体"`
      - `Aligner` -> `"对中器"`
      - `MainMenuButton` -> `"主菜单按钮"`
      - `CdsState` -> `"CDS状态"`
      - 其他名称按语义进行合理的中文翻译
*   **`icon`**: 
    - 如果 `组件元数据` 中提供了 `图标`,则使用该值
    - 否则默认使用 `component` 名称(如 `"SixAxisRobot"` -> `"SixAxisRobot"`)
*   **`group`**: 固定为 `"DCP"`
*   **`category`**: 固定为 `"DCP"`
*   `description`: 如果 `组件元数据` 中提供了 `description`,则使用该值;否则默认为 `""`。
*   `tags`: 如果 `组件元数据` 中提供了 `tags`,则使用该值;否则默认为 `""`。
*   `keywords`: 如果 `组件元数据` 中提供了 `keywords`,则使用该值;否则默认为 `""`。
*   `doc_url`: 如果 `组件元数据` 中提供了 `doc_url`,则使用该值;否则默认为 `""`。
*   `devMode`: 固定为 `"proCode"`。

#### 2. **`npm` 对象构建**

根据 `package.json` 的内容,动态构建 `npm` 对象:

*   `package`: 从 `package.json` 中读取 `name` 字段。
*   `exportName`: **必须**与顶层 `component` 字段的值保持一致。
*   `version`: 从 `package.json` 中读取 `version` 字段。
*   `script`: 基于 `package.json` 的信息,拼接成固定格式:`"http://192.168.0.212:4874/{package}@{version}/js/web-component.mjs"`。
*   `destructuring`: 固定为 `true`。
*   `npmrc`:
    1.  从 `package.json` 的 `name` 字段提取 scope (例如 `@dcp/component-library` -> `@dcp`)。
    2.  从 `package.json` 的 `publishConfig.registry` 字段提取 registry URL (并移除末尾的 `/`)。
    3.  拼接成 `"{scope}:registry={registry_url}"` 的格式。

#### 3. **`configure` 对象构建**

生成完整的 `configure` 对象,包含以下所有字段:

**基础行为控制***   `loop`: 固定为 `true`(支持循环渲染)
*   `condition`: 固定为 `true`(支持条件渲染)
*   `styles`: 固定为 `true`(支持样式配置)

**组件类型标识***   `isContainer`: 根据组件分析决定:
    - 如果组件模板中包含 `<slot>` 标签,设置为 `true`
    - 如果组件名称暗示容器用途(如 Layout、Container、Wrapper),设置为 `true`
    - 否则设置为 `false`
*   `isModal`: 固定为 `false`(除非组件明确是模态框)
*   `isPopper`: 固定为 `false`(除非组件明确是弹出框)
*   `isNullNode`: 固定为 `false`
*   `isLayout`: 根据组件用途判断,Layout 类组件设置为 `true`,否则为 `false`

**嵌套规则***   `nestingRule`: 对象包含以下字段,通常设置为默认值:
    - `childWhitelist`: `""`(允许的子组件白名单,通常为空)
    - `parentWhitelist`: `""`(允许的父组件白名单,通常为空)
    - `descendantBlacklist`: `""`(禁止的后代组件黑名单,通常为空)
    - `ancestorWhitelist`: `""`(允许的祖先组件白名单,通常为空)

**编辑器配置***   `rootSelector`: 固定为 `""`
*   `shortcuts.properties`: 识别出组件最核心、最常用的 1-3 个 props,填入此数组
*   `contextMenu`: 对象包含:
    - `actions`: 默认为 `["copy", "remove", "insert", "updateAttr", "bindEevent"]`
    - `disable`: 默认为 `[]`

**交互行为** (可选字段,根据组件类型添加):
*   `clickCapture`: 对于按钮类、交互类组件设置为 `true`,其他组件可省略或设置为 `false`
*   `framework`: 如果是第三方组件库保持原值,自定义组件设置为 `"Vue"`

#### 4. **`schema.properties` (Props 分组映射)**

将 Vue 组件的所有 props 按逻辑功能分组,生成一个**分组数组****分组策略***   **基础属性**: 核心功能相关的 props(如 name、size、type 等)
*   **样式属性**: 外观、颜色、尺寸相关的 props(如 width、height、backgroundColor、color 等)
*   **行为属性**: 交互、事件、状态相关的 props(如 disabled、loading、onClick 等)
*   **高级属性**: 可选的、专业配置项(如复杂对象配置、高级选项等)

**每个分组对象必须包含***   `name`: 分组标识符,使用数字字符串(如 `"0"`, `"1"`, `"2"`*   `label.zh_CN`: 分组的中文显示名称(如 `"基础属性"`, `"样式属性"`*   `description.zh_CN`: 分组的中文描述
*   `content`: 该分组下的具体属性配置数组

**`content` 数组中的每个属性对象必须包含以下固定字段***   `property`: Prop 的名称
*   `label.text.zh_CN`: 中文标签
*   `description`: 中文描述
*   `required`: 根据 Vue Prop 中的 `required` 字段决定,默认为 `false`
*   `readOnly`: 固定为 `false`
*   `disabled`: **固定为 `false`**
*   `cols`: **固定为 `12`**
*   `labelPosition`: 固定为 `"left"`
*   `type`: Vue 类型转换为小写字符串
*   `defaultValue`: Vue Prop 的默认值
*   `widget`: 根据以下规则推断

**Widget 推断规则 (按优先级顺序)**1. **validator 函数解析 (最高优先级)**:
   - 如果 Prop 定义中存在 `validator` 函数,解析函数体中的选项数组
   - 设置 `widget.component``"SelectConfigurator"`
   - 设置 `widget.props.options` 为解析出的选项数组

2. **属性名称模式匹配**:
   - 名称包含 `color` 或默认值以 `#` 开头 -> `"ColorConfigurator"`,props: `{}`
   - 名称包含 `icon` -> `"InputConfigurator"`,props: `{ "placeholder": "请输入图标名称" }`

3. **Vue 类型 + 语义推断**:
   - `Boolean` 类型:
     - 开关语义 (show*, enable*, is*) -> `"SwitchConfigurator"`,props: `{}`
     - 选项语义 (disabled, loading, plain, round, circle) -> `"CheckBoxConfigurator"`,props: `{}`
   - `Number` 类型 -> `"NumberConfigurator"`,根据属性名称设置 props:
     - 尺寸类 (width, height, size): `{ "min": 50, "max": 2000, "step": 10 }`
     - 角度类 (rotate, angle): `{ "min": 0, "max": 360, "step": 1 }`
     - 比例类 (scale): `{ "min": 0.1, "max": 5, "step": 0.1 }`
     - 时间类 (duration, delay): `{ "min": 0, "max": 50, "step": 0.1 }`
     - 默认: `{ "step": 1 }`
   - `String` 类型 -> `"InputConfigurator"`,props: `{ "placeholder": "请输入..." }`
   - `Object`/`Array` 类型 -> `"CodeConfigurator"`,props: `{ "language": "json", "height": 150 }`

4. **智能类型分析**:
   - 如果 Prop 类型为 `Array as PropType<SomeInterface[]>`,在 `description` 中补充接口结构信息

#### 5. **`schema.events` (事件映射)**

*   在组件 `<script>` 中搜索所有 `emit('event-name', ...)` 的调用。
*   每一个 `event-name` (kebab-case) 都对应 `events` 对象中的一个键。
*   该键的命名规则为 **`'on' +` 将 `event-name` 转换为首字母大写的驼峰式 (CamelCase)**。例如,`emit('menu-item-click')` 映射为 `onMenuItemClick`*   分析 `emit` 的参数,为该事件生成 `functionInfo.params` 数组。

#### 6. **`schema.slots` (插槽分析)**

*   扫描组件的 `<template>` 部分,寻找所有 `<slot>` 标签。
*   对于每一个**具名插槽** (例如 `<slot name="menu-items">`),在 `schema.slots` 对象中为其添加一个条目。
*   该条目的键为插槽名 (`menu-items`),值为一个包含 `label.zh_CN``description.zh_CN` 的对象,用于描述该插槽的用途。

#### 7. **`snippets` (智能代码片段生成)**

*   生成一个只包含**单个默认 Snippet** 的数组 `[]`*   `name.zh_CN`, `icon`, `snippetName` 与顶层字段保持一致。
*   `schema.props`:
    *   **优先策略**: 在工作区中查找与组件同名的 `.stories.ts` 文件。如果找到,请使用其 `args` 对象作为 `props` 的数据来源。
    *   **备用策略**: 如果找不到 Storybook 文件,请不要简单地使用 `defaultValue`。应根据每个 Prop 的语境,创建一组有意义、更具代表性的示例值(例如,`username` 使用 `'Admin'` 而不是 `'User'`)。

---

### 第三步:输出最终 JSON

请整合以上所有分析结果,生成最终的 JSON 文件。

实战指南:从理论到实践

第一步:智能生成Schema

运用AI生成schema的过程就像与一位专业顾问的对话。让我们以Cursor编辑器为例,一步步地体验这个过程:

操作步骤:

  1. 打开组件项目:启动Cursor,进入你的Vue组件库项目
  2. 准备上下文材料:在聊天界面中添加以下关键文件:
    • 项目根目录的package.json
    • 目标组件的.vue源码文件
  3. 粘贴Prompt:将上文提供的完整Prompt复制到输入框
  4. 发起对话:按下回车键,等待AI的魔法发生

为什么选择Claude Sonnet 4?

经过测试对比,我发现Claude Sonnet 4在理解Vue组件结构和生成精准schema方面表现出色,像deepseek、qwen这些国内模型暂时无法:

  • 精准的类型推断:能够正确识别Props的TypeScript类型并转换为对应的widget配置
  • 智能的语义理解:根据属性名称自动推断合适的编辑器组件(颜色选择器、数字输入框等)
  • 结构化输出:生成的JSON格式规范、完整,直接可用

实际效果展示

设想我们有一个名为CustomButton的Vue组件:

<template>
  <button 
    :class="buttonClass" 
    :disabled="disabled"
    @click="handleClick"
  >
    <slot>{{ label }}</slot>
  </button>
</template>

<script setup lang="ts">
import { computed } from 'vue'

interface Props {
  label?: string
  type?: 'primary' | 'secondary' | 'danger'
  size?: 'small' | 'medium' | 'large'
  disabled?: boolean
  loading?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  label: '按钮',
  type: 'primary',
  size: 'medium',
  disabled: false,
  loading: false
})

const emit = defineEmits<{
  click: [event: MouseEvent]
}>()

const handleClick = (event: MouseEvent) => {
  if (!props.disabled && !props.loading) {
    emit('click', event)
  }
}
</script>

通过AI处理后,会生成一个包含以下关键信息的schema:

  • Props分组:label、type、size被分类为“基础属性”,disabled、loading分类为“行为属性”
  • 组件类型:type属性自动配置为下拉选择器,并包含正确的选项
  • 事件映射:click事件被正确转换为onClick处理函数

第二步:添加物料

把上一步得到的json文件保存到TinyEngine项目根目录下的materials/components 文件夹下。

第三步:添加仓库

如果你没有添加过仓库配置,那么你需要在 项目根目录下的materials/packages.json 文件中添加你的仓库信息:

{
      "name": "DCP组件库",
      "package": "@dcp/component-library",
      "version": "0.0.60",
      "script": "http://192.168.0.212:4874/@dcp/component-library@0.0.60/js/web-component.mjs",
      "destructuring": true,
      "npmrc": "@dcp:registry=http://192.168.0.212:4873"
    }

第四步:构建物料

现在你可以直接在终端执行pnpm buildMaterials ,等终端不再有新的输出时,可以ctrl + c 退出脚本。

一切就绪,启动项目验收

至此,你可以pnpm serve:frontend 后访问项目来使用新增的组件了。

问题排查指南

在实际开发过程中,即使按照上述步骤操作,也可能遇到组件未正常显示的情况,这里为您提供了系统化的排查方法。

关键检查点一:组件获取机制

首先检查TinyEngine的组件获取机制是否正常工作。在以下位置添加调试信息:

文件位置: packages/canvas/render/src/material-function/material-getter.ts#L109

export const getComponent = (name) => {
  // 添加调试信息
  console.log(`正在获取组件: ${name}${getNative(name)}`)
  
  const result = Mapper[name] || getNative(name) || getBlock(name) || (isHTMLTag(name) ? name : getBlockComponent(name))
  
  console.log(`获取结果:`, result)
  return result
}

ℹ️ 排查要点:对于引入的组件,getNative(name)应该返回非空值。如果返回undefined,说明组件没有被正确注册。

关键检查点二:动态导入机制

如果上一步检查发现问题,接下来排查动态导入机制。在以下位置添加调试代码:

文件位置: packages/canvas/common/src/utils.ts#L100

const dynamicImportComponentLib = async ({ pkg, script }: DynamicImportParams): Promise<any> => {
  console.log(`开始导入组件库: ${pkg}`)
  
  if (window.TinyComponentLibs[pkg]) {
    console.log(`组件库已存在缓存中: ${pkg}`)
    return window.TinyComponentLibs[pkg]
  }

  if (!script) {
    console.warn(`组件库 ${pkg} 缺少 script 配置`)
    return {}
  }

  const href = window.parent.location.href || location.href
  const scriptUrl = script.startsWith('.') ? new URL(script, href).href : script
  
  console.log(`动态导入组件库脚本: ${scriptUrl}`)

  if (!window.TinyComponentLibs[pkg]) {
    try {
      const modules = await import(/* @vite-ignore */ scriptUrl)
      console.log(`组件库导入成功:`, modules)
      window.TinyComponentLibs[pkg] = modules
    } catch (error) {
      console.error(`组件库导入失败: ${pkg}`, error)
      return {}
    }
  }

  return window.TinyComponentLibs[pkg]
}

常见问题及解决方案

📌 最常见问题:组件未正确导出

在我的实际经验中,这个问题出现频率最高。很多时候我们在组件库中开发了新组件,但忘记在入口文件中导出它。

解决方案

  1. 检查组件库的index.tsindex.js文件
  2. 确认目标组件已经被正确导出:
    export { default as YourComponent } from './YourComponent.vue'
    

📌 脚本路径错误

检查packages.json中的script字段是否正确。常见错误包括:

  • 版本号不匹配
  • 域名或端口错误
  • 文件路径错误

通过以上系统化的排查步骤,绝大多数问题都能够得到快速解决。


如果您对AI开发感兴趣,欢迎关注我的公众号:观默视界

在这里,我会分享更多关于AI技术在实际开发中的实战经验和最新趋势。

关于OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~

OpenTiny 官网opentiny.design
OpenTiny 代码仓库github.com/opentiny
TinyVue 源码github.com/opentiny/ti…
TinyEngine 源码: github.com/opentiny/ti…
欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~
如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~

分步解析:如何开发接口接入淘宝商品评论实时 API

 一、淘宝 API 接入基础

1.1 API 接入流程概述

要接入淘宝商品评论实时 API,一般需要以下步骤:

  1. 注册开发者账号
  2. 获取 ApiKey 和 ApiSecret
  3. 申请所需 API 权限
  4. 开发 API 调用客户端
  5. 实现评论数据的获取与处理

1.2 开发者账号与应用创建

首先,注册选择适合的应用类型(如 Web 应用或移动应用),记录下分配的 ApiKey 和 ApiSecret,这是调用 API 的身份凭证。

二、API 请求签名机制

2.1 签名原理

淘宝 API 采用 HMAC-MD5 算法进行请求签名,确保请求的安全性和完整性。签名过程如下:

  1. 将所有请求参数(除签名参数外)按参数名 ASCII 码从小到大排序
  2. 将排序后的参数名和参数值连接成一个字符串
  3. 在字符串前后加上 ApiSecret
  4. 使用 HMAC-MD5 算法对字符串进行加密
  5. 将加密结果转换为大写的十六进制字符串

2.2 签名实现代码

def generate_sign(self, params):
    """生成API请求签名"""
    sorted_params = sorted(params.items(), key=lambda x: x[0])
    query_string = ''.join([f"{k}{v}" for k, v in sorted_params])
    string_to_sign = self.app_secret + query_string + self.app_secret
    sign = hmac.new(
        self.app_secret.encode('utf-8'),
        string_to_sign.encode('utf-8'),
        hashlib.md5
    ).hexdigest().upper()
    return sign

 

三、商品评论 API 调用实现

3.1 API 客户端基础类

创建一个基础 API 客户端类,负责处理通用的 API 请求逻辑,包括参数组装、签名生成和 HTTP 请求发送。

 

class TaobaoAPIClient:
    def __init__(self, app_key, app_secret, server_url="https://gw.api.taobao.com/router/rest"):
        self.app_key = app_key
        self.app_secret = app_secret
        self.server_url = server_url

    # 前面的generate_sign方法在这里...

    def execute(self, method, params):
        """执行API请求"""
        common_params = {
            "app_key": self.app_key,
            "method": method,
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
            "format": "json",
            "v": "2.0",
            "sign_method": "hmac",
        }
        all_params = {**common_params, **params}
        all_params["sign"] = self.generate_sign(all_params)
        
        try:
            response = requests.post(self.server_url, data=all_params)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            print(f"API请求异常: {e}")
            return {"error": str(e)}

3.2 评论获取与处理模块

基于基础客户端,创建专门的评论获取和处理模块,实现商品评论的获取、解析和实时监控功能。

class TaobaoCommentCrawler:
    def __init__(self, api_client):
        self.api_client = api_client

    def get_item_comments(self, num_iid, page_no=1, page_size=20):
        """获取商品评论"""
        method = "taobao.tmall.item.reviews.get"
        params = {
            "item_id": num_iid,
            "page_no": page_no,
            "page_size": page_size,
            "fields": "id,content,rate_star,created,auction_sku,user_nick"
        }
        return self.api_client.execute(method, params)

    # 其他方法如process_comments、crawl_comments_realtime等...

 

四、实时监控功能实现

4.1 定时获取评论

通过定时任务循环调用评论 API,实现对商品评论的实时监控。每次获取评论后,与上次记录的最新评论时间比较,筛选出新评论。

def crawl_comments_realtime(self, num_iid, interval=60):
    """实时监控商品评论"""
    last_comment_time = None
    while True:
        print(f"正在检查商品 {num_iid} 的新评论...")
        response = self.get_item_comments(num_iid)
        comments = self.process_comments(response)
        
        new_comments = []
        if comments and last_comment_time:
            new_comments = [c for c in comments if c["created_at"] > last_comment_time]
        
        if new_comments:
            print(f"发现 {len(new_comments)} 条新评论")
            for comment in new_comments:
                self.handle_new_comment(comment)
            last_comment_time = max(c["created_at"] for c in comments)
        else:
            print("没有新评论")
        
        time.sleep(interval)

 

4.2 新评论处理逻辑

当发现新评论时,可以执行自定义的处理逻辑,如保存到数据库、发送通知或进行情感分析等。

def handle_new_comment(self, comment):
    """处理单条新评论"""
    print(f"新评论来自 {comment['user']}: {comment['content'][:30]}...")
    # 这里可以添加评论保存、分析等逻辑

 

五、实际应用示例

5.1 初始化与简单调用

下面是一个完整的使用示例,展示如何初始化 API 客户端并获取商品评论:

if __name__ == "__main__":
    # 替换为你的应用凭证
    APP_KEY = "your_app_key"
    APP_SECRET = "your_app_secret"
    
    # 初始化API客户端
    client = TaobaoAPIClient(APP_KEY, APP_SECRET)
    crawler = TaobaoCommentCrawler(client)
    
    # 示例:获取单个商品评论
    product_id = "1234567890"  # 替换为实际商品ID
    response = crawler.get_item_comments(product_id)
    comments = crawler.process_comments(response)
    
    print(f"获取到 {len(comments)} 条评论")
    for comment in comments[:3]:  # 打印前3条评论
        print(f"{comment['user']}({comment['rating']}星): {comment['content'][:50]}...")

 

5.2 启动实时监控

取消下面代码的注释即可启动商品评论实时监控功能:

    # 示例:实时监控商品评论
    # crawler.crawl_comments_realtime(product_id, interval=300)  # 每5分钟检查一次

 

六、注意事项与优化建议

6.1 API 调用限制

淘宝 API 有调用频率限制,不同等级的应用权限不同。在开发时需要注意:

  • 合理设置监控间隔,避免频繁调用
  • 实现请求失败重试机制
  • 考虑使用异步请求提高效率

6.2 数据存储与分析

对于大量评论数据,建议:

  • 使用数据库存储评论,便于查询和统计
  • 实现评论分类和关键词提取
  • 进行情感分析,了解用户反馈

6.3 异常处理与稳定性

  • 完善异常处理机制,处理网络波动、API 错误等情况
  • 添加日志记录,方便问题排查
  • 考虑使用消息队列实现异步处理,提高系统稳定性

通过以上步骤,你可以成功接入淘宝商品评论实时 API,实现对商品评论的实时监控和处理。根据实际需求,你还可以进一步扩展功能,如多商品监控、评论数据分析报表等。

🚀 BFC:前端新手逆袭神器,5分钟告别浮动布局翻车现场!

作者 WildBlue
2025年7月2日 16:11

🧠 你是否也经历过这些痛?

  • 浮动布局后容器“瘦成闪电”?
  • 文字像“脱缰野马”一样绕着浮动元素狂奔?
  • “清除浮动”代码写得像咒语?

💡 别慌!BFC就是CSS界的“结界大师”,今天带你用5分钟吃透它!


1. 为什么你的容器“瘦成闪电”?——浮动的坑 🤯

image.png

浮动布局的“甜蜜陷阱”

<!-- 1.html 示例 -->
<div class="container">
  <div class="box"></div> <!-- 蓝色小方块 -->
  <p>文字环绕在蓝色方块周围...</p>
</div>
.container {
  background: lightblue;
  width: 400px;
}
.box {
  float: left; /* 浮动元素脱离文档流 */
  margin: 100px; /* 容器高度塌陷! */
}

image.png

👀 现象

  • 蓝色方块像“悬浮列车”飘在空中
  • 文字像“追星族”一样围着它转圈圈
  • 容器背景色只显示在文字下方(高度塌陷!)

🔥 问题根源
浮动元素脱离普通文档流,但仍在当前BFC内“鬼打墙”。容器无法感知浮动元素的高度,导致“秃头”!


2. BFC:CSS界的“结界术” 🧙‍♂️

image.png

什么是BFC?

BFC(Block Formatting Context) = 自成一派的“独立小世界”

  • 具有完整渲染区域
  • 不受外部浮动元素干扰
  • 能包裹内部浮动元素

触发BFC的“魔法阵” ✨

/* 常见触发方式 */
.container {
  overflow: hidden; /* 最常用! */
  /* 或 display: flex/grid/inline-block */
  /* 或 position: absolute/fixed */
}

3. 代码实战:BFC拯救容器“秃头危机” 💪

image.png

示例对比:1.html vs 2.html

<!-- 2.html 改进版 -->
<div class="container">
  <div class="box"></div>
  <div class="box"></div>
</div>
.container {
  overflow: hidden; /* 🔥 触发BFC! */
}
.box {
  float: left;
  margin: 100px;
}

image.png

🎉 效果

  • 容器成功包裹所有浮动元素
  • 背景色覆盖完整区域(高度不再塌陷)
  • 文字不再“追星”,乖乖待在容器里

🔍 开发者工具观察

  1. 打开浏览器开发者工具
  2. 比较1.html和2.html中.containerheight属性
  3. 你会发现BFC就像给容器戴了“紧箍咒”!

4. BFC实战:多列布局的“隐形支架” 🌐

image.png

场景:响应式导航栏

.nav {
  overflow: hidden; /* 触发BFC */
  border: 2px solid #333;
}

.nav-item {
  float: left;
  padding: 10px 20px;
}

💡 效果

  • 导航项横向排列但不会溢出父容器
  • 父容器高度自动适应所有子项
  • 移动端适配时,BFC会智能“换行”(需搭配媒体查询)

5. BFC进阶:解决经典布局问题 🛠️

image.png

案例:卡片式布局中的“高度不一致”

.card-container {
  overflow: hidden; /* 触发BFC */
  column-count: 3; /* 三列布局 */
}

.card {
  break-inside: avoid; /* 防止卡片被分割 */
  padding: 20px;
  border: 1px solid #ccc;
}

效果

  • 卡片自动填充三列
  • 每列高度自动对齐(BFC的“等高列”特性)
  • 内容过多时自动换行(无需计算宽度)

✨知识点回顾 + 💡行动指南

3个记忆点

  1. BFC是CSS的“结界”,隔离外部浮动干扰
  2. 触发方式overflow: hidden最常用,flex/grid也是“隐藏武器”
  3. 应用场景:解决高度塌陷、多列布局、清除浮动

1个行动建议

🎯 动手挑战

  • 修改1.html的.container添加overflow: hidden
  • 用开发者工具观察容器高度变化
  • 尝试用display: flex代替float实现多列布局

🧩 附录:代码示例全解析

1.html(未触发BFC)

<div class="container">
  <div class="box"></div>
  <p>文字环绕...</p>
</div>
.container {
  background: lightblue;
  width: 400px;
}
.box {
  float: left;
  margin: 100px; /* 容器高度塌陷! */
}

image.png

2.html(触发BFC)

<div class="container">
  <div class="box"></div>
  <div class="box"></div>
</div>
.container {
  overflow: hidden; /* 🔥 BFC魔法启动! */
}
.box {
  float: left;
  margin: 100px;
}

image.png


🧩 附录:代码完整版

1.html(未触发BFC)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BFC</title>
    <style>
        .container{
            background-color: lightblue;
            width: 400px;
        }
        .box{
            margin: 100px;
            width: 100px;
            height: 100px;
            background-color: blue;
            float: left;
            /* position: absolute;
            top:0;
            left:0; */
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="box"></div>
         <div>20050308 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦
         </div>
        <!-- <div class="box"></div>
        <div class="box"></div> -->
    </div>
</body>
</html>

2.html(触发BFC)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BFC</title>
    <style>
        .container{
            background-color: lightblue;
            /* width: 400px; */
            overflow: hidden;/* 触发BFC */
        }
        .box{
            margin: 100px;
            width: 100px;
            height: 100px;
            background-color: blue;
            float: left;
            /* position: absolute;
            top:0;
            left:0; */
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="box"></div>
         <div>20050308 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦 我爱杜 我爱羽毛球 我爱周杰伦
         </div>
        <!-- <div class="box"></div>
        <div class="box"></div> -->
    </div>
   
</body>
</html>

🎯 记住:BFC不是黑魔法,而是CSS的“空间折叠术”!
现在,去用BFC解决你的布局难题吧!🚀

面试官:你知道 React Fiber 吗?我:你是说 调度届的王者?

作者 Kincy
2025年7月2日 16:09

🧠 系列前言:
面试题千千万,我来帮你挑重点。每天一道,通勤路上、蹲坑时、摸鱼中,技术成长不设限!本系列主打幽默 + 深度 + 面霸必备语录,你只管看,面试场上稳拿 offer!

💬 面试官发问:

“你了解 React Fiber 吗?能说说它的核心原理和解决了什么问题吗?”

哎呀,这题我熟。React Fiber,听起来像个健身品牌,其实是 React 自己下的一盘“大棋”。

🎯 快答区(初级背诵版)

React Fiber 是 React 16 开始引入的新架构,用来优化之前递归更新的流程,解决了无法中断渲染、更新过程不够灵活的问题。Fiber 支持任务拆分、优先级调度和异步渲染,使得用户交互更流畅,页面不卡顿。

面试官:嗯,还行,但我想听点你背不出来的内容 😏

🧠 深入理解:Fiber 究竟是谁?

📌 1. React 旧架构的问题

React 15 及以前的版本在更新组件时,采用的是同步递归的方式:

<MyHugeComponent />

页面越大,递归越深,React 就越像一位卷王:咬牙更新完才休息。

问题是:

  • 你点个按钮,页面卡了 300ms;
  • 你输入表单,光标卡了一下;
  • 用户体验直接下滑成海底捞。

React 团队一拍桌子:我们要让更新能暂停、能恢复、能控制优先级!
于是 Fiber 诞生。

🧱 2. 什么是 Fiber 架构?

通俗点说,Fiber 是 React 的 任务调度中心 + 渲染流水线,是对 Virtual DOM 的一次架构升级。

它的目标是实现可中断、可恢复、可控制优先级的更新流程。

它怎么实现的?

  • 把组件更新任务拆成小块(Unit of Work)
  • 每一块都可以:
    • 执行一点点
    • 暂停
    • 恢复
    • 放到空闲时执行

React:我不再一口气更新到底了,我要 边干活边喘气

🧩 3. Fiber 是什么数据结构?

别怕,这不难!

Fiber 本质上是个双缓冲链表树结构(听起来吓人,画出来就很简单):

每个组件都会生成一个 FiberNode,类似这样:

type FiberNode = {
  type: string | Function;
  child: FiberNode | null;
  sibling: FiberNode | null;
  return: FiberNode | null;
  stateNode: any;
  alternate: FiberNode | null; // 旧节点,用于比较
  effectTag: string;
  // ...还有一些调度相关字段
}

每个节点都是可控的、可遍历的、可“后悔”的!

⏱️ 4. Fiber 的两大阶段

React Fiber 的更新流程分成两个阶段:

阶段名 作用 是否可中断
Reconciliation 构建 Fiber 树(diff) ✅ 可以
Commit 真实操作 DOM ❌ 不行

🚦在第一阶段,React 可以使用浏览器的空闲时间 requestIdleCallback 做点“碎片工作”。

🧠 如果任务来不及做完,React 可以暂停,先处理用户交互(比如你点个按钮),然后回来接着干

🎢 5. 支持优先级调度是怎么回事?

React Fiber 的另一个大招是:给不同的更新任务分等级!

任务类型 优先级
用户输入(点击、键盘)
动画
网络请求后的渲染
页面初始化加载

React 使用了自己的调度器(scheduler),确保重要的任务插队处理,低优先级的任务以后再说

💬 面试中可以抛出的装 X 语录

  • “Fiber 是 React 从同步递归向异步可控调度的一次范式转变。”
  • “Fiber 把 Virtual DOM 升级成任务流,赋予了调度权。”
  • “它让我重新相信:写前端也能讲并发。”

(说完记得停顿两秒,喝一口水,看面试官点头)

✅ 总结一句话

React Fiber = 可中断、可恢复、可优先级调度的 Virtual DOM 架构升级版,它让 React 更聪明、更流畅,也为后续的 Concurrent Mode 奠定了基础。

🔚 系列结尾:明日继续爆料!

明天继续来一道面试题,咱们聊聊 React 是怎么做 Virtual DOM diff 的(别看是老生常谈,其实里面猫腻不少🐱)。

📌 点赞 + 收藏 + 关注系列,让你成为面霸不是梦!

WebGPU 核心参数详解:从基础到实践

2025年7月2日 16:08

WebGPU 作为现代图形 API,提供了丰富的参数配置选项,理解这些参数对于充分发挥 GPU 性能至关重要。本文将深入剖析 WebGPU 的各个核心参数,帮助开发者掌握其底层工作原理。

一、设备与适配器参数

1.1 适配器请求参数

navigator.gpu.requestAdapter() 方法接受一个可选参数对象:

const adapter = await navigator.gpu.requestAdapter({
    powerPreference: 'high-performance', // 或 'low-power'
    forceFallbackAdapter: false
});

参数详解:

  • powerPreference:

    • 'high-performance': 优先选择独立显卡
    • 'low-power': 优先选择集成显卡
    • 默认值取决于浏览器实现
  • forceFallbackAdapter:

    • true: 强制使用软件回退适配器
    • false: 使用硬件加速(默认)
    • 主要用于调试和兼容性场景

1.2 设备请求参数

adapter.requestDevice() 方法也接受配置对象:

const device = await adapter.requestDevice({
    requiredFeatures: ['texture-compression-bc'],
    requiredLimits: {
        maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize
    },
    defaultQueue: {} // 队列描述符
});

参数详解:

  • requiredFeatures: 数组,指定需要的特性扩展

    • 常见特性:'depth-clip-control', 'texture-compression-etc2'
    • 可通过adapter.features查看支持的扩展
  • requiredLimits: 对象,覆盖默认资源限制

    • 重要限制项:
      {
          maxTextureDimension1D: 8192,
          maxTextureDimension2D: 8192,
          maxTextureArrayLayers: 256,
          maxBindGroups: 4,
          maxUniformBuffersPerShaderStage: 12,
          maxStorageBuffersPerShaderStage: 8,
          maxComputeWorkgroupSizeX: 256,
          maxComputeWorkgroupSizeY: 256,
          maxComputeWorkgroupSizeZ: 64
      }
      
    • 完整列表可通过adapter.limits查看
  • defaultQueue: 配置命令队列

    • 目前只有一个可选参数label用于调试

二、缓冲区(Buffer)参数

创建缓冲区时的核心参数:

const buffer = device.createBuffer({
    size: 1024, // 字节大小
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
    mappedAtCreation: true
});

usage标志位详解:

标志位 描述 典型用途
MAP_READ 可映射读取 结果回读
MAP_WRITE 可映射写入 初始化数据
COPY_SRC 可作为复制源 缓冲区复制
COPY_DST 可作为复制目标 数据上传
INDEX 可作为索引缓冲区 索引绘制
VERTEX 可作为顶点缓冲区 顶点数据
UNIFORM 可作为uniform缓冲区 统一变量
STORAGE 可作为存储缓冲区 计算着色器
INDIRECT 可作为间接参数缓冲区 间接绘制
QUERY_RESOLVE 可用于查询解析 计时查询

最佳实践:

  • 组合使用标志位时使用按位或(|)操作
  • 避免不必要的标志位,某些组合可能降低性能
  • 静态数据设置COPY_DST,动态数据考虑MAP_WRITE

三、纹理(Texture)参数

纹理创建参数结构:

const texture = device.createTexture({
    size: [512, 512, 1], // 宽度,高度,深度/数组层数
    mipLevelCount: 4,
    sampleCount: 1,
    dimension: '2d',
    format: 'rgba8unorm',
    usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
    viewFormats: ['rgba8unorm-srgb']
});

参数详解:

3.1 size

  • 1D纹理: [width]
  • 2D纹理: [width, height]
  • 3D纹理: [width, height, depth]
  • 纹理数组: [width, height, arrayLayerCount]

3.2 mipLevelCount

  • 指定mipmap链级别数
  • 1表示不生成mipmap
  • 计算最大级别: Math.floor(Math.log2(Math.max(width, height))) + 1

3.3 sampleCount

  • 多重采样数(1, 4, 8, 16)
  • 1表示禁用多重采样
  • 必须与渲染管线采样数匹配

3.4 dimension

  • '1d': 一维纹理
  • '2d': 二维纹理(默认)
  • '3d': 三维纹理

3.5 format

常见纹理格式:

格式 描述 通道顺序 每个通道位数
rgba8unorm 8位无符号归一化 RGBA 8-8-8-8
rgba8snorm 8位有符号归一化 RGBA 8-8-8-8
rgba8uint 8位无符号整数 RGBA 8-8-8-8
rgba8sint 8位有符号整数 RGBA 8-8-8-8
rgba16float 16位浮点 RGBA 16-16-16-16
rgba32float 32位浮点 RGBA 32-32-32-32
depth32float 深度缓冲 D 32
depth24plus-stencil8 深度+模板 DS 24+8

3.6 usage

纹理使用标志位:

标志位 描述
COPY_SRC 可作为复制源
COPY_DST 可作为复制目标
TEXTURE_BINDING 可绑定为采样纹理
STORAGE_BINDING 可绑定为存储纹理
RENDER_ATTACHMENT 可用作渲染目标

3.7 viewFormats

  • 指定纹理视图可用的额外格式列表
  • 主要用于sRGB格式转换
  • 例如: viewFormats: ['rgba8unorm-srgb']

四、渲染管线(Render Pipeline)参数

渲染管线是WebGPU中最复杂的对象之一,其参数结构如下:

const pipeline = device.createRenderPipeline({
    layout: 'auto' | pipelineLayout,
    vertex: {
        module: shaderModule,
        entryPoint: 'vertexMain',
        buffers: [vertexBufferLayout]
    },
    primitive: {
        topology: 'triangle-list',
        stripIndexFormat: undefined,
        frontFace: 'ccw',
        cullMode: 'none'
    },
    depthStencil: {
        format: 'depth24plus',
        depthWriteEnabled: true,
        depthCompare: 'less'
    },
    multisample: {
        count: 1,
        mask: 0xFFFFFFFF,
        alphaToCoverageEnabled: false
    },
    fragment: {
        module: shaderModule,
        entryPoint: 'fragmentMain',
        targets: [{
            format: 'rgba8unorm',
            blend: {
                color: {
                    operation: 'add',
                    srcFactor: 'one',
                    dstFactor: 'zero'
                },
                alpha: {
                    operation: 'add',
                    srcFactor: 'one',
                    dstFactor: 'zero'
                }
            },
            writeMask: GPUColorWrite.ALL
        }]
    }
});

4.1 primitive 参数详解

topology: 指定图元类型

  • 'point-list': 点列表
  • 'line-list': 线列表(每2个顶点一条线)
  • 'line-strip': 线带(顶点共享)
  • 'triangle-list': 三角形列表(默认)
  • 'triangle-strip': 三角形带

frontFace: 定义正面朝向

  • 'ccw': 逆时针为正面(默认)
  • 'cw': 顺时针为正面

cullMode: 面剔除模式

  • 'none': 不剔除(默认)
  • 'front': 剔除正面
  • 'back': 剔除背面

4.2 depthStencil 参数详解

format: 深度/模板格式

  • 'depth16unorm': 16位深度
  • 'depth24plus': 至少24位深度
  • 'depth32float': 32位浮点深度
  • 'depth24plus-stencil8': 24位深度+8位模板

depthCompare: 深度测试函数

  • 'never': 从不通过
  • 'less': 小于通过(默认)
  • 'equal': 等于通过
  • 'less-equal': 小于等于通过
  • 'greater': 大于通过
  • 'not-equal': 不等于通过
  • 'greater-equal': 大于等于通过
  • 'always': 总是通过

4.3 blend 参数详解

混合配置控制颜色如何与目标混合:

blend: {
    color: {
        operation: 'add', // 混合操作
        srcFactor: 'src-alpha', // 源因子
        dstFactor: 'one-minus-src-alpha' // 目标因子
    },
    alpha: {
        operation: 'add',
        srcFactor: 'one',
        dstFactor: 'zero'
    }
}

常见混合模式配置:

  1. 不混合(默认):
{
    operation: 'add',
    srcFactor: 'one',
    dstFactor: 'zero'
}
  1. 传统Alpha混合:
{
    operation: 'add',
    srcFactor: 'src-alpha',
    dstFactor: 'one-minus-src-alpha'
}
  1. 加法混合:
{
    operation: 'add',
    srcFactor: 'one',
    dstFactor: 'one'
}

五、计算管线(Compute Pipeline)参数

计算管线相对简单,主要关注工作组配置:

const computePipeline = device.createComputePipeline({
    layout: 'auto',
    compute: {
        module: computeShaderModule,
        entryPoint: 'computeMain',
        constants: {
            'WORKGROUP_SIZE_X': 8,
            'WORKGROUP_SIZE_Y': 8
        }
    }
});

工作组配置要点:

  1. 在着色器中定义工作组大小:
@compute @workgroup_size(8, 8)
fn computeMain() {
    // ...
}
  1. 调度工作组数量:
passEncoder.dispatchWorkgroups(
    Math.ceil(textureWidth / 8), 
    Math.ceil(textureHeight / 8)
);
  1. 硬件限制:
  • 通过device.limits.maxComputeWorkgroupSizeX/Y/Z获取最大工作组尺寸
  • 典型值:256(x) × 256(y) × 64(z)

六、资源绑定与绑定组

6.1 绑定组布局(BindGroupLayout)

const bindGroupLayout = device.createBindGroupLayout({
    entries: [
        {
            binding: 0,
            visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
            buffer: {
                type: 'uniform'
            }
        },
        {
            binding: 1,
            visibility: GPUShaderStage.FRAGMENT,
            texture: {
                sampleType: 'float'
            }
        },
        {
            binding: 2,
            visibility: GPUShaderStage.COMPUTE,
            storageTexture: {
                access: 'write-only',
                format: 'rgba8unorm'
            }
        }
    ]
});

资源类型配置:

  1. 缓冲区:
buffer: {
    type: 'uniform' | 'storage' | 'read-only-storage',
    hasDynamicOffset: false, // 是否支持动态偏移
    minBindingSize: 0 // 最小绑定大小
}
  1. 纹理:
texture: {
    sampleType: 'float' | 'unfilterable-float' | 'depth' | 'sint' | 'uint',
    viewDimension: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d',
    multisampled: false
}
  1. 存储纹理:
storageTexture: {
    access: 'write-only' | 'read-only' | 'read-write',
    format: 'rgba8unorm', // 必须为存储纹理格式
    viewDimension: '1d' | '2d' | '2d-array' | '3d'
}

6.2 绑定组(BindGroup)

const bindGroup = device.createBindGroup({
    layout: bindGroupLayout,
    entries: [
        {
            binding: 0,
            resource: { buffer: uniformBuffer }
        },
        {
            binding: 1,
            resource: texture.createView()
        },
        {
            binding: 2,
            resource: storageTexture.createView()
        }
    ]
});

七、性能优化建议

  1. 管线创建开销

    • 提前创建所有需要的管线
    • 使用device.createRenderPipelineAsync()避免阻塞
  2. 资源上传

    • 使用device.queue.writeBuffer()而非映射写入
    • 批量上传数据
  3. 绑定组更新

    • 尽量减少绑定组更新
    • 将频繁变化的资源放在单独的绑定组
  4. 渲染通道优化

    • 最小化渲染通道数量
    • 合理安排附件加载/存储操作
  5. 计算调度

    • 选择合适的工作组大小(通常8x8或16x16)
    • 避免工作组尺寸过小导致利用率不足

结语

WebGPU 的参数系统设计既灵活又复杂,需要开发者深入理解各个参数的相互关系和性能影响。通过合理配置这些参数,可以充分发挥现代 GPU 的硬件能力,构建高性能的图形和计算应用。建议在实际开发中结合浏览器开发者工具的 WebGPU 调试功能,不断优化参数配置。

不允许还有人不会给开源项目提交代码

作者 comelong
2025年7月2日 16:04

前言

近期为了面试也是一直在看Vue3的部分源码实现思路,再结合AI可以很好的了解到一些源码的具体实现过程,这样在应对面试过程中也能做到举一反三,话不多说,直接上干货!!!

1. Fork 项目到自己的 GitHub 账号

在 GitHub 上点击 Fork 按钮

2. Clone 自己的 fork 到本地

git clone github.com/YOUR_USERNA…

3. 设置远程仓库

git remote add upstream github.com/original/re… # 添加上游仓库

git remote set-url origin github.com/YOUR_USERNA… # 设置你的 fork

创建新分支 git checkout -b fix-name # 分支名应该反映改动内容

2. 安装依赖

pnpm install # Vue 使用 pnpm

3. 进行代码修改

修改相关文件

4. 运行测试(非必须)

pnpm test

提交信息格式:

type(scope): description

类型(type):

  • fix: 修复bug
  • feat: 新功能
  • docs: 文档改动
  • style: 代码格式改动
  • refactor: 重构
  • test: 测试相关
  • chore: 构建过程或辅助工具的变动

示例:

   git add .
   git commit -m "fix(compiler-sfc): correct method name in templateUtils"
   这里意思就是提交了一个修改的正确方法名在templateUtils

1. 推送到你的 fork

git push origin fix-name

2. 在 GitHub 上创建 PR
- 使用清晰的标题(与 commit 消息一致)
- 写详细的描述
- 确保选择正确的基础分支

标题

fix(compiler-sfc): correct method name in templateUtils

描述

Problem

  • Describe the issue you're fixing

Solution

  • Explain your changes
  • What was changed and why

Additional Info

  • Any side effects?
  • Testing done?
  • Screenshots (if UI changes)?

注意事项

  • 确保一个 PR 只做一件事
  • 遵循项目的代码风格
  • 保持 commit 历史清晰
  • 及时响应 review 意见
  • 保持耐心,维护者可能比较忙

更新 fork 与上游同步

  • git fetch upstream
  • git checkout main
  • git merge upstream/main

压缩/整理 commit

git rebase -i HEAD~n # n是要合并的commit数量

如果需要修改 PR

  • git add .
  • git commit --amend # 修改最后一次提交
  • git push origin fix-name -f # 强制推送(谨慎使用)

浮动布局揭秘:从『脱标』到BFC的现代布局艺术

2025年7月1日 19:46

引言:被低估的布局王者

在Flexbox和Grid横行的时代,为何我们还要学习浮动布局?想象一下:当你需要实现文字优雅地环绕图片,或创建经典的多列布局时,浮动依然是最简洁的解决方案。本文将带你深入理解浮动布局的工作原理,掌握清除浮动的5种实战技巧,并揭秘BFC这个神秘的布局『结界』。

一、文档流:网页布局的自然法则

文档流就像水流,元素按照HTML结构从上到下、从左到右自然排列。块级元素独占一行,行内元素则像文字一样并排显示。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="box1">123</div>
    <div class="box2">hello</div>
</body>
</html>

示意图:

image.png

二、浮动登场:打破常规的布局魔法

2.1 浮动的初心:文字环绕

浮动最初设计的目的是实现文字环绕图片的效果,就像报纸排版一样:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        img{
            float: left
        }
    </style>
</head>
<body>
    <div class="box">
        <img width="200" height="200" src="[图片地址]" alt="">
        <p>已经补了前几年更新出的第五部...</p>
    </div>
</body>
</html>

示意图:

image.png

2.2 意外的惊喜:水平布局

浮动可以实现多列布局,比table布局更简洁灵活:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .list{
            font-size: 0;
        }
        li{
            width: 300px;
            height: 100px;
            display: inline-block;
            font-size: 20px;
        }
        li:nth-child(1){ background-color: aqua; }
        li:nth-child(2){ background-color: red; }
        li:nth-child(3){ background-color: green; }
    </style>
</head>
<body>
    <ul class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
</body>
</html>

示意图:

image.png

三、浮动的『副作用』与清除技巧

3.1 恼人的高度塌陷

当子元素设置浮动后,父元素会失去高度,就像子元素『消失』了一样:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{ margin: 0; padding: 0; }
        li{ width: 300px; height: 100px; float: left; 
        li:nth-child(1){ background-color: aqua; }
        li:nth-child(2){ background-color: red; }
        li:nth-child(3){ background-color: green; }
    </style>
</head>
<body>
    <ul class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
    <h2 class="title">hello</h2>
</body>
</html>

示意图:

image.png

3.2 清除浮动的五种武器

武器一:手动设置高度(不推荐)

.list{ height: 100px; }

武器二:额外标签法(不推荐)

.clear{
    clear: both;
}
<ul class="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <div class="clear"></div> <!-- 额外添加的标签 -->
</ul>

武器三:伪元素清除法(推荐)

.list::after{
    content: '';
    clear: both;
    display: block;
}

武器四:受影响元素清除法(场景特定)

.title{ clear: both; }

武器五:BFC容器法(推荐)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{ margin: 0; padding: 0; }
        .list{ overflow: hidden; /* 创建BFC容器 */ }
        li{ width: 300px; height: 100px; float: left; }
        li:nth-child(1){ background-color: aqua; }
        li:nth-child(2){ background-color: red; }
        li:nth-child(3){ background-color: green; }
    </style>
</head>
<body>
    <ul class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
    <h2 class="title"><span>hello</span></h2>
</body>
</html>

示意图:

image.png

四、BFC:神秘的布局结界

4.1 什么是BFC?

BFC(Block Formatting Context)即块级格式化上下文,可以理解为一个独立的布局结界。元素进入这个结界后,会遵循一套独特的渲染规则,不受外界干扰,也不干扰外界。

4.2 创建BFC的五种方式

  1. overflow: hidden || auto || scroll || overlay
  2. position: absolute || fixed
  3. display: inline-block || flex || grid
  4. float: left || right
  5. contain: layout || content || paint

4.3 BFC的神奇作用

作用一:解决margin重叠问题

作用二:包含浮动元素

BFC容器在计算高度时,会包含浮动元素的高度,这正是它能清除浮动的原因。

作用三:阻止文字环绕

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{ margin: 0; padding: 0; }
        .parent{
            width: 100%;
            height: 500px;
            background-color: #0a929a;
            margin-top: 100px;
            /* overflow: hidden; */
            /* position: absolute; */
            float: left;
        }
        .child{
            height: 200px;
            background-color: #aa04ba;
            margin-top: 50px;
        }
    </style>
</head>
<body>
    <div class="parent">
        <div class="child"></div>
    </div>
</body>
</html>

image.png

五、浮动布局的现代应用

虽然Flexbox和Grid已成为主流,但浮动在某些场景下依然无可替代:

  1. 文字环绕效果:这是浮动最经典的应用场景
  2. 媒体对象组件:头像+文字描述的布局
  3. 栅格系统回退方案:为不支持Flex/Grid的旧浏览器提供支持
  4. 清除浮动技巧:在现代布局中仍有实用价值

六、总结:布局工具的智慧选择

没有永远的王者,只有最合适的工具。浮动布局虽然有『脱标』的特性,但理解其原理后,就能驾驭它的力量。BFC作为CSS中的重要概念,不仅能解决浮动问题,在现代布局中也有广泛应用。

掌握本文介绍的浮动布局技巧和BFC原理,你将能够从容应对各种布局挑战,写出更优雅、更健壮的CSS代码。

结语

CSS布局就像搭积木,浮动、Flexbox、Grid各有所长。理解每种布局方式的原理和适用场景,才能在实际开发中做出最佳选择。希望本文能帮助你真正理解浮动布局,让它成为你CSS工具箱中的得力助手。

记录:申请ios证书、描述文件,ios上架Apple Store

作者 CC965
2025年7月1日 14:17

1.首先打开网址

developer.apple.com/account ,选择证书

image.png

2.因为主要目的是为了app上架,打包只需要证书和描述文件,所以这里不讨论其他场景

如图所示 image.png

3.生成证书请求文件

3.1 使用mac电脑打开钥匙串访问

image.png

image.png

3.2打开菜单 “钥匙串访问”->“证书助理”,选择“从证书颁发机构请求证书...”:

image.png

3.3选择保存位置,点击 “存储” 将证书请求文件保存到指定路径下

文件名称为“CertificateSigningRequest.certSigningRequest”,后面申请开发(Development)证书和发布(Production)证书时需要用到

image.png

4.申请开发(Development)证书和描述文件

4.1申请开发(Development)证书

image.png

在 “Software” 栏下选中 “iOS App Development” 然后点击 “Continue”: 因为我这里已经生成过了,所以这里不让选了,正常情况下是可以选的

image.png

接下来需要用到刚刚生成的证书请求文件,点击“Choose File...”选择刚刚保存到本地的 “CertificateSigningRequest.certSigningRequest”文件,点击 “Continue” 生成证书文件:

image.png

生成证书后选择 “Download” 将证书下到本地 (ios_development.cer):这个是用来导出p12证书的

image.png

双击保存到本地的 ios_development.cer 文件,会自动打开 “钥匙串访问” 工具说明导入证书成功,可以在证书列表中看到刚刚导入的证书,接下来需要导出 .p12 证书文件,选中导入的证书,右键选择 “导出...”:

image.png

image.png

注意:这里的密码就是打包时的密码,这两个密码是要对应的

image.png

image.png

至此,我们已经完成了开发证书的制作(得到了 xxx.p12 证书文件)这个p12证书就是我们hb打包app时使用的证书

4.2申请开发 (Development) 描述文件

image.png

在 “Development” 栏下选中 “iOS App Development”,点击“Continue”按钮:

image.png

这里要选择已有的 “App ID” (没有的话可以自行创建),点击“Continue”:

image.png

接下来选择需要绑定的证书,这里建议直接勾选 “Select All”,点击“Continue”:

image.png

选择授权调试设备,这里建议直接勾选 “Select All”,点击 “Continue”:

image.png

输入描述文件的名称(如“testDevelopMent”), 点击 “Generate” 生成描述文件:

image.png

点击“Download”下载保存开发描述文件(文件后缀为 .mobileprovision)

image.png

image.png 至此,我们已经得到了开发证书(.p12)及对应的描述文件(.mobileprovision)

image.png

接下看一下如何制作发布证书及发布描述文件

5.申请发布(Distribution)证书和描述文件

5.1申请发布(Production)证书

在 “Software” 栏下选中 “App Store and Ad Hoc”,点击 “Continue”:

image.png

接下来同样需要用到之前生成的证书请求文件,点击“Choose File...”选择刚刚保存到本地的 “CertificateSigningRequest.certSigningRequest”文件,点击 “Continue” 生成证书文件:

image.png

生成证书成功,选择“Download” 将证书下载到本地 (ios_distribution.cer):

image.png

同样双击保存到本地的 ios_production.cer 文件将证书导入到 “钥匙串访问”工具中,可以在证书列表中看到刚刚导入的证书,接下来需要导出 .p12 证书文件,选中导入的证书,右键选择 “导出...”:

image.png

输入文件名、选择路径后点击 “存储”: 至此,我们已经完成了发布证书的制作(得到了 xxx.p12 证书文件),接下来,继续生成发布描述文件

5.2申请发布 (Distribution) 描述文件

在证书管理页面选择 “Profiles”,可查看到已申请的所有描述文件,点击页面上的加号来添加一个新的描述文件:

image.png

在 “Distribution” 栏下选中 “App Store”,点击“Continue”按钮:

image.png

这里要选择已有的 “App ID” (没有的话可以自行创建),点击“Continue”:

image.png

接下来选择需要绑定的发布证书(iOS Distribution),这里勾选刚刚生成的发布证书”,点击“Continue”:

image.png

接下来输入描述文件的名称, 点击 “Generate” 生成描述文件:

image.png

然后点击 “Download” 将描述文件下载到本地(文件后缀为 .mobileprovision)

至此,我们已经得到了发布证书(.p12)及对应的发布描述文件(.mobileprovision)

image.png

6.打包上架Apple Store

6.1打包ipa文件

打开HBuilderX打包我们的app,重要:打包的app版本号最好与上传到Transporter软件的版本号一致 这里我们的证书私钥密码就是我们生成证书时的密码 image.png

6.2上传至Transporter

macos下载Transporter软件,然后登录公司账号,然后选择“+”上传自己刚打包好的app到这里,或者直接把app拖进来也可以

重要:打包的app版本号最好与上传到Transporter软件的版本号一致

image.png

6.3 登录Apple Store connect,选择app构建

首先需要登录到Apple Store connect:appstoreconnect.apple.com/ ,登录成功后选择app

image.png

选择我们需要更新版本的app

image.png

6.4 app信息存储以及提交审核

这里会有一个 “+”让你来构建你的新的app版本,由于我们这里的app还未审核成功,所以暂时不显示

image.png

点击后填写好app的基本信息,这里只写关键点: app的构建版本直接选择我们刚刚上传至Transporter的,然后一定要先存储才能提交审核

image.png

为了防止审核通过后忘记,这里我们选择审核通过后自动发布 image.png

点击提交即可完成上架操作

image.png

注:本文不讨论从0开始上架,是基于已有的app进行更新审核

Swift也要抢Android饭碗?一个语言统御全世界

作者 JarvanMo
2025年7月1日 09:58

如果五年前你告诉一位移动开发者,Swift将正式支持Android,他们很可能会笑出猪叫,并冲你甩了一个“鄙夷”的表情。长期以来,苹果的整个生态系统都被视为一个精心打磨、运行流畅且严格专属的 “围墙花园”。

但快进到 2025 年年中,苹果的主力编程语言突然以一种重大方式实现跨平台。

一个新成立的Swift Android工作组正在努力将Android打造成Swift的官方支持目标平台。

请仔细体会这几个关键词:
SwiftAndroid官方原生

这是自 Flutter 和 Kotlin Multiplatform 成为头条新闻以来,移动开发领域最大的跨平台变革之一。

image.png

image.png

而且,这并非仅仅是 Swift 在 Android 上 “运行”—— 它关乎重新定义我们构建移动应用的方式、组建开发团队的架构,以及代码在不同平台间的共享机制。

下面我们逐一拆解。

为什么是现在?

这一变革源于开发者对维护两套分歧代码库的疲惫感与日俱增。两种语言(Swift 与 Kotlin)、两套架构、两拨独立的 Bug—— 简直一团乱麻。想象一下:只需用 Swift 编写一次应用核心逻辑,就能在 iOS 和 Android 上原生运行。这不仅是便利,更是生产力的革命。

Swift 自身也已足够成熟。自 2014 年为苹果平台诞生以来,它已全面开源并官方支持 Linux 和 Windows。如今,Swift 不仅驱动 iOS 应用,还支撑服务器后端、命令行工具,甚至 WebAssembly 实验。在此背景下,Android 成了最后一片未被征服的 “疆域”。而现在,通过社区驱动(而非企业强制)的努力,这片疆域正在被攻克。

核心要点

  1. 效率痛点:维护独立的 iOS/Android 代码库消耗巨大
  2. 语言优势:Swift 现代、高性能且天然跨平台
  3. 工程价值:统一代码可共享业务逻辑,减少重复开发
  4. 社区驱动:开源生态的协作模式让 Android 支持更具生命力,而非自上而下的强制推行

为何这条新闻的重要性远超想象

因为它挑战了我们此前认为移动开发中 “理所当然” 的一切:

  • 历史性转变:苹果向安卓敞开大门(即便间接敞开),这是前所未有的。世界不再是 “苹果专属” 的了。
  • 打破拉锯战:像 Flutter 和 KMP 这类跨平台框架,曾试图搭建起 iOS 与安卓之间的桥梁。而现在,编程语言本身成为了桥梁。开发团队无需再执着于 “选 Swift 还是选 Kotlin” 。
  • 更快、更省、更简洁:创业公司和独立开发团队可以 “一次开发,两处部署” 。网络通信、模型、数据格式化等代码都能共享,只有平台特定的 UI(Jetpack Compose 对应安卓,SwiftUI 对应 iOS )需要重新编写。
  • 全栈 Swift 愿景推进:Swift 早已能支撑服务端(比如借助 Vapor 框架 ),甚至能开发 WebAssembly 应用。随着对安卓的支持落地,“全栈 Swift”(后端 + iOS + 安卓 )的愿景离现实更近一步。
  • 语言胜过平台,成为主导力量:这传递出更深层的趋势:工程师会优先选择自己钟爱的工具,而非受限于厂商锁定。要是说 Flutter 实现了 UI 的跨平台编写,KMP 实现了逻辑的跨平台共享,那么 Swift 对安卓的支持,则实现了编程语言本身的跨平台突破。

Flutter 证明了 UI 可一次编写跨平台使用,KMP 证明了逻辑能跨平台共享,而 Swift 对安卓的支持,证明就连苹果的 “王牌” 编程语言,都能突破其诞生之初的生态边界实现演进。这传递出一个信号:移动开发的未来由编程语言和灵活性定义,而非企业生态的边界。

工作组具体在做什么?

这不是单个开发者的副业项目,而是在Swift.org上正式列出、有结构化组织且获社区支持的倡议行动。Swift 安卓工作组聚焦于以下工作:

  • 将安卓纳入官方 Swift 工具链。
  • 针对安卓,优化 Foundation 框架、Dispatch 库以及 Swift 并发模型。
  • 明确支持的安卓 API 级别和架构。
  • 提供持续集成(CI )工具、Docker 镜像以及 Gradle 支持。
  • 梳理 Java 原生接口(JNI )交互的最佳实践并形成文档。
  • 强化调试支持(比如安卓上的 LLDB 调试 )。
  • 制作入门文档和示例项目。

其使命不是 “凑合着把功能拼凑出来”,而是让安卓成为能与 Swift 原生适配的一流平台。

对开发者而言有哪些变化?

以下是这一进展为实际开发团队带来的新可能:

  • 用 Swift 编写核心逻辑,在安卓和 iOS 两端共享。
  • 借助 Swift 包管理器(SPM )构建共享模块。
  • 针对安卓用 Jetpack Compose、针对 iOS 用 SwiftUI,开发平台特定 UI。
  • 避免模型、解析器、业务规则等代码重复编写。
  • 利用共享的 XCTest 逻辑,提升跨平台测试覆盖率。

简而言之,开发团队可围绕共享层(逻辑、网络通信、业务领域等 )进行组织架构调整,同时针对不同平台定制 UI 。

实现 Swift 与安卓的桥接:终于成为可能

这之所以可行,要归功于 Java 原生接口(JNI)。像 Skip、Readdle 的 Gradle 插件以及开源的 SwiftJava 项目这类工具,让以下操作成为可能:

  • 从 Swift 直接调用安卓应用程序接口(APIs)。

  • 使用swift build为安卓编译 Swift 包。

  • 通过 Gradle 将 Swift 逻辑导入安卓工作室(Android Studio)。

  • 借助 Swift 包管理器(SPM)在不同平台间共享 Swift 模块。

  • 根据需要混合使用 Swift 和 Kotlin 模块。

JNI 桥接代码负责处理桥接工作,而且部分工具可自动完成这种配置。

你不必局限于 “只用 Swift”,在合理的情况下,你可以混合搭配(不同语言和技术)。

目前效果如何?

以下是如今已能实现的情况:

  • 构建与编译:像 Skip 这类工具,能让你为安卓 ARM 和 x86 目标设备编译 Swift 代码。
  • 应用运行:用 Swift 编写的 “Hello World” 应用和逻辑模块,确实能在模拟器和真实安卓设备上运行。
  • XCTest 支持:Swift 单元测试(包括苹果的 Swift 算法测试套件)已在安卓设备上成功运行。
  • Gradle 支持:Readdle 的开源插件能让安卓工作室集成 Swift 库。
  • 共享模块:为 iOS 编写的 Swift 包,现在也能为安卓编译,其中包括可复用的业务逻辑。
  • 持续集成(CI)与 Docker 工具:社区成员已发布 GitHub Actions 和 Docker 配置,以简化交叉编译流程。

它完美吗?并不。但它真实可用吗?是的 —— 而且即便在当前这个早期阶段,它的能力也令人意外地强大。

哪些人已在使用?

尽管还处于早期阶段,但我们已看到以下群体在采用:

  • 探索更精简跨平台解决方案的独立开发者。

  • 以 iOS 开发为主的初创公司,拓展到安卓平台时无需用 Kotlin 重写逻辑。

  • 为客户应用构建共享 Swift 软件开发工具包(SDKs)的代理公司。

  • 开发插件、SDK 和文档原型的开源贡献者。

  • 开发 Vapor/Swift 服务端的团队,在安卓上复用他们的 Swift 模型和逻辑。

它目前还不是主流 —— 但正在从 “我们能不能做” 转向 “我们能做到什么程度”。

未来面临的真正挑战

有一些实际存在的限制,你需要了解:

  • SwiftUI 在安卓上不可用 —— 目前只能共享逻辑。

  • 工具尚不成熟 —— 目前还没有官方的安卓工作室集成。

  • 调试靠手动操作 —— 需要用到 Docker、LLDB 和终端操作。

  • 二进制文件体积大 ——Swift 的运行时会让安卓安装包(APKs)增加数十兆字节。

  • JNI 桥接如果手动管理,会很棘手且代码冗长。

  • 文档很简略 —— 你得经常查看 GitHub 的问题列表和论坛帖子。

  • 学习曲线陡峭 —— 安卓开发者必须学习 Swift,反之亦然。

但要记住:Flutter、React Native 和 Kotlin 多平台(Kotlin Multiplatform)最初也都很粗糙。现在 Swift 也有了成长的机会,而且在社区的助力下,它会不断发展。

这对开发者意味着什么

这种转变不只是技术层面的 —— 还涉及理念层面。

  • 编写一次代码,在多处运行(配合合适的用户界面)。
  • 选择你想用的语言 —— 而非受平台归属束缚。
  • 复用模型、逻辑、测试和库。
  • 赋予小团队和初创公司更大能力。
  • 专注打造出色的产品,而非重复编写代码。

未来,移动开发可能会从 “以平台为先” 转变为 “以语言为先”,而 Swift 正在引领这场变革。

开发者们的反应如何

前期反响十分热烈:

image.png

  • 开发者们正在 GitHub 上分享 Swift Android 的演示项目。
  • 论坛帖子里满是实验报告、工具链更新和测试结果。
  • 在安卓上运行 XCTest 让许多人感到惊讶 —— 这表明这项工作已经取得了很大进展。
  • 像 “我以为这是个梗” 和 “这可能会取代我们的 KMP” 这样的评论频频出现。

势头正在形成,而且这是从社区自下而上推动的。

想亲自尝试吗?

以下是入门指南:

  1. 访问 Swift Android 工作组官网
  2. 使用 Skip 工具链 构建安卓版本
  3. 通过 swift package init 命令创建 Swift 包
  4. 使用跨平台编译目标为安卓编译代码
  5. 借助 SwiftJava 或 Skip 工具添加 JNI 桥接
  6. 通过 ADB 推送 XCTest 二进制文件并在设备上运行
  7. 加入 Swift 论坛 和 Slack 社区提问或贡献代码

虽然尚处早期阶段,但如果你是乐于探索的开发者,这无疑是一片蓝海。

未来可能是什么样子

让我们畅想一下:

  • 适用于安卓的 SwiftUI:一个可在两个平台上运行的声明式 UI 框架。

  • 一键式 Swift 模块:能够原生构建和捆绑适用于安卓的 Swift 工具。

  • 官方工具链Swift.org分发适用于安卓的二进制文件和 SDK。

  • 技术栈统一的团队:所有开发者都使用 Swift 编写后端、iOS 和安卓应用。

  • 快速入门:新开发者学习一种语言,即可在所有平台上发布应用。

虽然现在还为时尚早,但这一切正在成为现实。几年后,我们可能会回顾过去,说:“还记得 Swift 只用于 iOS 的时候吗?”


最终思考

Swift 在 Android 上的应用不仅是一项技术实验,更是我们对移动开发认知的一次范式转变。

它关乎效率与灵活性,关乎打破技术孤岛;它意味着让编程语言而非厂商生态主导开发模式;最重要的是,它关乎开发者的选择权。

如果你厌倦了维护两个独立的开发世界,厌倦了重复编写业务逻辑,或者只是对未来技术好奇 —— 现在正是探索的最佳时机。

不妨尝试在 Android 上使用 Swift,持续关注技术动态,参与社区讨论。

因为当下发生的一切,可能正在重新定义我们构建未来应用的方式。

如果本文对你有所启发,别忘了关注、分享,并在评论区留下你的想法 —— 让我们共同塑造移动开发的未来。

理解本地 `hosts` 文件原理:从本地解析到完整域名解析流程

作者 Sailing
2025年7月1日 08:24

在日常开发和运维中,我们经常通过配置 hosts 文件来修改某些域名的解析行为。但你是否真正了解它的原理?它在整个域名解析流程中扮演着什么角色?本文将带你从 hosts 文件的本质出发,逐步理解整个域名解析的流程,并结合流程图深入解析其工作机制。

1280.jpeg

什么是 hosts 文件?

hosts 是操作系统中一个用于将域名映射为 IP 地址的纯文本文件。它相当于一个本地的微型 DNS,在域名解析过程中拥有最高优先级

典型配置示例:

127.0.0.1   example.com
10.0.0.12   dev.api.com

这意味着:

  • 当访问 example.com,系统会直接使用 127.0.0.1,不再进行其他 DNS 查询;
  • 访问 dev.api.com 时会直接解析到 10.0.0.12

hosts 文件的工作原理

当你在浏览器输入一个域名时,系统会按如下顺序解析:

  1. 应用(浏览器)发起域名请求;
  2. 操作系统优先检查 hosts 文件
  3. 如果找到匹配的域名映射 → 直接使用映射 IP;
  4. 如果未命中 → 进入后续的 DNS 查询流程(包括本地 DNS 缓存、本地域名服务器查询、远程 DNS 查询等)。

image.png

各系统中的 hosts 文件路径

系统类型 路径
Windows C:\Windows\System32\drivers\etc\hosts
macOS/Linux /etc/hosts

一个完整的域名解析流程(含 hosts

我们来详细拆解一次完整的域名解析流程图,从用户在浏览器输入网址那一刻开始,到最终获取到服务器 IP 地址的整个过程:

  1. 优先检查本地 hosts 文件,看是否存在该域名对应的 IP 映射;
  2. 若找到映射,直接返回该 IP,跳过后续 DNS 查询流程;
  3. 若未命中 hosts 文件,继续执行正常的 DNS 查询流程:
    • 检查本地 DNS 缓存,若命中则返回缓存 IP;
    • 若未命中,向配置的本地 DNS 服务器发起请求(通常是路由器或 ISP 提供的 DNS);
    • 若本地 DNS 服务器无缓存,开始递归查询网络 DNS 服务器(根域名服务器 → 顶级域名服务器 → 权威 DNS 服务器);
  4. 若 DNS 查询失败,则域名解析失败,网站无法访问;
  5. 获取最终 IP 地址后,结果逐级返回客户端,同时进行本地缓存。

image.png

应用场景:为什么要配置 hosts

  • 前端/后端联调:将 api.example.com 映射到测试服务器 IP;
  • 屏蔽广告或不良网站:将域名映射到 127.0.0.1,实现本地阻断;
  • 故障切换和预热:临时修改域名解析地址,绕过故障节点;
  • 本地开发环境:模拟生产环境的域名,方便开发调试;

小结

  • hosts 文件是本地域名解析的起点,优先级高于一切 DNS 查询;
  • 它可用于快速配置、联调、屏蔽或应急处理
  • 熟悉它的原理和解析流程,有助于排查网络异常、构建本地开发环境;

希望这篇文章,对你有所帮助、有所借鉴~

loader和plugin区别

2025年7月1日 01:08

一、基础定义与定位

概念 本质 作用阶段 核心职责
loader 模块转换器(函数) 编译阶段 将源文件转换为可打包的模块
plugin 事件监听与处理器(对象) 整个构建周期 扩展 webpack 功能,执行特定任务

二、核心区别对比

1. 功能维度
  • loader

    • 专注于文件内容的转换,例如:
      • .jsx 转为 JS(Babel-loader)
      • 将 Sass 转为 CSS(sass-loader)
      • 将图片转为 Base64(url-loader)
    • 单文件处理,输入源文件,输出转换后的代码。
  • plugin

    • 监听 webpack 构建过程中的生命周期事件,执行对应操作:
      • 打包后生成 HTML 文件(HtmlWebpackPlugin)
      • 压缩 JS/CSS(UglifyJSPlugin)
      • 热更新(HotModuleReplacementPlugin)
    • 全局影响,可操作构建后的资源、修改打包配置等。
2. 用法与形态
  • loader 用法

    // webpack.config.js
    module.exports = {
      module: {
        rules: [
          {
            test: /\.js$/,
            use: 'babel-loader', // 字符串形式
            // 或对象形式配置参数
            use: {
              loader: 'babel-loader',
              options: { presets: ['@babel/preset-env'] }
            }
          }
        ]
      }
    };
    
  • plugin 用法

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      plugins: [
        new HtmlWebpackPlugin({ template: './index.html' }), // 实例化插件
        new webpack.DefinePlugin({ NODE_ENV: JSON.stringify('production') })
      ]
    };
    
3. 执行时机与流程
  • loader 执行流程

    1. 从右到左、从下到上调用(如 style-loader!css-loader!less-loader)。
    2. 每个 loader 处理前一个 loader 的输出,最终转为 JS 模块。
  • plugin 事件机制

    1. 通过 compiler.hooks 监听事件(如 beforeCompileafterEmit)。
    2. 例如:HtmlWebpackPlugincompilation.finish 阶段生成 HTML 文件。

三、问题

1. 问:为什么 loader 是函数,plugin 是对象?
    • loader 需直接处理文件内容,函数形态更适合输入输出转换;
    • plugin 需注册事件钩子,对象形态可通过 apply 方法绑定到 webpack 实例,符合发布-订阅模式。
2. 问:如何实现一个简单的 loader?
  • // 示例:将文本转为大写的 loader
    module.exports = function(source) {
      // this.cacheable() // 开启缓存
      return source.toUpperCase();
    };
    
    • 核心:导出函数,接收源文件内容,返回转换后的代码。
3. 问:plugin 如何监听打包事件?
  • // 示例:打包完成后打印日志的 plugin
    class LogPlugin {
      apply(compiler) {
        compiler.hooks.afterEmit.tap('LogPlugin', (compilation) => {
          console.log('打包完成!文件数量:', Object.keys(compilation.assets).length);
        });
      }
    }
    
    • 关键:实现 apply 方法,通过 compiler.hooks 注册事件处理器。
4. 问:生产环境中常见的 plugin 有哪些?
    • 优化类TerserPlugin(JS 压缩)、MiniCssExtractPlugin(CSS 抽离);
    • 资源类HtmlWebpackPlugin(生成 HTML)、ImageMinimizerPlugin(图片压缩);
    • 环境类DefinePlugin(注入环境变量)、ProvidePlugin(自动加载模块)。

四、实战场景与选型

  • 当需要处理文件内容时:用 loader(如 .vue 文件 → vue-loader)。
  • 当需要控制构建流程时:用 plugin(如打包后自动清理旧文件)。
  • 组合使用案例
    // 场景:将 ES6 转为 ES5 并压缩 JS
    module.exports = {
      module: {
        rules: [{ test: /\.js$/, use: 'babel-loader' }] // loader 转换代码
      },
      plugins: [
        new TerserPlugin() // plugin 压缩代码
      ]
    };
    
❌
❌