реагирующий рендеринг и состояние

#javascript #reactjs #state

#javascript #reactjs #состояние

Вопрос:

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

У меня есть набор вопросов, и я хочу выбрать случайное число от 0 до длины вопроса (в моем случае 8) без повторения чисел.

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

 const SomeComponent = props => {
  const [random, setRandom] = useState(null)

  function getRandomNumber(min, max) {
    let stepOne = max - min   1;
    let stepTwo = Math.random() * stepOne;
    let result = Math.floor(stepTwo)   min;

    return result
  }

  // this creates the array[0, 1, 2, 3, 4, 5, 6, 7]
  function createArrayOfNumbers(start, end) {
    let myArray = [];

    for (let i = start; i <= end; i  ) {
      myArray.push(i)
    }

    return myArray
  }

  let numbers = []

  function generator() {
    let numbersArray = createArrayOfNumbers(0, qAndA.length - 1)
    let finalNumbers = [];

    while (numbersArray.length > 0) {
      let randomIndex = getRandomNumber(0, qAndA.length - 1)
      let randomNumber = numbersArray[randomIndex];
      numbersArray.splice(randomIndex, 1);
      finalNumbers.push(randomNumber)
    }
    for (let nums of finalNumbers) {
      if (typeof nums === 'number') {
        numbers.push(nums)
        // for some reason i was getting undefined for a few so i weeded them out here and pushed into a new array
      }
    }
    const tester = numbers.splice(0, 1) ** this part works fine and
    console.log(tester) // each time my button is pressed it console logs a non repeating random number until ALL numbers(from 0 to 7 are chosen)
    setRandom(tester) // THIS IS THE LINE THAT SCREWS IT UP.re rendering seems to toss all the previous logic out...
  }


  return (<button onClick={generator}>click this to call function</button>)
}
  

Все до последней строки работает.

Это дает мне случайное число и кнопку, которая у меня есть, которая выполняет функцию (таким образом, давая случайное число)

НЕ повторяет случайные числа и выдает мне что-то вроде 0, затем 4, затем 1 и т.д. Каждый раз, когда я нажимаю на него, пока он не выдаст мне все возможные числа от 0 до 7.

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

Чтобы кое-что прояснить: это нужно сделать с помощью state, потому что я хочу установить случайный вопрос в это состояние случайного числа, а затем отобразить случайный вопрос без его повторения (подумайте о базовом тесте).

Я также не хочу, чтобы количество чисел было установлено или определено. Он должен работать динамически, учитывая, что со временем я буду добавлять больше вопросов в тест.

Комментарии:

1. Послушайте, вы упускаете один важный момент: вы действительно теряете все при повторном запуске. вы можете сохранить свой массив «использованных чисел» в memo-хуке, или вы также можете сохранить свой массив в state-хуке. P.s. Я бы рекомендовал вам также попробовать Angular, чтобы у вас была возможность сравнить фреймворки.

2. Не связано, но публикация JS-кода, который на самом деле не является JS-кодом (используйте комментарии JS), и отсутствие пробелов как в коде, так и в тексте усложняют рассуждения, а не облегчают.

3. да, на самом деле это смешно, как ты теряешь все при повторном рендеринге… реакция в этом отношении довольно глупая. также передача реквизитов другому компоненту — это такой кошмар. react заставляет вас структурировать код таким специфическим образом, что это становится такой головной болью и вызывает ооочень много переписываний. но в любом случае, спасибо, я рассмотрю этот хук для заметок. я очень новичок в react, поэтому давайте посмотрим, поможет ли это! Спасибо

4. @DaveNewton я действительно новичок в stack overflow, так что извините за это. было бы здорово, если бы вы могли просмотреть его, хотя я пытался добавить несколько комментариев…

5. Я думаю, что вы чрезмерно усложняете это (и имеете несвязанную функциональность в компоненте). Если единственный смысл состоит в том, чтобы взять список вопросов и получать новый случайный вопрос при каждом нажатии, просто создайте массив вопросов, перетасуйте его и при каждом нажатии извлекайте вопрос из ранее перетасованного списка.

Ответ №1:

Посмотрите эту демонстрацию, которую я собрал вместе: https://codesandbox.io/s/competent-pine-0hxxi?file=/src/index.tsx

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

Ваш подход выглядит следующим образом:

 component => create data => render
  

Когда часто лучший способ сделать это выглядит так:

 receive data => component => render
  

Я думаю, что ваша проблема на самом деле заключается в том, «Как мне перетасовать массив элементов?». Тогда остальная часть вашей проблемы заключается в том, как вы решаете ее представить, и в реакции на взаимодействие с пользователем.

