将道具从父母传递给孩子并在子组件中对其进行操作

时间:2019-04-26 13:18:37

标签: reactjs

父级中有一个名为visibleMonth的函数,该函数返回一个对象数组,该对象数组显示在视口中可见的月份。我已经成功地将一个名为getVisibleMonth的道具从父项[Calendar]传递给其子项[CalendarBarChart]

我现在想要实现的是分配一个css类来显示当前在视口中的月份,所以像someVisibleMonths(可以通过this.getVisibileMonth()属性实现,并以正确的格式获取月份,它将是{{1}中的month.start.format('MMMM'))=== month,然后将类分配给该范围。我有点困惑如何去做。

这是我的父组件,我知道它很长!但重点将放在averageByMonth上:

visibleMonth

这是子组件:

import React, { Component, Fragment } from 'react';
import filter from 'lodash/filter';
import find from 'lodash/find';
import flatten from 'lodash/flatten';
import some from 'lodash/some';
import isEqual from 'lodash/isEqual';
import cx from 'classnames';

import moment from 'utils/moment/moment';
import ScrollWatcher from 'utils/ScrollWatcher';
import CalendarDay from './CalendarDay';
import CalendarMonth from './CalendarMonth';
import CalendarBarChart from './CalendarBarChart';
import css from './Calendar.css';

import type { BookableMonth, DayState, Period } from './types';
import { startOfMonth } from './utils';

import { Icon } from '@appearhere/bloom';

type Props = {
  direction: 'row' | 'column',
  mask: boolean,
  maskSize: number,
  minimumDurationInDays: number,
  months: Array<BookableMonth>,
  onSelectionUpdate: (Array<moment>) => mixed,
  selectedDates: Array<Period>,
  sparse: boolean,
};

type State = {
  currentMonth: number,
  startOn?: moment,
  endOn?: moment,
  endHighlight?: moment,
};

export default class Calendar extends Component<Props, State> {
  static defaultProps = {
    sparse: false,
    mask: false,
    maskSize: 3,
    direction: 'row',
    selectedDates: [],
  };

  state = {
    currentMonth: 0,
    startOn: null,
    endOn: null,
    endHighlight: null,
  };
  dynamicBarChartContainerRef: any = React.createRef();
  barChartContainerRef: any = React.createRef();
  scrollTriggeredByClickingOnLink: boolean = false;
  calendarScrollWatcher = new ScrollWatcher({
    onScrollUp: scrollState => {
      this.scrollTriggeredByClickingOnLink = false;
      const barChartContainer = this.barChartContainerRef.current;
      const dynamicBarChartContainer = this.dynamicBarChartContainerRef.current;

      // Let the dynamic chart disappear when we reach the top and make sure the
      // static chart is visible.
      if (scrollState.enteringTop) {
        // Make sure the static chart is visible.
        barChartContainer.classList.remove(css.invisible);

        // Make sure the dynamic chart is invisible during repositioning.
        dynamicBarChartContainer.classList.add(css.invisible);

        // Set the dynamic chart to the scrolled-up position instantly, skipping the transition,
        // as read at https://medium.com/building-blocks/how-to-skip-css-transitions-with-jquery-e0155d06e82e
        dynamicBarChartContainer.classList.remove(css.dynamicBarChartContainerTransition);
        dynamicBarChartContainer.classList.remove(css.dynamicBarChartVisiblePosition);
        dynamicBarChartContainer.offsetHeight; // Check the link above for why this is necessary.
        dynamicBarChartContainer.classList.add(css.dynamicBarChartContainerTransition);

        // Repositioning is over. Make the dynamic chart visible again.
        dynamicBarChartContainer.classList.remove(css.invisible);

        // If the static chart is already out of the viewport, slide in the dynamic chart.
      } else if (scrollState.scrollTop > dynamicBarChartContainer.offsetHeight) {
        dynamicBarChartContainer.classList.add(css.dynamicBarChartVisiblePosition);
      }
    },
    onScrollDown: () => {
      const dynamicBarChartContainer = this.dynamicBarChartContainerRef.current;

      // If this is not a regular scroll but we're jumping to a certain
      // location using a link, ensure the dynamic bar chart is visible.
      if (this.scrollTriggeredByClickingOnLink) {
        dynamicBarChartContainer.classList.add(css.dynamicBarChartVisiblePosition);
        this.scrollTriggeredByClickingOnLink = false;

        // Otherwise hide the dynamic bar chart.
      } else {
        dynamicBarChartContainer.classList.remove(css.dynamicBarChartVisiblePosition);
      }
    },
  });

  componentWillReceiveProps(newProps: Props) {
    const { selectedDates } = newProps;

    if (!isEqual(selectedDates, this.props.selectedDates)) {
      this.setDatesState(newProps);
    }
  }

  componentDidMount() {
    this.setDatesState(this.props);
  }

  setDatesState = (props: Props) => {
    const { selectedDates, sparse } = props;

    if (sparse) return;

    let startOn = null;
    let endOn = null;

    if (selectedDates[0]) {
      startOn = selectedDates[0].startOn;
      endOn = selectedDates[0].endOn;
    }

    this.setState({
      startOn,
      endOn,
      endHighlight: endOn || startOn,
    });
  };

  goToPrev = () =>
    this.canGoToPrev() && this.setState({ currentMonth: this.state.currentMonth - 1 });
  goToNext = () =>
    this.canGoToNext() && this.setState({ currentMonth: this.state.currentMonth + 1 });

  visibleMonths = (): Array<BookableMonth> => {
    const { mask, maskSize, months } = this.props;
    const { currentMonth } = this.state;

    if (mask) {
      return months.slice(currentMonth, currentMonth + maskSize);
    }
    return months;
  };

  toPeriod = (startOn: moment, endOn?: moment): Period => ({
    startOn,
    endOn: endOn || null,
  });

  updateSparseDates = (selectedDates: Array<Period>, date: moment): Array<Period> => {
    if (this.isSelected(date)) return filter(selectedDates, ({ startOn }) => !startOn.isSame(date));
    return [...selectedDates, this.toPeriod(date, date)];
  };

  updateConsecutiveDates = (selectedDates: Array<Period>, date: moment): Array<Period> => {
    let startOn;
    let endOn;

    if (selectedDates[0]) ({ startOn, endOn } = selectedDates[0]);

    if (startOn && !endOn && !date.isBefore(startOn)) return [this.toPeriod(startOn, date)];

    return [this.toPeriod(date)];
  };

  handleInteraction = (e: any, date: moment): void => {
    const { onSelectionUpdate, selectedDates, sparse } = this.props;

    if (this.isMinimumBooking(date) && !this.isLastMinimum(date) && !this.state.endOn) return;

    if (sparse) onSelectionUpdate(this.updateSparseDates(selectedDates, date));
    else onSelectionUpdate(this.updateConsecutiveDates(selectedDates, date));
  };

  handleHighlight = (date: moment): void => {
    if (this.props.sparse) return;
    const { startOn, endOn } = this.state;

    if (startOn && endOn) {
      this.setState({
        endHighlight: null,
      });
    }

    if (startOn && !endOn) {
      if (!this.isMinimumBooking(date) || this.isLastMinimum(date)) {
        this.setState({
          endHighlight: moment.max(date, startOn),
        });
      } else {
        this.setState({
          endHighlight: startOn,
        });
      }
    }
  };

  isDisabled = (date: moment): boolean => {
    const month = find(this.props.months, m => m.start.isSame(startOfMonth(date)));

    if (month) {
      const monthDays = flatten(month.weeks);
      return find(monthDays, d => d.date.isSame(date)).disabled;
    }

    return true;
  };

  isMinimumBooking = (date: moment): boolean => {
    const { startOn } = this.state;

    if (startOn) {
      return date.isBetween(
        startOn,
        startOn.clone().add(this.props.minimumDurationInDays - 1, 'days'),
        null,
        '[]',
      );
    }

    return false;
  };

  isLastMinimum = (date: moment): boolean =>
    !!this.state.startOn &&
    date.isSame(this.state.startOn.clone().add(this.props.minimumDurationInDays - 1, 'days'));

  isSelected = (date: moment): boolean =>
    some(this.props.selectedDates, ({ startOn, endOn }: Period) => {
      if (startOn && endOn) return date.isBetween(startOn, endOn, null, '[]');
      return date.isSame(startOn);
    });

  isFirstSelected = (date: moment): boolean =>
    some(this.props.selectedDates, ({ startOn }: Period) => date.isSame(startOn, 'day'));

  isLastSelected = (date: moment): boolean =>
    some(this.props.selectedDates, ({ endOn }: Period) => date.isSame(endOn, 'day'));

  isHighlighted = (date: moment): boolean =>
    !!this.state.startOn &&
    !!this.state.endHighlight &&
    !this.state.endOn &&
    date.isBetween(this.state.startOn, this.state.endHighlight, null, '[]');

  isLastHighlighted = (date: moment): boolean =>
    date.isSame(this.state.endHighlight, 'day') ||
    (!!this.state.startOn &&
      !this.state.endOn &&
      !this.state.endHighlight &&
      date.isSame(this.state.startOn, 'day'));

  getDayState = (date: moment): DayState => ({
    isDisabled: this.isDisabled(date),
    isSelected: this.isSelected(date),
    isFirstSelected: this.isFirstSelected(date),
    isLastSelected: this.isLastSelected(date),
    isHighlighted: this.isHighlighted(date),
    isFirstHighlighted: false,
    isLastHighlighted: this.isLastHighlighted(date),
    isMinimumBooking: this.isMinimumBooking(date),
    isLastMinimum: this.isLastMinimum(date),
  });

  canGoToPrev = (): boolean => this.state.currentMonth > 0;
  canGoToNext = (): boolean => {
    const { maskSize, months } = this.props;
    const { currentMonth } = this.state;

    return currentMonth < months.length - maskSize;
  };

  handleCalendarScroll = (event: SyntheticEvent<HTMLButtonElement>): void => {
    this.calendarScrollWatcher.registerScrollEvent(event);
  };

  goToMonth = (month: number): void => {
    const { mask } = this.props;

    if (mask) {
      this.setState({ currentMonth: month });
    } else {
      this.scrollTriggeredByClickingOnLink = true;
      const monthId = `Calendar-calendar-month-${month}-container`;
      const calendarMonthContainer = document.getElementById(monthId);
      const calendarContainer = document.getElementById('Calendar-calendar-container');
      const dynamicBarChartContainer = this.dynamicBarChartContainerRef.current;

      if (!calendarMonthContainer || !calendarContainer || !dynamicBarChartContainer) {
        return;
      }

      const newScrollTop = calendarMonthContainer.offsetTop - dynamicBarChartContainer.offsetHeight;
      calendarContainer.scrollTop = newScrollTop;
    }
  };

  render() {
    console.log(this.visibleMonths());
    const { mask, direction } = this.props;
    const calendarBarChart = (
      <CalendarBarChart
        getVisibleMonth={this.visibleMonths}
        months={this.props.months}
        onClickOnMonth={this.goToMonth}
      />
    );
    return (
      <div className={css.calendarWrapper}>
        <div
          id="Calendar-calendar-container"
          className={css.root}
          onScroll={this.handleCalendarScroll}
        >
          <div ref={this.barChartContainerRef}>{calendarBarChart}</div>
          <div className={css.arrowButtons}>
            {mask && (
              <Fragment>
                <span
                  data-testid="calendar-prev-month-button"
                  onClick={this.goToPrev}
                  className={cx(css.prev, !this.canGoToPrev() && css.arrowDisabled)}
                >
                  <Icon name="chevron" className={css.prevIcon} />
                </span>
                <span
                  data-testid="calendar-next-month-button"
                  onClick={this.goToNext}
                  className={cx(css.next, !this.canGoToNext() && css.arrowDisabled)}
                >
                  <Icon name="chevron" className={css.nextIcon} />
                </span>
              </Fragment>
            )}
          </div>
          <div
            className={cx(
              css.monthsWrapper,
              this.props.direction === 'column'
                ? css.monthsWrapperVertical
                : css.monthsWrapperHorizontal,
            )}
          >
            {this.visibleMonths().map((month, index) => (
              <div
                key={month.start.format('YYYYMM')}
                className={css.monthWrapper}
                id={`Calendar-calendar-month-${index}-container`}
              >
                <CalendarMonth
                  month={month}
                  onHighlight={this.handleHighlight}
                  onInteraction={this.handleInteraction}
                  getDayState={this.getDayState}
                  DayComponent={CalendarDay}
                  direction={direction}
                />
              </div>
            ))}
          </div>
        </div>
        <div
          ref={this.dynamicBarChartContainerRef}
          className={cx(css.dynamicBarChartContainer, css.dynamicBarChartContainerTransition)}
        >
          <div className={css.dynamicBarChart}>{calendarBarChart}</div>
        </div>
      </div>
    );
  }
}

我可以通过执行以下操作来获取视口中当前显示的月份:

// @flow
import React, { Component } from 'react';
import moment from 'utils/moment/moment';
import i18n from 'utils/i18n/i18n';
import css from './CalendarBarChart.css';
import type { BookableMonth } from './types';
import cx from 'classnames';
import flatten from 'lodash/flatten';

const t = i18n.withPrefix('client.apps.space');

type Props = {
  months: Array<BookableMonth>,
  numberOfDisplayableMonths: number,
  minPercentage: number,
  remainingPercentage: number,
  onClickOnMonth: (month: number) => void,
  getVisibleMonth: () => Array<BookableMonth>,
};

export default class CalendarBarChart extends Component<Props> {
  static defaultProps = {
    numberOfDisplayableMonths: 12,
    minPercentage: 20,
    remainingPercentage: 80,
  };

  onClickOnMonth = (event: SyntheticEvent<*>): void => {
    const { onClickOnMonth } = this.props;
    if (!onClickOnMonth) return;
    const dataMonth = event.currentTarget.getAttribute('data-month');
    const month = parseInt(dataMonth, 10);
    onClickOnMonth(month);
  };

  convertToAverageByMonth = (months: Array<BookableMonth>): { [key: string]: number } =>
    months.slice(0, this.props.numberOfDisplayableMonths).reduce(this.averageMonths, {});

  averageMonths = (accumulator: Object, month: BookableMonth): { [key: string]: number } => {
    /* eslint-disable no-param-reassign */
    const today = moment();
    const currentMonth = month.start.month();
    const validDays = flatten(month.weeks).filter(
      day => currentMonth === day.date.month() && day.date.isSameOrAfter(today, 'day'),
    );
    accumulator[month.start.format('MMM')] =
      validDays.reduce((average, day) => average + day.price.amount, 0) / validDays.length;
    return accumulator;
  };

  barPercentage = (month: string, averageByMonth: Object) => {
    const averages: Array<number> = Object.values(averageByMonth);
    const min = Math.min(...averages);
    const max = Math.max(...averages);
    if (min === max) return this.props.minPercentage + this.props.remainingPercentage;
    const difference = max - min;
    const monthAverage = averageByMonth[month];
    return (
      this.props.minPercentage + (monthAverage - min) / difference * this.props.remainingPercentage
    );
  };

  isDynamic = (lowestPrice: number, averageByMonth: Object) =>
    !Object.values(averageByMonth).every(price => price === lowestPrice);

  render() {
    const averageByMonth = this.convertToAverageByMonth(this.props.months);
    const averages: Array<number> = Object.values(averageByMonth);
    const lowestPrice = Math.min(...averages);
    const dynamicPricing = this.isDynamic(lowestPrice, averageByMonth);

    return dynamicPricing ? (
      <div>
        <ul className={css.chart}>
          {Object.keys(averageByMonth).map((month, index) => (
            <li
              className={css.barPosition}
              key={month}
              data-month={index}
              onClick={this.onClickOnMonth}
            >
              <span
                style={{ height: `${this.barPercentage(month, averageByMonth)}%` }}
                className={cx(css.bar, lowestPrice === averageByMonth[month] && css.cheapestBar)}
                title={month}
              />
            </li>
          ))}
        </ul>
        <div className={css.chartLegend}>
          <div className={css.bulletPoint} />
          <span>{t('calendar_modal.lowest_price')}</span>
        </div>
      </div>
    ) : null;
  }
}

但是我该如何使用它并检查跨度中的{this.props.getVisibleMonth().map(month => <li>{month.start.format('MMMM')}</li>)} 并在{month}时分配css

0 个答案:

没有答案