手把手搭建Vue轮子从0到1:2. 搭建框架雏形

上一篇:# 手把手搭建Vue轮子从0到1:1. 前期准备

1. 下载Vue源码(版本:3.5.17)

基本结构:

arduino 复制代码
vue-next-3.5.17 (Vue.js 核心仓库)
├── tsconfig.json          // TypeScript 配置文件
├── rollup.config.js        // rollup 的配置文件
├── packages                // 核心代码包
│   ├── vue-compat          // 用于兼容 vue2 的代码
│   ├── vue                 // 重要: 浏览器实例,打包之后的 dist 都会放在这里
│   ├── template-explorer   // 提供了一个线上的测试 (https://template-explorer.vuejs.org/),用于把 template 转化为 render
│   ├── size-check          // 测试运行时包大小
│   ├── shared              // 重要: 共享的工具类
│   ├── sfc-playground      // sfc 工具,比如: https://sfc.vuejs.org/
│   ├── server-renderer     // 服务器渲染
│   ├── runtime-test        // runtime 测试相关
│   ├── runtime-dom         // 重要: 基于浏览器平台的运行时
│   ├── runtime-core        // 重要: 运行时的核心内容,内部针对不同平台进行了实现
│   ├── reactivity-transform // 已过期,无需关注
│   ├── reactivity          // 重要: 响应式的核心模块
│   ├── global.d.ts         // 全局的 ts 声明
│   ├── compiler-ssr        // 服务端渲染的编译模块
│   ├── compiler-sfc        // 单文件组件 (.vue) 的编译模块
│   ├── compiler-dom        // 重要: 浏览器相关的编译模块
│   └── compiler-core       // 重要: 编译器核心代码
├── package.json            // npm 包管理工具
├── netlify.toml            // 自动化部署相关
├── jest.config.js          // 测试相关
├── api-extractor.json      // TypeScript 的 API 分析工具
├── SECURITY.md             // 报告漏洞,维护安全的说明文件
├── README.md               // 项目声明文件
├── LICENSE                 // 开源协议
├── CHANGELOG.md            // 更新日志
├── BACKERS.md              // 赞助声明
├── test-dts                // 测试相关,不需要关注
├── scripts                 // 配置文件相关,不需要关注
├── pnpm-workspace.yaml     // pnpm 相关配置
└── pnpm-lock.yaml         // 使用 pnpm 下载的依赖包本

核心包结构详解:

  • 🔥 核心模块 (最重要)

    • vue - 主入口包,整合所有功能模块
    • compiler-core - 编译器核心,处理模板编译逻辑
    • compiler-dom - DOM相关的编译器,处理浏览器特定的编译
    • runtime-core - 运行时核心,平台无关的运行时逻辑
    • runtime-dom - DOM运行时,浏览器平台的运行时实现
    • reactivity - 响应式系统,Vue的响应式核心
    • shared - 共享工具函数和常量
  • 🛠️ 构建与工具

    • compiler-sfc - 单文件组件编译器
    • compiler-ssr - 服务端渲染编译器
    • server-renderer - 服务端渲染器
    • runtime-test - 测试运行时环境
  • 🔄 兼容与扩展

    • vue-compat - Vue 2.x 兼容层
  • 📁 私有包 (packages-private/)

    • sfc-playground - 单文件组件在线演练场
    • template-explorer - 模板编译探索工具
    • dts-test - TypeScript 类型定义测试
  • ⚙️ 配置与脚本

    • scripts/ - 构建脚本和开发工具
    • 各种配置文件 (TypeScript, ESLint, Rollup 等)

这个项目采用 monorepo 架构,使用 pnpm 作为包管理器,支持多种构建格式 (ESM, CJS, UMD等),为不同的使用场景提供了完整的解决方案。

2. 运行Vue源码

  1. 安装 pnpm 包管理工具

    npm install -g pnpm

pnpm 会通过一个 集中管理 的方式来管理 电脑中所有项目 的依赖包,以达到 节约电脑磁盘 的目的。

