MobX: 10-минутный быстрый старт для MobX и React

внешний интерфейс модульный тест React.js MobX


MobX— это простое, масштабируемое и проверенное решение для управления состоянием.

Этот учебник проведет вас через все важные концепции Mobx через десять минут. MOBX - автономная библиотека, но большинство людей используют ее с реагированием, поэтому этот учебник будет сосредоточен на их комбинации.

основная идея

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

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

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

MobX повторно упрощает управление состоянием, решая фундаментальную проблему: мы просто не можем создавать нестабильные состояния.

Стратегия достижения этого проста:Гарантирует, что все, полученное из состояния приложения, может быть выведено автоматически.

В принципе, MobX рассматривает ваше приложение как электронную таблицу:

  1. Во-первых, давайте посмотримсостояние приложения. Объекты, массивы, прототипы и ссылки составляют модель вашего приложения.
  2. Во-вторых, посмотрите напроизводные. Справедливости ради, все значения, которые могут быть автоматически рассчитаны из состояния приложения, выводятся. Эти производные или вычисляемые значения могут варьироваться от простых значений (таких как количество незавершенных задач) до сложных значений (таких как визуальный HTML, представляющий задачи). С точки зрения электронной таблицы: это формулы и диаграммы для приложения.
  3. РеакцииАналогично выведению. Основное отличие состоит в том, что эти функции не производят значений, а вместо этого автоматически выполняют некоторые задачи, которые обычно связаны с вводом-выводом. Они автоматически обновляют DOM или делают сетевые запросы в нужное время.
  4. Наконец мы видимдействия. Действие - это все измененияstateПредмет. MobX 将保证所有由你的操作触发的 state 变更都可以被所有的派生和响应处理。这个过程是同步且无故障的。

Простой магазин дел

После того, как теория закончена, фактическая операция может быть более объяснительной, чем внимательное чтение вышеперечисленных вещей. Ради творчества давайте начнем с очень простого магазина todo. Примечание. Все блоки кода ниже доступны для редактирования и кликабельны.run codeкнопку для их выполнения. Ниже приведен очень простойTodoStoreИспользуется для управления элементами списка дел. Еще не присоединился к MobX.

class TodoStore {
    todos = [];

    get completedTodosCount() {
        return this.todos.filter(
            todo => todo.completed === true
        ).length;
    }

    report() {
        if (this.todos.length === 0)
            return "<none>";
        return `Next todo: "${this.todos[0].task}". ` +
            `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }

    addTodo(task) {
        this.todos.push({
            task: task,
            completed: false,
            assignee: null
        });
    }
}

const todoStore = new TodoStore();

Мы только что создали содержащий待办事项один из спискаtodoStoreПример.是时候给它填充一些对象了。为了保证我们可以看到我们改变的影响,我们在每个变更之后调用todoStore.reportи распечатать его. Обратите внимание, что этот отчет преднамеренно печатает толькоПервыйЗадача. Это делает пример немного неуклюжим, но, как вы увидите, это хороший пример динамического отслеживания зависимостей в MobX.

todoStore.addTodo("read MobX tutorial");
console.log(todoStore.report());

todoStore.addTodo("try MobX");
console.log(todoStore.report());

todoStore.todos[0].completed = true;
console.log(todoStore.report());

todoStore.todos[1].task = "try MobX in own project";
console.log(todoStore.report());

todoStore.todos[0].task = "grok MobX tutorial";
console.log(todoStore.report());

Becoming reactive

Пока в этом коде нет ничего особенного. Но если нам не нужно явно вызыватьreport, но вместо жизни мы хотим, чтобы она вызывалась при каждом изменении состояния? Это избавит нас от необходимости запутываться вообщевозможноМестный отчет о воздействииreport. Мы хотим, чтобы последние отчеты были напечатаны. Но мы не хотим бороться с тем, как это организовать.

К счастью, это именно то, что MobX может сделать для вас. Автоматически выполнять код, который полностью зависит от состояния. Поэтому нашreportФункции обновляются автоматически, как диаграммы в электронной таблице. Для достижения этой цели,TodoStoreОн должен быть наблюдаемым, чтобы MobX мог отслеживать все изменения. Давайте изменим код вместе, чтобы это произошло.

дальше,completedTodosCountСвойства могут быть автоматически выведены из списка задач. мы можем использовать@observableа также@computedДекоратор добавляет наблюдаемое свойство к объекту:

class ObservableTodoStore {
    @observable todos = [];
    @observable pendingRequests = 0;

    constructor() {
        mobx.autorun(() => console.log(this.report));
    }

    @computed get completedTodosCount() {
        return this.todos.filter(
            todo => todo.completed === true
        ).length;
    }

    @computed get report() {
        if (this.todos.length === 0)
            return "<none>";
        return `Next todo: "${this.todos[0].task}". ` +
            `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }

    addTodo(task) {
        this.todos.push({
            task: task,
            completed: false,
            assignee: null
        });
    }
}


const observableTodoStore = new ObservableTodoStore();

Сделанный! Мы пометили некоторых для MobX@observableсвойства, значения которых могут измениться в любой момент. Рассчитайте значение с помощью@computedФлаги для указания того, что они могут быть выведены из состояния.

pendingRequestsа такжеassigneeСвойства еще не используются, но будут использоваться позже в этом руководстве. Для краткости в примерах на этой странице используются ES6, JSX и декораторы. Но не волнуйтесь, все декораторы в MobX соответствуютES5форма.

В конструкторе создаем небольшую функцию для печатиreportиспользовать вместеautorunзаверните это. автозапуск создаетОтветИ выполните его один раз, затем, когда какие-либо наблюдаемые данные в этой функции изменятся, ответ будет выполнен автоматически. из-заreportиспользуемый наблюдаемыйtodosсвойство, поэтому он будет печатать отчет в любое подходящее время. Следующий пример иллюстрирует это, всего одним щелчком мышиrunКнопка (Переводчик: ...):

observableTodoStore.addTodo("read MobX tutorial");
observableTodoStore.addTodo("try MobX");
observableTodoStore.todos[0].completed = true;
observableTodoStore.todos[1].task = "try MobX in own project";
observableTodoStore.todos[0].task = "grok MobX tutorial";

Это весело, правда?reportАвтоматически печатает, процесс автоматический и никакие промежуточные переменные не просачиваются. Если вы внимательно посмотрите на лог, то увидите, что четвертая строка не генерирует новую строку лога. потому что отчет недействительноИзменено из-за переименования, хотя основные данные изменились. И изменить имя первого отчета об изменениях todo, потому что его имя используется в отчете. Это полностью объясняетautorunне только мониторtodoмассив, но также слушает отдельные свойства в элементе todo.

Сделать React реактивным

Итак, мы создали простой адаптивный отчет. Пришло время создать адаптивный пользовательский интерфейс вокруг этого магазина. Компоненты React не могут реагировать на внешний мир (кроме собственного имени).mobx-reactупаковка@observerДекоратор работает, добавляя компонент Reactrenderметод завернут вautorunЭта проблема решена в , который автоматически синхронизирует ваши компоненты и состояние. Теоретически это то же самое, что и наш предыдущийreportподход не имеет значения.

В следующем примере определяются некоторые компоненты React. Только из этих компонентов@observerпринадлежит MobX. Но этого достаточно для того, чтобы все компоненты могли независимо перерисовываться при изменении соответствующих данных. вам больше не нужно звонитьsetState, и вам не нужно думать о том, как подписаться на соответствующие части состояния приложения с помощью селекторов конфигурации или компонентов более высокого порядка. Можно сказать, что все компоненты стали интеллектуальными. Но они определены в глупых декларациях.

нажмитеRun codeкнопку, чтобы увидеть результат кода ниже. Этот пример доступен для редактирования, так что вы можете играть с ним как хотите. попробуй удалить все@oberverили просто снять украшениеTodoViewВон тот. Цифры в предварительном просмотре справа выделяются каждый раз при повторном рендеринге компонента.

@observer
class TodoList extends React.Component {
  render() {
    const store = this.props.store;
    return (
      <div>
        { store.report }
        <ul>
        { store.todos.map(
          (todo, idx) => <TodoView todo={ todo } key={ idx } />
        ) }
        </ul>
        { store.pendingRequests > 0 ? <marquee>Loading...</marquee> : null }
        <button onClick={ this.onNewTodo }>New Todo</button>
        <small> (double-click a todo to edit)</small>
        <RenderCounter />
      </div>
    );
  }

  onNewTodo = () => {
    this.props.store.addTodo(prompt('Enter a new todo:','coffee plz'));
  }
}

@observer
class TodoView extends React.Component {
  render() {
    const todo = this.props.todo;
    return (
      <li onDoubleClick={ this.onRename }>
        <input
          type='checkbox'
          checked={ todo.completed }
          onChange={ this.onToggleCompleted }
        />
        { todo.task }
        { todo.assignee
          ? <small>{ todo.assignee.name }</small>
          : null
        }
        <RenderCounter />
      </li>
    );
  }

  onToggleCompleted = () => {
    const todo = this.props.todo;
    todo.completed = !todo.completed;
  }

  onRename = () => {
    const todo = this.props.todo;
    todo.task = prompt('Task name', todo.task) || todo.task;
  }
}

ReactDOM.render(
  <TodoList store={ observableTodoStore } />,
  document.getElementById('reactjs-app')
);

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

