1. react-i18next resources key默认杠后大写

新增了繁体中文模块,如下导入,切换语言并不生效

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

import translation_en from './en';
import translation_zh from './zh';
import translation_zh_traditional from './zh-traditional';

const resources = {
  en: translation_en,
  zh: translation_zh,
  'zh-traditional': translation_zh_traditional,
};

i18n.use(initReactI18next).init({
  resources,
  lng: 'en',
  fallbackLng: 'en',
  interpolation: {
    escapeValue: false,
  },
});

export default i18n;

原来i18next默认杠后面要大写,
image.png
或者配置lowerCaseLng为true,全小写就会生效了

2. 关于使得pdf文档部分高亮的技术选型

需求:点击左边的item,对应右边的pdf高亮显示
image.png

  • react-pdf@7.7.1
    腾讯一篇文章效果像是我们想要的,去翻看其使用到的react-pdf wikiHighlight text on the page,是遍历整个pdf内容,将每块内容切成小块跟你提供的text去匹配,匹配成功则高亮,而我们提供的text一般都是段落很长,这会导致有很多地方的小块内容被text匹配上,从而匹配的结果看起来像是狸花猫,而不是指定的一整块,例如下面的效果:
    image.png
    这不是我们想要的
  • react-pdf-highlighter@6.1.0
    该库通过坐标去高亮pdf,可以很好的实现我们的需求,后端解析文档的段落,将对应的坐标给到前端就可以实现了,但是其中也有坑react-pdf-highlighter@6.1.0 高亮定位不准

3. key的唯一性是多么的重要

神奇的bug出现了,
6aadfae193c24e6447dc13cbfbb7e3c.png

e2ce5fad8e170140c092a052f3bd7b1.png
切换过来 上一条数据还在,上一个对话的内容不应该出现当前的对话里,排查了很久,百思不得其解。

              {conversation?.message?.map((message, i) => {
                return (
                  <MessageItem
                    loading={
                      message.role === MessageType.Assistant &&
                      sendLoading &&
                      conversation?.message.length - 1 === i
                    }
                    key={message.id}
                    item={message}
                    nickname={userInfo.nickname}
                    avatar={userInfo.avatar}
                    reference={buildMessageItemReference(conversation, message)}
                    clickDocumentButton={clickDocumentButton}
                  ></MessageItem>
                );
              })}

还以为我这里遍历的代码有问题,之前都是好好的
2a5d021b6359c5d6e90fba6b934bb7e.png
发现是数据的问题,
0e18a9d5e1d4c9e76d6e9938f81bc3f.png
导致我的组件的key会重复
4d0ff864c576049d91e81fd00cf08d9.png
控制台已经报错了,没放在心上。

4. react 更新查询字符串 (search params)

"umi": "^4.0.90",
用下面的方式更新查询参数

export const useClickConversationCard = () => {
  const [currentQueryParameters, setSearchParams] = useSearchParams();
  const newQueryParameters: URLSearchParams = useMemo(
    () => new URLSearchParams(currentQueryParameters.toString()),
    [currentQueryParameters],
  );

  const handleClickConversation = useCallback(
    (conversationId: string) => {
      newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
      setSearchParams(newQueryParameters);
    },
    [newQueryParameters, setSearchParams],
  );

  return { handleClickConversation };
};

使用:

  // When you first enter the page, select the top conversation card
  const checkTopConversationCard = useCallback(
    (conversationList: IConversation[]) => {
      const firstConversation = conversationList.at(0);
      if (firstConversation) {
        handleClickConversation(firstConversation.id);
      }
    },
    [handleClickConversation],
  );

  useEffect(() => {
    checkTopConversationCard(conversationList);
  }, [conversationList, checkTopConversationCard]);

image.png
如上图,点击除了第一个以外的对话,始终会选中第一个,逐步排查是handleClickConversation以来的问题,将newQueryParameters从handleClickConversation移除就没问题了。但是我用setSearchParams({ [ChatSearchParams.ConversationId]: conversationId });这种方式会覆盖而不是更新查询参数,查阅资料,Update search params without re-rendering everything #9851 也有类似的讨论,根据React Router V6 手摸手随便指南指北发现,
测试,react-router@v6.26.2

  function handleUpdateParams() {
    setSearchParams((preParams)=>{
      preParams.set("conversationId","10000000000")
      return preParams
    });
  }

回调函数可以实现search params的增量更新,而不是覆盖。

5. useEffect 回调函数返回函数的调用时机

react@18.2.0

import { useCallback, useEffect, useState } from 'react';

const Demo = () => {
  const [answer, setAnswer] = useState('111');

  const handleClick = useCallback(() => {
    setAnswer('');
  }, []);

  const handleChange = useCallback((e: any) => {
    setAnswer(e.target.value);
  }, []);

  useEffect(() => {
    console.log('🚀 ~ useEffect:', answer);

    return () => {
      console.log('🚀 ~ unmount:', answer);
    };
  }, [answer]);

  return (
    <div>
      <input type="text" value={answer} onChange={handleChange} />
      <button type="button" onClick={handleClick}>
        reset
      </button>
    </div>
  );
};

export default Demo;

一直以为useEffect回调函数返回的函数只有在页面卸载的时候才会被调用,直到遇见了个issue,做了测试,每次修改输入框的值,都会先调用返回的函数,再调用回调函数
image.png
查看官方文档
image.png