项目初衷 | pnpm

  1. 项目根目录下安装依赖
css 复制代码
pnpm i
  1. 项目打包
arduino 复制代码
npm run build
  1. 查看生成的文件 packages/vue/dist
csharp 复制代码
vue.runtime.esm-browser.prod.js      // 浏览器用 ESM 运行时生产环境版
vue.esm-browser.prod.js              // 浏览器用 ESM 完整版生产环境
vue.runtime.global.prod.js           // 浏览器用全局运行时生产环境版
vue.global.prod.js                   // 浏览器用全局完整版生产环境
vue.cjs.prod.js                      // CommonJS 生产环境版
vue.runtime.esm-browser.js           // 浏览器用 ESM 运行时开发版
vue.esm-browser.js                   // 浏览器用 ESM 完整开发版
vue.runtime.global.js                // 浏览器用全局运行时开发版
vue.global.js                        // 浏览器用全局完整版开发版
vue.cjs.js                           // CommonJS 开发版
vue.runtime.esm-bundler.js           // ESM Bundler 运行时(供打包工具用)
vue.esm-bundler.js                   // ESM Bundler 完整版(供打包工具用)

3. 构建运行测试实例

  1. 在源码中 \core\packages\vue\examples(vue自己的测试实例) 下新建 my-examples 文件夹,用于放我们的测试实例。

  2. VSCode中安装 Live Server 插件,帮我们直接启动一个 本地服务。

  1. 创建实例文件:reactive.html
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>
    <script src="../../dist/vue.global.js"></script>
</head>
<body>
    <div id="app"></div>
    <script>
        // 1. 从 Vue 中解构出 reactive、effect 函数
        const { reactive, effect } = Vue;

        // 2. 创建响应式对象
        const state = reactive({
            count: 0
        });

        // 3. 创建副作用函数(effect)
        effect(() => {
            document.querySelector('#app').innerHTML = `count is: ${state.count}`;
        });

        // 4. 定时修改响应式对象的属性,触发副作用函数,更新 DOM(视图发生变化)
        setTimeout(() => {
            state.count++;
        }, 1000);

    </script>
</body>
</html>
  1. 鼠标右键 -> Open with Live Server
  1. 等待 1s,count 由 0 变成了 1,说明已经成功运行了一个测试实例。

4. 对 Vue 进行debugger

  1. 开启 SourceMap(源代码映射)

未开启SourceMap时的开发者工具(F12 Sources模块):

开启SourceMap之后:

在执行 npm run build 时,实际执行的是 scripts/build.js 文件

(scripts/build.js)可以找到这么一行代码: ```sourceMap ? SOURCE_MAP:true : `````

在 rollup.config.js 文件中 output.sourcemap = !!process.env.SOURCE_MAP赋值给 output.sourcemap。

sourceMap 是通过 parseArgs(nodejs.org/api/util.ht...) 解析命令行参数得到的。parseArgs 解析后,values 对象中会包含 sourceMap 字段,然后通过解构赋值。

  • --sourceMap 是完整参数名
  • -s是它的短写

所以 sourceMap 的值最终取决于 运行 scripts/build.js 时是否传递了 --sourceMap 或 -s 参数。

  1. 修改 package.json,修改build脚本命令
json 复制代码
"build": "node scripts/build.js -s"

重新执行 npm run build

  1. 查看源代码

ctrl+p 搜索文件,可以看到有对应的 effect.ts 和 reactive.ts 文件

  1. 添加断点

点击行序号 94,断点调试 reactive 的代码执行逻辑。

刷新页面,进入调试

5. 开始搭建

  1. 创建 package.json 模块
csharp 复制代码
npm init -y
  1. 新建文件夹结构 packages (核心代码区域)
