如何在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都需要模拟在线/离线状态,可以把逻辑抽成全局装饰器,后续只用传参数即可:
- 先在
.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 />; } ]
- 对应story只需要加一行参数配置即可,不需要写重复的装饰器逻辑:
// 模拟离线状态的story export const Offline = { parameters: { mockNavigatorOnLine: false } } // 模拟在线状态的story export const Online = { parameters: { mockNavigatorOnLine: true } }
说明
这个方案规避了直接修改window.navigator实例属性的只读报错问题,直接修改原型上的属性描述符即可生效,且执行完自动恢复原生属性,不会影响其他story的运行,也不需要重载页面。
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

