Лучшие практики для создания многократно используемых компонентов React в приложениях 18n / l10n с настраиваемым форматированием - PullRequest
1 голос
/ 24 января 2020

У меня есть следующий компонент React, который я хотел бы использовать в нескольких приложениях:

import React from "react";
import { useFactory } from "react-js-utl/hooks";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

/**
 * Shows current account balance with variation since previous balance.
 */
const Balance = compose(React.memo)(function Balance({
    current,
    previous,
    label,
    sinceLabel,
    increaseColor = "light-green",
    increaseIcon = "arrow-up",
    decreaseColor = "red",
    decreaseIcon = "arrow-down",
    sameColor = "blue",
    sameIcon = "equals",
    formatNum = void 0,
    formatPerc = void 0
} = {}) {
    // Compute the change.
    let change = ((current - previous) / previous) * 100;

    // Implementation detail. Switch objects depeding on runtime value of change.
    const outcomeFactory = useFactory(
        () => [
            // Increase:
            [
                change > 0, // Condition, if true, the object following it will be returned by `useFactory()`.
                {
                    outcome: "increase",
                    color: increaseColor,
                    icon: increaseIcon
                }
            ],
            // Decrease:
            [
                change < 0,
                {
                    outcome: "decrease",
                    color: decreaseColor,
                    icon: decreaseIcon
                }
            ],
            // Same (default, returned if the previous conditions evaluate to false):
            {
                outcome: "same",
                color: sameColor,
                icon: sameIcon
            }
        ],
        // deps array
        [
            change,
            increaseColor,
            increaseIcon,
            decreaseColor,
            decreaseIcon,
            sameColor,
            sameIcon
        ]
    );

    change = Math.abs(change);

    return (
        <div className="balance">
            <div className="balance-label">{label}</div>
            <div className="balance-current">
                {formatNum ? formatNum(current) : current}
            </div>
            <span
                className={`balance-outcome balance-outcome-${outcomeFactory.outcome} balance-outcome-${outcomeFactory.color}`}
            >
                {typeof outcomeFactory.icon === "string" ? (
                    // Defaults to FontAwesome's icon.
                    <FontAwesomeIcon icon={outcomeFactory.icon} />
                ) : (
                    // Custom component rendered by client code.
                    outcomeFactory.icon
                )}
                {outcomeFactory.outcome === "same"
                    ? // Same (no change since previous):
                      ""
                    : // Increase or decrease since previous:
                    // Edge case: previous balance was zero
                    Number(previous) === 0
                    ? ""
                    : // Percentage increase/decrease:
                    formatPerc
                    ? formatPerc(change)
                    : change}
            </span>
            <span className="balance-since-label">{sinceLabel}</span>
        </div>
    );
});
Balance.displayName = "Balance";
export default Balance;

, который я использую в таком приложении:

...

function myFormattingFunctionForNumbers(t, num) {
   // I use accounting.js to format numbers:
   return accounting.format(num, {
       decimal: t("formatting:numbers.decimal_separator"), // For EN lang this will be "."
       thousand: t("formatting:numbers.thousand_separator") // For EN this this will be ","
   })
}

function myFormattingFunctionForPercentages(t, perc) {
   return myFormattingFunctionForNumbers(t, perc) + "%";
}

...

// Inside app component:
// Using react-i18next:
const { t, i18n } = useTranslation();

// If the language changes, so does formatting:
formatNum = useCallback(current => myFormattingFunctionForNumbers(t, current), [t, i18n.language])
formatPerc = useCallback(changePerc => myFormattingFunctionForPercentages(t, changePerc), [t, i18n.language])

...

<Balance
    current={11234.56} // These hardcoded values could be props, of course.
    previous={9321.45}
    label={t("Your account balance")} // i18n
    sinceLabel={t("Since previous month")} // i18n
    formatNum={formatNum} // Function to format current value.
    formatPerc={formatPerc} // Function to format change/variation percentage.
/>

...

, который выводит что-то выглядящее примерно так:

Your account balance
11,234.56

↑ 20,52% Since previous month

Сейчас я сталкиваюсь со следующими "проблемами":

  1. Повторно используемый компонент является чистым (использует React.memo), поэтому его функции форматирования formatNum и formatPerc должны меняться при изменении языка приложения, даже если другие реквизиты, такие как current и previous, не меняются, потому что другой язык потенциально предполагает другое форматирование и, следовательно, компонент должен перерисовываться;

  2. Из-за пункта 1 клиент несет ответственность за соединение всех функций форматирования внутри потребляющего компонента, используя useCallback, который создает много биологической таблицы ...;

  3. Два useCallback не предупреждают меня, что мне нужно передать i18n.language в массив deps, просто потому что на текущий язык не ссылаются напрямую из-за веселья при форматировании ctions myFormattingFunctionForNumbers и myFormattingFunctionForPercentages, которые используют только функцию t i18next (которая, насколько я знаю, не меняется при изменении языка);

  4. Может быть есть точка 4 и даже точка 5, о которой я пока не знаю.

Какова текущая лучшая практика для повторно используемых компонентов React, которые поддерживают форматирование и i18n / l10n ?

Советы и рекомендации по организации кодирования форматирования / i18n / l10n и отделению этих проблем от повторно используемых компонентов будут приветствоваться.

Спасибо за внимание.

...