在 VScode 中配置前端代码格式化，可以通过以下几个步骤进行：1. 安装 Prettier 插件Prettier 是一个流行的代码格式化工具，支持很多编程语言，包括 JavaScript、TypeScript、HTML 和 CSS。打开 VScode，点击左侧的 扩展（Extensions）图标，搜索并安装 Prettier - Code formatter。2. 配置 Prettier通过修改 .vscode/settings.json 文件来配置 Prettier 格式化选项：在 React 项目的根目录下创建（如果没有的话）一个 .vscode/settings.json 文件，添加以下内容：{ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, // 保存时自动格式化 "prettier.singleQuote": true, // 使用单引号 "prettier.trailingComma": "es5", // 在可能的情况下，添加尾随逗号 "prettier.semi": true, // 结尾加分号 "prettier.tabWidth": 2 // 缩进宽度 }3. 配置项目中的 .prettierrc 文件（可选）为了让团队中的每个人都使用相同的代码格式，可以在项目根目录下添加一个 .prettierrc 配置文件，内容如下：{ "singleQuote": true, "trailingComma": "es5", "semi": true, "tabWidth": 2 }这样，无论是使用 Prettier 插件，还是通过命令行运行 prettier，都会遵循这些配置。
1. 作用域差异普通CSS：全局作用域，所有样式都是全局的容易出现样式冲突和覆盖问题/* styles.css */ .button { background-color: blue; }// 使用普通CSS import './styles.css'; 按钮 CSS模块：局部作用域，样式只在当前组件内生效自动生成唯一的类名，避免冲突/* Button.module.css */ .button { background-color: blue; }// 使用CSS模块 import styles from './Button.module.css'; 按钮 2. 类名生成机制普通CSS：类名保持原样：.button → buttonCSS模块：自动生成哈希类名：.button → Button_button__2x3kl确保每个组件的样式独立3. 使用方式普通CSS：// 直接使用字符串 CSS模块：// 通过styles对象引用 4. 优缺点对比特性普通CSSCSS模块学习成本低中等样式冲突容易发生几乎不会代码维护困难（大项目）容易性能一般更好（按需加载）调试容易稍复杂（类名被哈希）5. 适用场景普通CSS适合：小型项目全局样式（重置样式、主题等）第三方库的样式覆盖CSS模块适合：中大型项目组件化开发需要样式隔离的场景
useFocusEffectuseFocusEffect 是 React Navigation 提供的一个 Hook，专门用于处理页面获得焦点时的副作用。基本用法：import { useFocusEffect } from '@react-navigation/native'; import { useCallback } from 'react'; function MyScreen() { useFocusEffect( useCallback(() => { // 页面获得焦点时执行的代码 console.log('页面获得焦点'); // 返回清理函数（可选） return () => { console.log('页面失去焦点'); }; }, []) ); }特点：每次页面获得焦点时都会执行支持清理函数，页面失去焦点时执行常用于数据刷新、订阅管理等场景useCallbackuseCallback 是 React 提供的性能优化 Hook，用于缓存函数引用。基本语法：const memoizedCallback = useCallback( () => { // 函数逻辑 }, [dependency1, dependency2] // 依赖数组 );工作原理：只有当依赖数组中的值发生变化时，才会重新创建函数如果依赖数组为空 []，函数只会创建一次返回的是函数的缓存版本使用场景：防止子组件不必要的重渲染：function Parent() { const [count, setCount] = useState(0); // 没有 useCallback - 每次渲染都创建新函数 const handleClick = () => { console.log('clicked'); }; // 使用 useCallback - 函数引用保持不变 const memoizedHandleClick = useCallback(() => { console.log('clicked'); }, []); return <Child onClick={memoizedHandleClick} />; }与其他 Hook 配合使用：function MyComponent() { const [data, setData] = useState(null); const fetchData = useCallback(async () => { const result = await api.getData(); setData(result); }, []); // 空依赖数组，函数只创建一次 useEffect(() => { fetchData(); }, [fetchData]); // fetchData 不会导致无限循环 }性能考虑useCallback 的使用原则：考虑使用：传递给子组件的函数作为其他 Hook 的依赖计算成本较高的函数不考虑使用：简单的事件处理函数（如果没有传递给子组件）过度使用（useCallback 本身也有开销）示例对比：// 不需要 useCallback function SimpleComponent() { const handleClick = () => { console.log('simple click'); }; return Click ; } // 需要 useCallback function ComplexComponent({ children }) { const [count, setCount] = useState(0); const handleClick = useCallback(() => { // 复杂逻辑或传递给子组件 setCount(prev => prev + 1); }, []); return ( {React.Children.map(children, child => React.cloneElement(child, { onClick: handleClick }) )} ); }总结useFocusEffect：处理页面焦点变化的副作用，常用于数据刷新useCallback：缓存函数引用，优化性能，防止不必要的重渲染结合使用：在导航场景中实现高效的数据刷新机制这两个 Hook 在 React Native 应用中非常实用，特别是在处理页面导航和性能优化方面。
前言在开发React Native应用时，我们经常会遇到需要在多个组件间共享状态和数据的场景。如果处理不当，很容易出现数据不一致、重复初始化等问题。本文将通过一个真实的项目案例，展示如何使用单例模式来解决这些问题。问题背景在我们的LoveConnect项目中，用户信息管理是一个核心功能。我们有一个InfoManager类来处理用户信息的获取、更新和存储。最初的实现如下：// 原始实现 - 存在问题的版本 export const getInfoManager = (): InfoManager => { const isDevelopment = mod === 'development'; if (isDevelopment) { return new InfoManagerText(); // 每次都创建新实例 } else { return new InfoManagerImpl(); // 每次都创建新实例 } };这种实现方式看似简单，但在实际使用中暴露出了严重的问题。遇到的问题1. 多实例数据不一致当不同的React组件调用getInfoManager()时，每次都会创建新的实例：// ProfilePage.tsx const infoManager = getInfoManager(); // 实例A // EditNamePage.tsx const infoManager = getInfoManager(); // 实例B // EditPhonePage.tsx const infoManager = getInfoManager(); // 实例C每个实例都有自己的内存缓存和初始化状态：class InfoManagerImpl implements InfoManager { private currentInfo: Info | null = null; // 每个实例都有独立的缓存 private initialized = false; // 每个实例都有独立的状态 }2. 重复初始化问题每个新实例都需要重新从AsyncStorage加载数据，造成不必要的性能开销：async initialize(): Promise<void> { if (this.initialized) return; try { // 每个实例都要重新从存储加载数据 const stored = await AsyncStorage.getItem(STORAGE_KEY); if (stored) { this.currentInfo = JSON.parse(stored); } this.initialized = true; } catch (error) { console.error('初始化用户信息失败:', error); } }3. 状态同步问题用户在一个页面更新信息后，其他页面的实例可能仍然显示旧数据，导致UI不一致。解决方案：单例模式实现思路单例模式确保一个类只有一个实例，并提供全局访问点。对于我们的场景，这意味着：整个应用只有一个InfoManager实例所有组件共享同一份数据和状态只需要初始化一次具体实现// 单例实例存储 let infoManagerInstance: InfoManager | null = null; export const getInfoManager = (): InfoManager => { // 如果已经有实例，直接返回 if (infoManagerInstance) { return infoManagerInstance; } // 首次调用时创建实例 const isDevelopment = mod === 'development'; if (isDevelopment) { infoManagerInstance = new InfoManagerText(); } else { console.log('使用生产环境信息管理器'); infoManagerInstance = new InfoManagerImpl(); } return infoManagerInstance; }; // 重置单例实例的方法（用于测试或特殊情况） export const resetInfoManager = (): void => { infoManagerInstance = null; };在React组件中的使用现在所有组件都会获得同一个实例：// ProfilePage.tsx const infoManager = getInfoManager(); // 获取单例实例 const ProfilePage = () => { const [info, setInfo] = useState<any>({}); const fetchInfo = useCallback(async () => { const info = await infoManager.getInfo(); // 使用单例实例 setInfo(info); }, []); // 使用useFocusEffect确保页面聚焦时刷新数据 useFocusEffect( useCallback(() => { fetchInfo(); }, [fetchInfo]) ); // ... };优势分析1. 数据一致性保证// 现在所有地方都使用同一个实例 const manager1 = getInfoManager(); // 同一个实例 const manager2 = getInfoManager(); // 同一个实例 console.log(manager1 === manager2); // true2. 性能优化减少内存占用：只创建一个实例避免重复初始化：只从AsyncStorage加载一次数据减少网络请求：缓存的数据在所有地方共享3. 状态同步当用户在任何页面更新信息时，所有其他页面都能立即看到最新数据：// EditNamePage.tsx - 更新姓名 await infoManager.updateName(newName); // ProfilePage.tsx - 自动获取到最新数据 const info = await infoManager.getInfo(); // 包含最新的姓名
背景：后端完好。expo启动在安卓模拟器。问题：本地api地址使用localhost报TypeError: Network request failed分析在Android模拟器中， localhost:8080 或 127.0.0.1:8080 指向的是 模拟器内部 ，而不是宿主机（电脑）。这就是为什么会出现 Network request failed 错误的根本原因。解决：使用电脑的实际IP地址，在命令行运行 ipconfig （Windows）查看你的IP地址，将api地址中localhost改为实际的ip
在 Next.js 的服务端组件中获取当前请求的路径
如果希望在 Next.js 的服务端组件（Server Component）中获取当前请求的路径（pathname），但又不想使用 API 路由或客户端组件（Client Component），可以通过使用 Middleware 来实现。使用 Middleware 获取当前路径可以在 Next.js 的 Middleware 中拦截请求，并将当前路径添加到请求头中，然后在服务端组件中读取该请求头，从而获取当前路径。1. 创建 Middleware在项目的根目录下创建一个 middleware.ts 文件：// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const requestHeaders = new Headers(request.headers); requestHeaders.set('x-current-path', request.nextUrl.pathname); return NextResponse.next({ request: { headers: requestHeaders, }, }); } export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };上述 Middleware 会将当前请求的路径添加到请求头中的 x-current-path 字段。(Medium)2. 在服务端组件中读取路径在服务端组件中，可以使用 headers() 方法读取请求头，从而获取当前路径：(propelauth.com)import { headers } from 'next/headers'; export default async function Getsuffix(layer : number):Promise<string>{ const headerList = await headers(); const pathname = headerList.get('x-current-path') || '/'; // 解析路径后缀 const segments = pathname.split('/'); const titleSuffix = segments[layer] || ''; return ( titleSuffix ); }
在构建中大型的 Next.js 应用时，我们往往需要在请求进入应用之前做一些预处理，比如用户认证、国际化设置、重定向等。Next.js 从 12 版本开始支持 Middleware，它运行于边缘节点，可以在请求到达页面组件之前拦截和处理请求。本文将逐步讲解如何在 Next.js 中实现多个中间件的逻辑，并且保持代码的清晰与模块化。一、Middleware 的基础概念在 Next.js 中，你只需要在项目根目录创建一个名为 middleware.ts 或 middleware.js 的文件即可启用 Middleware。这个文件会自动被 Next.js 识别，不需要额外配置。一个最基础的中间件看起来如下：// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { return NextResponse.next(); }该中间件会对所有请求生效，并简单地允许请求继续往下走。二、实现多个中间件逻辑的需求假设我们现在有两个独立的功能需要处理：authMiddleware: 用于判断用户是否登录，未登录则重定向到 /logini18nMiddleware: 用于设置用户语言偏好我们希望它们都在每个请求前依次执行。如何做到这点呢？三、模块化中间件逻辑为了保持代码整洁，我们将每个功能模块拆分到单独文件中：middlewares/authMiddleware.tsimport { NextRequest, NextResponse } from 'next/server'; export function authMiddleware(request: NextRequest): NextResponse | void { const isAuthenticated = true; // 示例逻辑 if (!isAuthenticated) { return NextResponse.redirect(new URL('/login', request.url)); } }middlewares/i18nMiddleware.tsimport { NextRequest, NextResponse } from 'next/server'; export function i18nMiddleware(request: NextRequest): NextResponse | void { const locale = request.headers.get('accept-language')?.split(',')[0] || 'en'; request.headers.set('x-locale', locale); }四、在 middleware.ts 中组合这些中间件// middleware.ts import { NextRequest, NextResponse } from 'next/server'; import { authMiddleware } from './middlewares/authMiddleware'; import { i18nMiddleware } from './middlewares/i18nMiddleware'; export function middleware(request: NextRequest): NextResponse { const authResponse = authMiddleware(request); if (authResponse) return authResponse; const i18nResponse = i18nMiddleware(request); if (i18nResponse) return i18nResponse; return NextResponse.next(); } export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };我们依次执行每一个中间件函数：如果某个中间件返回了 NextResponse，表示它希望拦截或终止请求流程，例如重定向；如果返回 undefined，表示它只是修改了请求状态或头部，不影响流程，继续执行下一个。这种模式非常直观，也方便调试和扩展。五、为什么使用 return 会中止后续中间件？很多开发者初看可能会疑惑："既然我要执行多个中间件，为什么用 return 会跳过后面的逻辑？"这是因为我们在实现“链式中间件”的处理逻辑——每个中间件有权决定是否终止请求流程。举例来说，用户未认证时我们希望立即重定向到 /login，那么其他逻辑（比如设置语言、加载用户数据）就不需要再执行。这种设计思路与很多后端框架如 Express.js 的中间件模型类似。六、总结Next.js 的 Middleware 是一个强大且灵活的工具，允许你在请求生命周期的早期阶段拦截和处理逻辑。你只需一个 middleware.ts 文件将每个中间件功能模块化，方便复用与测试在主中间件函数中按顺序调用它们，并根据是否返回 NextResponse 控制流程这样的设计既保持了结构的清晰，又便于日后扩展。如果你在构建应用时需要实现用户权限、国际化、AB 测试等功能，Middleware 是一个值得深入使用的机制。希望本文对你理解和使用 Next.js Middleware 有所帮助！
使用 Cookies 存储和获取 Access Token 和 Refresh Token 是一种相对安全的做法，尤其是当你设置了 HttpOnly 标志时，这样可以防止 JavaScript 访问这些 Cookies，从而降低 XSS 攻击的风险。以下是如何在 React 中存储和获取 Cookies 的示例。1. 安装 js-cookie 库为了更方便地处理 Cookies，可以使用 js-cookie 库。首先，需要安装它：npm install js-cookie2. 示例代码下面是如何使用 js-cookie 进行存储和获取 Cookies 的示例。存储令牌当你从登录 API 获取 Access Token 和 Refresh Token 后，可以使用 Cookies.set 来存储它们。import Cookies from 'js-cookie'; // 假设你在登录成功时得到了这两个令牌 const accessToken = 'your_access_token'; const refreshToken = 'your_refresh_token'; // 存储 Cookies Cookies.set('accessToken', accessToken, { expires: 7, secure: true, sameSite: 'Strict' }); // 7天过期 Cookies.set('refreshToken', refreshToken, { expires: 30, secure: true, sameSite: 'Strict' }); // 30天过期在上面的代码中，expires 参数设置了 Cookies 的过期时间，secure 参数表示只有在 HTTPS 协议下才能发送 Cookies，sameSite 限制了 Cookies 在一定情况下的使用。获取令牌在需要访问令牌时，可以通过 Cookies.get 进行获取。import Cookies from 'js-cookie'; // 获取令牌 const accessToken = Cookies.get('accessToken'); const refreshToken = Cookies.get('refreshToken'); // 使用令牌 if (accessToken) { console.log('Access Token:', accessToken); } if (refreshToken) { console.log('Refresh Token:', refreshToken); }3. 应用 Cookies 的情况当你在发起API请求时，需要在请求头中附带 Access Token，可以通过上述方式获取它：import axios from 'axios'; import Cookies from 'js-cookie'; // 创建一个 Axios 实例 const api = axios.create({ baseURL: '<http://your-api-endpoint.com>', }); // 添加请求拦截器 api.interceptors.request.use( config => { const token = Cookies.get('accessToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } ); // 示例 API 调用 const fetchData = async () => { try { const response = await api.get('/protected-resource'); console.log(response.data); } catch (error) { console.error(error); } }; // 调用 fetchData fetchData();4. 注意事项HttpOnly Cookies：如果你将 Cookies 设置为 HttpOnly，JavaScript 将无法访问这些 Cookies，因此你需要在服务器端设置 Cookies，并通过 set-cookie 响应头发送给客户端。Cross-Origin Resource Sharing (CORS)：如果你的 API 在不同的域上，要确保 CORS 设置正确，以允许 cookies 传递。Secure Flag：在生产环境中部署时，请确保你的应用使用 HTTPS，并且在存储 Cookies 时设置 secure 标志，使 Cookies 只能通过安全的连接传输。通过这种方式，可以安全地存储和获取 Access Token 和 Refresh Token, 提高应用的安全性。
在 React Bootstrap 中，span、order 和 offset 这三个参数用于定义网格布局中的列（Col）的行为：span: 这个参数指定了列应该占用的模板列的数量。例如，span: 6 意味着该列将在一个12列的网格中占用6列。这样可以灵活地控制列的宽度和布局。order: 这个参数用于控制内容的视觉顺序。通过设置该参数，可以改变列在页面上显示的顺序，而不仅仅是它们在代码中的顺序。例如，order: 1 和 order: 2 可以改变两个列的排列顺序，即使它们在代码中声明的顺序是相反的。offset: 这个参数用于生成列的偏移量，即在某一列前留出的空白列数。例如，offset: 2 将在该列前面留出2列的空白，允许创建更加灵活的布局。
useLocation 用法:useLocation 提供当前路由的完整路径（例如：/admin/create）。我们通过location.pathname.split('/')将路径拆分，将第一级子路径用于动态后缀更新。例：import React from 'react'; import {useLocation} from 'react-router-dom'; export default function App () { const location = useLocation(); // 获取当前路径 let titleSuffix = ""; // 初始化动态后缀为空 // 动态解析路径后缀，只取整个路径中的第一级 if (location.pathname !== "/") { titleSuffix = location.pathname.split('/')[1]; } return titleSuffix }
安装 Axios首先，在项目下安装 Axios 依赖：npm install axios或者：yarn add axios在组件中直接调用 Axios在 React 的函数组件中，你可以直接使用 Axios 进行数据请求。通常我们会结合 React 的 useState 和 useEffect 实现数据的获取和管理，例如：import React, { useState, useEffect } from 'react'; import axios from 'axios'; const MyComponent = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { axios.get('/api/data') .then(response => { setData(response.data); setLoading(false); }) .catch(err => { setError(err); setLoading(false); }); }, []); if (loading) return 加载中... ; if (error) return 发生错误... ; return ( 数据展示 {JSON.stringify(data, null, 2)} ); }; export default MyComponent;这种方式简单直观，适用于单个组件内部的数据请求需求。封装 Axios 实例为了在整个项目中统一管理和复用 Axios 的配置（如基础 URL、超时配置以及请求与响应拦截器），可以创建一个 Axios 实例。例如：// axiosInstance.js import axios from 'axios'; const axiosInstance = axios.create({ baseURL: '<https://api.example.com>', timeout: 5000, }); // 请求拦截器：如添加认证 token axiosInstance.interceptors.request.use( config => { // Modify config before request is sent, e.g.,添加token // config.headers.Authorization = 'Bearer yourToken'; return config; }, error => Promise.reject(error) ); // 响应拦截器：统一处理响应 axiosInstance.interceptors.response.use( response => response, error => Promise.reject(error) ); export default axiosInstance;然后在你的组件中引入这个实例，而不是直接使用 Axios，通过这种方式可以确保全局请求配置的一致性和便于维护。结合 React Context 和 Provider如果你的应用中有很多组件都需要发起请求，并且希望在统一配置（例如统一注册请求拦截器）后进行调用，可以使用 React 的 Context 来组织 Axios 实例。通过 Provider 将配置好的 Axios 实例传递给整个组件树，以便在各个组件中通过 useContext 取得该实例，这也是符合 React Hooks 思想的一种方式。例如：// AxiosContext.js import React, { createContext } from 'react'; import axiosInstance from './axiosInstance'; export const AxiosContext = createContext(axiosInstance); export const AxiosProvider = ({ children }) => { return ( <AxiosContext.Provider value={axiosInstance}> {children} </AxiosContext.Provider> ); };然后在组件中使用：import React, { useContext, useEffect, useState } from 'react'; import { AxiosContext } from './AxiosContext'; const MyComponent = () => { const axios = useContext(AxiosContext); const [data, setData] = useState(null); useEffect(() => { axios.get('/api/data').then(response => { setData(response.data); }); }, [axios]); return {data ? JSON.stringify(data) : '加载中...'} ; }; export default MyComponent;这种 Provider 模式可以有效地将拦截器和其他配置注册到整个应用中，方便管理和使用。
什么是异步操作？异步操作（Asynchronous Operation）是指程序在执行某个操作时，不需要等待该操作完成就能继续执行后续代码的编程模式。与同步操作（必须等待操作完成才能继续）不同，异步操作允许程序在等待某些耗时操作（如I/O、网络请求等）完成的同时继续执行其他任务。异步操作的核心特点非阻塞：主线程不会被阻塞，可以继续执行其他任务延迟处理：操作结果将在未来某个时间点可用回调机制：通常通过回调函数、Promise或async/await来处理结果提高效率：特别适合I/O密集型操作，能更好地利用系统资源同步 vs 异步同步操作示例（阻塞式）// 同步读取文件（Node.js） const fs = require('fs'); const data = fs.readFileSync('file.txt'); // 程序会停在这里直到文件读取完成 console.log(data); console.log('这行代码会在文件读取完成后执行');异步操作示例（非阻塞式）// 异步读取文件（Node.js） const fs = require('fs'); fs.readFile('file.txt', (err, data) => { // 这个回调函数会在文件读取完成后执行 if (err) throw err; console.log(data); }); console.log('这行代码会立即执行，不必等待文件读取完成');异步操作的常见场景网络请求：fetch('<https://api.example.com/data>') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); console.log('这个日志会立即显示，不必等待请求完成');定时操作：setTimeout(() => { console.log('这条消息将在2秒后显示'); }, 2000); console.log('这条消息会立即显示');数据库操作：// 伪代码示例 database.query('SELECT * FROM users', (err, results) => { if (err) throw err; console.log(results); }); console.log('查询已发起，继续执行其他代码');用户交互：document.getElementById('button').addEventListener('click', () => { console.log('按钮被点击了'); }); console.log('已设置点击监听器，继续执行其他代码');异步编程的实现方式回调函数（最基础的方式）：function asyncOperation(callback) { setTimeout(() => { callback('操作完成'); }, 1000); } asyncOperation((message) => { console.log(message); // 1秒后输出"操作完成" });Promise（ES6引入）：function asyncOperation() { return new Promise((resolve) => { setTimeout(() => { resolve('操作完成'); }, 1000); }); } asyncOperation() .then(message => console.log(message));async/await（ES2017引入，语法糖）：async function doWork() { const message = await asyncOperation(); console.log(message); } doWork();为什么需要异步操作？性能优化：避免阻塞主线程，特别是在单线程环境（如浏览器中的JavaScript）用户体验：保持UI响应流畅，避免"卡死"现象资源利用：在等待I/O操作（如磁盘读写、网络请求）时，CPU可以处理其他任务可扩展性：适合处理高并发场景（如服务器同时处理多个请求）异步操作的挑战代码复杂性：回调嵌套可能导致"回调地狱"错误处理：异步错误不像同步错误那样可以通过try-catch直接捕获调试困难：执行流程不如同步代码直观状态管理：多个异步操作之间的协调可能变得复杂实际应用示例多个异步操作并行执行// 使用Promise.all const fetchUser = fetch('/user'); const fetchPosts = fetch('/posts'); Promise.all([fetchUser, fetchPosts]) .then(responses => Promise.all(responses.map(r => r.json()))) .then(([user, posts]) => { console.log('用户数据:', user); console.log('帖子数据:', posts); });异步操作串行执行// 使用async/await async function getData() { const user = await fetch('/user').then(r => r.json()); const posts = await fetch('/posts').then(r => r.json()); console.log('用户数据:', user); console.log('帖子数据:', posts); }异步操作是现代编程中不可或缺的概念，特别是在Web开发、Node.js、移动应用开发等领域。理解异步编程是成为高效开发者的关键一步。
使用 white-space 的 CSS 样式可以用 CSS 的 white-space 属性来实现换行符的显示。具体代码：export default function Display({ content }) { return ( {content} ); }解释：white-space: pre-wrap 是一种 CSS 样式，能够保留换行符和空格，并根据需要自动换行。pre-wrap 的效果如下：保留输入中的换行符。允许长内容自动换行（减少水平滚动条）。例如：输入的内容 "12312\\n123132" 会渲染为：12312 123132
什么是回调函数？回调函数是一种编程模式，指的是将一个函数作为参数传递给另一个函数，并在特定条件或事件发生时由后者调用前者。回调函数允许异步操作和事件驱动的编程。简单来说：回调函数是你定义好但由别人（通常是系统或其他函数）在适当时候调用的函数。回调函数的主要特点函数作为参数：回调函数通常作为参数传递给另一个函数延迟执行：回调函数不会立即执行，而是在特定条件满足时执行异步处理：常用于处理异步操作的结果解耦：将调用者与被调用者分离，提高代码灵活性回调函数的例子1. JavaScript中的简单回调// 定义一个回调函数 function greeting(name) { console.log('Hello ' + name); } // 定义一个接收回调的函数 function processUserInput(callback) { const name = prompt('Please enter your name.'); callback(name); // 在适当时候调用回调 } // 使用回调 processUserInput(greeting); 2. Node.js中的文件读取（异步I/O）const fs = require('fs'); // 回调函数 function fileReadCallback(err, data) { if (err) { console.error('Error reading file:', err); return; } console.log('File content:', data); } // 异步读取文件，完成后调用回调 fs.readFile('example.txt', 'utf8', fileReadCallback); 3. 事件监听（浏览器DOM事件）// 回调函数 function handleClick(event) { console.log('Button clicked!', event); } // 注册回调 document.getElementById('myButton').addEventListener('click', handleClick); 4. 排序中的比较回调const numbers = [3, 1, 4, 1, 5, 9, 2, 6]; // 使用回调函数定义排序规则 numbers.sort(function(a, b) { return a - b; // 升序排序 }); console.log(numbers); // [1, 1, 2, 3, 4, 5, 6, 9] 回调函数的优缺点优点：实现异步编程提高代码复用性解耦调用者和被调用者适用于事件驱动编程缺点：回调地狱（Callback Hell）：多层嵌套回调使代码难以阅读和维护错误处理复杂：需要在每个回调中单独处理错误控制流不直观回调地狱示例// 多层嵌套的回调，难以阅读和维护 doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result:', finalResult); }, failureCallback); }, failureCallback); }, failureCallback); 现代JavaScript通常使用Promise或async/await来解决回调地狱问题。回调函数是编程中非常重要的概念，特别是在JavaScript和Node.js中广泛使用。理解回调函数是掌握异步编程的基础。
安装在vue ui中的插件处安装使用在 store 文件夹下面的 index.js 处import { createStore } from 'vuex' export default createStore({ state: { }, getters: { }, mutations: { }, actions: { }, modules: { } })state 存放的是变量， getters 存放的是需要由 state 中的变量动态计算生成的变量， mutations 存放的是直接对 state 中变量进行修改的函数， actions 存放的是对 state 的各种更新方式（不能对state进行直接修改）， modules 可以将 state 进行分割，避免代码过长。在标签中使用变量示例： <router-link class="nav-link" :to="{name: 'login', params: {}}">登录</router-link> <router-link class="nav-link" :to="{name: 'register', params: {}}">注册</router-link> {{ $store.state.user.username }} 使用 $store 访问store，user是一个module，所以通过这样的方式来访问user中的is_login变量在 script 中使用示例：import { useStore } from 'vuex'; export default { name: 'UserListView', components: { }, setup() { const store = useStore(); console.log(store.state.user.username); } }在外部使用 actions 中的函数需要用到 useStore 这个组件例如：setup() { const store = useStore(); const login = () => { store.dispatch("login", { username: username.value, password: password.value, succuss() { console.log("success"); }, error() { console.log("error"); } }) } return { login } }这里用 store 来接收 useStore() 返回的对象，使用 store.dispatch 来调用 login 函数， store.dispatch 传了两个参数，第一个是函数名，第二个是数据。user.js:import $ from 'jquery'; const ModuleUser = { state: { id: "", username: "", photo: "", followerCount: 0, }, getters: { }, mutations: { }, actions: { login(context, data) { console.log(data); $.ajax({ url: "xxx", type: "POST", data: { username: data.username, password: data.password, }, success(resp) { console.log(resp) } }) } }, modules: { } }; export default ModuleUser;actions中的login函数两个参数，第一个是上下文context，里面有很多自带的api，第二个是数据，可以通过外部使用这个函数时传入。index.js:import { createStore } from 'vuex' import ModuleUser from './user' export default createStore({ state: { }, getters: { }, mutations: { }, actions: { }, modules: { user: ModuleUser, } })这里user单独写一个module独立出去，只要在index.js中引入并在modules中添加这个变量即可。mutations 中的函数需要传两个参数一个是 state 用来访问state中的变量，另外一个是自定义的参数。mutations: { updateUser(state, user) { state.id = user.id; state.username = user.username; state.photot = user.photo; state.followerCount = user.followerCount; state.access = user.access; state.refresh = user.refresh; state.is_login = user.is_login; }, },在 actions 中使用 mutations 中的函数示例：context.commit("updateUser", { ...resp, access: access, refresh: refresh, is_login: true, })使用 context 中的commit函数，传两个参数，一个是函数名称，另外一个是自定义参数。
在form第一个便签里加上 @submit 属性，后面跟上执行的函数，例如： Username Password {{ errorMessage }} Submit 如果需要禁用掉默认事件可以用 @submit.prevent 例如： Username Password {{ errorMessage }} Submit
安装npm i jquery引入import $ from 'jquery';使用$.ajax({ url: url, type: "xxx", data: { }, dataType: "json", success: function (resp) { }, });注： xxx 填方法类型，比如 get，post，delete其中的 success: function (resp) 可以直接写成 success(resp) 比如：$.ajax({ url: 'url', type: 'get', success(resp) { console.log(resp); } })需要验证 jwt 的get方法示例：$.ajax({ url: "xxx", type: "get", data: { user_id: access_obj.user_id, }, headers: { 'Authorization': "Bearer " + access, }, success(resp) { console.log(resp); } })headers: { 'Authorization': "Bearer " + access, },添加了一个headers属性，里面需要加一个 Authorization 属性，表示授权，access就是访问令牌jwt。其中 "Bearer" 看具体的api可能会有不同的定义。
使用一个包 jwt-decode 安装：npm i jwt-decode引入：import { jwtDecode } from 'jwt-decode';使用：const access_obj = jwtDecode(access);返回的一个对象，可以打印出来看下属性。
事件函数的编写需要在 export default{} 中的 setup() 函数中例如：setup(props) { let fullname = computed(() => props.user.firstName + ' ' + props.user.lastName); const follow = () => { console.log("follow"); } const unFollow = () => { console.log("unfollow"); } return { fullname, follow, unFollow, } }注意，写的函数要return出去才可以使用使用样例： follow un-follow 使用 v-on:click 绑定事件函数，其中 v-on: 可以简写为 @ 例如： follow un-follow 触发父组件的函数：使用 context.emit() ，例如：setup(props, context) { let fullName = computed(() => props.user.lastName + ' ' + props.user.firstName); const follow = () => { context.emit('follow'); }; const unfollow = () => { context.emit("unfollow"); } return { fullName, follow, unfollow, } }和上一个 setup 相比，这里需要传一个上下文 context 然后使用 context.emit 来绑定注意：这里的props和context是由位置决定的，不是由名字决定的，所以假如在没有用到props时，想绑定事件，也依旧要传一个props。父组件中template：<UserProfileInfo @follow="handlefollow" @unFollow="handleunFollow" :user="user"/>context.emit 绑定的事件名称就是 @ 后面的名称，所以只要一一对应就行，也可以不和函数名称相同。 = 后面的就是触发的函数。这些被触发的函数同样写在 setup 中，也同样需要被return出去，例如：setup() { const user = reactive({ id: 1, username: "heky", lastName: "he", firstName: "ky", followerCount: 999, is_followed: false, }); const handlefollow = () => { console.log("u"); }; const handleunFollow = () => { console.log("u") } return { user, handlefollow, unFollow, } }假如有传递参数，就在 context.emit() 再多传一个参数例如：const post_a_post = () => { context.emit('post_a_post', content.value); content.value = ""; }而对应的触发事件需要加一个传入的形参,例如：const post_a_post = (content) => { posts.count ++; posts.posts.unshift({ id: post_a_post.count, user_id: 1, content: content, }) }
setup(props, context)：初始化变量、函数ref定义变量，可以用.value属性重新赋值reactive定义对象，不可重新赋值，当他的值发生改变（一般是对象中的某一个属性改变）会将有使用这个对象的组件重新渲染一遍。props存储父组件传递过来的数据context.emit()：触发父组件绑定的函数vue 中通过 setup() 函数来初始化变量。setup函数写在 export default {} 中，可以用 setup = () => {} 即箭头函数的写法，也可以直接写作 setup() 例子：export default { name: 'UserProfileView', components: { ContentBase, UserProfileInfo, UserProfilePosts }, setup() { const user = reactive({ id: 1, username: "heky", lastName: "he", firstName: "ky", followerCount: false, }); return { user, } } }这个例子中定义了一个user的变量对象，里面放着5各变量，之后将要用到的变量对象 user return出去之后可以在子组件的标签中用 v-bind: 来绑定例如：<UserProfileInfo v-bind:user="user"/>v-bind: 也可以省略为 : ，例如：<UserProfileInfo :user="user"/>等号后面的冒号可以传入一个表达式，这里将 user 传入之后要在组件中使用，需要将用到的组件放到 export default{} 中 props 中 props 是一个对象。例如：export default { name: "UserProfileInfo", props: { user: { type: Object, required: true, }, } }其中user也是作为一个对象来写，有两个值，一个是类型，另外一个是是否需要。这样写完之后就可以在 template 中用了，使用时用两个大括号括起来 {{}}例如： {{ user.username }} fans: {{ user.followerCount }} 如果想作为属性的值进行使用，可以在对应的属性前面加上 : ，例如： 这里的 user.photo 是一个变量而不是当作一个字符串，作用于src这个属性，所以src前面加上一个冒号（ : 是 v-bind: 的简写）如果需要对变量进行计算然后实现动态变化，需要在组件中设置一个新的变量，以及用到vue中的 computed 函数例如：import { computed } from 'vue'; export default { name: "UserProfileInfo", props: { user: { type: Object, required: true, }, }, setup(props) { let fullname = computed(() => props.user.firstName + ' ' + props.user.lastName); return { fullname } } }这里setup函数传入一个变量props表示当前组件的props，然后在里面定义了一个新的变量 fullname 调用 computed 函数动态计算变量的值，注意 computed 函数接收一个函数，之后也是一样将要用到的变量return出去就可以了。在template中使用： {{ fullname }} 使用 v-model 属性获取html标签内的内容示例： 变量对象可以用 reactive ，单个变量可以使用 ref 。示例中使用 v-model 将textarea中的内容绑定到content这个变量中，之后要取得这个值可以用 content.value 来取。之后如果要修改这个值也可以直接对 content.value 进行赋值。注：其实用 reactive 也是可以赋值的，不过是对对象中的属性赋值，一般如果变量是对象就用 reactive 否则可能就用 ref
安装安装 @vue/cli-plugin-router 插件使用注册路由在 router 文件夹中的 index.js 中将路由添加到 routes 数组中例如：const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/login', name: 'login', component: LoginView }, { path: '/register', name: 'register', component: RegisterView }, { path: '/404', name: '404', component: NotFoundView }, ]注意：有使用到的组件要import进index.js中重定向示例：{ path: '/:catchAll(.*)', redirect: '/404/', }:catchAll() 是关键字，括号里面接收正则表达式，这里的 . 表示任意字符， 表示任意多个 .* ，redirect 表示重定向的链接。使用参数的示例：{ path: '/userprofile/:userId/', name: 'userprofile', component: UserProflieView }这里 userId 是一个变量，在要当作变量的路由前面加上 : 即可。在组件中使用路由参数，需要使用 useRoute 组件示例：import { useRoute } from 'vue-router'; export default { name: 'UserProfileView', components: { ContentBase, }, setup() { const route = useRoute(); console.log(route.params.userId); }对应的跳转：<router-link class="nav-link" :to="{name: 'userprofile', params: {userId : 2,}}">用户动态</router-link>需要在params对象中添加对应的参数。跳转<router-link class="navbar-brand" :to="{name: 'home', params: {}}">Myspace</router-link>router-link 是vue中的一个组件，使用 : + 名称绑定属性，这里绑定一个to属性表示要跳转的组件，里面是一个对象，对象一个key是name，表示组件的名称在函数中使用跳转示例：import router from '@/router/index' success() { router.push({name: 'userlist'}); },使用 router 的 push api 里面传的参数是一个对象，和 router-link 中 :to 是一样的。组件中路由的使用使用 <router-view /> 表示这个标签通过链接的不同渲染不同的组件。一般放在 App.vue 中， 例如： <NavBar /> <router-view /> 这里 NavBar 一直保持不变， router-view 根据链接的不同渲染不同页面。一般链接中可能会带有参数，例如 /userprofile/65 、 /userprofile/32 ，如果想要严格判断完整的路径可以给 router-view 加一个属性 :key="$route.fullPath" 例如： <NavBar /> <router-view :key="$route.fullPath"/> 如果不加的话，/userprofile/65 、 /userprofile/32 这两个页面可能显示一样的内容。（比如先访问 /userprofile/65 后访问 /userprofile/32 ，可能页面还是 /userprofile/65 的内容而不是 /userprofile/32 的内容，原因就是只匹配 userprofile 这个路径，而没有匹配后面的参数 ）。
每一个组件由三个部分组成分别是 template script style其中style部分可以在头标签加上一个 scoped 字段，之后者部分的style就不会影响到其他组件script部分需要export一个对象，这个对象有两个key，一个是name表示当前组件的名字，另外一个是components表示当前组件用到的组件。有用到的组件也同时需要在script标签中用import关键字来引入。引入之后的组件可以在template中通过标签的形式来使用。例如：App.vue <NavBar /> <router-view/> 假如在一个组件内使用其他组件，且那个被使用的组件的标签中有其他的内容，在那个被使用的组件内可以使用 <slot></slot> 标签将传入的内容全部渲染出来比如：ContentBase.vue <slot></slot>
安装在vue ui 中项目依赖处搜索 bootstrap 然后安装同时也需要安装 @popperjs/core使用在 main.js 中加入import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/js/bootstrap';
安装jquerynpm i jquery使用引入import $ from 'jquery';GET方法：$.ajax({ url: url, type: "GET", data: { }, dataType: "json", success: function (resp) { }, });POST方法：$.ajax({ url: url, type: "POST", data: { }, dataType: "json", success: function (resp) { }, });
安装安装Route组件：npm i react-router-domRoute组件介绍BrowserRouter：所有需要路由的组件，都要包裹在BrowserRouter组件内Link：跳转到某个链接，to属性表示跳转到的链接Routes：类似于C++中的switch，匹配第一个路径Route：路由，path属性表示路径，element属性表示路由到的内容useParams 和 useSearchParams 获取路径参数Navigate：和Route组件组合使用来实现重定向使用BrowserRouter引入import { BrowserRouter } from 'react-router-dom';使用BrowserRouter在root.render()中用<BrowserRouter></BrowserRouter>将App组件（或者自己定义的其他有使用路由的组件）括起来。例如<BrowserRouter> <App /> </BrowserRouter>Link引入import { Link } from 'react-router-dom';使用将页面内部跳转所用到的 改为 ，其他保持不变Routes和Route引入import {Routes, Route} from 'react-router-dom';使用先写一个<Routes></Routes>标签，然后在里面写<Route />标签其中<Route /> 标签中有两个参数一个是path另外一个是element，path中放匹配的路径，element中放要渲染的标签例如<Routes> <Route path='/' element={<Home />}/> <Route path='like' element={<Like />}/> <Route path='about' element={<About />}/> <Route path='issues' element={<Issues />}/> </Routes>element中不仅可以放组件的标签，正常的html标签也可以放例如：<Routes> <Route path='/' element={ Hello world }/> </Routes>嵌套路由<Route path="/web" element={<Web />}> <Route index path="a" element={ a } /> <Route index path="b" element={ b } /> <Route index path="c" element={ c } /> </Route>注意：需要在父组件中添加<Outlet />组件，用来填充子组件的内容。（即<Outlet />会根据路由的不同动态的渲染不同的子标签，假如当前的路由为/web/a 则<Outlet />等效于 a ）useParams 和 useSearchParamsURL中传递参数解析URL：<Route path="/linux/:chapter_id/:section_id/" element={<Linux />} />获取参数，类组件写法：import React, { Component } from 'react'; import { useParams } from 'react-router-dom'; class Linux extends Component { state = { } render() { console.log(this.props.params); return Linux ; } } export default (props) => ( <Linux {...props} params={useParams()} /> )函数组件写法：import React, { Component } from 'react'; import { useParams } from 'react-router-dom'; const Linux = () => { console.log(useParams()); return ( Linux ); } export default Linux;Search Params传递参数类组件写法：import React, { Component } from 'react'; import { useSearchParams } from 'react-router-dom'; class Django extends Component { state = { searchParams: this.props.params[0], // 获取某个参数 setSearchParams: this.props.params[1], // 设置链接中的参数，然后重新渲染当前页面 } handleClick = () => { this.state.setSearchParams({ name: "abc", age: 20, }) } render() { console.log(this.state.searchParams.get('age')); return Django ; } } export default (props) => ( <Django {...props} params={useSearchParams()} /> );函数组件写法：import React, { Component } from 'react'; import { useSearchParams } from 'react-router-dom'; const Django = () => { let [searchParams, setSearchParams] = useSearchParams(); console.log(searchParams.get('age')); return ( Django ); } export default Django;Navigate引入import {Routes, Route, Navigate} from 'react-router-dom';使用<Route path="*" element={ <Navigate replace to="/404" /> } />
redux将所有数据存储到树中，且树是唯一的。Redux基本概念store：存储树结构。state：维护的数据，一般维护成树的结构。reducer：对state进行更新的函数，每个state绑定一个reducer。传入两个参数：当前state和action，返回新state。action：一个普通对象，存储reducer的传入参数，一般描述对state的更新类型。dispatch：传入一个参数action，对整棵state树操作一遍。React-Redux基本概念Provider组件：用来包裹整个项目，其store属性用来存储redux的store对象。connect(mapStateToProps, mapDispatchToProps)函数：用来将store与组件关联起来。mapStateToProps：每次store中的状态更新后调用一次，用来更新组件中的值。mapDispatchToProps：组件创建时调用一次，用来将store的dispatch函数传入组件。安装npm i redux react-redux @reduxjs/toolkit使用导入包在定义store的jsx中导入，当然如果是写在一个文件中就没关系import { configureStore } from "@reduxjs/toolkit";在main.jsx/index.jsx中导入Providerimport { Provider } from 'react-redux';在每一个需要使用到redux全局变量的组件内部导入connectimport { connect } from 'react-redux';定义actions可以单独写一个文件，中放一个对象的全局变量来存actions定义reducerreducer是一个函数，需要传入两个参数，一个是state，另外一个是actionaction是一个对象，state是当前存的数据，需要赋一个初值例如：const reducer = (state="0", action) => { switch(action.type) { } };state同时可以维护一个对象，此时reducer可以这样写const reducer = (state={ currentOperand: "", lastOperand: "", operation: "", }, action) => { switch(action.type) { default: return state; } };定义storeconst store = configureStore({ reducer: reducer });store 和两个reducer的名称可以任意，自己知道就行。这里reducer和reducer重名，可以只写一个reducer来表示reducer: reducer例如：const store = configureStore({ reducer });使用store在main.jsx/index.jsx中将有使用redux的组件用<Provider></Provider>括起来，同时需要给Provider传一个参数store，就是我们定义的store例如： <Provider store={store}> <BrowserRouter> <App /> </BrowserRouter> </Provider>mapDispatchToProps将dispatch函数绑定到组件上mapDispatchToProps函数定义例如：const mapDispatchToProps = { add_digit: digit => { return { type: ACTIONS.ADD_DIGIT, digit: digit, } } }使用connect组件绑定例如：export default connect(null, mapDispatchToProps)(DigitButton);之后就可以通过 this.props.add_digit 来使用add_digit函数了mapStateToProps将state数据绑定到组件上mapStateToProps函数定义例如：const mapStateToProps = (state, props) => { return { currentOperand: state.currentOperand, lastOperand: state.lastOperand, operation: state.operation, } }使用connect组件绑定例如export default connect(mapStateToProps)(Calculator);之后就可以通过 this.props. ...来访问state中的数据了
安装npm i bootstrap20250405更新使用reaact-bootstrapnpm install react-bootstrap bootstrap引入在main.jsx中引入import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/js/bootstrap.js';文档https://v5.bootcss.com/docs/getting-started/introduction/https://react-bootstrap.netlify.app/
参考资料： https://stackoverflow.com/questions/79289166/something-is-wrong-with-my-react-installation-node-as-a-whole问题：当使用 create-react-app时出现如下报错npm error code ERESOLVE npm error ERESOLVE unable to resolve dependency tree npm error npm error While resolving: newapp1@0.1.0 npm error Found: react@19.0.0 npm error node_modules/react npm error react@"^19.0.0" from the root project npm error npm error Could not resolve dependency: npm error peer react@"^18.0.0" from @testing-library/react@13.4.0 npm error node_modules/@testing-library/react npm error @testing-library/react@"^13.0.0" from the root project npm error npm error Fix the upstream dependency conflict, or retry npm error this command with --force or --legacy-peer-deps npm error to accept an incorrect (and potentially broken) dependency resolution. npm error npm error npm error For a full report see: npm error C:\Users\chira\AppData\Local\npm-cache\_logs\2024-12-17T19_27_59_239Z-eresolve-report.txt npm error A complete log of this run can be found in: C:\Users\chira\AppData\Local\npm-cache\_logs\2024-12-17T19_27_59_239Z-debug-0.log `npm install --no-audit --save @testing-library/jest-dom@^5.14.1 @testing-library/react@^13.0.0 @testing-library/user-event@^13.2.1 web-vitals@^2.1.0` failed原因可能是：因为 React 上周刚刚更新到 v19，而 testing-library/react 还没有更新他们的依赖项要求。解决：使用 Vite 创建一个新的 React 应用程序。npm create vite@latest运行之后会让我们选择创建哪种模板，选择react之后再选择使用JavaScript即可。
解决浏览器 CORS 报错：Access to Script Blocked by CORS Policy
问题背景在前端开发中，尤其是本地开发时，我们常常直接通过双击 HTML 文件的方式使用浏览器打开网页，路径通常是 file:// 开头的 URL。例如：file:///D:/web_study/20241217/index.html在这种情况下，如果你尝试加载 JavaScript 文件，会遇到以下报错：报错信息Access to script at 'file:///D:/web_study/20241217/index.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-extension, chrome-untrusted, data, http, https, isolated-app. GET file:///D:/web_study/20241217/index.js net::ERR_FAILED报错解析CORS 限制：浏览器出于安全考虑，默认启用了同源策略（CORS）。当使用 file:// 协议时，浏览器无法确定源 (origin)，因此请求被阻止。ERR_FAILED：网络请求失败，是因为 CORS 策略拒绝加载 JavaScript 文件。报错原因现代浏览器的安全机制 CORS（跨域资源共享） 仅支持以下协议：http / httpsdatachrome-extension等等file:// 协议不在允许列表内，因此直接打开 HTML 文件并加载 JavaScript 时会触发错误。解决方案方法一：使用本地 HTTP 服务器（推荐）最根本的解决方法是通过本地 HTTP 服务器运行项目，而不是直接打开 file:// 链接。1. 使用 VSCode 的 Live Server 插件安装 Live Server 插件：打开 VSCode，进入插件市场，搜索 "Live Server" 并安装。启动 Live Server：在项目目录下右键点击 index.html 文件，选择 "Open with Live Server"。浏览器自动打开页面，访问地址：http://localhost:55002. 使用 Python 内置 HTTP 服务器Python 自带一个简单的 HTTP 服务器，可以在本地快速启动：打开命令行终端，进入 HTML 文件所在目录：cd D:/web_study/20241217运行以下命令（Python 3.x）：python -m http.server 8000在浏览器中访问：http://localhost:8000/index.html3. 使用 Node.js 的 http-server 工具如果你安装了 Node.js，可以使用 http-server 包：安装 http-server：npm install -g http-server启动服务器：http-server浏览器访问：http://localhost:8080方法二：调整 Chrome 浏览器启动参数（临时解决，不推荐）这种方法临时禁用浏览器的安全策略，仅适用于测试环境。不要在生产环境使用！在 Chrome 浏览器启动时添加以下参数：chrome.exe --allow-file-access-from-files --disable-web-security然后重新打开 HTML 文件。⚠️ 警告：此方法存在安全风险，绕过了浏览器的安全机制，仅用于临时测试。总结出现 "Access to script at ... has been blocked by CORS policy" 的错误，通常是因为浏览器默认禁止了通过 file:// 协议加载外部资源。解决这个问题最好的方法是使用本地 HTTP 服务器运行项目，比如：VSCode 的 Live Server 插件Python 内置服务器Node.js 的 http-server 工具通过这些工具，你可以在本地以 http:// 协议访问文件，解决 CORS 报错问题，提升开发效率。推荐做法启动一个本地 HTTP 服务器，这是现代前端开发的标准做法，既安全又高效。希望这篇文章能帮你解决开发中遇到的 CORS 问题！😊 如果有其他问题，欢迎留言交流。