go 复制代码
vue-mini (Vue.js MVP)
├── packages                // 核心代码包
│   ├── vue                 // 打包、测试实例、项目整体入口
│   ├── shared              // 共享的工具类
│   ├── runtime-dom         // 基于浏览器平台的运行时(浏览器部分运行时内容)
│   ├── runtime-core        // 运行时的核心内容,内部针对不同平台进行了实现
│   ├── reactivity          // 响应式的核心模块
│   ├── compiler-dom        // 重要: 浏览器相关的编译模块
│   └── compiler-core       // 重要: 编译器核心代码
├── package.json            // npm 包管理工具
  1. 导入 ts 配置
  • 项目根目录下创建 tsconfig.json 配置文件(What is a tsconfig.json),用于指定编译项目所需的入口文件和编译器配置。(也可以通过 tsc -init 生成默认配置,这个需先 npm i -g typescript)
  • 指定如下配置(www.typescriptlang.org/tsconfig/
json 复制代码
{
  // 编译器配置
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    // 指定JS语言版本
    "target": "es5" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
    // https://www.typescriptlang.org/tsconfig#lib
    "lib": [
      "esnext",
      "dom"
    ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "libReplacement": true,                           /* Enable lib replacement. */
    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    // 模块化
    "module": "esnext" /* Specify what module code is generated. */,
    // 指定根目录
    "rootDir": "." /* Specify the root folder within your source files. */,
    // 指定模块解析策略(指定类型脚本如何从给定的模块说明符查找文件)
    "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
    // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
    // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
    // 允许解析 .json 文件
    "resolveJsonModule": true /* Enable importing .json files. */,
    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // 禁用 sourceMap
    "sourceMap": false /* Create source map files for emitted JavaScript files. */,
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // 转换为 JavaScript 时, 从 TypeScript 文件中删除所有注释
    "removeComments": false /* Disable emitting comments. */,
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // 向下兼容迭代器(支持语法迭代) https://www.typescriptlang.org/tsconfig#downlevelIteration
    "downlevelIteration": true /* Emit more compliant, but verbose and less performant JavaScript for iteration. */,
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
    // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
    // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    // 启用 ES 模块互操作性 https://www.typescriptlang.org/tsconfig#esModuleInterop
    "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,

    /* Type Checking */
    // 启用所有严格类型检查选项
    "strict": true /* Enable all strict type-checking options. */,
    // 启用隐式 any 类型检查(有助于简化 ts 复杂度,从而更加专注于逻辑本身)
    "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // 允许未使用的局部变量
    "noUnusedLocals": false /* Enable error reporting when local variables aren't read. */,
    // 允许未使用的参数
    "noUnusedParameters": false /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    // "skipLibCheck": true,                             /* Skip type checking all .d.ts files. */
  },
  // 入口
  "include": ["packages/*/src"]
}
  1. 配置 prettier 格式化代码工具
  • VSCode中安装 prettier 辅助插件
  • 项目根目录下创建 .prettierrc 文件
json 复制代码
{
  // 结尾无分号
  "semi": false,
  // 全部使用单引号
  "singleQuote": true,
  // 每行最大长度
  "printWidth": 80,
  // 末尾不添加逗号
  "trailingComma": "none",
  // 省略箭头函数括号
  "arrowParens": "avoid"
}

6. 模块打包器 rollup(www.rollupjs.com/

与 webpack(webpack.docschina.org/concepts/)一样,可以将 JS 打包为指定模块。区别在于,对于 webpack 而言,打包时会产生许多冗余的代码,在开发大型项目的时候没有什么影响,但是在开发一个库的时候,冗余代码会大大增加库体积,导致不够美好。

rollup 可以将小块代码编译成大块复杂的代码,例如 libray 或应用程序。

Rollup 是一个用于 JavaScript 的模块打包工具,它将小的代码片段编译成更大、更复杂的代码,例如库或应用程序。

6.1. 如何理解更大,更复杂?

这其实是指 Rollup 的主要用途和工作方式:

  1. 将多个小模块合并成一个文件

在开发时,我们通常会把代码拆分成很多小的模块(每个功能一个文件),这样方便维护和复用。但在发布时,如果直接把这些小文件交给浏览器加载,会导致很多网络请求,影响性能。

  1. 打包过程

Rollup 会把这些分散的小模块"打包"成一个或几个大的文件。这个过程叫做"bundling"(打包),其实就是把很多小的代码片段合并成一个更大的整体。

  1. 更复杂的代码

合并后,Rollup 还会做一些优化,比如去除没用的代码(tree-shaking)、转换语法等。最终生成的文件,虽然体积可能更小,但结构上比单个小模块要复杂,因为它包含了所有模块的内容和模块之间的依赖关系处理。

举个例子:

假设你有三个模块:a.js、b.js、c.js。每个文件都很简单,但打包后,Rollup 会把它们合成一个 bundle.js,这个文件里包含了所有模块的代码和它们的依赖关系。这样,浏览器只需要加载一个文件。

总结

这里的"更大、更复杂"不是说代码变得难以理解,而是说:

  • 文件体积变大(因为合并了多个模块)
  • 代码结构更复杂(因为要处理模块之间的依赖和作用域)

但这样做的好处是:加载更快、部署更方便、可以做更多优化。

可以理解为 rollup 为一个打包 库 的模块打包器,而应用程序打包时则选择 webpack。

6.2. 引入 rollup

  1. 项目根目录下创建 rollup.config.js 文件
js 复制代码
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';

/**
 * 默认导出一个数组,数组的每一个对象都是一个单独的导出文件配置。https://www.rollupjs.com/guide/big-list-of-options
 */
export default {
  // 入口文件
  input: 'packages/vue/src/index.ts',
  // 打包出口
  output: [
    // 导出 iife 模式的包
    {
      // 开启 sourcemap
      sourcemap: true,
      // 导出 iife 模式的包
      format: 'iife',
      // 导出的文件地址
      file: './packages/vue/dist/vue.js',
      // 变量名
      name: 'Vue',
    }
  ],
  // 插件
  plugins: [
    // ts 支持
    typescript({
      sourceMap: true,
    }),
    // 将 CommonJS 转换为 ES2015 模块
    commonjs(),
    // 模块导入的路径补全
    resolve(),
  ],
}
  1. 修改 package.json 文件
json 复制代码
{
  "name": "vue-mini",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "rollup -c"
  },
  "repository": {
    "type": "git",
    "url": "https://gitee.com/carrierxia/vue-mini.git"
  },
  "devDependencies": {
    "rollup": "^3.12.0",
    "@rollup/plugin-commonjs": "^26.0.2",
    "@rollup/plugin-node-resolve": "^15.0.0",
    "@rollup/plugin-typescript": "^10.0.0",
    "typescript": "^5.7.3",
    "tslib": "^2.8.1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module"
}
  1. 执行 npm i 安装依赖
  2. 在新增 \vue-mini\packages\vue\src\index.ts 文件,输入
js 复制代码
console.log("Vue")
  1. 执行 npm build 打包,可以看到生成了对应 dist 文件夹

7. 配置路径映射

json 复制代码
{
  // 编译器配置
  "compilerOptions": {
    ...
    // 指定基础目录
    "baseUrl": "./" ,
    // 指定模块路径别名
    "paths": {
      "@vue/*": ["packages/*/src"]
    }
  }
}
相关推荐
前端小趴菜059 分钟前
React - createPortal
前端·vue.js·react.js
晓131333 分钟前
JavaScript加强篇——第四章 日期对象与DOM节点(基础)
开发语言·前端·javascript
菜包eo1 小时前
如何设置直播间的观看门槛,让直播间安全有效地运行?
前端·安全·音视频
烛阴1 小时前
JavaScript函数参数完全指南:从基础到高级技巧,一网打尽!
前端·javascript
chao_7892 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼3 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原3 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf3 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js
爱编程的喵4 小时前
React Router Dom 初步:从传统路由到现代前端导航
前端·react.js
每天吃饭的羊4 小时前
react中为啥使用剪头函数
前端·javascript·react.js