Redux Toolkit linting 悖论

Redux Toolkit linting paradox

我在文件 searchSlice.js 中定义了一个 Redux Toolkit 切片,它负责查询 API 并将响应数据存储在商店的状态中。目前看起来像这样:

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

const initialState = {
  query: '',
  status: 'idle',
  movies: [],
  totalResults: null,
};

// Create slice
export const searchSlice = createSlice({
  name: 'search',
  initialState,
  reducers: {
    updateQuery: (state, action) => {
      state.query = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(getMovies.pending, (state) => {
        state.status = 'pending';
      })
      .addCase(getMovies.fulfilled, (state, action) => {
        state.status = 'idle';
        state.movies = action.payload.results;
        state.totalResults = action.payload.total_results;
      });
  },
});

// Actions
export const { updateQuery } = searchSlice.actions;

// Reducers
export default searchSlice.reducer;

// Selectors
export const selectQuery = (state) => state.search.query;
export const selectStatus = (state) => state.search.status;
export const selectAllMovies = (state) => state.search.movies;
export const selectTotalResults = (state) => state.search.totalResults;

// Thunks
export const getMovies = createAsyncThunk(
  'search/getMovies',
  async (payload, store) => {
    if (!store.getState().search) {
      dispatchEvent(updateQuery(payload));
    }
    try {
      console.log('payload: ', payload);
      const res = await axios.get(`/search?query=${store.getState().search}`);
      return res.data;
    } catch (err) {
      return err;
    }
  }
);

据我所知,除了导出实际的切片对象本身,您还必须导出其必要的和附带的组件:

由于上述组件高度耦合和相互依赖的性质,ESLint 将根据 searchSlice.js 文件中组件的顺序(按行号)抛出不同的 linting 错误。例如,在上面的代码片段中,linting 错误是:

'getMovies' was used before it was defined. eslint(no-use-before-define)

如果我们尝试通过将 getMovies 函数声明重新排列到调用上方来修复错误,如下所示:

// ...

// Thunks
export const getMovies = createAsyncThunk(
  'search/getMovies',
  async (payload, store) => {
    if (!store.getState().search) {
      dispatchEvent(updateQuery(payload));
    }
    try {
      console.log('payload: ', payload);
      const res = await axios.get(`/search?query=${store.getState().search}`);
      return res.data;
    } catch (err) {
      return err;
    }
  }
);

// ...

// Create slice
export const searchSlice = createSlice({
  name: 'search',
  initialState,
  reducers: {
    updateQuery: (state, action) => {
      state.query = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(getMovies.pending, (state) => {
        state.status = 'pending';
      })
      .addCase(getMovies.fulfilled, (state, action) => {
        state.status = 'idle';
        state.movies = action.payload.results;
        state.totalResults = action.payload.total_results;
      });
  },
});

// ...

然后我们得到相同的 linting 错误,但函数定义不同:

'updateQuery' was used before it was defined. eslint(no-use-before-define)

似乎无论文件的排列如何,ESLint 都会抛出一个自相矛盾的 no-use-before-define 错误。

是否有不涉及更改 ESLint 规则的解决方案?有没有更好的方法来构建代码?我已经尝试将它拆分成更小的文件,但由于伴随切片功能的高度相互依赖性,ESLint 将开始抛出 import/no-cycle 错误,因为两个文件都需要从彼此导入内容。

作为一个附加问题,提升如何在这里发挥作用?

当你处理一个循环时,你需要弄清楚哪个是有意义的依赖,哪个是依赖。在这种情况下,从 thunk 中删除 reducer 比从 reducer 中删除 thunk 更容易。

您可以通过从 thunk 中删除额外调度的 updateQuery 操作并在您的 reducer 中处理该逻辑来修复依赖关系。您可以通过 action.meta.arg 属性 从 getMovies.pending case reducer 中的 thunk 访问(混淆命名的)payload 变量,其中包含您称为 thunk 动作创建者的参数与.

export const getMovies = createAsyncThunk(
  'search/getMovies',
  async (query) => {
    const res = await axios.get(`/search?query=${query}`);
    return res.data;
    // Don't catch errors here. Let them be thrown and handled by the 'rejected' action.
  }
);

export const searchSlice = createSlice({
  name: 'search',
  initialState,
  reducers: {
    // you might not even need this anymore, unless you use it elsewhere.
    updateQuery: (state, action) => {
      state.query = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(getMovies.pending, (state, action) => {
        // Update the query property of the state.
        state.query = action.meta.arg;
        state.status = 'pending';
      })
      .addCase(getMovies.fulfilled, (state, action) => {
        state.status = 'idle';
        state.movies = action.payload.results;
        state.totalResults = action.payload.total_results;
      });
  },
});

顺便说一下,条件 if (!store.getState().search) 没有意义。那将检查整个切片是否真实,它总是真实的。