即使组件没有卸载,cleanup 逻辑也会运行

依赖项变化导致副作用重新执行前
useEffect 的依赖项数组中的值发生变化时,先执行上一次的清理函数,再运行新的副作用逻辑。

const [count, setCount] = useState(0);
useEffect(() => {
  console.log("副作用执行,count=", count);
  return () => console.log("清理函数执行,count=", count); // 依赖变化时先执行
}, [count]);

点击按钮更新 count 时,控制台输出:
清理函数执行,count=0 → 副作用执行,count=1

5. react-markdown 数学公式没法正常显示

import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you

    <Markdown
      rehypePlugins={[rehypeKatex, rehypeRaw]}
      remarkPlugins={[remarkGfm, remarkMath]}
    >
      {contentWithCursor}
    </Markdown>

虽然做了上述的配置,但是latex公式还是无法正常显示,查阅It's not parsing LaTex syntax correctly, even with plugins #785 需要对文本做如下处理

export const preprocessLaTeX = (content: string) => {
  const blockProcessedContent = content.replace(
    /\\\[([\s\S]*?)\\\]/g,
    (_, equation) => `$$${equation}$$`,
  );
  const inlineProcessedContent = blockProcessedContent.replace(
    /\\\(([\s\S]*?)\\\)/g,
    (_, equation) => `$${equation}$`,
  );
  return inlineProcessedContent;
};

6. lexical 得到最后的文本结果

image.png

需求:
在文本框输入 / 弹出菜单选择一个选项将该选项以自定义样式的形式显示在文本框内。

  1. editorState可以转成json格式,但是我最后想要得到文本框内的文本,可以用如下方式获得,

    const text = $getRoot().getTextContent();

参考:
Convert a saved editorState to plain text without editor? #3806

  1. 在文本框显示了Badge组件,但是我最后想拿到自定义节点的文本
    这个时候需要用到 getTextContent

    export class VariableNode extends DecoratorNode<ReactNode> {
      __id: string;
    
      static getType(): string {
     return 'variable';
      }
    
      static clone(node: VariableNode): VariableNode {
     return new VariableNode(node.__id, node.__key);
      }
    
      constructor(id: string, key?: NodeKey) {
     super(key);
     this.__id = id;
      }
    
      createDOM(): HTMLElement {
     const dom = document.createElement('span');
     dom.className = 'mr-1';
    
     return dom;
      }
    
      updateDOM(): false {
     return false;
      }
    
      decorate(): ReactNode { // 在文本框显示Badge
     return <Badge>{this.__id}</Badge>;
      }
    
      getTextContent(): string { // 用来获取最后的文本值
     return `{${this.__id}}`;
      }
    }

7. react-hook-form watch 导致页面频繁刷新

  • antd版本
    监测antd form变化,将改变的值同步到zustand

    export const useHandleFormValuesChange = (id?: string) => {
    const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
    const handleValuesChange = useCallback(
      (changedValues: any, values: any) => {
        let nextValues: any = values;
        // Fixed the issue that the related form value does not change after selecting the freedom field of the model
        if (
          Object.keys(changedValues).length === 1 &&
          'parameter' in changedValues &&
          changedValues['parameter'] in settledModelVariableMap
        ) {
          nextValues = {
            ...values,
            ...settledModelVariableMap[
              changedValues['parameter'] as keyof typeof settledModelVariableMap
            ],
          };
        }
        if (id) {
          updateNodeForm(id, nextValues);
        }
      },
      [updateNodeForm, id],
    );
    
    return { handleValuesChange }; // 传递给表单的onValuesChange
    };

表单页面监测zustand的改变,将其同步到表单上


  useEffect(() => {
    if (visible) {
      if (node?.id !== previousId.current) {
        form.resetFields();
      }

      if (operatorName === Operator.Categorize) {
        const items = buildCategorizeListFromObject(
          get(node, 'data.form.category_description', {}),
        );
        const formData = node?.data?.form;
        if (isPlainObject(formData)) {
          form.setFieldsValue({ ...formData, items });
        }
      } else {
        form.setFieldsValue(node?.data?.form); // 这里不会导致input失去焦点,神奇
      }
      previousId.current = node?.id;
    }
  }, [visible, form, node?.data?.form, node?.id, node, operatorName]);
  • react-hook-form shadcn 版

表单页面监测zustand的改变,将其同步到表单上

  useEffect(() => {
    if (visible) {
      if (node?.id !== previousId.current) {
        form.reset();
        form.clearErrors();
      }

      if (operatorName === Operator.Categorize) {
        const items = buildCategorizeListFromObject(
          get(node, 'data.form.category_description', {}),
        );
        const formData = node?.data?.form;
        if (isPlainObject(formData)) {
          //   form.setFieldsValue({ ...formData, items });
          console.info('xxx');
          form.reset({ ...formData, items });
        }
      } else {
        form.reset(node?.data?.form); // 会导致页面不断刷新,即使用debounce也会导致输入框失去焦点
      }
      previousId.current = node?.id;
    }
  }, [visible, form, node?.data?.form, node?.id, node, operatorName]);

assassin_cike
1.3k 声望74 粉丝

生活不是得过且过