Сегодня мы собираемся написать простой список дел с помощью React Hooks и MobX.
Я дам краткое введение в эти библиотеки для людей, которые с ними не знакомы. Если вы это уже знаете, можете пропустить этот раздел.
Реагировать
Библиотека JavaScript для создания пользовательских интерфейсов, представленная Facebook в 2013 году и используемая для создания пользовательского интерфейса для веб-приложений. В отличие от Angular и Vue, React не является фреймворком и предоставляет только инструменты для отображения компонентов пользовательского интерфейса.
Реагировать на хуки
Для людей, которые уже знакомы с React, хуки - это расширение функциональных компонентов, более подробную информацию вы можете прочитать по ссылке.
MobX
MobX - это проверенная на практике библиотека, которая делает управление состоянием простым и масштабируемым за счет прозрачного применения функционального реактивного программирования (TFRP). Философия MobX очень проста (из документации).
Теперь, когда мы знаем, что будем использовать, мы можем приступить к работе над проектом.
Давайте откроем новый проект React. Существует большой стартовый проект под названием Create React App, который поддерживается сообществом и имеет все необходимые функции для запуска нового приложения React. (Настройка нового проекта React может занять время, поэтому неплохо иметь этот шаблон). Мы также будем использовать для этого TypeScript :)
Откройте терминал и напишите следующие команды:
- Установите приложение create-response-app с машинописным текстом
- Установите типы, необходимые для машинописного текста (в разделе devDependencies, поскольку мы не используем их во время выполнения)
- Установите mobx и его подключение к React
npx create-react-app my-app --template typescript
npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest
npm i mobx mobx-react-lite
MobX использует декораторы, поэтому нам нужно будет добавить в наш tsconfig следующую строку "experimentalDecorators": true
.
Давайте настроим наши магазины mobx и подключим их к нашему проекту React. Нашими первыми шагами будут:
- Создать TodoList и TodoItem
- Создайте контекст и оберните наше приложение его поставщиком, чтобы поделиться магазинами mobx
- Создайте способ получить магазин из контекста
export class TodoList { @observable.shallow list: TodoItem[] = []; constructor(todos: string[]) { todos.forEach(this.addTodo); } @action addTodo = (text: string) => { this.list.push(new TodoItem(text)); } @action removeTodo = (todo: TodoItem) => { this.list.splice(this.list.indexOf(todo), 1); }; @computed get finishedTodos(): TodoItem[] { return this.list.filter(todo => todo.isDone); } @computed get openTodos(): TodoItem[] { return this.list.filter(todo => !todo.isDone); } }
Мы создаем класс TodoList
, содержащий массив @observable
из TodoItem
. Обратите внимание, что мы присваиваем ему начальное значение и делаем наблюдаемое неглубоким, чтобы оно не переносило значения внутри массива. Мы заставляем его получать массив строк для инициализации нашего списка и добавления 2 простых функций добавления и удаления, мы используем стрелочные функции для действий, потому что мы полагаемся на this
, и мы не хотим терять контекст при выполнении действий todos.forEach(this.addTodo)
на Функции получения, мы не используем стрелочные функции, потому что мы используем их только как средство получения.
export default class TodoItem { id = Date.now(); @observable text: string = ''; @observable isDone: boolean = false; constructor(text: string) { this.text = text; } @action toggleIsDone = () => { this.isDone = !this.isDone } @action updateText = (text: string) => { this.text = text; } }
Мы создаем простой класс TodoItem
и даем ему 2 наблюдаемых свойства текста и isDone
и 2 функции для переключения его статуса и обновления текста.
Теперь воспользуемся React Context. Это позволяет нам обмениваться данными между всеми компонентами React внутри поставщика, который он нам дает, мы создадим контекст, используя createContext
из React, и передадим ему значение по умолчанию для пустого объекта и назначим ему тип нашего хранилища TodoList
(обычно в большой проект может быть корневым хранилищем).
import { createContext } from 'react'; import {TodoList} from "../stores/todo-list"; export const StoreContext = createContext<TodoList>({} as TodoList); export const StoreProvider = StoreContext.Provider;
Мы переходим к нашему index.tsx
файлу, создаем наше хранилище списков дел и обертываем или <App />
компонент с поставщиком, который мы получили из нашего контекста, и передаем ему todoList в качестве значения.
const todoList = new TodoList([ 'Should Starting Writing in React', 'Should Learn MobX', 'Should Watch Once Piece :)' ]); ReactDOM.render( <StoreProvider value={todoList}> <App/> </StoreProvider> , document.getElementById('root'));
Наконец, мы добавляем функцию, которая поможет нам получить хранилища внутри функций React. Используя useContext
React, мы передаем ему контекст, который мы создали ранее, и получаем значение, которое мы предоставили (todoList).
export const useStore = (): TodoList => useContext(StoreContext);
Теперь мы можем приступить к написанию наших компонентов перехватчиков реакции 🎆. Мы собираемся создать 3 компонента:
- Список дел
- TodoItem
- TodoNew
Начнем с TodoList
:
export const TodoList = () => { const todoList = useStore(); return useObserver(() => ( <div className="todo-list"> <div className="open-todos"> <span>Open Todos</span> {todoList.openTodos.map(todo => <TodoItem key={`${todo.id}-${todo.text}`} todo={todo}/>)} </div> <div className="finished-todos"> <span>Finished Todos</span> {todoList.finishedTodos.map(todo => <TodoItem key={`${todo.id}-${todo.text}`} todo={todo}/>)} </div> </div> )); };
Мы используем нашу вспомогательную функцию useStore
для получения списка задач, затем мы просто возвращаем 2 списка завершенных и незавершенных задач из вычисленных значений хранилища списков задач.
Здесь важно использовать useObserver
из mobx-react-lite
, чтобы обернуть возвращаемое значение, чтобы mobx знал, что нужно отслеживать наблюдаемые внутри значения. Без этого, если вы попытаетесь обновить список извне, это не сработает (подробнее об этом можно прочитать здесь https://mobx-react.js.org/observe-how).
export const TodoItem = ({todo}: Props) => { const todoList = useStore(); const [newText, setText] = useState(''); const [isEditing, setEdit] = useState(false); const saveText = () => { todo.updateText(newText); setEdit(false); setText(''); }; return ( <div className="todo-item"> { isEditing ? <div> <input type="text" onKeyDown={onEnterPress(saveText)} onChange={(e) => setText(e.target.value)}/> <button onClick={saveText}>save</button> </div> : <div> <span>{todo.text}</span> <input type="checkbox" onChange={todo.toggleIsDone} defaultChecked={todo.isDone}></input> <button onClick={() => setEdit(true)}>edit</button> <button onClick={() => todoList.removeTodo(todo)}>X</button> </div> } </div> ) };
Мы создаем TodoItem
, который получает в props модель класса mobx для задачи. Мы даем компоненту базовые возможности по редактированию, удалению и обновлению его текста. Здесь важно увидеть, как мы позволяем задаче обновлять сам текст, и нет необходимости обновлять для этого весь список (я добавил вспомогательную функцию onEnterPress
, чтобы упростить проверку ввода ключей), здесь мы не использовали useObserver
потому что todo
уже наблюдается внутри TodoList
.
Компонент TodoNew
, который вы можете увидеть в репозитории Github. Я не буду добавлять его сюда, потому что это те же концепции и код.
Репозиторий Github: https://github.com/stolenng/react-hooks-mobx
Подводя итог, это простое руководство показывает, как мы соединяем react
и hooks
с mobx
с помощью mobx-react-lite
. Я думаю, что эта комбинация компонентов функции React (хуков) с mobx дает отличный код, очень простой и понятный. Вы можете использовать это как стартовый проект с mobx и настраивать его по своему усмотрению.
Обновление от 20 сентября
Я выпустил обширный курс по MobX, вы можете проверить его здесь:
Если у кого-то есть вопросы или просто хочется поговорить на эту тему, добро пожаловать в личку :)