Когда я работал над приложением Sudoku, я принял сознательное решение использовать Redux для управления состоянием. В моем блоге под названием Как я создал веб-приложение для судоку я сказал, что попробую использовать только Context API для управления состоянием приложения в будущем проекте. .
Этот проект был завершен вчера, когда я решил, что лучше потратить время на что-нибудь интересное, чем рыться в Reddit и Blind в поисках последних обновлений всей саги SVB. Я создал игру Snake на основе React.
Игра доступна бесплатно на http://sean-snake.netlify.com/. Проверьте это и дайте мне знать, сможете ли вы дойти до конца. Исходный код доступен по адресу https://github.com/seanluong/snake.
На высоком уровне это приложение на основе React, написанное на TypeScript с React MUI в качестве библиотек компонентов. Хотя это приложение (пока) не требует какой-либо внутренней работы (ознакомьтесь с моим сообщением в блоге о внутренней части приложения судоку здесь), оно сопряжено с изрядной долей проблем и интересным извлеченным уроком. В этом посте мы обсудим 3 моих любимых момента в работе над этим проектом.
Обработка событий клавиатуры
В то время как приложение судоку в основном управляется щелчком мыши, за исключением случаев, когда пользователям нужно вводить числа, природа игры Змейка требует более активного взаимодействия между пользователем и игрой. На рабочем столе, вероятно, имеет смысл разрешить людям перемещать змею и изменять ее направление, нажимая клавиши со стрелками.
Разница между обработкой событий щелчка мыши и событий нажатия клавиши заключается в том, что проще определить элемент, с которым должен быть связан обработчик события для щелчка мыши, чем для нажатия клавиши.
Для игры «Змейка» нам бы хотелось, чтобы обработчик событий нажатия клавиш был связан со всем HTML-документом, чтобы, пока пользователи фокусируются на документе, события обрабатывались. Это было сделано с помощью комбинации useEffect
и useRef
следующим образом
// Use useRef to have a reference to the `document` object const documentRef = useRef<Document>(document); useEffect(() => { // Check if the document is available for access if (documentRef.current) { const document = documentRef.current; // Add the event listener document.addEventListener('keydown', handleKeyDowned); // Clean up return () => { document.removeEventListener('keydown', handleKeyDowned) } } }, [documentRef]);
Поддержка пользователей на мобильных устройствах
Хотя для пользователей настольных компьютеров, вероятно, лучше всего взаимодействовать с игрой с помощью событий нажатия клавиш, мобильным пользователям, вероятно, будет проще играть в игру, нажимая на экран. Это одна из проблем игры «Змейка», с которой я не сталкивался в приложении «Судоко».
Сначала я думал о том, чтобы позволить людям нажимать в любом месте на доске. Обработчик событий определяет, где находится цель, на которую нажали, по отношению к текущему положению змеи, и соответственно вычисляет новое направление. Одна потенциальная проблема UX с этим подходом заключается в том, что касание доски может помешать пользователям получить полное представление об игре из-за присутствия их собственных пальцев. Поскольку я не хотел, чтобы пользователи касались доски пальцами, я решил создать на экране кнопки для каждого из 4 направлений движения. Окончательный интерфейс выглядит так:
Я использовал react-device-detect, чтобы проверить, играет ли пользователь с мобильного или с рабочего стола.
Управляйте игровым состоянием
Context API позволяет сделать определенные фрагменты данных доступными для компонентов-потомков без необходимости сверления реквизита. В отличие от Redux, который является внешней библиотекой, Context API встроен в React, поэтому нет необходимости устанавливать другую зависимость. Еще одна приятная особенность Context API заключается в том, что мы можем воспользоваться крючком под названием useContext
для доступа к данным, хранящимся в контексте.
Чтобы использовать Context API для управления состоянием, мы можем создать контекст на верхнем уровне приложения для хранения объекта состояния приложения. Чтобы обновить состояние приложения, нам нужно использовать useReducer
, крючок React, который предназначен для управления нетривиальным локальным состоянием компонента. Если состояние не является примитивным значением (например, числом, строкой или логическим значением и т. д.), вам, вероятно, следует использовать useReducer
вместо useState
.
Создайте поставщика контекста для состояния
В игре Snake я определил компонент GameStateProvide
interface GameStateContextType { gameState: GameState; dispatch: (action: Action) => void; } const GameStateContext = createContext<GameStateContextType>({ gameState: {} as GameState, dispatch: () => {}, }) const GameStateProvider = ({ children }: PropsWithChildren) => { const [gameState, dispatch] = useReducer(reducer, /* initial game state */); return ( <GameStateContext.Provider value={{ gameState, dispatch }}> {children} </GameStateContext.Provider> ); }
затем использовал GameStateProvider
на верхнем уровне приложения следующим образом
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( <React.StrictMode> <GameStateProvider> <App /> </GameStateProvider> </React.StrictMode>, )
Благодаря тому, что GameStateProvided
обернуто вокруг App
, все данные, хранящиеся в GameStateContext
, теперь доступны для App
и любого его потомка.
Доступ к данным в контексте состояния игры
Хотя useContext
достаточно, чтобы любой компонент в нашем дереве компонентов мог получить доступ к состоянию приложения, более удобно определить собственный хук. Это было сделано с помощью кода ниже:
const useGameStateContext = () => useContext(GameStateContext);
Когда компонент хочет получить доступ к состоянию приложения, мы можем вызвать useGameStateContext
следующим образом.
const { gameState, dispatch } = useGameStateContext();
gameState
объект — это состояние приложения. dispatch
— это функция, возвращаемая вызовом useReducer
. Мы вызываем dispatch
всякий раз, когда нам нужно отправить действие, которое будет обработано редьюсером приложения. Этот шаблон должен быть знаком людям, знакомым с Redux.