前后端分离的 API 设计:技术深度剖析

在现代 Web 开发中,前后端分离(Frontend-Backend Separation)已成为主流架构模式,前端负责用户界面和交互逻辑,后端专注于数据处理和业务逻辑,而 API(应用程序编程接口)作为两者之间的桥梁,决定了系统的通信效率、可维护性和可扩展性。设计一套高效、清晰、可扩展的 API 是前后端分离项目的核心任务。


1. 前后端分离的 API 设计基础

1.1 为什么需要前后端分离?

前后端分离通过明确职责分工,解决了传统单体应用(如 JSP、PHP 渲染页面)中的诸多问题:

  • 开发效率:前端和后端可并行开发,减少耦合。
  • 技术栈灵活性:前端可使用 React、Vue,后端可选择 Node.js、Java 等。
  • 可维护性:前后端代码分离,逻辑更清晰。
  • 用户体验:前端通过异步 API 调用,实现动态加载和流畅交互。

API 作为前后端通信的纽带,必须满足以下要求:

  • 清晰性:接口定义直观,易于理解。
  • 一致性:命名、结构和响应格式统一。
  • 可扩展性:支持功能迭代和版本演进。
  • 性能:响应速度快,资源占用低。

1.2 API 设计的核心原则

在前后端分离中,API 设计应遵循以下原则:

  • 以资源为中心:将数据抽象为资源(如用户、订单),通过 URI 标识。
  • 无状态:每个请求包含完整信息,服务器不保存客户端状态。
  • 一致性命名:使用统一的命名规范(如小写、连字符)。
  • 可预测性:响应结构和错误格式可预期。
  • 文档化:提供详细的 API 文档,便于前后端协作。

2. RESTful API 设计

REST(Representational State Transfer)是前后端分离中最常用的 API 设计范式,其核心是将数据建模为资源,通过 HTTP 方法操作。

2.1 资源与 URI 设计

RESTful API 以资源为核心,每个资源通过唯一的 URI 标识。设计 URI 时,应遵循以下规则:

  • 使用名词 :URI 表示资源(如 /users,而不是 /getUsers)。
  • 层级清晰 :通过路径反映资源关系(如 /users/123/orders)。
  • 避免过长:保持 URI 简洁,通常不超过三层。
  • 使用连字符 :如 /user-profiles 而非 /user_profiles

示例:

http 复制代码
GET /users              # 获取用户列表
GET /users/123          # 获取单个用户
POST /users             # 创建用户
PUT /users/123          # 更新用户
DELETE /users/123       # 删除用户

2.2 HTTP 方法

RESTful API 使用标准的 HTTP 方法:

  • GET:查询资源,无副作用。
  • POST:创建资源或触发操作。
  • PUT:更新整个资源(全量更新)。
  • PATCH:部分更新资源。
  • DELETE:删除资源。

示例:

http 复制代码
PATCH /users/123
Content-Type: application/json
{
  "email": "[email protected]"
}

2.3 响应结构

RESTful API 的响应应具有一致的结构,通常包括:

  • data:返回的核心数据。
  • status :请求状态(如 successerror)。
  • message:错误或提示信息。
  • meta:分页或附加信息。

示例响应:

json 复制代码
{
  "status": "success",
  "data": {
    "id": 123,
    "name": "Alice",
    "email": "[email protected]"
  },
  "meta": {
    "total": 100,
    "page": 1,
    "limit": 10
  }
}

错误响应:

json 复制代码
{
  "status": "error",
  "message": "User not found",
  "code": 404
}

2.4 状态码

使用 HTTP 状态码表示请求结果:

  • 200 OK:请求成功。
  • 201 Created:资源创建成功。
  • 204 No Content:请求成功,无返回内容(如 DELETE)。
  • 400 Bad Request:请求参数错误。
  • 401 Unauthorized:未授权。
  • 403 Forbidden:权限不足。
  • 404 Not Found:资源不存在。
  • 500 Internal Server Error:服务器错误。

