如何用 safari 缓存触发 componentDidMount?

Posted

技术标签:

【中文标题】如何用 safari 缓存触发 componentDidMount?【英文标题】:How does react trigger componentDidMount with safari cache? 【发布时间】:2019-10-09 01:40:31 【问题描述】:

React 16 在返回 Safari 时触发 componentDidMount(),即使组件从未卸载。 react如何知道何时挂载?

class Foo extends React.Component 
  state = 
    loading: false
  

  componentDidMount() 
    // when going back in safari
    // triggers in react 16, but not in 15.3 or preact
    console.log('mounted');
  

  componentWillUnmount() 
    // will never trigger
    console.log('will unmount');
  

  leave() 
    this.setState(
      loading: true
    );
    setTimeout(() => 
      window.location.href = 'https://github.com/';
    , 2000);
  

  render() 
    return this.state.loading ? <div>loading...</div> : <button onClick=this.leave.bind(this)>leave</button>;
  

背景

Safari 使用 bfcache。如果您返回,它会从缓存中取出最后一页。

使用react 15.3或者preact等库时,离开页面不会触发componentWillUnmount,返回也不会触发componentDidMount

此行为会导致几个问题 - 例如,当您在重定向之前将页面状态设置为 loading。如果用户返回,状态仍然设​​置为正在加载,您甚至无法使用 componentDidMount 重置状态,因为它永远不会触发。

有一个solution,使用onpageshow,但由于它是only triggers one time,你必须使用window.location.reload()重新加载整个页面。 这也是 react 不能依赖这个解决方案的原因。

【问题讨论】:

你在使用 React 路由器吗? SPA 通过使用浏览器暴露历史 API 的 history.push/pop 来处理前进/后退 否 - 重定向到另一个页面。我正在使用 preact 并想弄清楚 react 是如何做到最终使用此功能的。 显然它已知的 Safari 页面缓存限制:webkit.org/blog/427/webkit-page-cache-i-the-basics 你可能想进一步探索他们的文档,因为我确信他们可能已经有了解决方案,因为它在 2009 年实施,它似乎基于发布日期。 我阅读了这些文章,他们没有提供更多信息。 gist.github.com/oshell/bb1b3eec49a98cf6d59cef44806f0fa6 只需使用它并将 react cdn 链接替换为 15.3 【参考方案1】:

我不知道 React 16 是如何调用 mount 的,但它是一个完全不同的引擎,所以它可能是故意的,也可能不是。 要解决此问题,您可以做的一件事是在重定向之前安排状态重置,如下所示:

<html>
  <head>
    <script
      crossorigin
      src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.js"
    ></script>
    <script
      crossorigin
      src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.js"
    ></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script type="text/babel">
      class Foo extends React.Component 
        state = 
          loading: false
        ;
        componentDidMount() 
          console.log("mounted");
        
        leave() 
          this.setState(
            loading: true
          );
          setTimeout(() => 
            this.setupReset();
            window.location.href = "https://github.com";
          , 2000);
        

        setupReset() 
          let interval = setInterval(() => 
            if (
              !!window.performance &&
              window.performance.navigation.type === 2
            ) 
              clearInterval(interval);
              console.log('reseting');
              this.setState( loading: false );
            
          ,500);
        

        render() 
          return this.state.loading ? (
            <div>loading...</div>
          ) : (
            <button onClick=this.leave.bind(this)>leave</button>
          );
        
      
      ReactDOM.render(<Foo />, document.getElementById("app"));
    </script>
  </body>
</html>

然后当您返回执行恢复时,可以检测它是否来自历史记录并重置状态。

您实际上可以第一次在 componentDidMount 上设置此重置机制。

【讨论】:

我不太喜欢任何有间隔和超时的解决方案,但我想它有效,所以我会接受它。

以上是关于如何用 safari 缓存触发 componentDidMount?的主要内容,如果未能解决你的问题,请参考以下文章

如何用 Chrome DevTools 调试 iOS Safari

如何用MAC上的Safari检查iPhone手机App运行的Html页面

程序设计中,如何用好缓存?

如何用键盘和活动触发按钮

如何用java实现缓存

如何用php清除浏览器缓存?