Сопоставьте массив для возврата компонентов с разными именами классов

#javascript #reactjs

Вопрос:

Я пытаюсь создать приложение, которое создаст компонент с 7-сегментным светодиодным дисплеем, но на светодиодном дисплее должен отображаться введенный номер. Я пытался найти в этом логику, но это не работает.

Логические:

  1. Для каждого введенного числа создайте компонент.
  2. Внутри этого компонента у него есть a switch , и в зависимости от введенного числа он добавит className к каждому из них тот , который содержит отключенный стиль div .

И когда я пытаюсь ввести только одно число, оно отлично работает! Но когда я пытаюсь сделать это с двумя цифрами, это просто ошибка. Как вы можете видеть на изображениях:

Один номер
Один номер

У меня всего два компонента: <App/> и <Numbers/> .

Апп:

 function App() {
  const [input, setInput] = useState('');
  const [number, setNumber] = useState([]);

  const changeNumbers = () => {
    // Splitting inputted numbers to add each number to array.
    const inputSplit = input.toString().split('');
    const inputArray = inputSplit.map(Number);
    setNumber(inputArray);
  }

  return (
    <div>      
      <input onChange={(e) => {setInput(e.target.value)}}/>
      <button onClick={() => {changeNumbers()}}>Confirm</button>

      { 
        number.length ? 
        number.map((singleNumber, idx) => (
          <Numbers singleNumber={singleNumber} key={idx} />
        ))
        :
        <Numbers singleNumber={0} /> 
      }
    </div>
  );
}
 

Числа:

 const Numbers = ({ singleNumber }) => {
    const switchDisabled = (element) => {                
        const segmentB = document.querySelector('.segment-b');
        const segmentC = document.querySelector('.segment-c');        
        const segmentF = document.querySelector('.segment-f');        

        switch(element) {
            case 2:
                segmentC.classList.add('segment-disabled');
                segmentF.classList.add('segment-disabled');
                break;
            case 6:
                segmentB.classList.add('segment-disabled');
                break;            
            default:                
                break;
        }
    }

    useEffect(() => {
        switchDisabled(singleNumber);
    }, [singleNumber]);

    return (
        <div className="container">
            <div className="segment segment-a"></div>
            <div className="segment segment-b"></div>
            <div className="segment segment-c"></div>
            <div className="segment segment-d"></div>
            <div className="segment segment-e"></div>
            <div className="segment segment-f"></div>
            <div className="segment segment-g"></div>
        </div>
    )
}
 

Стили:

 .container {
    width: 110px;
    height: 160px;
    position: relative; }        

.segment {
    position: absolute;
    background-color: red; }

.segment-a {    
    top: 0; 
    left: 10px;     
    width: 90px;
    height: 10px; }

.segment-b {
    top: 13px;
    right: 0;
    height: 60px;
    width: 10px; }

.segment-c {
    bottom: 13px;
    right: 0;
    height: 60px;
    width: 10px; }

.segment-d {
    bottom: 0; 
    left: 10px;     
    width: 90px;
    height: 10px; }

.segment-e {
    bottom: 13px;
    left: 0;
    height: 60px;
    width: 10px; }

.segment-f {
    top: 13px;
    left: 0;
    height: 60px;
    width: 10px; }

.segment-g {
    top: 75px; 
    left: 16px;     
    width: 80px;
    height: 10px; }

.segment-disabled {opacity: 0.1;}
 

Obs: Я не разместил imports и exports , чтобы сэкономить место.

Ответ №1:

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

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

Вот пример codesandbox со светодиодным дисплеем codesandbox

App.js

 import { useState } from "react";
import Number from "./Number";

function App() {
  const [input, setInput] = useState("0");

  return (
    <>
      <input
        onChange={(e) => {
          setInput(e.target.value);
        }}
      />
      {input.split("").map((number, i) => (
        <Number key={i} number={number} />
      ))}
    </>
  );
}

export default App;
 

Number.js

 const Number = ({ number }) => {
  let segments = [];

  switch (number) {
    case "1":
      segments = ["b", "c"];
      break;
    case "2":
      segments = ["a", "b", "d", "e", "g"];
      break;
    case "3":
      segments = ["a", "b", "c", "d", "g"];
      break;
    case "4":
      segments = ["b", "c", "f", "g"];
      break;
    case "5":
      segments = ["a", "c", "d", "f", "g"];
      break;
    case "6":
      segments = ["a", "c", "d", "f", "e", "g"];
      break;
    case "7":
      segments = ["a", "b", "c"];
      break;
    case "8":
      segments = ["a", "b", "c", "d", "e", "f", "g"];
      break;
    case "9":
      segments = ["a", "b", "c", "d", "f", "g"];
      break;
    case "0":
      segments = ["a", "b", "c", "d", "e", "f"];
      break;
    default:
      segments = ["a", "b", "c", "d", "e", "f"];
  }

  return (
    <div className="container">
      {segments.map((segment) => (
        <div key={segment} className={`segment segment-${segment}`}></div>
      ))}
    </div>
  );
};

export default Number;
 

Ответ №2:

Проблема в том, что document.querySelector вызывается в Numbers компоненте для получения сегмента, например

 const segmentB = document.querySelector('.segment-b');
 

Этот вызов найдет первый элемент DOM на всей странице , содержащий класс segment-b , который затем означает, что при отображении 2-го числа изменяются сегменты неправильного номера. Вы можете проверить это, введя двузначное число, где обе цифры совпадают, например, 66 — первое число должно быть отображено 6 , а 2-е число будет неправильным.

Вместо использования document.querySelector с classList.add и classList.remove вы могли бы вместо этого использовать динамическую переменную для вычисления классов отключенных сегментов, необходимых для каждого числового компонента, которые затем отображаются в этом конкретном числовом компоненте:

 const Numbers = ({ singleNumber }) => {
  const switchDisabled = (element) => {
    const allSegments = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
    let disabledSegments = [];

    switch (element) {
      case 2:
        disabledSegments.push("c");
        disabledSegments.push("f");
        break;
      case 6:
        disabledSegments.push("b");
        break;
      default:
        break;
    }
  };

  return (
    <div className="container">
      {allSegments.forEach(segmentLetter => {
        const disabledClass = disabledSegments.includes(segmentLetter) ? 'segment-disabled' : '';

        return (
          <div className={`segment segment-${segmentLetter} ${disabledClass}`} />
        )
      })}
    </div>
  );
};

 

P.S. в общем, вы хотите в 99,99% случаев звонить document.querySelector в react из-за подобных проблем