const store = observableTodoStore;
store.todos[0].completed = !store.todos[0].completed;
store.todos[1].task = "Random todo " + Math.random();
store.todos.push({ task: "Find a fine cheese", completed: true });
// etc etc.. add your own statements here...

использовать цитаты

На данный момент мы создали наблюдаемые объекты (как прототипы, так и простые объекты), массивы и примитивы. Вы можете быть удивлены, как MobX манипулирует этими ссылками? Можно ли использовать наше состояние для создания графа? В приведенном выше примере вы можете обнаружить, что наassigneeАтрибуты. Давайте придадим им некоторую ценность, введя еще одно «хранилище», содержащее информацию о людях (на самом деле это просто прославленный массив) и назначим им задачи.

var peopleStore = mobx.observable([
    { name: "Michel" },
    { name: "Me" }
]);
observableTodoStore.todos[0].assignee = peopleStore[0];
observableTodoStore.todos[1].assignee = peopleStore[1];
peopleStore[0].name = "Michel Weststrate";

Сейчас у нас два отдельных магазина. Один содержит информацию о человеке, а другой содержит информацию о задачах. Назначить человека человеку в магазине людейassignee, нам просто нужно добавить ссылку. Эти изменения будутTodoViewАвтоматическое приобретение. С помощью MobX нам не нужно сначала форматировать данные и писать соответствующий селектор, чтобы обеспечить возможность обновления нашего компонента. На самом деле, даже не имеет значения, где хранятся данные. Пока объекты установлены как наблюдаемые, MobX сможет их отслеживать. Реальные ссылки на JavaScript будут работать. Если они связаны с выводом, то MobX автоматически отследит их. Чтобы проверить это, просто попробуйте изменить имя поля ввода ниже (убедитесь, что вы нажали кнопку «Выполнить код» перед тестированием!).

Асинхронная операция

Поскольку все данные в наших небольших приложениях Todo получены из состояния, изменение состояния не имеет значения. Это упрощает создание асинхронных операций. Нажмите кнопку (несколько), чтобы смоделировать новый элемент обработки асинхронно.



Код очень прост. Сначала мы обновляемpendingRequestsЭто свойство хранилища заставляет пользовательский интерфейс отображать текущее состояние загрузки. Когда загрузка заканчивается, Вон переходит с задачами в новый магазин и снова декрементируетpendingRequestsсчитать. Объедините этот код с приведенным вышеTodoListОпределите сравнение, чтобы узнать, как использовать свойство pendingRequests.

observableTodoStore.pendingRequests++;
setTimeout(function() {
    observableTodoStore.addTodo('Random Todo ' + Math.random());
    observableTodoStore.pendingRequests--;
}, 2000);

Инструменты разработки

mobx-react-devtoolsПакет предоставляет средство разработки, которое отображается в правом верхнем углу экрана для приложений MOBX + ReactJS. Нажатие на первую кнопку выделяет каждый визуализированный@observerкомпоненты.如果你点击第二个按钮,预览中的组件依赖树将会显示出来,你可以在任何时候准确地检测出它正在观察的是哪一段数据。

В заключение

это все! Нет шаблона. Есть только несколько простых декларативных компонентов, используемых для формирования нашего общего пользовательского интерфейса. Этот пользовательский интерфейс полностью реактивно получен из нашего состояния. Теперь вы можете начать использовать в своем приложенииmobxа такжеmobx-reactУпакуйте это. Вот краткое изложение того, что вы уже узнали:

  1. использовать@observableдекоратор илиobservable(object or array)Функции, позволяющие MobX отслеживать объекты.
  2. @computedДекораторы можно использовать для создания функций, автоматически вычисляющих значения на основе состояния.
  3. использоватьautorunдля автоматического запуска функций, которые зависят от наблюдаемого состояния. Это полезно для ведения журнала, создания сетевых запросов и т. д.
  4. использоватьmobx-reactв упаковке@observerДекораторы делают ваши компоненты React по-настоящему отзывчивыми. Они будут обновляться автоматически и эффективно. Даже в больших и сложных проектах с достаточным количеством данных.

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

MobX не является контейнером состояния

Люди часто думают о MobX как об альтернативе Redux. Обратите внимание, однако, что MobX — это просто библиотека, которая решает технические проблемы и не имеет контейнера состояния как такового. В этом смысле приведенные выше примеры надуманы, поэтому мы рекомендуем вам использовать правильные инженерные приемы, такие как инкапсуляция логики в методах, организация их в хранилищах или контроллерах и т. д. Или, как выразился один пользователь HackerNews:

«MobX, он всегда поднимался, но я не могу не похвалить его. Написание с помощью MobX означает, что он выполняет все контроллеры/диспетчеры/действия/супервизоры и другие вещи, чтобы думать об управлении потоком данных, а не только о том, что делает приложение Todo. требуется по умолчанию».