2.5 实现 RESTful API

以 Node.js 和 Express 为例,创建一个简单的 RESTful API:

bash 复制代码
mkdir rest-api
cd rest-api
npm init -y
npm install express typescript ts-node @types/express @types/node --save-dev

创建 tsconfig.json

json 复制代码
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

创建 src/index.ts

typescript 复制代码
import express, { Request, Response } from 'express';

const app = express();
app.use(express.json());

interface User {
  id: string;
  name: string;
  email: string;
}

let users: User[] = [
  { id: '1', name: 'Alice', email: '[email protected]' },
];

app.get('/users', (req: Request, res: Response) => {
  res.status(200).json({
    status: 'success',
    data: users,
    meta: { total: users.length },
  });
});

app.get('/users/:id', (req: Request, res: Response) => {
  const user = users.find(u => u.id === req.params.id);
  if (!user) {
    return res.status(404).json({ status: 'error', message: 'User not found' });
  }
  res.status(200).json({ status: 'success', data: user });
});

app.post('/users', (req: Request, res: Response) => {
  const { name, email } = req.body;
  if (!name || !email) {
    return res.status(400).json({ status: 'error', message: 'Name and email required' });
  }
  const newUser: User = { id: String(users.length + 1), name, email };
  users.push(newUser);
  res.status(201).json({ status: 'success', data: newUser });
});

app.put('/users/:id', (req: Request, res: Response) => {
  const userIndex = users.findIndex(u => u.id === req.params.id);
  if (userIndex === -1) {
    return res.status(404).json({ status: 'error', message: 'User not found' });
  }
  const { name, email } = req.body;
  users[userIndex] = { id: req.params.id, name, email };
  res.status(200).json({ status: 'success', data: users[userIndex] });
});

app.delete('/users/:id', (req: Request, res: Response) => {
  const userIndex = users.findIndex(u => u.id === req.params.id);
  if (userIndex === -1) {
    return res.status(404).json({ status: 'error', message: 'User not found' });
  }
  users.splice(userIndex, 1);
  res.status(204).send();
});

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

运行:

bash 复制代码
npx ts-node src/index.ts

使用 cURL 测试:

bash 复制代码
curl http://localhost:3000/users
curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d '{"name":"Bob","email":"[email protected]"}'

3. GraphQL API 设计

GraphQL 是一种替代 REST 的查询语言,特别适合前后端分离中复杂数据需求场景。它的核心优势是允许客户端精确指定所需数据,避免过量或不足。

3.1 GraphQL Schema 设计

GraphQL 使用 Schema 定义数据模型和操作,包括:

  • Type:定义数据结构。
  • Query:查询操作。
  • Mutation:变更操作。
  • Subscription:实时更新。

示例 Schema:

graphql 复制代码
type User {
  id: ID!
  name: String!
  email: String
  orders: [Order!]
}

type Order {
  id: ID!
  total: Float!
  createdAt: String!
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

type Mutation {
  createUser(name: String!, email: String): User!
}

3.2 实现 GraphQL API

使用 Apollo Server 实现 GraphQL API:

bash 复制代码
mkdir graphql-api
cd graphql-api
npm init -y
npm install apollo-server graphql typescript ts-node @types/node --save-dev

创建 tsconfig.json(同上)。

创建 src/schema.ts

typescript 复制代码
import { gql } from 'apollo-server';

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String
    orders: [Order!]
  }

  type Order {
    id: ID!
    total: Float!
    createdAt: String!
  }

  type Query {
    user(id: ID!): User
    users: [User!]!
  }

  type Mutation {
    createUser(name: String!, email: String): User!
  }
