You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Storybook的单个story中mock window全局变量

问题描述

我在React应用中使用Navigator online接口判断客户端在线状态,当客户端离线时会展示对应的离线降级组件。目前我将该组件设置为纯组件,通过传入在线状态属性的方式在Storybook中展示该组件,但这一方案并非在所有场景下都适用。

因此我想知道如何在Storybook中仅为单个story mock全局(window)变量?我目前找到的实现方案非常繁琐不优雅,代码如下:

ClientOffline.decorators = [
  (Story) => {
    const navigatiorInitally = global.navigator

    // 我没有直接修改online属性值,因为会抛出'TypeError: "x" is read-only'错误,所以选择重写整个navigator对象
    Object.defineProperty(global, 'navigator', {
      value: { onLine: false },
      writable: false,
    })

    useEffect(() => {
      return () => {
        Object.defineProperty(global, 'navigator', {
          value: navigatiorInitally,
          writable: false,
        })
       location.reload() // 必须重载页面,否则其他story运行会抛出错误
      }
    }, [])

    return <Story />
  },
]

请问有没有更简洁直接的解决方案?


解决方案

可以通过重写Navigator原型上的onLine属性getter实现需求,不需要替换整个navigator对象,也无需重载页面,实现非常简洁:

方案1:单story直接配置

直接给对应story加装饰器即可,代码如下:

ClientOffline.decorators = [
  (Story) => {
    // 保存原生的onLine getter
    const originalOnLineGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'onLine').get;
    // 重写为固定返回false
    Object.defineProperty(Navigator.prototype, 'onLine', {
      get: () => false,
      configurable: true
    });
    // story渲染完成后恢复原生属性
    setTimeout(() => {
      Object.defineProperty(Navigator.prototype, 'onLine', {
        get: originalOnLineGetter,
        configurable: true
      });
    }, 0);

    return <Story />;
  },
]

方案2:抽成通用配置(适合多story复用)

如果多个story都需要模拟在线/离线状态,可以把逻辑抽成全局装饰器,后续只用传参数即可:

  1. 先在.storybook/preview.js中添加全局装饰器:
export const decorators = [
  (Story, context) => {
    const mockOnlineState = context.parameters.mockNavigatorOnLine;
    // 没有配置mock参数时直接返回原组件
    if (mockOnlineState === undefined) return <Story />;

    const originalOnLineGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'onLine').get;
    Object.defineProperty(Navigator.prototype, 'onLine', {
      get: () => mockOnlineState,
      configurable: true
    });

    setTimeout(() => {
      Object.defineProperty(Navigator.prototype, 'onLine', {
        get: originalOnLineGetter,
        configurable: true
      });
    }, 0);

    return <Story />;
  }
]
  1. 对应story只需要加一行参数配置即可,不需要写重复的装饰器逻辑:
// 模拟离线状态的story
export const Offline = {
  parameters: {
    mockNavigatorOnLine: false
  }
}

// 模拟在线状态的story
export const Online = {
  parameters: {
    mockNavigatorOnLine: true
  }
}

说明

这个方案规避了直接修改window.navigator实例属性的只读报错问题,直接修改原型上的属性描述符即可生效,且执行完自动恢复原生属性,不会影响其他story的运行,也不需要重载页面。


内容的提问来源于stack exchange,提问作者Jonathan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 13:27:07