Прежде чем мы начнем думать о вашем компоненте, ваши вопросы где-то определены. Давайте назовем этот начальный пул вопросов data .

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

С этими данными мы можем сказать: «ХОРОШО, наше начальное состояние для представления — это случайный порядок этих данных, он же «перетасованный»».

   // given our question pool `data`
  // we can simply set the initial state to a shuffled version
  const [questions, setQuestions] = React.useState<Question[]>(
    shuffle([...data])
  );
  

Хорошо, итак, у нас есть наши «случайные» (перемешанные) вопросы. Нам больше не нужно беспокоиться об этом вообще.

Если я пропустил тот факт, что вы хотите, чтобы он продолжал перетасовывать вопросы по мере получения ответа на каждый, с удовольствием продолжу свой ответ.

Теперь все, что нам нужно сделать, это отобразить их так, как нам нравится. Если мы показываем только один вопрос за раз, нам нужно отслеживать это.

   // keep track of which question we're displaying right now
  const [qIndex, setQIndex] = React.useState<number>(0);
  

Когда пользователь выбирает или дает ответ на вопрос, мы можем просто заменить этот вопрос на наш ответный вопрос. Именно так любит работать React state; не изменяйте то, что у вас уже есть, просто бросайте все на это снова и снова.

   const handleAnswerChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    // create our updated question
    // now with an answer
    const theQuestion = questions[qIndex];
    const answeredQuestion = {
      ...theQuestion,
      answer: event.target.value
    };
    // copy our questions, and flip the old question for the new one
    const newQuestions = [...questions];
    newQuestions.splice(qIndex, 1, answeredQuestion);
    setQuestions(newQuestions);
  };
  

Остальное просто зависит от того, чтобы позволить пользователям перемещаться по вашему набору вопросов. Вот полный компонент:

 interface QuizProps {
  data: Question[];
}

export const Quiz = (props: QuizProps) => {
  // keep track of which question we're displaying right now
  const [qIndex, setQIndex] = React.useState<number>(0);

  // given our question pool `data`
  // we can simply set the initial state to a shuffled version
  const [questions, setQuestions] = React.useState<Question[]>(
    shuffle([...props.data])
  );

  // create our updated question
  // now with an answer
  const handleAnswerChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const theQuestion = questions[qIndex];
    const answeredQuestion = {
      ...theQuestion,
      answer: event.target.value
    };
    // copy our questions, and flip the old question for the new one
    // using slice (there are many ways to do this)
    const newQuestions = [...questions];
    newQuestions.splice(qIndex, 1, answeredQuestion);
    setQuestions(newQuestions);
  };

  const handleBackClick = () => setQIndex((i) => (i > 0 ? i - 1 : 0));
  const handleNextClick = () =>
    setQIndex((i) => (i < questions.length - 1 ? i   1 : i));

  return (
    <div>
      <h1>Quiz</h1>
      <div>
        <h2>{questions[qIndex].title}</h2>
        <h3>
          Question {qIndex   1} of {questions.length}
        </h3>
        <p>{questions[qIndex].description}</p>
        <ul>
          {questions[qIndex].options.map((answer, i) => (
            <li key={i}>
              <input
                id={answer.id}
                type="radio"
                name={questions[qIndex].id}
                checked={answer.id === questions[qIndex]?.answer}
                value={answer.id}
                onChange={handleAnswerChange}
              />
              <label htmlFor={answer.id}>{answer.value}</label>
            </li>
          ))}
        </ul>
      </div>
      <button onClick={handleBackClick}>Previous</button>
      <button onClick={handleNextClick} disabled={!questions[qIndex].answer}>
        Next
      </button>
    </div>
  );
};
  

Комментарии:

1. Извините за машинопись, но лично мне гораздо проще понять, что происходит с необработанным JS.

2. я не понимаю typescript и не изучал его. я ценю время, которое вы потратили, чтобы ответить на это, но tbh это не очень помогает и кажется несколько иным, чем то, что я спрашиваю. например, когда я запускаю ваш код, он не меняет порядок вопросов каждый раз. возможно, вам помогут вопросы [qindex].id . спасибо за приложенные усилия! я просмотрю это еще раз и посмотрю, смогу ли я понять это лучше во второй раз

3. Когда порядок вопросов должен измениться?

4. Здесь та же песочница со статическими вопросами и без TS 🙂 codesandbox.io/s/epic-franklin-1l0kj?file=/src/index.js