`;

export default typeDefs;

创建 src/data.ts

typescript 复制代码
export const users = [
  { id: '1', name: 'Alice', email: '[email protected]' },
  { id: '2', name: 'Bob', email: '[email protected]' },
];

export const orders = [
  { id: '1', userId: '1', total: 99.99, createdAt: '2023-01-01' },
  { id: '2', userId: '1', total: 49.99, createdAt: '2023-01-02' },
];

创建 src/resolvers.ts

typescript 复制代码
import { users, orders } from './data';

interface User {
  id: string;
  name: string;
  email?: string;
}

interface Order {
  id: string;
  userId: string;
  total: number;
  createdAt: string;
}

const resolvers = {
  Query: {
    user: (_: any, { id }: { id: string }) => users.find(user => user.id === id),
    users: () => users,
  },
  Mutation: {
    createUser: (_: any, { name, email }: { name: string; email?: string }) => {
      const newUser: User = { id: String(users.length + 1), name, email };
      users.push(newUser);
      return newUser;
    },
  },
  User: {
    orders: (parent: User) => orders.filter(order => order.userId === parent.id),
  },
};

export default resolvers;

创建 src/index.ts

typescript 复制代码
import { ApolloServer } from 'apollo-server';
import typeDefs from './schema';
import resolvers from './resolvers';

const server = new ApolloServer({ typeDefs, resolvers });

server.listen({ port: 4000 }).then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

运行:

bash 复制代码
npx ts-node src/index.ts

访问 http://localhost:4000,使用以下查询:

graphql 复制代码
query {
  user(id: "1") {
    name
    email
    orders {
      id
      total
    }
  }
}

3.3 GraphQL 客户端集成

在前端使用 Apollo Client 调用 GraphQL API:

bash 复制代码
mkdir frontend
cd frontend
npm init -y
npm install @apollo/client graphql react react-dom typescript @types/react @types/react-dom --save-dev

创建 src/client.ts

typescript 复制代码
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:4000',
  cache: new InMemoryCache(),
});

export default client;

创建 src/App.tsx

typescript 复制代码
import React from 'react';
import { useQuery, gql } from '@apollo/client';

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      orders {
        id
        total
      }
    }
  }
`;

const App: React.FC = () => {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: '1' },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
      <ul>
        {data.user.orders.map((order: any) => (
          <li key={order.id}>Order #{order.id}: ${order.total}</li>
        ))}
      </ul>
    </div>
  );
};

export default App;

创建 src/index.tsx

typescript 复制代码
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ApolloProvider } from '@apollo/client';
import App from './App';
import client from './client';

const root = createRoot(document.getElementById('root')!);
root.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);

4. API 版本管理

在前后端分离中,API 演进不可避免,版本管理确保向后兼容。

4.1 URI 版本化

在 URI 中嵌入版本号:

http 复制代码
GET /api/v1/users
GET /api/v2/users

实现:

typescript 复制代码
app.get('/api/v1/users', (req: Request, res: Response) => {
  res.status(200).json({ status: 'success', data: users });
});

app.get('/api/v2/users', (req: Request, res: Response) => {
  const updatedUsers = users.map(u => ({ ...u, version: '2.0' }));
  res.status(200).json({ status: 'success', data: updatedUsers });
});

4.2 请求头版本化

通过 Accept 头指定版本:

http 复制代码
GET /api/users
Accept: application/vnd.example.v1+json

实现:

typescript 复制代码
app.get('/api/users', (req: Request, res: Response) => {
  const accept = req.header('Accept');
  if (accept === 'application/vnd.example.v2+json') {
    const updatedUsers = users.map(u => ({ ...u, version: '2.0' }));
    return res.status(200).json({ status: 'success', data: updatedUsers });
  }
  res.status(200).json({ status: 'success', data: users });
});

4.3 GraphQL 的版本管理

GraphQL 通过 Schema 演进避免显式版本管理:

  • 添加字段:向类型添加新字段,不会破坏现有查询。

  • 弃用字段 :使用 @deprecated 标记:

    graphql 复制代码
    type User {
      id: ID!
      name: String!
      email: String @deprecated(reason: "Use emailAddress instead")
      emailAddress: String
    }

5. 认证与授权

5.1 JWT 认证

JSON Web Token(JWT)是前后端分离中常用的认证方式。

安装依赖:

