React Hook для повторного рендеринга состояния, обновляемого асинхронной рекурсивной функцией - PullRequest
0 голосов
/ 01 мая 2020

Я создаю приложение реагирования, которое решает судоку и визуализирует алгоритм решения.

Я пытаюсь преобразовать компоненты класса в функциональные компоненты, но не уверен, как получить изменения состояния работает.

Код проблемного c:

  async solve() {
    await this.sleep(0);
    const board = this.state.board;
    for (let row = 0; row < 9; row++) {
      for (let col = 0; col < 9; col++) {
        if (board[row][col] !== 0) {
          // can't change filled cells
          continue;
        }
        // try 1 to 9 for the empty cell
        for (let num = 1; num <= 9; num++) {
          if (App.isValidMove(board, row, col, num)) {
            // valid move, try it
            board[row][col] = num;
            this.setState({ board: board });
            if (await this.solve()) {
              return true;
            } else {
              // didn't solve with this move, backtrack
              board[row][col] = 0;
              this.setState({ board: board });
            }
          }
        }
        // nothing worked for empty cell, must have deprived boards solution with a previous move
        return false;
      }
    }
    // all cells filled
    return true;
  }

( Полный код здесь и приложение размещено здесь )

Здесь мне нужно async, чтобы я мог использовать sleep() для визуализации алгоритма. Я использую this.setState({ board: board }); для запуска повторного рендеринга каждый раз при мутации платы.


При попытке конвертировать в функциональный компонент я пытался:

  • useState hook Я использовал const [board, setBoard] = useState(initialBoard); и заменил this.setState звонки на setBoard(board). Это не сработало, потому что ловушка вызывается в al oop
  • , оборачивая useState с useEffect (то есть useEffect(() => setBoard(board), [board])). Это не скомпилируется, получает ошибку React Hook "useEffect" may be executed more than once...
  • также изучил useReducer, но прочитал , он не работает с async

Мои вопросы:

  • Можно ли вообще конвертировать в функциональный компонент?
  • Если да, какой хук я должен использовать и потребуется ли перепроектирование my solve() function?
  • Даже лучше ли преобразовать это из компонента класса в функциональный компонент?

Ответы [ 3 ]

1 голос
/ 01 мая 2020

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

Вот пример того, как вы можете это сделать:

const { useState, useEffect } = React;
const getCell = (board) => {
  const row = board.findIndex((row) =>
    row.some((cell) => !cell.checked)
  );
  if (row === -1) {
    return [-1, -1, false];
  }
  const col = board[row].findIndex((cell) => !cell.checked);
  return [row, col, true];
};
const initialState = [
  [{ checked: false }, { checked: false }],
  [{ checked: false }, { checked: false }],
];
const App = () => {
  const [board, setBoard] = useState(initialState);
  useEffect(() => {
    const timer = setTimeout(
      () =>
        setBoard((board) => {
          const [row, col, set] = getCell(board);
          if (set) {//do we need to set board (there are moves)
            const boardCopy = [...board];
            boardCopy[row] = [...board[row]];
            boardCopy[row][col] = {
              ...boardCopy[row][col],
              checked: true,
            };
            return boardCopy;
          }
          //this will return the same as passed in
          //  so App will not re render because board
          //  will not have changed
          return board;//no moves left because set is false
        }),
      1000
    );
    return () => clearTimeout(timer);
  }, [board]);
  return (
    <div>
      <button onClick={() => setBoard(initialState)}>
        reset
      </button>
      <table>
        <tbody>
          {board.map((row, i) => (
            <tr key={i}>
              {row.map(({ checked }, i) => (
                <td key={i}>{checked ? 'X' : 'O'}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>
1 голос
/ 01 мая 2020

Попробуйте useAsyncEffect hook отсюда

I wi sh это было частью стандартной библиотеки React

0 голосов
/ 02 мая 2020

Работает с useState крючком.

Проблема была в том, что я неправильно копировал плату; необходимо использовать const boardCopy = [...board]; (вместо const boardCopy = board).

Полный код функционального компонента здесь .

Код компонента предыдущего класса здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...