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默認槓後面要大寫,
或者配置lowerCaseLng為true,全小寫就會生效了
2. 關於使得pdf文檔部分高亮的技術選型
需求:點擊左邊的item,對應右邊的pdf高亮顯示
- react-pdf@7.7.1
騰訊一篇文章效果像是我們想要的,去翻看其使用到的react-pdf wikiHighlight text on the page,是遍歷整個pdf內容,將每塊內容切成小塊跟你提供的text去匹配,匹配成功則高亮,而我們提供的text一般都是段落很長,這會導致有很多地方的小塊內容被text匹配上,從而匹配的結果看起來像是狸花貓,而不是指定的一整塊,例如下面的效果:
這不是我們想要的 - react-pdf-highlighter@6.1.0
該庫通過座標去高亮pdf,可以很好的實現我們的需求,後端解析文檔的段落,將對應的座標給到前端就可以實現了,但是其中也有坑react-pdf-highlighter@6.1.0 高亮定位不準
3. key的唯一性是多麼的重要
神奇的bug出現了,
切換過來 上一條數據還在,上一個對話的內容不應該出現當前的對話裏,排查了很久,百思不得其解。
{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>
);
})}
還以為我這裏遍歷的代碼有問題,之前都是好好的
發現是數據的問題,
導致我的組件的key會重複
控制枱已經報錯了,沒放在心上。
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]);
如上圖,點擊除了第一個以外的對話,始終會選中第一個,逐步排查是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,做了測試,每次修改輸入框的值,都會先調用返回的函數,再調用回調函數
查看官方文檔
即使組件沒有卸載,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 得到最後的文本結果
需求:
在文本框輸入 / 彈出菜單選擇一個選項將該選項以自定義樣式的形式顯示在文本框內。
-
editorState可以轉成json格式,但是我最後想要得到文本框內的文本,可以用如下方式獲得,
const text = $getRoot().getTextContent();
參考:
Convert a saved editorState to plain text without editor? #3806
-
在文本框顯示了Badge組件,但是我最後想拿到自定義節點的文本
這個時候需要用到getTextContentexport 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變化,將改變的值同步到zustandexport 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]);