1

我正在尝试显示时间列表(例如 07:00、07:30),但是当出现重复时间时,显示其旁边的重复次数(例如 07:30、08:00³)

当我遍历一个列表时,每个项目都需要自己的状态,以便可以在每个时间旁边设置和显示计数器

目前,我遇到了太多重新渲染的问题,但我也不确定我的减速器是否正确

没有任何注释的代码可以在这个 repo 中看到:https ://github.com/charles7771/decrease-number-wont-work/blob/master/index.js

const TimeGrid = () => {

  const reducer = (state, action) => {
    switch(action.type) {
      case 'SET_COUNTER':
        return {
          ...state,
          [`counter${action.id}`]: action.payload
        }
        default:
          return state
    }
  }

  //not sure if this bit is correct
  let [{ counter }, dispatchReducer] = useReducer(reducer, {
    counter: '',
  })

我的上下文导入和 allBookedTimes

const { theme, timesUnavailable, 
        removeFromTimesUnavailable, 
        addToTimesUnavailable } = useContext(Context)

const allBookedTimes = allBookings.map(element => element.time)
//below, both have been mapped out of a JSON file
const extractedTimesOnly = availableTimesJSON.map(item => item.time)
const availableTimes = availableTimesJSON.map(item => item)

我有有用的功能来计算时间重复的次数

  //used to count instances. (e.g. 22:30: 3, 23:00: 1)
  const counts = {}
  extractedTimesOnly.forEach(x => {
    counts[x] = (counts[x] || 0) + 1
  })

  //used to not repeat a Time
  const timeAlreadyDisplayed = []

这就是我用来循环遍历 Time 列表并显示每个 Time 及其旁边的计数器的逻辑,以及尝试通过单击来减少计数器。

  const displayAvailableTimes = availableTimes.map((item, index) => {
    //tries to set the value of counter0 (or counter1, ... counterN) 
    //to the number of instances it appears,
    //too many rerenders occurs...
    dispatchReducer({
      type: 'SET_COUNTER',
      id: item.id,
      payload: counts[`${item.time}`] //doesn't seem to be working. tried logging it and it shows nothing
    })

    //counter = counts[`${item.time}`] -----> works, but then I am not doing this through the dispatcher

    //maybe this logic could be flawed too?
    if (index > 0 &&
      item.time === availableTimes[index - 1].time &&
      item.time !== availableTimes[index - 2].time) {
      return (
          //tries to show the counter below
        <span> {counter} </span>
      )
    }
    else if (item.time > currentTime - 10 && !timeAlreadyDisplayed[item.time]) {
      timeAlreadyDisplayed[item.time] = true
      return (
        <li
          key={item.id}
          id={item.id}
          onClick={() => {
            //tries to decrease the counter, I don't think it works
            counter > 1 ? dispatchReducer({
              type: 'SET_COUNTER',
              id: item.id,
              payload: counter - 1
            }) :
              allBookedTimes.includes(item.time) && item.day === 'today'
                ? void 0
                timesUnavailable.includes(item)
                  ? removeFromTimesUnavailable(item)
                  : addToTimesUnavailable(item)
          }}>
          {item.time}
        </li>
      )
    } 
    return null
  })

  return (
    <>
      <ul>{displayAvailableTimes}</ul>
    </>
  )
}
4

2 回答 2

3

我会给你一些关于计算时间和减少点击值的意见。我解释了您的代码中的主要问题,并提供了一种不同的实现方法,使您可以继续您的业务逻辑。

1. 正确访问counts

循环使用数组的forEach值作为counts对象的键。您似乎更愿意使用该x.time值,因为这是您以后访问它的方式(payload: counts[${item.time} ])。x本身就是一个对象。

2.正确使用useReducer

useReducer在返回数组的第一项中为您提供一个状态对象。您立即使用{ counter }. 该计数器变量的值是初始值 ( '')。您的 reducer 使用 形式的键在状态对象中设置值counter${action.id},因此分解后的counter变量不会改变。

我想你想要这样的东西:

const [counters, dispatchReducer] = useReducer(reducer, {}); // not decomposed, the counters variable holds the full state of all counters you add using your `SET_COUNTER` action.

