Không sử dụng tùy chọn destroyOnClose 3 - đá gà thomo trực tiếp hôm nay gà đòn

/imgposts/kt8n37xg.jpg

Đây là một lỗi cực kỳ kỳ lạ từ thành phần biên tập nội dung React Quill, đã làm tôi mất cả buổi chiều thứ Năm để giải quyết. Một điều may mắn duy nhất là tôi đã có cơ hội ôn lại kiến thức về cách đóng gói và giao tiếp giữa các thành phần trong React mà lâu nay đã quên sạch.

Trong giao diện quản trị được xây dựng bằng Ant Design Pro, khi mở Modal để hiển thị thành phần React Quill, thực hiện các thao tác nhập nội dung: gõ "hello", xuống dòng, thêm một dòng trống, sau đó gõ "world" và lưu lại. Khi mở lại Modal, nội dung hiển thị bởi Quill sẽ bị mất đi dòng trống ở giữa. Tiếp tục lặp lại quy trình trên, cuối cùng "hello world" sẽ dính liền nhau thành một dòng duy nhất. Thử nghiệm thêm cho thấy không chỉ dấu xuống dòng biến mất, mà cả định dạng danh sách cũng không còn nữa.

Qua kiểm tra, dữ liệu được gửi lên máy chủ qua phương thức POST như sau:

<p>hello</p><p><br></p><p>world</p>

Khi lấy dữ liệu trở lại từ máy chủ qua GET, kết quả vẫn giữ nguyên:

<p>hello</p><p><br></p><p>world</p>

Điều này chứng tỏ rằng máy chủ đá gà thomo trực tiếp hôm nay gà đòn xử lý và lưu trữ dữ liệu hoàn toàn bình thường.

Nội dung được truyền vào Modal và tới Quill qua logic phía trước-end cũng không có vấn đề gì. Tuy nhiên, khi render ra giao diện, nó xuất hiện lỗi như sau:

<div class="ql-editor" data-gramm="false" contenteditable="true">
  <p>hello</p>
  <p>world</p>
</div>

Lúc này, thẻ <br> đại diện cho dòng trống đã biến mất. Nếu tiếp tục lưu lại, thậm chí cả thẻ <p> cũng mất đi, chỉ còn lại:

<p>helloworld</p>

Kiểm tra trên một số dự án cũ hơn, không phát hiện vấn đề tương tự. Tuy nhiên, sự khác biệt nằm ở phiên bản thư viện:

  • Dự án cũ: "react-quill": "^2.0.0-beta.2"
  • Dự án mới: "react-quill": "^2.0.0-beta.4"

Các thư viện phụ thuộc khác như Quill và Ant Design đều có cùng phiên bản. Đã thử hạ cấp hoặc nâng cấp react-quill lên phiên bản chính thức 2.0, nhưng không cải thiện tình hình.

Sau nhiều giờ tìm kiếm với các từ khóa khác nhau, cuối cùng tìm được một thảo luận trên Stack Overflow liên quan đến việc sử dụng Quill trong Angular. Hiện tượng mô tả rất giống:

Tôi vừa gặp phải vấn đề này, đặc biệt rõ ràng khi editor được render bên trong tabs hoặc steppers. Giải pháp tôi áp dụng là sử dụng *ngIf để chỉ hiển thị editor khi nó đang ở trạng thái visible (hoặc có thể kích hoạt sau whenViewInit nếu cách làm của tôi không phù hợp với dự án của bạn). Có vẻ như nó loại bỏ tất cả các thẻ HTML và chỉ bọc nội dung trong thẻ <p>.

Cuối cùng đã tìm ra nguồn gốc của vấn đề: Trong một số trường hợp cụ thể, Quill sẽ loại bỏ các thẻ HTML và đơn giản hóa nội dung chỉ còn thẻ <p>.

Dựa trên hướng dẫn này, đã có một số ý tưởng giải quyết:

  1. Tắt hiệu ứng chuyển chơi bắn cá động của Modal (animation)
  2. Không sử dụng tùy chọn destroyOnClose
  3. Kiểm tra xem đây có phải lần render đầu tiên hay không
  4. Kiểm tra xem nội dung có phải do người dùng nhập vào hay không (không khả thi vì còn có trường hợp chèn hình ảnh qua API)

Để tối ưu hóa mã nguồn và giảm thiểu lặp lại, đã quyết định đóng gói thành phần này thành một module riêng, đồng thời sử dụng lazy loading. Dù không chắc chắn rằng đây là nguyên nhân chính, nhưng việc đóng gói component là một bước cần thiết.

Đồng thời, đã thêm điều kiện kiểm tra lần render đầu tiên theo gợi ý từ thảo luận trên GitHub. Kết quả cuối cùng được trả về thông qua cơ chế props.valueprops.onChange của tu vi trong ngay Form.Item trong Ant Design.

Thêm hỗ trợ style nội bộ:

yarn add styled-components --save

Mã nguồn component:

 1import React from 'react';
 2import ReactQuill from 'react-quill';
 3import 'react-quill/dist/quill.snow.css';
 4import { uploadImage } from '@/services/ant-design-pro/api';
 5import styled from 'styled-components';
 6
 7const Wrapper = styled.div`
 8  .ql-editor {
 9    min-height: ${(props) => props.minHeight || 200}px;
10    max-height: ${(props) => props.maxHeight || 500}px;
11  }
12`;
13
14function QuillFucker(props) {
15  const quill = React.useRef(null);
16  const [content, setContent] = React.useState(props.value || '');
17  const [isInitialRender, setIsInitialRender] = React.useState(true);
18
19  React.useEffect(() => {
20    setIsInitialRender(false);
21  }, []);
22
23  const toolbarContainer = [
24    // ...
25  ];
26
27  const imageHandler = () => {
28    // ...
29  };
30
31  const modules = React.useMemo(
32    () => ({
33      toolbar: {
34        container: toolbarContainer,
35        handlers: {
36          image: imageHandler,
37        },
38      },
39    }),
40    [],
41  );
42
43  return (
44    <Wrapper minHeight={props.minHeight} maxHeight={props.maxHeight}>
45      {!isInitialRender && (
46        <ReactQuill
47          ref={quill}
48          theme="snow"
49          value={content}
50          onChange={(value, _delta, source) => {
51            setContent(value);
52            props.onChange(value);
53          }}
54          modules={modules}
55        />
56      )}
57    </Wrapper>
58  );
59}
60
61export default QuillFucker;

Viết mã front-end đôi khi giống như việc bơi trong bãi phân - bạn phải đủ can đảm để lao xuống. Sau đó, hãy giữ bình tĩnh, đừng hoảng hốt khi gặp những đống phân, vì chúng sẽ chỉ càng xuất hiện nhiều hơn. Đồng thời, hãy tiếp tục tạo ra những đống phân mới trong quá trình này, để tránh bị nghẹt thở. Ngay cả AI cũng không thể giải quyết triệt để những vấn đề lịch sử của front-end.