我们公司是做跨境电商的,商城用next自己开发的多租户模式,目前有5个品牌80+个站点在用这套代码
前一段时间发现有一个品牌站点运行一段时间后CPU越来越高,怀疑是内存泄漏,但是排查代码,用clinic.js测试也没发现有什么问题,按道理内存泄漏应该内存一直涨,啥都不涨就涨CPU
我能想到可能性就是自定义事件在不断重复添加同一个函数,然后事件触发的时候同一个函数执行次数越来越多,才能解释的通,否则内存也一定会跟着涨(哪位大佬有高见,欢迎在评论区讨论)
折腾了好几天一点线索都没有,下午茶聊天的时候,另一个部门的同事说他们有一次内存泄漏是i18n引起的,我就试着注释了初始化i18n的代码......
我们国际化方案用的i18next和react-i18next库,翻译数据是从后端接口读取的,翻译做了分组,主要分为基础组,客户端组,和其他组,我们想在_app.js里统一请求基础组,就不用每个页面到处写了,其他分组按需加载,否则一次性加载完,html页面体积会变得很大影响性能,然鹅next-i18next要每个页面级组件写serverSideTranslations,果断抛弃,改为使用react-i18next,初始化代码如下
// _app.js
function MyApp({ Component, pageProps }) {
const { language, i18nResources } = pageProps;
// 初始化 store
initStore({ ...pageProps });
// 初始化 i18n
initI18n({ language, resources: i18nResources });
return (...);
}
// initI18n
export function initI18n({ language, resources }) {
const namespaces = Object.keys(resources);
i18n.use(initReactI18next).init({
lng: language,
fallbackLng: defaultLanguage,
resources: { [language]: resources },
ns: namespaces,
defaultNS: Enums.i18nNamespace.Default,
fallbackNS: Enums.i18nNamespace.Fallback,
initImmediate: true,
});
}
打死我都没想到竟然是这段平平无奇的代码造成的,搞的劳资整天提心吊胆,隔几天就要重启一次进程
i18next源码太多没空研究,得先赶紧解决问题,最简单的办法就是自己写一个
// i18next.js
class I18n {
store = {
data: {},
};
options = {};
#currentLanguage = "en";
#defaultAndFallbackNS = [];
constructor() {
this.t = this.t.bind(this);
}
get currentLanguage() {
return this.#currentLanguage;
}
use() {
return this;
}
#getLoadedNS() {
return Object.keys(this.store.data[this.#currentLanguage]);
}
#fillDefaultAndFallbackNS(ns) {
ns = this.#toNSArray(ns);
return ns.length > 0 ? [...ns, ...this.#defaultAndFallbackNS] : this.#getLoadedNS();
}
#toNSArray(ns) {
let result;
if (Array.isArray(ns)) {
result = this.#removeDuplicates(ns);
} else {
if (typeof ns === "string") {
result = [ns];
} else {
result = [];
}
}
return result;
}
#removeDuplicates(arr) {
arr = Array.isArray(arr) ? arr : [];
return Array.from(new Set(arr));
}
init({ lng, fallbackLng, resources, ns, defaultNS, fallbackNS, initImmediate }) {
this.store.data = resources;
this.options = { lng, fallbackLng, ns, defaultNS, fallbackNS, initImmediate };
this.#currentLanguage = lng;
this.#defaultAndFallbackNS = this.#removeDuplicates([
...this.#toNSArray(defaultNS),
...this.#toNSArray(fallbackNS),
]);
return this;
}
addResources(lng, ns, resources) {
if (!this.store.data[lng]?.[ns]) {
this.store.data[lng][ns] = {};
}
Object.keys(resources).forEach((key) => {
this.store.data[lng][ns][key] = resources[key];
});
}
t(key, args = {}) {
return this.#TFunction({ lng: this.#currentLanguage, key, args });
}
getFixedT(lng, ns) {
return (key, args = {}) => this.#TFunction({ lng, ns, key, args });
}
#TFunction({ lng, ns: specifiedNS, key, args = {} }) {
const languages = this.#removeDuplicates([lng, this.#currentLanguage, this.options.fallbackLng]);
const namespaces =
this.#toNSArray(specifiedNS).length > 0 ? this.#fillDefaultAndFallbackNS(specifiedNS) : this.#getLoadedNS();
for (const lng of languages) {
for (const ns of namespaces) {
const data = i18n.store.data[lng]?.[ns] || {};
const translated = data[key];
if (translated) {
return translated.replace(/\{\{(.*?)}}/g, (matched, p1) => args[p1]);
}
}
}
return key;
}
}
const i18n = new I18n();
export default i18n;
// react-i18next.js
import i18n from "i18next";
export function getI18n() {
return i18n;
}
export function useTranslation(ns) {
return { t: i18n.getFixedT(i18n.currentLanguage, ns), i18n };
}
export function I18nextProvider(props) {
return props.children;
}
export function initReactI18next() {}
秘术:业务代码零改动,卸载i18next和react-i18next,修改jsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["/*"],
...
"i18next": ["libs/i18next"],
"react-i18next": ["libs/react-i18next"]
}
}
}
问题解决,CPU稳如老狗
溜了溜了