React Hooks: есть ли способ рассчитать значения состояния на основе другого значения состояния - PullRequest
1 голос
/ 20 июня 2019

Я ищу способ вычисления значений состояния, которые зависят от других значений состояния, с помощью React Hooks.

Я знакомлюсь с React Hooks. До сих пор я использовал useState и useEffect. Я знаю о других хуках, например useReduce, useCallback и т. д.

Я сделал простой калькулятор тарифов, который конвертирует тарифы на почасовой, еженедельной, ежемесячной и годовой основе. Когда я изменю соответствующий тариф, другие тарифы будут обновляться соответственно.

Код приложения выглядит следующим образом:

import React, { useState } from 'react';
import {
    CssBaseline,
    Container,
    Typography,
    TextField
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles(theme => ({
    paper: {
        marginTop: theme.spacing(8), // = 4 * 2
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center'
    }
}));

function App() {
    const classes = useStyles();
    const [hourlyRate, setHourlyRate] = useState(10);
    const [workHour, setWorkHour] = useState(40);
    const [weeklyRate, setWeeklyRate] = useState(400);
    const [monthlyRate, setMonthlyRate] = useState(1600);
    const [yearlyRate, setYearlyRate] = useState(19200);

    const tryConvert = ({ name, value }) => {
        if (name === 'work-hours') {
            setWorkHour(value);
            setWeeklyRate(value * hourlyRate);
            setMonthlyRate(value * hourlyRate * 4);
            setYearlyRate(value * hourlyRate * 4 * 12);
        } else if (name === 'hourly-rate') {
            setHourlyRate(value);
            setWeeklyRate(value * workHour);
            setMonthlyRate(value * workHour * 4);
            setYearlyRate(value * workHour * 4 * 12);
        } else if (name === 'weekly-rate') {
            setWeeklyRate(value);
            setHourlyRate(value / workHour);
            setMonthlyRate(value * 4);
            setYearlyRate(value * 4 * 12);
        } else if (name === 'monthly-rate') {
            setMonthlyRate(value);
            setWeeklyRate(value / 4);
            setHourlyRate(value / workHour / 4);
            setYearlyRate(value * 12);
        } else if (name === 'yearly-rate') {
            setYearlyRate(value);
            setMonthlyRate(value / 12);
            setWeeklyRate(value / 12 / 4);
            setHourlyRate(value / 12 / 4 / workHour);
        }
    };

    return (
        <Container component="main" maxWidth="xs">
            <CssBaseline />
            <div className={classes.paper}>
                <Typography component="h1" variant="h5">
                    Rate Kalkulator
                </Typography>
                <TextField
                    variant="outlined"
                    margin="normal"
                    id="hourly-rate"
                    label="Hourly Rate"
                    name="hourly-rate"
                    autoFocus
                    inputProps={{ 'data-testid': 'hourly-rate' }}
                    value={hourlyRate ? hourlyRate : 0}
                    onChange={event => tryConvert(event.target)}
                />
                <TextField
                    variant="outlined"
                    margin="normal"
                    id="work-hours"
                    label="Work Hours"
                    name="work-hours"
                    inputProps={{ 'data-testid': 'work-hours' }}
                    value={workHour}
                    onChange={event => tryConvert(event.target)}
                />
                <TextField
                    variant="outlined"
                    margin="normal"
                    id="weekly-rate"
                    label="Weekly Rate"
                    name="weekly-rate"
                    inputProps={{ 'data-testid': 'weekly-rate' }}
                    value={weeklyRate ? weeklyRate : 0}
                    onChange={event => tryConvert(event.target)}
                />
                <TextField
                    variant="outlined"
                    margin="normal"
                    id="monthly-rate"
                    label="Monthly Rate"
                    name="monthly-rate"
                    inputProps={{ 'data-testid': 'monthly-rate' }}
                    value={monthlyRate ? monthlyRate : 0}
                    onChange={event => tryConvert(event.target)}
                />
                <TextField
                    variant="outlined"
                    margin="normal"
                    id="yearly-rate"
                    label="Yearly Rate"
                    name="yearly-rate"
                    inputProps={{ 'data-testid': 'yearly-rate' }}
                    value={yearlyRate ? yearlyRate : 0}
                    onChange={event => tryConvert(event.target)}
                />
            </div>
        </Container>
    );
}

export default App;

Ссылка Codesandbox - tryConvert

Оценить Kalkulator - tryConvert

Приложение работало, как и ожидалось, насколько я тестировал, еще не работало на крайних случаях.

Упрощение конвертации курсов

Я пытаюсь упростить конвертацию tryConvert с другими React Hooks, но не удалось. Это код для преобразования из приведенных выше кодов:


    const tryConvert = ({ name, value }) => {
        if (name === 'work-hours') {
            setWorkHour(value);
            setWeeklyRate(value * hourlyRate);
            setMonthlyRate(value * hourlyRate * 4);
            setYearlyRate(value * hourlyRate * 4 * 12);
        } else if (name === 'hourly-rate') {
            setHourlyRate(value);
            setWeeklyRate(value * workHour);
            setMonthlyRate(value * workHour * 4);
            setYearlyRate(value * workHour * 4 * 12);
        } else if (name === 'weekly-rate') {
            setWeeklyRate(value);
            setHourlyRate(value / workHour);
            setMonthlyRate(value * 4);
            setYearlyRate(value * 4 * 12);
        } else if (name === 'monthly-rate') {
            setMonthlyRate(value);
            setWeeklyRate(value / 4);
            setHourlyRate(value / workHour / 4);
            setYearlyRate(value * 12);
        } else if (name === 'yearly-rate') {
            setYearlyRate(value);
            setMonthlyRate(value / 12);
            setWeeklyRate(value / 12 / 4);
            setHourlyRate(value / 12 / 4 / workHour);
        }
    };

Моя попытка использования useEffect

Я пытался использовать useEffect для добавления обратных вызовов при изменении состояний. Коды ниже иллюстрируют то, что я пробовал:

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


    const [hourlyRate, setHourlyRate] = useState(10);
    const [workHour, setWorkHour] = useState(40);
    const [weeklyRate, setWeeklyRate] = useState(400);
    const [monthlyRate, setMonthlyRate] = useState(1600);
    const [yearlyRate, setYearlyRate] = useState(19200);

    useEffect(() => {
        setWeeklyRate(hourlyRate * workHour);
        setMonthlyRate(hourlyRate * workHour * 4);
        setYearlyRate(hourlyRate * workHour * 4 * 12);
    }, [hourlyRate, workHour]);

    useEffect(() => {
        setHourlyRate(weeklyRate / workHour);
        setMonthlyRate(weeklyRate * 4);
        setYearlyRate(weeklyRate * 4 * 12);
    }, [weeklyRate, workHour]);

    useEffect(() => {
        setHourlyRate(monthlyRate / 4 / workHour);
        setWeeklyRate(monthlyRate / 4);
        setYearlyRate(monthlyRate * 12);
    }, [monthlyRate, workHour]);

    useEffect(() => {
        setHourlyRate(yearlyRate / 12 / 4 / workHour);
        setWeeklyRate(yearlyRate / 12 / 4);
        setMonthlyRate(yearlyRate / 12);
    }, [yearlyRate, workHour]);

Если я использую этот подход, при изменении workHour преобразование не будет работать должным образом. Я думаю, это потому, что workHour стал зависимым от 4 различных useEffect. Я до сих пор не уверен в этом.

Ссылка Codesandbox - useEffect

Rate Kalkulator - useEffect

если вы измените рабочий час, преобразование не будет работать


В настоящее время я пробую другие подходы, использующие useReduce, но я все еще не уверен, будет ли это лучшим способом конвертировать ставки.

Как лучше всего упростить мой tryConvert?

Буду признателен за любую помощь в этом.

1 Ответ

3 голосов
/ 20 июня 2019

Здесь действительно нет необходимости useEffect. useEffect для побочных эффектов (например, setInterval, document.title, вещей, которые не являются частью цикла рендеринга).

Ваше первое решение работает нормально, но вы можете упростить его, имея единый источник правды для ставки и рассчитывая другие ставки на основе этого. Примерно так:

function App() {
  const classes = useStyles();
  const [hourlyRate, setHourlyRate] = useState(10);
  const [workHour, setWorkHour] = useState(40);

  const convertTo = name => {
    const map = {
      weekly: hourlyRate * workHour,
      monthly: hourlyRate * workHour * 4,
      yearly: hourlyRate * workHour * 52
    };
    return map[name] || 0;
  };

  const convertFrom = name => e => {
    const { value } = e.target;
    const map = {
      weekly: value / workHour,
      monthly: value / workHour / 4,
      yearly: value / workHour / 52
    };
    setHourlyRate(map[name]);
  };

  return (
      <div className={classes.paper}>
        <Typography component="h1" variant="h5">
          Rate Kalkulator
        </Typography>
        <TextField
          label="Hourly Rate"
          value={hourlyRate || 0}
          onChange={e => setHourlyRate(e.target.value)}
        />
        <TextField
          label="Work Hours"
          value={workHour}
          onChange={e => setWorkHour(e.target.value)}
        />
        <TextField
          label="Weekly Rate"
          value={convertTo("weekly") || 0}
          onChange={convertFrom("weekly")}
        />
        <TextField
          label="Monthly Rate"
          value={convertTo("monthly") || 0}
          onChange={convertFrom("monthly")}
        />
        <TextField
          label="Yearly Rate"
          value={convertTo("yearly") || 0}
          onChange={convertFrom("yearly")}
        />
      </div>
  );
}

Для рабочего примера вот коды и коробка: https://codesandbox.io/s/rate-kalkulator-ej5m8

...