Menu
CoddyTech

Sắp xếp văn bản

Thuộc phần REST API trong hành trình Node.js của Coddy. Bài học 15 trên 25.

Bộ theo dõi của bạn có thể lọc công việc. Giờ hãy để ứng dụng khách yêu cầu sắp xếp tiêu đề theo thứ tự bảng chữ cái mà không làm thay đổi thứ tự đã lưu. Phép so sánh khi sắp xếp trả về một số âm khi phần tử đầu tiên đứng trước phần tử thứ hai, một số dương khi nó đứng sau và số không khi chúng bằng nhau.

const sorted = cities.toSorted((a, b) => a.name.localeCompare(b.name));

Trừ các chuỗi không tạo ra thứ tự hữu ích. localeCompare so sánh văn bản. toSorted trả về một mảng mới, giữ nguyên mảng ban đầu. Hãy sắp xếp danh sách đã lọc để việc lọc và sắp xếp hoạt động cùng nhau. Bài học tiếp theo về sắp xếp sẽ bổ sung các trường khác, thứ tự giảm dần và cách xử lý truy vấn không hợp lệ.

challenge icon

Thử thách

Dễ

Tiếp tục xây dựng trình theo dõi công việc trong routes/tasks.js. Trong tuyến danh sách, sau các bộ lọc hiện có, hãy để ?sort=title trả về các công việc theo thứ tự bảng chữ cái của tiêu đề. Nếu không có giá trị đó, hãy giữ nguyên hành vi hiện tại. Đừng thay đổi thứ tự công việc đã lưu.

Thử GET /api/tasks?sort=title, rồi đến GET /api/tasks. Danh sách thứ hai vẫn phải giữ nguyên thứ tự ban đầu. Lọc bằng ?done=false&sort=title vẫn phải chỉ trả về các công việc chưa hoàn thành.

Tự làm thử

import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';

const router = Router();

// Một tác vụ đầy đủ, đúng như dữ liệu PUT gửi đến. .strict() từ chối các trường không có trong danh sách của nó.
const Task = z
    .object({
        title: z.string().trim().min(1).max(100),
        done: z.boolean(),
    })
    .strict();
const NewTask = Task.partial({ done: true });
const TaskChanges = Task.partial();

// True nếu một tác vụ khác đã có tiêu đề này, bất kể chữ hoa hay chữ thường.
const titleTaken = (title, exceptId) =>
    tasks.some((t) => t.id !== exceptId && t.title.toLowerCase() === title.toLowerCase());

router.get('/', (req, res) => {
    const { done, q } = req.query;
    let list = tasks;
    if (done !== undefined) {
        if (done !== 'true' && done !== 'false') {
            return sendError(res, 400, 'invalid_query', 'done must be true or false');
        }
        list = list.filter((t) => t.done === (done === 'true'));
    }
    if (q !== undefined) {
        const words = String(q).toLowerCase();
        list = list.filter((t) => t.title.toLowerCase().includes(words));
    }
    res.json(list);
});

router.get('/:id', (req, res) => {
    const task = tasks.find((t) => t.id === Number(req.params.id));
    if (!task) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    res.json(task);
});

router.post('/', (req, res) => {
    const result = NewTask.safeParse(req.body);
    if (!result.success) {
        return sendInvalid(res, result.error);
    }
    const { title, done = false } = result.data;
    if (titleTaken(title)) {
        return sendError(res, 409, 'conflict', 'A task with this title already exists');
    }
    const task = { id: nextId(), title, done, createdAt: new Date().toISOString() };
    tasks.push(task);
    res.status(201).location(`/api/tasks/${task.id}`).json(task);
});

router.patch('/:id', (req, res) => {
    const task = tasks.find((t) => t.id === Number(req.params.id));
    if (!task) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    const result = TaskChanges.safeParse(req.body);
    if (!result.success) {
        return sendInvalid(res, result.error);
    }
    if (result.data.title !== undefined && titleTaken(result.data.title, task.id)) {
        return sendError(res, 409, 'conflict', 'A task with this title already exists');
    }
    Object.assign(task, result.data);
    res.json(task);
});

router.put('/:id', (req, res) => {
    const index = tasks.findIndex((t) => t.id === Number(req.params.id));
    if (index === -1) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    const result = Task.safeParse(req.body);
    if (!result.success) {
        return sendInvalid(res, result.error);
    }
    const { id, createdAt } = tasks[index];
    if (titleTaken(result.data.title, id)) {
        return sendError(res, 409, 'conflict', 'A task with this title already exists');
    }
    tasks[index] = { id, ...result.data, createdAt };
    res.json(tasks[index]);
});

router.delete('/:id', (req, res) => {
    const index = tasks.findIndex((t) => t.id === Number(req.params.id));
    if (index === -1) {
        return sendError(res, 404, 'not_found', 'Task not found');
    }
    tasks.splice(index, 1);
    res.status(204).end();
});

export default router;

Tất cả bài học trong REST API

Tự luyện tập: Playground Express