【问题标题】:How to prevent parent component from re-rendering with React (next.js) SSR two-pass rendering?React (next.js) SSR 二次渲染如何防止父组件重新渲染?
【发布时间】:2020-03-18 02:36:35
【问题描述】:

所以我有一个使用 Next.js 的 SSR 应用程序。我正在使用利用 WEB API 的 3rd 方组件,因此它需要加载到客户端而不是服务器上。我正在使用“两次”渲染来执行此操作,我在这里读到:https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57

我试图弄清楚为什么当 next.js 页面状态中的 'ssrDone' 状态发生变化时,整个 <Layout> 组件会不必要地重新渲染,包括页面的页眉、页脚等。

我已经阅读了有关 React.memo() 以及利用 shouldComponentUpdate() 的信息,但我似乎无法阻止它重新渲染 <Layout> 组件。

<Layout> 的 console.log 消息触发两次,但 <ThirdPartyComponent> 控制台消息按预期触发一次。这是一个问题还是 React 足够聪明以至于实际上没有更新 DOM,所以我什至不应该担心这个。让它无缘无故地重新渲染我的页面页眉和页脚似乎很愚蠢。

在控制台中,输出为:

Layout rendered
Layout rendered
3rd party component rendered

index.js(下一个.js 页面)

import React from "react";
import Layout from "../components/Layout";
import ThirdPartyComponent from "../components/ThirdPartyComponent";

class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      ssrDone: false
    };
  }

  componentDidMount() {
    this.setState({ ssrDone: true });
  }

  render() {
    return (
      <Layout>
        {this.state.ssrDone ? <ThirdPartyComponent /> : <div> ...loading</div>}
      </Layout>
    );
  }
}

export default Home;

ThirdPartyComponent.jsx

import React from "react";

export default function ThirdPartyComponent() {
  console.log("3rd party component rendered");
  return <div>3rd Party Component</div>;
}

布局.jsx

import React from "react";

export default function Layout({ children }) {
  return (
    <div>
      {console.log("Layout rendered")}
      NavBar here
      <div>Header</div>
      {children}
      <div>Footer</div>
    </div>
  );
}

【问题讨论】:

    标签: reactjs next.js server-side-rendering


    【解决方案1】:

    您可以做的是定义一个新的&lt;ClientSideOnlyRenderer /&gt; 组件,如下所示:

    const ClientSideOnlyRenderer = memo(function ClientSideOnlyRenderer({
      initialSsrDone = false,
      renderDone,
      renderLoading,
    }) {
      const [ssrDone, setSsrDone] = useState(initialSsrDone);
    
      useEffect(
        function afterMount() {
          setSsrDone(true);
        },
        [],
      );
    
      if (!ssrDone) {
        return renderLoading();
      }
    
      return renderDone();
    });
    

    你可以这样使用它:

    class Home extends React.Component {
      static async getInitialProps({ req }) {
        return {
          isServer: !!req,
        };
      };
    
      renderDone() {
        return (
          <ThirdPartyComponent />
        );
      }
      renderLoading() {
        return (<div>Loading...</div>);
      }
    
      render() {
        const { isServer } = this.props;
    
        return (
          <Layout>
            <ClientSideOnlyRenderer
              initialSsrDone={!isServer}
              renderDone={this.renderDone}
              renderLoading={this.renderLoading}
            />
          </Layout>
        );
      }
    }
    

    这样,只有ClientSideOnlyRenderer 组件在初始挂载后重新渲染。 ?

    【讨论】:

    • 这似乎是正确的方法,但我运行您的代码,&lt;Layout&gt; 中的 console.log 仍然运行两次(渲染两次?),就像我的示例一样?
    • @TaylorA.Leach 我编辑了我的密码,我忘了删除 Home 组件中的旧代码
    • 啊,对了,从页面中删除状态并将其传递给 HOC 可以防止页面重新渲染整个 &lt;Layout&gt;,而现在只正确地渲染 &lt;ThirdPartyComponent&gt;。我知道那是我无法得到的答案。谢谢!
    【解决方案2】:

    Layout 组件重新渲染,因为它的 children 属性发生了变化。首先是&lt;div&gt; ...loading&lt;/div&gt;(当ssrDone = false)然后是&lt;ThirdPartyComponent /&gt;(当ssrDone = true)

    【讨论】:

    • 但是我的问题是我有办法防止不必要的重新渲染,因为唯一会改变的是第三方组件。
    【解决方案3】:

    我最近遇到了类似的问题,你可以做的是使用redux来存储导致组件重新渲染的状态。

    然后使用 useSelector 和 shallowEqual 您可以使用它并更改其值,而无需重新渲染组件。

    这是一个例子

    import styles from "./HamburgerButton.module.css";
    import { useSelector, shallowEqual } from "react-redux";
    const selectLayouts = (state) => state.allLayouts.layouts[1];
    
    export default function HamburgerButton({ toggleNav }) {
        let state = useSelector(selectLayouts, shallowEqual);
        let navIsActive = state.active;
        console.log("navIsActive", navIsActive); // true or false
    
    const getBtnStyle = () => {
        if (navIsActive) return styles["hamBtn-active"];
        else return styles["hamBtn"];
    };
    
    return (
        <div
            id={styles["hamBtn"]}
            className={getBtnStyle()}
            onClick={toggleNav}
        >
            <div className={styles["stick"]}></div>
        </div>
    );
    }
    

    这是一个用于切换侧边栏的动画按钮组件,全部包裹在标题组件(父组件)中

    在我将侧边栏状态存储在标题中之前,在其更改时,所有标题都必须重新渲染,从而导致按钮动画出现问题。

    相反,我需要我的所有标题、按钮状态和侧边栏在导航期间保持不变,并且能够在不重新渲染的情况下与它们进行交互。

    我猜现在状态不再在组件中,而是在“上方”,因此 next 不会开始重新渲染。 (我可能对这部分有误,但看起来是这样)

    请注意,toggleNav 在 header 中定义并作为 prop 传递,因为我也需要在其他组件中使用它。这是它的样子:

     const toggleNav = () => {
        dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
    }; //toggleLayout is my redux action
    

    我使用 id 和 fn 是因为我所有的布局都存储在 redux 中的一个数组中,但您可以为此部分使用任何逻辑或解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-11
      • 2018-06-05
      • 2017-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-03
      • 1970-01-01
      相关资源
      最近更新 更多