bash 复制代码
npm install jsonwebtoken @types/jsonwebtoken

实现登录端点:

typescript 复制代码
import jwt from 'jsonwebtoken';

const SECRET = 'your-secret-key';

app.post('/login', (req: Request, res: Response) => {
  const { username, password } = req.body;
  if (username === 'admin' && password === 'password') {
    const token = jwt.sign({ username }, SECRET, { expiresIn: '1h' });
    return res.status(200).json({ status: 'success', token });
  }
  res.status(401).json({ status: 'error', message: 'Invalid credentials' });
});

中间件验证:

typescript 复制代码
const authenticate = (req: Request, res: Response, next: Function) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  if (!token) {
    return res.status(401).json({ status: 'error', message: 'No token provided' });
  }
  try {
    const decoded = jwt.verify(token, SECRET);
    (req as any).user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ status: 'error', message: 'Invalid token' });
  }
};

app.get('/users', authenticate, (req: Request, res: Response) => {
  res.status(200).json({ status: 'success', data: users });
});

前端发送请求:

javascript 复制代码
fetch('http://localhost:3000/users', {
  headers: {
    Authorization: `Bearer ${token}`,
  },
})
  .then(res => res.json())
  .then(data => console.log(data));

5.2 OAuth 2.0

OAuth 2.0 适合第三方授权场景。使用 passport 实现:

bash 复制代码
npm install passport passport-google-oauth20 @types/passport @types/passport-google-oauth20

配置:

typescript 复制代码
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';

passport.use(
  new GoogleStrategy(
    {
      clientID: 'your-client-id',
      clientSecret: 'your-client-secret',
      callbackURL: 'http://localhost:3000/auth/google/callback',
    },
    (accessToken, refreshToken, profile, done) => {
      // 保存用户
      done(null, profile);
    }
  )
);

app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));

app.get(
  '/auth/google/callback',
  passport.authenticate('google', { session: false }),
  (req: Request, res: Response) => {
    const token = jwt.sign({ id: (req.user as any).id }, SECRET);
    res.redirect(`/dashboard?token=${token}`);
  }
);

5.3 GraphQL 认证

在 GraphQL 中,通过 context 传递用户信息:

typescript 复制代码
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const token = req.headers.authorization?.replace('Bearer ', '');
    if (token) {
      const user = jwt.verify(token, SECRET);
      return { user };
    }
    return {};
  },
});

在 Resolver 中验证:

typescript 复制代码
const resolvers = {
  Query: {
    user: (_: any, { id }: { id: string }, { user }: { user: any }) => {
      if (!user) throw new Error('Unauthorized');
      return users.find(u => u.id === id);
    },
  },
};

6. 错误处理

6.1 REST 错误处理

定义全局错误中间件:

typescript 复制代码
app.use((err: Error, req: Request, res: Response, next: Function) => {
  console.error(err.stack);
  res.status(500).json({ status: 'error', message: 'Internal server error' });
});

自定义错误类:

typescript 复制代码
class ApiError extends Error {
  status: number;
  constructor(message: string, status: number) {
    super(message);
    this.status = status;
  }
}

app.get('/users/:id', (req: Request, res: Response, next: Function) => {
  const user = users.find(u => u.id === req.params.id);
  if (!user) {
    return next(new ApiError('User not found', 404));
  }
  res.status(200).json({ status: 'success', data: user });
});

app.use((err: ApiError, req: Request, res: Response, next: Function) => {
  res.status(err.status || 500).json({ status: 'error', message: err.message });
});

6.2 GraphQL 错误处理

GraphQL 使用 errors 字段返回错误:

typescript 复制代码
const resolvers = {
  Query: {
    user: (_: any, { id }: { id: string }) => {
      const user = users.find(u => u.id === id);
      if (!user) {
        throw new Error('User not found');
      }
      return user;
    },
  },
};

自定义错误:

typescript 复制代码
import { ApolloError } from 'apollo-server';