稍后,当您尝试渲染您当前所做的计数器时{ counter },它始终为空(''),因为这仍然指的是您的原始初始状态。现在,在counters保持完整状态的情况下,您可以counters使用其 id 访问当前项目的对象的计数器:

    {if (index > 0 &&
      item.time === availableTimes[index - 1].time &&
      item.time !== availableTimes[index - 2].time) {
      return (
        <span> {counters[`counter${item.id}`]} </span>
      )
    }

3. 通用代码结构

还有更多问题,代码非常疯狂且难以理解(例如,因为以令人困惑的方式混合概念)。即使您修复了上述观察结果,我怀疑它会导致您想要做的事情或您曾经能够维持的事情。所以我想出了一个不同的代码结构,它可能会给你一种新的方式来思考如何实现它。

你不需要useReducer,因为你的状态很平坦。Reducers 更适合更复杂的状态,但最终它仍然是本地组件状态。

我不知道您在单击项目时究竟想要实现什么,所以我只是减少了计数,因为我认为这就是这个问题的意义所在。

这是以下代码的代码框: https ://codesandbox.io/s/relaxed-roentgen-xeqfi?file=/src/App.js

import React, { useCallback, useEffect, useState } from "react";

const availableTimes = [
  { time: "07:30" },
  { time: "08:00" },
  { time: "08:00" },
  { time: "08:00" },
  { time: "09:30" },
  { time: "10:00" }
];

const CounterApp = () => {
  const [counts, setCounts] = useState({});
  useEffect(() => {
    const counts = {};
    availableTimes.forEach(x => {
      counts[x.time] = (counts[x.time] || 0) + 1;
    });
    setCounts(counts);
  }, []);

  const onClick = useCallback(time => {
    // Your logic on what to do on click goes here
    // Fore example, I only reduce the count of the given time.
    setCounts(prev => ({
      ...prev,
      [time]: prev[time] - 1,
    }));
  }, []);

  return (
    <div>
      <h2>Counts:</h2>
      <ul>
        {Object.keys(counts).map(time => (
          <li key={time} onClick={() => onClick(time)}>
            {time} ({counts[time]})
          </li>
        ))}
      </ul>
    </div>
  );
};

export default CounterApp;
于 2020-04-19T01:24:05.653 回答
1

您在减速器中设置状态的方式与您检索它的方式不匹配。dispatchReducer由于您多次调用(对于 中的每个元素一次),您也得到了太多的重新渲染availableTimes。所有的逻辑都displayAvailableTimes应该在初始化 reducer 的状态时发生。

  const reducer = (state, action) => {
    switch(action.type) {
      case 'SET_COUNTER':
        return {
          ...state,
          [`counter${action.id}`]: action.payload
        }
        default:
          return state
    }
  }

  const counts = {}
  extractedTimesOnly.forEach(x => {
    counts[x] = (counts[x] || 0) + 1
  })

  const init = (initialState) => availableTimes.reduce((accum, item, index) => ({
      ...accum,
      `counter${item.id}`: counts[`${item.time}`]
  }), initialState);

  let [state, dispatchReducer] = useReducer(reducer, {
    counter: '',
  }, init)
const displayAvailableTimes = availableTimes.map((item, index) => {
  if (index > 0 &&
    item.time === availableTimes[index - 1].time &&
    item.time !== availableTimes[index - 2].time) { //An array out of bounds error could happen here, FYI
    return (
      <span> {state[`counter${item.id}`]} </span>
    )
  } else if (item.time > currentTime - 10 && !timeAlreadyDisplayed[item.time]) {
    timeAlreadyDisplayed[item.time] = true
      return (
        <li
          key={item.id}
          id={item.id}
          onClick={() => {
            state[`counter${item.id}`] > 1 ? dispatchReducer({
              type: 'SET_COUNTER',
              id: item.id,
              payload: state[`counter${item.id}`] - 1
            }) :
              allBookedTimes.includes(item.time) && item.day === 'today'
                ? void 0  //did you miss a colon here?
                timesUnavailable.includes(item)
                  ? removeFromTimesUnavailable(item)
                  : addToTimesUnavailable(item)
          }}>
          {item.time}
        </li>
      )
    } 
});

这将解决您现在面临的问题。但是,如果这就是你使用它的全部,你真的不需要减速器。请参考 Stuck 的答案,以了解如何更好地构建它,使其更具可读性和可维护性。

于 2020-04-19T01:32:45.037 回答