Как стать автором
Обновить

Redux это бойлерплейт, а Mobx нет! Но есть нюанс

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров4.2K

На прошлой неделе впервые поучаствовал в конференции по Frontend, где один из докладчиков, расказывал, как удачно его команда переехала с Redux на Mobx. Главным преимуществом он назвал отсутствие бойлерплейта и ускорение разработки в полтора раза.

Я прочитал несколько статей и посмотрел другие доклады, где все как один говорят, что Mobx лучше, чем Redux. Возможно это и так, но почему в сравнение всегда идет Redux, а не Redux-Toolkit, я не понимаю. Попытаемся конструктивно посмотреть действительно ли Mobx настолько хорош как о нем говорят.

Главный аргумент адептов Mobx звучит примерно так

При разработке на Redux приходится писать тону шаблонного кода, чтобы все работало. Нужно написать action-ы и selectors-ы.

Для примера давайте напишем самую простую логику для запроса постов и изменения счетчика и посмотрим сколько строчек кода мы сможем сэкономить.

import { makeAutoObservable } from "mobx";
import { IPromiseBasedObservable, fromPromise } from "mobx-utils";

/* Типизация */
const PostListSchema = z.object({
  id: z.number(),
  title: z.string(),
  description: z.string(),
  tag: z.string(),
  tags: z.array(z.string()),
  image: z.string(),
  progress: z.number(),
  progressTotal: z.number()
})

type PostListModel = z.infer<typeof PostListSchema>

/* Запрос на получение данных */
export const fetchPostList = async (limit: number) => {
  try {
    const response = await _api.get<PostListModel[]>(`api/posts`)

    if (!response.data) {
      throw new Error("Ошибка")
    }

    return response.data.data
  } catch {
    throw new Error("Ошибка")
  }
}


/* Создание стора */
class PostListStore {
    posts?: IPromiseBasedObservable<PostListModel[]>
    counter: 0

    constructor() {
        makeAutoObservable(this)
    }

    incrementCounter = () => {
      this.counter += 1
    }

    decrementCounter = () => {
      this.counter -= 1
    }

    fetchCoursesData = (limit: number) => {
        this.courses = fromPromise(fetchPostList(limit))
    }
}

export const postListStore = new PostListStore()

Теперь попробуем написать такую же логику на Redux-Toolkit. Но чтобы избежать предвзятости в нашей оценке давайте попросим chatGPT написать код за нас.

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AppThunk } from "./store";

/* Типизация */
interface PostListState {
  posts: PostListModel[] | null;
  loading: boolean;
  error: string | null;
  counter: number;
}

const initialState: PostListState = {
  posts: null,
  loading: false,
  error: null,
  counter: 0,
};

/* Создание слайса */
const postListSlice = createSlice({
  name: "postList",
  initialState,
  reducers: {
    incrementCounter(state) {
      state.counter += 1;
    },
    decrementCounter(state) {
      state.counter -= 1;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPostListAsync.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchPostListAsync.fulfilled, (state, action: PayloadAction<PostListState[]>) => {
        state.loading = false;
        state.posts = action.payload;
      })
      .addCase(fetchPostListAsync.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message ?? "Ошибка";
      });
  },
});

export const { incrementCounter, decrementCounter } = postListSlice.actions;

export default postListSlice.reducer;

/* Запрос на получение данных */
export const fetchPostListAsync = createAsyncThunk("fetchPostList", async () => {
  try {
    const response = await axios.get("/api/posts")

    if (!response.data) {
      throw new Error("Ошибка")
    }

    return response.data
  } catch {
    throw new Error("Ошибка")
  }
})

Реализация кода очень похожа, единственное, что в mobx это выглядит немного проще. Однако в сумме разница в 10 строчек, не могу назвать это бойлерплейтом. Actions писать тоже не нужно toolkit все делает за нас.

В рамках эксперимента, давайте попросим chatGPT написать компонент PostList с использованием Mobx и Redux-Toolkit.

/* Код с использованием Mobx */

import React, { useEffect } from "react";
import { observer } from "mobx-react-lite";
import { postListStore } from "../stores/postListStore";
import { IPromiseBasedObservableState } from "mobx-utils";

const PostListMobX: React.FC = observer(() => {
  useEffect(() => {
    postListStore.fetchCoursesData(10); // Загружаем посты при монтировании компонента
  }, []);

  const { state } = postListStore.posts ?? {};

  switch (state) {
    case "pending":
      return <div>Loading...</div>;
    case "rejected":
      return <div>Error: Failed to fetch posts</div>;
    case "fulfilled":
      return (
        <div>
          {postListStore.posts?.value.map((post) => (
            <div key={post.id}>
              <h2>{post.title}</h2>
              <p>{post.description}</p>
              {/* Другие поля поста */}
            </div>
          ))}
        </div>
      );
    default:
      return null;
  }
});

export default PostListMobX;

Возможно стоит отметить, что с кодом для Mobx у GPT возникли трудности и правильный результат удалось получить только с четвертой попытки.

/* Код с использованием Redux-toolkit */

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../store";
import { fetchPostListAsync } from "../postListSlice";

const PostListRedux: React.FC = () => {
  const dispatch = useDispatch();
  const { posts, loading, error } = useSelector((state: RootState) => state.postList);

  useEffect(() => {
    dispatch(fetchPostListAsync(10)); // Загружаем посты при монтировании компонента
  }, [dispatch]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!posts) return null;

  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.description}</p>
          {/* Другие поля поста */}
        </div>
      ))}
    </div>
  );
};

export default PostListRedux;

Опять же результат примерно одинаковый в плане количества кода. Однако решение с использованием Redux-Toolkit смотрится проще.

На мой взгляд само сравнение не Toolkit версии с Mobx, крайне странно. Я думаю, это сравнение имело актуальность в 2020 году может быть, но в 2024 точно нет. Для себя я все таки сделаю вывод, что оба инструмента не заставляют разработчика писать "тону" шаблонного кода.

Теги:
Хабы:
+4
Комментарии100

Публикации

Истории

Работа

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург