Your_Uncle_Ostap
@Your_Uncle_Ostap
Учусь премудростям

Как оптимизировать вычисления в функциональном компоненте?

Писал компонент, который вычисляет разные значения. Данные прилетают постоянно через вэбсокет и вычисляются в useEffect.
Вот что мне прислал заказчик:

упадет т.к. память закончится и не сможет работать как требуется в задании, неделями-месяцами

и когда наберется много данных считать будет очень долго

а должно выдавать ответ в течении секунды после нажатия кнопки

Как можно оптимизировать компонент?

...
export const Statistics = () => {
    const [data, setData] = useState([])
    const [average, setAverage] = useState('')
    const [calculationTime, setCalculationTime] = useState(0)
    const [standardDeviation, setStandardDeviation] = useState('')
    const [modes, setModes] = useState('')
    const [median, setMedian] = useState('')
    const [statistic, setStatistic] = useState(0)
    const [isConnectionStarted, setIsConnectionStarted] = useState(false)
    const ws = useRef(null);
  
// ----------вычисления ----------

    const getAverageScores = () => {
        const initialValues = {avg: 0, n: 0};
        return data.reduce(function (initialValues, data) {
            const avg = (data.value + initialValues.n * initialValues.avg) / (initialValues.n + 1)
            const n = initialValues.n + 1
            return {
                avg,
                n
            }
        }, initialValues).avg;
    }

    const getStandardDeviation = () => {
        const total = 0;
        const averageScore = getAverageScores()
        const squaredDeviations = data.reduce(function (total, data) {
            const deviation = data && data.value - averageScore;
            const deviationSquared = deviation * deviation;

            return total + deviationSquared;
        }, total);

        return Math.sqrt(squaredDeviations / data.length);
    }


    const getModes = () => {
        let frequency = [];
        let maxFreq = 0;
        let modes = [];

        for (let i in data){
            frequency[data[i].value] = (frequency[data[i].value] || 0) + 1;
            if (frequency[data[i].value] > maxFreq) {
                maxFreq = frequency[data[i].value];
            }
        }
        for (let j in frequency) {
            if (frequency[j] === maxFreq) {
                modes.push(j);
            }
        }
        if ( maxFreq === 1) {
            return ' - '
        }
        if (!modes.length) {
            return ' - '
        }
        return modes.join(', ')
    }

    const getMedian = () => {
        data.sort(function (a, b) {
            return a.value - b.value;
        });
        const half = Math.floor(data.length / 2);

        if (data.length % 2) {
            return data[half].value;
        }
        return (data[half - 1].value + data[half].value) / 2;

    }

    const calculation = () => {

        const startTime = new Date().getTime();
        setAverage(getAverageScores())
        setStandardDeviation(getStandardDeviation())
        setModes(getModes())
        setMedian(getMedian())
        const finishTime = new Date().getTime();
        setCalculationTime(finishTime - startTime)
    }


// ----------Зыпускаю вычисления при обновлении данных ----------

    useEffect(() => {
        if (data.length) {
            calculation()
        }
    }, [data])

// ----------Коннекчусь к серверу----------
    const start = () => {
        ws.current = new WebSocket('wss://trade.trademux.net:8800/?password=1234');
        if (isConnectionStarted) {
            ws.current.close()
            setData([])
            setStatistic(0)

        }
        ws.current.onopen = () => {
            console.log('ws opened');
            setIsConnectionStarted(true)
        }
        ws.current.onclose = () => {
            setIsConnectionStarted(false)
        }
        ws.current.onmessage = e => {
            const message = JSON.parse(e.data);
            setData(oldData => [...oldData, message])
        }

    }
// ----------Расконекчиваюсь с сервером ----------
    useEffect(() => {
        return () => {
            ws.current.close();
        };
    }, []);

    const getStatistic = () => {
        setStatistic(prevState => prevState + 1)
    }

    return (
        <>
            <div className="quote-statistics">
                <Item  value={Boolean(statistic) && average}/>
                <Item  value={Boolean(statistic) && standardDeviation}/>
                <Item value={Boolean(statistic) && modes}/>
                <Item value={Boolean(statistic) && median}/>
                <Item value={Boolean(statistic) && calculationTime}/>
            </div>
            <div className="quote-statistics_ctrl">
                <Btn func={start} title='START'/>
                <Btn func={getStatistic} title='STAT'/>
            </div>
        </>
    )
}
  • Вопрос задан
  • 139 просмотров
Пригласить эксперта
Ответы на вопрос 2
WblCHA
@WblCHA
Какой прекрасный код, кровь из глаз пошла уже от первой функции. Распишу её, на большее меня не хватит.
const getAverageScores = () => {
        const initialValues = {avg: 0, n: 0};
        return data.reduce(function (initialValues, data) {
            const avg = (data.value + initialValues.n * initialValues.avg) / (initialValues.n + 1)
            const n = initialValues.n + 1
            return {
                avg,
                n
            }
        }, initialValues).avg;
    }

Шедевр.
0. Брать массив данных напрямую из стейта, вместо передачи в аргументах и перетаскивания функции за пределы компонента.
1. Бесполезное объявление initialValues, ухудшающее читабельность.
2. function вместо стрелочной.
3. Триггер еслинта на дублирование имени переменной в нижнем скопе.
4. Просто шедевральные математические операции. На каждой (!) итерации умножать, делить, считать длину массива и создавать новый объект. И всё это вместо одной лаконично строки:
return data.reduce((acc, { value }) => acc + value, 0) / data.length;
Ответ написан
Alexandroppolus
@Alexandroppolus
кодир
Помимо сделанного WbICHA разбора функций (сурового, но справедливого), накину ещё чисто реактовских поинтов:
1) функции с вычислениями выселить нахрен из компонента, передавать в них данные.
2) Пачка стейтов (average... median) - ни что иное как богомерзкие производные стейты, подлежащие перековке на useMemo, с выбросом на помойку того useEffect, в котором calculation().
3) isConnectionStarted выглядит как лишний. По крайней мере, это не стейт. Возможно, стоит его переделать в ref
4) Назначение statistic непонятно. Подозреваю, что он не нужен, так как дублирует data.length (признак наличия данных).
5) Работу с вебсокетом было бы хорошо вынести в отдельный класс с логикой. Экземпляр класса хранить, например, в ref.
6) в функции старт какая-то ересь внутри ифа
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы