为啥我在 nuxt 中的“仅限客户端”组件抱怨“未定义窗口”?

Posted

技术标签:

【中文标题】为啥我在 nuxt 中的“仅限客户端”组件抱怨“未定义窗口”?【英文标题】:Why is my `client-only` component in nuxt complaining that `window is not defined`?为什么我在 nuxt 中的“仅限客户端”组件抱怨“未定义窗口”? 【发布时间】:2020-04-08 08:56:59 【问题描述】:

我有 Vue SPA,我正在尝试迁移到 nuxt。我在一个包含在<client-only> 标签中的组件中使用vue2leaflet,但仍然收到来自nuxt 的错误消息,即window is not defined

我知道我可以使用 nuxt-leaflet 或创建一个插件,但这会显着增加供应商捆绑包,我不希望这样。我只想为需要它的组件导入传单插件。有什么办法吗?

<client-only>
   <map></map>
</client-only>

还有map 组件:

<template>
  <div id="map-container">
    <l-map
      style="height: 80%; width: 100%"
      :zoom="zoom"
      :center="center"
      @update:zoom="zoomUpdated"
      @update:center="centerUpdated"
      @update:bounds="boundsUpdated"
    >
      <l-tile-layer :url="url"></l-tile-layer>
    </l-map>
  </div>
</template>

<script>
import 
  LMap,
  LTileLayer,
  LMarker,
  LFeatureGroup,
  LGeoJson,
  LPolyline,
  LPolygon,
  LControlScale
 from 'vue2-leaflet';
import  Icon  from 'leaflet';
import 'leaflet/dist/leaflet.css';

// this part resolve an issue where the markers would not appear
delete Icon.Default.prototype._getIconUrl;

export default 
  name: 'map',
  components: 
    LMap,
    LTileLayer,
    LMarker,
    LFeatureGroup,
    LGeoJson,
    LPolyline,
    LPolygon,
    LControlScale
  ,
//...

【问题讨论】:

组件不是这里的问题,而是包含不适合 s-s-r 的传单。 &lt;client-only&gt; 所做的只是阻止 s-s-r 期间的渲染,而不是阻止包含脚本。 我明白了。有什么办法可以为这个组件使用像nuxt-leaflet only 这样的插件? 不,因为即使使用动态导入,它仍然会在 s-s-r 期间被转译。我会假设图书馆的包含必须在有条件的情况下发生 【参考方案1】:

我找到了一种可行的方法,但我不确定如何。在父组件中,您将 import 语句移动到组件声明中。

<template>
  <client-only>
    <map/>
  </client-only>
</template>

<script>
export default 
  name: 'parent-component',
  components: 
    Map: () => if(process.client)return import('../components/Map.vue'),
  ,

</script>

【讨论】:

您提出的语法在我的情况下不被接受,但我稍微修改了一下,然后它就可以正常工作了。我将其修改为:Map: () =&gt; process.client ? import('@/components/Map') : null, 您应该从模式为“客户端”的插件中注册地图组件。 nuxtjs.org/guide/plugins#client-or-server-side-only @idmean 我希望它仅在必要时加载。插件在全球范围内加载 AFAIK。 地图组件有一个导出默认值。你也知道如何导入被破坏的组件吗?导入功能是否提供此功能?【参考方案2】:

&lt;client-only&gt; 组件并没有像你想象的那样做。是的,它会跳过在服务器端渲染你的组件,但它仍然会被执行!

https://deltener.com/blog/common-problems-with-the-nuxt-client-only-component/

【讨论】:

【参考方案3】:
    <template>
      <client-only>
        <map/>
      </client-only>
    </template>

    <script>
      export default 
        name: 'parent-component',
        components: 
          Map: () =>
            if (process.client) 
              return import ('../components/Map.vue')
            ,
        ,
      
    </script>

上述解决方案对我不起作用。

为什么?这花了我一段时间才知道,所以我希望它可以帮助其他人。

“问题”是 Nuxt 自动包含“组件”文件夹中的组件,因此您不必手动包含它们。这意味着即使您仅在 process.client 上动态加载它,由于这种自动性,它仍然会在服务器端加载它。

我找到了以下两种解决方案:

    将“组件”文件夹重命名为其他名称以停止自动导入,然后使用上述解决方案 (process.client)。

    (以及更好的选择 IMO)还有另一个功能可以延迟加载自动加载的组件。为此,请在组件名称前加上“lazy-”。这与结合将阻止组件在服务器端呈现。

最后你的设置应该是这样的 文件:

./components/map.vue
./pages/index.html

index.html:

    <template>
      <client-only>
        <lazy-map/>
      </client-only>
    </template>
    <script>
    export default 
    
    </script>

【讨论】:

【参考方案4】:

我在这里复制我的答案,因为这是第一个搜索此类问题的帖子,并且在我的情况下使用上述解决方案仍然导致 nuxt 崩溃或错误。

您可以在挂载的钩子中导入插件,该钩子只能在客户端运行。所以:

async mounted() 
  const MyPlugin = await import('some-vue-plugin');
  Vue.use(MyPlugin);

我不知道您尝试使用的具体插件,但在我的情况下,我必须在插件的默认属性上调用 Vue.use(),导致 Vue.use(MyPlugin.default)

【讨论】:

【参考方案5】:

这是我在通用模式下使用 Nuxt 的方法: 这将: 1. 使用 s-s-r 2. 不抛出与缺少标记图像/阴影相关的错误 3.确保传单仅在需要的地方加载(意味着不需要插件) 4.允许自定义图标设置等 5.允许一些插件(它们很痛苦,出于某种原因,我认为你可以将它们添加为插件。结果将它们添加到插件会破坏传单的本地导入并强制它与 vendor.js 捆绑)

将您的模板包装在&lt;client-only&gt;&lt;/client-only&gt;

<script>
let LMap, LTileLayer, LMarker, LPopup, LIcon, LControlAttribution, LControlZoom, Vue2LeafletMarkerCluster, Icon
if (process.client) 
  require("leaflet");
  (
    LMap,
    LTileLayer,
    LMarker,
    LPopup,
    LIcon,
    LControlAttribution,
    LControlZoom,
   = require("vue2-leaflet/dist/vue2-leaflet.min"));
  (
    Icon
   = require("leaflet"));
  Vue2LeafletMarkerCluster = require('vue2-leaflet-markercluster')



import "leaflet/dist/leaflet.css";
export default 
  components: 
    "l-map": LMap,
    "l-tile-layer": LTileLayer,
    "l-marker": LMarker,
    "l-popup": LPopup,
    "l-icon": LIcon,
    "l-control-attribution": LControlAttribution,
    "l-control-zoom": LControlZoom,
    "v-marker-cluster": Vue2LeafletMarkerCluster,
   
  ,

  mounted() 
    if (!process.server) //probably not needed but whatever
     
      // This makes sure the common error that the images are not found is solved, and also adds the settings to it.
      delete Icon.Default.prototype._getIconUrl;
      Icon.Default.mergeOptions(
        // iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'), // if you want the defaults
        // iconUrl: require('leaflet/dist/images/marker-icon.png'), if you want the defaults
        // shadowUrl: require('leaflet/dist/images/marker-shadow.png') if you want the defaults
        shadowUrl: "/icon_shadow_7.png",
        iconUrl: "/housemarkerblue1.png",
        shadowAnchor: [10, 45],
        iconAnchor: [16, 37],
        popupAnchor: [-5, -35],
        iconSize: [23, 33],
        // staticAnchor: [30,30],
      );
    
  ,
并且有使用 nuxt build --modern=server --analyze 的证明 https://i.stack.imgur.com/kc6q4.png

【讨论】:

以上是关于为啥我在 nuxt 中的“仅限客户端”组件抱怨“未定义窗口”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 nuxt-auth-next 中进行 google 授权后用户未通过身份验证?

Nuxt.js 中的“文档未定义”

将 Jest 与 Nuxt 组件一起使用时无法读取未定义的属性 $loading

从直接链接导航时,Nuxt.js `this.$route.params.id` 未定义

当 Flickity 使用轮播渲染子组件时,Nuxt 应用程序抛出“无法读取未定义的属性”

覆盖 Nuxt 组件中的 Bulma 变量