reactjs - 如何在单个故事中模拟窗口变量

标签 reactjs storybook

我正在使用Navigator online在我的 React 应用程序中确定客户端是否在线。当客户端离线时,我显示离线后备组件。现在我已经将组件设为 Pure - 因此我可以通过将在线状态作为属性传递来在 Storybook 中显示它。但这并不总是合适的解决方案。

所以我想知道如何模拟 Storybook 中单个故事的全局(窗口)变量?唯一的 - 非常肮脏的解决方案 - 我发现如下所示:

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

    // I am overwritting the navigator object instead of directly the 
    // online value as this throws an 'TypeError: "x" is read-only' Error
    Object.defineProperty(global, 'navigator', {
      value: { onLine: false },
      writable: false,
    })

    useEffect(() => {
      return () => {
        Object.defineProperty(global, 'navigator', {
          value: navigatiorInitally,
          writable: false,
        })
       location.reload() //needed because otherwilse other stories throw an error
      }
    }, [])

    return <Story />
  },
]

有人有更直接的解决方案吗?

最佳答案

在这个答案中,我假设所有副作用(与导航器通信是副作用)都从组件主体分离到钩子(Hook)中。

假设您有如下所示的组件:

function AwesomeComponent () {
  let [connectionStatus, setConnectionStatus] = useState('unknown');
  useEffect(function checkConnection() {
    if (typeof window === 'undefined' || window.navigator.onLine) setConnectionStatus('online'); 
    else setConnectionStatus('offline');
  }, []);
  
  if (connectionStatus === 'online') return <OnlineComponent/>
  if (connectionStatus === 'offline') return <FallbackComponent/>
  return null;
}

您可以将 Hook 提取到单独的模块中。在我的示例中,它既是状态又是效果。

function useConnectionStatus() {
  let [connectionStatus, setConnectionStatus] = useState("unknown");
  useEffect(function checkConnection() {
    if (typeof window === "undefined" || window.navigator.onLine)
      setConnectionStatus("online");
    else setConnectionStatus("offline");
  }, []);
  return connectionStatus;
}

通过这种方式,您可以将逻辑与表示分离,并可以模拟各个模块。故事书有guide to mock individual modules并根据故事配置它们。最好查阅文档,因为软件正在发生变化,并且随着时间的推移,某些事情可能会以其他方式完成。

假设您将文件命名为 useConnectionStatus.js。要模拟它,您必须创建 __mock__ 文件夹,并在其中创建您的模拟模块。例如,它会是这样的:

// __mock__/useConnectionStatus.js
let connectionStatus = 'online';
export default function useConnectionStatus(){
  return connectionStatus;
}

export function decorator(story, { parameters }) {
  if (parameters && parameters.offline) {
    connectionStatus = 'offline';
  }
  return story();  
}

下一步是修改 webpack 配置以使用模拟 Hook 而不是实际 Hook 。文档提供了一种从 .storybook/main.js 执行此操作的方法。在撰写本文时,它是这样完成的:

// .storybook/main.js

module.exports = {
  // Your Storybook configuration

  webpackFinal: (config) => {
    config.resolve.alias['path/to/original/useConnectionStatus'] = require.resolve('../__mocks__/useConnectionStatus.js');
    return config;
  },
};

现在,使用我们的新装饰器装饰您的预览,您将能够单独为每个特定故事设置配置。

// inside your story
Story.parameters = {
  offline: true
}

关于reactjs - 如何在单个故事中模拟窗口变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70114493/

相关文章:

javascript - 使用 axios 发布后不执行任何反应

reactjs - 下面两个setState有什么区别

node.js - 即使存在匹配值,Jest/Jasmine .toContain() 也会失败

angular - 如何在 Storybook 中使用相对 Angular 分量?

javascript - Storybook 5.2 (CSF) - 如何添加react-router-dom链接 - "You should not use <Link> outside a <Router>"

javascript - Reducer 不导入 Action 类型常量

reactjs - ReactCSSTransitionGroup 与 React 0.14 和漂亮的不变违规 : addComponentAsRefTo(. ..)

angular - 如何将 Angular Material 组件正确导入 StoryBook CSF

react-native - 如何在react-native中创建嵌套的故事书结构?

javascript - 将 Storybook 文档与 Svelte 一起使用