5. Добро пожаловать, мой чувак, это действительно приятно слышать. Я боялся, что переступил черту, перейдя к большому рефакторингу вместо того, чтобы ответить на ваш вопрос напрямую. Я профессионально занимаюсь разработкой интерфейсов всего ~ 3 года, но у меня гораздо больший опыт в дизайне и CSS. Я мог читать JS до этого, но не мог хорошо его написать. Я только углубляюсь в TypeScript (настоятельно рекомендую) с тех пор, как начал работать штатным разработчиком интерфейса. 8 месяцев — это совсем немного времени, конечно, совершенно нормально чувствовать себя потерянным. Продолжайте в том же духе, рано или поздно вы найдете нужный подход!

Ответ №2:

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

Создайте функцию, которая генерирует случайное значение и поддерживает свой собственный кэш последнего сгенерированного случайного числа.

 const randomNumberGenerator = () => {
  let previous;

  return max => {
    let randomNumber;
    do {
      randomNumber = Math.floor(Math.random() * max);
    } while(randomNumber === previous);
    previous = randomNumber;
    return randomNumber;
  }
};
  

Затем в вашем компоненте react, когда вам нужно захватить случайное, непоследовательное значение:

 const getRandomNumber = randomNumberGenerator();

...

getRandomNumber(8)
getRandomNumber(10)
// etc..
  

Редактировать реагирующий рендеринг и состояние

Если вы знаете, что ваш max никогда не изменится, т. Е. Он всегда будет равен 8, тогда вы можете перейти max к функции внешней фабрики.

 const randomNumberGenerator = max => {
  let previous;

  return () => {
    let randomNumber;
    do {
      randomNumber = Math.floor(Math.random() * max);
    } while(randomNumber === previous);
    previous = randomNumber;
    return randomNumber;
  }
};
  

Тогда использование в коде немного упрощается.

 const getRandomNumber = randomNumberGenerator(8);

...

getRandomNumber()
  

Если вам все еще нужно обрабатывать диапазон случайных чисел, добавьте min значение в функцию и вычислите случайное значение в диапазоне.

 const randomNumberGenerator = () => {
  let previous;

  return (min, max) => {
    let randomNumber;
    do {
      randomNumber = Math.floor(Math.random() * (max - min)   min);
    } while (randomNumber === previous);
    previous = randomNumber;
    return randomNumber;
  };
};
  

Комментарии:

1. спасибо @drew reese, но причина, по которой мне нужно сохранить его в состоянии, заключается в том, что позже я хочу отобразить случайный вопрос, связанный со случайным номером состояния… я хочу установить случайное состояние, а затем задать случайный вопрос для этого состояния, чтобы сделать что-то вроде этого:

2. @Roberto’Toole Ничто не мешает вам сохранять текущее случайное значение в состоянии для использования другими частями пользовательского интерфейса, поскольку проблема заключается в том, как вы кэшируете ранее использованные числа, это должно сохраняться в циклах рендеринга.

3. @Roberto’Toole Ну, перехватчики реакции, контекстный API и redux — это все три довольно разные технологии. Redux построен поверх контекстного API, но он больше относится к шаблону редуктора, то есть к чистым функциям, которые используют объект текущего состояния и действие для воздействия на состояние и возвращает следующее состояние. Это очень простой синхронный способ обновления состояния. Перехваты реакции больше касаются жизненного цикла компонента. И контекстный API — это инструмент, помогающий решить проблему детализации реквизитов, поэтому вам не нужно явно передавать данные через props на каждом уровне компоненту, который заботится об этом.

4. @Roberto’Toole Взгляните на redux-toolkit он был разработан как способ попытаться сократить большую часть шаблонного интерфейса react-redux, и он принадлежит той же команде, поэтому он должен хорошо работать из коробки.

5. @Roberto’Toole Надеюсь, это ответит на ваш вопрос, но используйте лучший инструмент для работы / требований. Вы можете использовать оба. Простым примером использования состояния локального компонента может быть удержание текущей активной вкладки на определенной странице. Поскольку никакая другая страница / компонент не заботится об этом, имеет смысл ограничить область видимости и сохранить ее в состоянии компонента. Он будет «сбрасываться» при каждой загрузке страницы. Но возьмем тот же пример, но теперь ваше подразделение решает, что они хотят, чтобы приложение «запоминало» активную вкладку при следующем посещении пользователем этой страницы, тогда вам нужно будет сохранить это в состоянии приложения. Имеет ли это смысл?

Ответ №3:

Попробуйте этот хук для сохранения списка посещенных номеров:

const [numbers, setVisited] = useState([]) …..

добавьте свой номер в массив «посещенных» и клонируйте массив одним нажатием кнопки (реагируйте на хаки / способы повторного отображения, пока ссылки сравниваются)

setVisited(numbers.slice(0))