const resolvers = {
  Query: {
    user: (_: any, { id }: { id: string }) => {
      const user = users.find(u => u.id === id);
      if (!user) {
        throw new ApolloError('User not found', 'NOT_FOUND', { status: 404 });
      }
      return user;
    },
  },
};

7. 性能优化

7.1 REST 性能优化

  • 分页

    http 复制代码
    GET /users?page=1&limit=10
    typescript 复制代码
    app.get('/users', (req: Request, res: Response) => {
      const page = parseInt(req.query.page as string) || 1;
      const limit = parseInt(req.query.limit as string) || 10;
      const start = (page - 1) * limit;
      const paginatedUsers = users.slice(start, start + limit);
      res.status(200).json({
        status: 'success',
        data: paginatedUsers,
        meta: { total: users.length, page, limit },
      });
    });
  • 缓存

    使用 ETag 或 Redis 缓存:

    typescript 复制代码
    import redis from 'redis';
    
    const client = redis.createClient();
    
    app.get('/users', async (req: Request, res: Response) => {
      const cached = await client.get('users');
      if (cached) {
        return res.status(200).json(JSON.parse(cached));
      }
      const response = { status: 'success', data: users };
      await client.setEx('users', 3600, JSON.stringify(response));
      res.status(200).json(response);
    });
  • 压缩

    bash 复制代码
    npm install compression
    typescript 复制代码
    import compression from 'compression';
    
    app.use(compression());

7.2 GraphQL 性能优化

  • DataLoader

    bash 复制代码
    npm install dataloader
    typescript 复制代码
    import DataLoader from 'dataloader';
    
    const userLoader = new DataLoader(async (ids: string[]) => {
      const users = await db.users.find({ id: { $in: ids } });
      return ids.map(id => users.find(user => user.id === id));
    });
    
    const resolvers = {
      Query: {
        user: (_: any, { id }: { id: string }) => userLoader.load(id),
      },
    };
  • 查询深度限制

    bash 复制代码
    npm install graphql-depth-limit
    typescript 复制代码
    import depthLimit from 'graphql-depth-limit';
    
    const server = new ApolloServer({
      typeDefs,
      resolvers,
      validationRules: [depthLimit(5)],
    });
  • 持久化查询

    typescript 复制代码
    const server = new ApolloServer({
      persistedQueries: {
        cache: new InMemoryLRUCache(),
      },
    });

8. API 文档生成

8.1 REST 文档(OpenAPI/Swagger)

使用 swagger-ui-express 生成文档:

bash 复制代码
npm install swagger-ui-express @types/swagger-ui-express

创建 src/swagger.json

json 复制代码
{
  "openapi": "3.0.0",
  "info": {
    "title": "REST API",
    "version": "1.0.0"
  },
  "paths": {
    "/users": {
      "get": {
        "summary": "Get all users",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string" },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": { "type": "string" },
                          "name": { "type": "string" },
                          "email": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

index.ts 中集成:

typescript 复制代码
import swaggerUi from 'swagger-ui-express';
import swaggerDocument from './swagger.json';

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

访问 http://localhost:3000/api-docs 查看文档。

8.2 GraphQL 文档

Apollo Server 内置文档支持,访问 http://localhost:4000 进入 Apollo Studio。也可以使用 graphql-codegen 生成类型:

bash 复制代码
npm install @graphql-codegen/cli @graphql-codegen/typescript

创建 codegen.yml

yaml 复制代码
schema: http://localhost:4000
documents: './src/**/*.graphql'
generates:
  ./src/generated/graphql.ts:
    plugins:
      - typescript
      - typescript-operations

运行:

bash 复制代码
npx graphql-codegen

9. 实时 API 设计

9.1 WebSocket(REST)

使用 ws 实现实时更新:

bash 复制代码
npm install ws @types/ws

src/websocket.ts

typescript 复制代码
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', ws => {
  ws.on('message', message => {
    wss.clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(`Broadcast: ${message}`);
      }
    });
  });
});

前端连接:

javascript 复制代码
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = event => console.log(event.data);
ws.send('Hello, server!');

