【问题标题】:Leaflet functions in redux-sagas with NextJS - window is not defined带有 NextJS 的 redux-sagas 中的传单功能 - 未定义窗口
【发布时间】:2022-02-26 07:05:38
【问题描述】:

我正在重构使用 NextJS 和 redux 编写的现有 react-leaflet 应用程序。我正在引入 redux-sagas 来清理 api 调用逻辑。在某些 sagas 中,我想使用一些传单地理功能:

function* handleGeocode(): Generator {
    if (window) {
        try {
            const response = yield call(axios.get, `nominatim-url-blah-blah`);

            const { lat, lon: lng } = response[0];

            const bounds = L.latLngBounds([ // Use leaflet function here
                [lat - 0.5, lng - 0.5],
                [lat + 0.5, lng + 0.5],
            ]);

                
            yield put(
                ActionCreators.requestDataInBounds(bounds))
            );
            
        } catch (e) { }
    }
}

function* watchHandleGeocode() {
    yield takeEvery(ActionTypes.REQ_GEOCODE, handleGeocode);
}

function* searchSagas() {
    yield all([fork(watchHandleGeocode),
}

L.latLngBounds 的使用运行了一些传单代码,这些代码在深处最终需要访问窗口对象。

我收到错误ReferenceError: window is not defined

我知道这一直是 react-leaflet 的问题。我已经阅读了Leaflet with next.js 的问题,但这不是我的问题。我的实际地图组件已经使用动态导入:

const Map = dynamic(() => import("../components/Map"), {
    ssr: false,
});

我对此没有意见。一旦我将传单本机代码引入 sagas,我的问题就出现了。一旦使用 saga 中间件,就好像所有 sagas 都在运行:

const sagaMiddleware = createSagaMiddleware();

function initStore(preloadedState) {
    const store = createStore(
        rootReducer,
        preloadedState,
        composeWithDevTools(applyMiddleware(sagaMiddleware))
    );

    return store;
}

export const initializeStore = (preloadedState = {}) => {
    let _store = store ?? initStore(preloadedState);

    // After navigating to a page with an initial Redux state, merge that state
    // with the current state in the store, and create a new store
    if (preloadedState && store) {
        _store = initStore({
            ...store.getState(),
            ...preloadedState,
        });
        // Reset the current store
        store = undefined;
    }

    // For SSG and SSR always create a new store
    if (typeof window === "undefined") return _store;

    if (typeof window !== "undefined") {
        sagaMiddleware.run(rootSaga);
    }

    // Create the store once in the client
    if (!store) store = _store;

    return _store;
};

export function useStore(initialState) {
    const store = useMemo(() => initializeStore(initialState), [initialState]);
    return store;
}

因此,尽管我只运行 sagaMiddleware if window !== "undefined",并且 saga 本身被包裹在 if (window) 语句中,但我仍然收到此错误。就好像代码正在运行并运行 L.latLngBounds 函数,即使调用 handleGeocode saga 的操作当前甚至没有在代码中的任何地方被调用!一旦我删除对 L.latLngBounds 的调用,错误就会消失。

这里出了什么问题?如果运行这个 saga 的操作甚至没有被调用,NextJS 是如何运行这段代码甚至出现这种类型的错误的?如何重新连接我的 saga,以便可以在其中使用本机传单函数?

【问题讨论】:

  • 如果我理解正确,您的 handleGeocode 函数与您的 Map 组件完全分开。它还导入 Leaflet,但您尝试通过检查 window 来保护其仅用于客户端。但它是如何导入的呢?
  • 也许尽量避免在 sagas 中使用传单代码并仅调度 lat lng 或从异步调用接收的任何其他数据。然后在您想要使用它们的组件内部,使用传单代码使用从 redux 接收的 lat lng 形成边界。
  • @kboul 我正在考虑这样做,我可能不得不这样做。但是能够在 saga 中包含一些处理逻辑,利用 Leaflet 的强大地理功能,将非常有用。我想我不明白为什么在此代码发送到客户端之前尝试访问窗口
  • @ghybs,它不会在任何地方导入。我们使用 saga 中间件 applyMiddleware(sagaMiddleware) 和运行该中间件 sagaMiddleware.run(rootSaga) 将所有 redux sagas 链接到商店,其中 rootSaga 基本上包含应用程序中的所有 sagas。其中一个传奇是watchHandleGeocode,它监听ActionTypes.REQ_GEOCODE 从应用程序的任何地方被触发。 Sagas 监听它,然后在handleGeocode 中运行代码。我不明白的是为什么 L.latLngBounds 在 compilation 时被调用,而不是仅在动作触发时调用
  • @SethLutske 它可能不是javascript错误,而是打字稿错误吗?这意味着代码实际上并没有运行,它只是编译期间的 TS 静态分析?

标签: reactjs next.js leaflet redux-saga react-leaflet


【解决方案1】:

我找到了一种方法来解决这个问题 - 如果其他人遇到此问题,我将把它放在这里。在任何需要调用leaflet L.something 方法的saga 中,不能直接导入leaflet。但是,您可以使用这个技巧:

let L;

if (typeof window !== "undefined") {
    L = require("leaflet");
}

由于几个原因,这并不理想。你失去了L 的所有智能感知,所以你最好知道你在做什么。此外,如果您导入任何使用L.something 方法的函数,您需要做同样的事情。例如:

import { convertBoundsToCorners } from './some/utils';

function* handleGeocode(): Generator {
    try {
        const response = yield call(axios.get, `nominatim-url-blah-blah`);
        const { lat, lon: lng } = response[0];
        const bounds = L.latLngBounds([ 
            [lat - 0.5, lng - 0.5],
            [lat + 0.5, lng + 0.5],
        ]);

        const corners = convertBoundsToCorners(bounds);
 
        yield put(ActionCreators.requestDataInBounds(corners)));
            
    } catch (e) { }
}

在导入的方法中,如果你使用L.something方法,你也必须做掩码导入:

// utils.js

let L;

if (typeof window !== "undefined") {
    L = require("leaflet");
}

export const convertBoundsToCorners = bounds => {
  // do some stuff here that involves L.latLngBounds methods
}

所以这种方法有一些明显的缺点,但它是唯一一个我可以在没有崩溃的情况下工作的方法。现在我可以在我的 sagas 中使用漂亮的传单逻辑!

这似乎是一种包含需要访问窗口对象的库的通用方法,因为我必须对 device-uuid 做同样的事情。

【讨论】:

  • "你失去了所有智能感知":你可以尝试只导入类型:import type Leaflet from "leaflet"; let L: Leaflet;
  • @ghybs 啊,我想知道键入整个传单库的语法到底是什么。谢谢!
猜你喜欢
  • 2019-10-12
  • 2022-12-24
  • 2018-10-03
  • 2021-03-30
  • 2019-10-05
  • 2020-06-25
  • 2020-09-18
  • 1970-01-01
  • 2022-06-19
相关资源
最近更新 更多