близкие. У меня, наверное, нубский вопрос, но все же сниму. Приведенные ниже фрагменты кода довольно просты, и у меня не должно возникнуть проблем, но начнем. Я пытаюсь получить список цветов в компоненте ColorsGrid
. Короче говоря, когда пользователь изменяет уровень сложности через раскрывающийся список, должен быть сгенерирован новый набор цветов и, таким образом, отображен. Я думал, что это довольно простое упражнение, но все работает не так, как ожидалось. Всякий раз, когда я меняю сложность, он не реагирует (перерисовывает компонент ColorsGrid
), и только после того, как я снова выбираю другой (сложность) уровень, предыдущий вызывает повторную визуализацию. Например, если я выберу «Средний» после начального рендеринга (уровень по умолчанию установлен на «Легкий»), ничего не изменится. Однако, если я вернусь к Легкому (или выберу любую другую сложность), то происходят ли изменения, соответствующие предыдущей (Средней сложности), т.е. ColorsGrid перерисовывает и, таким образом, отображает сетку, соответствующую средней сложности. Что я делаю неправильно?
Ниже приведен соответствующий код.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// Get random rgb color
function randomColor() {
let r = Math.floor(Math.random() * 256);
let g = Math.floor(Math.random() * 256);
let b = Math.floor(Math.random() * 256);
return `rgb(${r}, ${g}, ${b})`;
}
// Set length of color list array as a funciton of difficulty
function colorsListLength(difficulty) {
switch (true) {
case difficulty === 'expert':
return 25;
case difficulty === 'hard':
return 20;
case difficulty === 'medium':
return 10;
default:
return 5;
}
}
// Get color list array
function colorsList(colorsLength = 5) {
const colors = [];
while (colors.length < colorsLength) {
colors.push(randomColor());
}
return colors;
}
// Set random color to guess from (above) color list array
function randomColorToGuess(colors) {
const index = Math.floor(Math.random() * colors.length);
return colors[index];
}
// Set number of game tries as a function of difficulty
function numberOfTries(difficulty) {
switch (true) {
case difficulty === 'expert' || difficulty == 'hard':
return 2;
case difficulty === 'medium':
return 1;
default:
return 0;
}
}
// Colors grid component
function ColorsGrid({ difficulty, colorsList }) {
return (
<div>
<strong>Colors Grid</strong>
<p>Difficulty: {difficulty}</p>
<div>
{colorsList.length > 0 ? (
colorsList.map(color => (
<div
style = {{
backgroundColor: color,
height: '3rem',
width: '3rem',
borderRadius: '50%',
}}
key = {color}
/>
))
) : (
<div>Loading colors...</div>
)}
</div>
</div>
);
}
// Main component
class App extends Component {
constructor(props) {
super(props);
this.state = {
difficulty: 'easy',
colorsList: [],
};
this.colorsArray = this.colorsArray.bind(this);
this.handleChangeEvent = this.handleChangeEvent.bind(this);
}
componentDidMount() {
this.colorsArray(this.state.difficulty);
}
colorsArray() {
const colors = colorsList(colorsListLength(this.state.difficulty));
const colorToGuess = randomColorToGuess(colors);
this.setState(() => ({
colorsList: colors,
gameTries: numberOfTries(this.state.difficulty),
colorToGuess,
}));
}
handleChangeEvent(e) {
this.setState({
difficulty: e.target.value,
});
this.colorsArray(this.state.difficulty); // I was under the impression the (difficulty) state had already been updated here
}
render() {
return (
<div className = "App">
<h1>Colors</h1>
<div style = {{ textAlign: 'right' }}>
<select
id = "difficulty"
value = {this.state.difficulty}
onChange = {this.handleChangeEvent}
>
<option value = "easy">Easy</option>
<option value = "medium">Medium</option>
<option value = "hard">Hard</option>
<option value = "expert">Expert</option>
</select>
</div>
<ColorsGrid
colorsList = {this.state.colorsList}
difficulty = {this.state.difficulty}
/>
</div>
);
}
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
setState
является асинхронной операцией, поэтому this.state.difficulty
может быть установлен или не установлен при вызове this.colorsArray()
Кроме того, switch(true)
в colorsListLength
мне кажется запахом кода. Почему бы не просто switch
на difficulty
?
@RobinZigmond Что теперь сказать? Не могли бы вы рассказать об этом подробнее?
Я просто не понимаю, почему у вас есть switch(true)
с падежами, которые имеют форму case difficulty === xxx
, когда естественный способ сделать это switch(difficulty)
с падежами case 'medium'
, case 'difficult'
и т. д.
@RobinZigmond Да, я понял. В основном я использовал переключатель так, как он был в коде. Думаю, это сделало его немного многословным. Спасибо!
Это потому, что setState()
это асинхронный:
setState(newState, callback);
Чтобы получить только что выбранную сложность, вам нужно изменить код следующим образом:
this.setState({
difficulty: e.target.value,
}, () => this.colorsArray(this.state.difficulty)
);
Ага! Это работает. У меня была другая версия этого, но что-то не так, и поэтому она выдавала ошибку. Спасибо!
Проблема заключается в координации ваших вызовов setState. Следующее проясняет ситуацию:
colorsArray(difficulty) {
const colors = colorsList(colorsListLength(difficulty));
const colorToGuess = randomColorToGuess(colors);
this.setState(() => ({
difficulty,
colorsList: colors,
gameTries: numberOfTries(this.state.difficulty),
colorToGuess
}));
}
handleChangeEvent(e) {
this.colorsArray(e.target.value);
}
Вы можете видеть, что ваш обработчик событий делает один вызов функции обновления цвета. Затем он обрабатывает новые цвета и устанавливает состояние в одном месте.
Используйте обратный вызов для
setState
, а внутри обратного вызова вместоthis.state
используйтеstate
из аргументов reactjs.org/docs/react-component.html#setstate