9.2 GraphQL Subscription

schema.ts 添加:

graphql 复制代码
type Subscription {
  orderAdded(userId: ID!): Order!
}

resolvers.ts

typescript 复制代码
import { PubSub } from 'apollo-server';

const pubsub = new PubSub();
const ORDER_ADDED = 'ORDER_ADDED';

const resolvers = {
  Subscription: {
    orderAdded: {
      subscribe: (_: any, { userId }: { userId: string }) => pubsub.asyncIterator([ORDER_ADDED]),
    },
  },
  Mutation: {
    createOrder: (_: any, { userId, total }: { userId: string; total: number }) => {
      const newOrder = { id: String(orders.length + 1), userId, total, createdAt: new Date().toISOString() };
      orders.push(newOrder);
      pubsub.publish(ORDER_ADDED, { orderAdded: newOrder });
      return newOrder;
    },
  },
};

前端使用:

typescript 复制代码
import { useSubscription, gql } from '@apollo/client';

const ORDER_ADDED = gql`
  subscription OrderAdded($userId: ID!) {
    orderAdded(userId: $userId) {
      id
      total
    }
  }
`;

const OrderListener: React.FC<{ userId: string }> = ({ userId }) => {
  const { data, loading } = useSubscription(ORDER_ADDED, {
    variables: { userId },
  });

  if (loading) return <p>Waiting...</p>;
  return data ? <p>New Order: #{data.orderAdded.id}</p> : null;
};

10. 测试 API

10.1 REST 测试

使用 Jest 和 Supertest:

bash 复制代码
npm install jest supertest @types/jest @types/supertest ts-jest --save-dev

创建 src/__tests__/users.test.ts

typescript 复制代码
import request from 'supertest';
import app from '../index';

describe('Users API', () => {
  it('should get all users', async () => {
    const res = await request(app).get('/users');
    expect(res.status).toBe(200);
    expect(res.body.status).toBe('success');
    expect(res.body.data).toBeInstanceOf(Array);
  });

  it('should create a user', async () => {
    const res = await request(app)
      .post('/users')
      .send({ name: 'Charlie', email: '[email protected]' });
    expect(res.status).toBe(201);
    expect(res.body.data.name).toBe('Charlie');
  });
});

配置 jest.config.js

javascript 复制代码
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

运行:

bash 复制代码
npx jest

10.2 GraphQL 测试

创建 src/__tests__/graphql.test.ts

typescript 复制代码
import { ApolloServer } from 'apollo-server';
import typeDefs from '../schema';
import resolvers from '../resolvers';

const server = new ApolloServer({ typeDefs, resolvers });

describe('GraphQL API', () => {
  it('should get user', async () => {
    const query = `
      query {
        user(id: "1") {
          name
          email
        }
      }
    `;
    const res = await server.executeOperation({ query });
    expect(res.data?.user.name).toBe('Alice');
  });
});

运行:

bash 复制代码
npx jest
相关推荐
于冬恋7 分钟前
Web后端开发(请求、响应)
前端
red润13 分钟前
封装hook,复刻掘金社区,暗黑白天主题切换功能
前端·javascript·vue.js
Fly-ping14 分钟前
【前端】vue3性能优化方案
前端·性能优化
curdcv_po15 分钟前
前端开发必要会的,在线JS混淆加密
前端
天生我材必有用_吴用17 分钟前
深入理解JavaScript设计模式之单例模式
前端
LuckySusu17 分钟前
【HTML篇】DOCTYPE 声明:掌握浏览器渲染模式的关键
前端·html
Darling哒18 分钟前
HTML块拖拽交换
前端
码农之王19 分钟前
(一)TypeScript概述和环境搭建
前端·后端·typescript
葬送的代码人生31 分钟前
React组件化哲学:如何优雅地"变秃也变强"
前端·javascript·react.js
用户527096487449032 分钟前
🚀 前端项目代码质量配置Prettier + Commitlint + Husky + Lint-staged
前端