如何在React (next.js) SSR双重渲染中防止父组件重新渲染?

5
所以我有一个使用Next.js的SSR应用程序。我正在使用第三方组件,该组件利用WEB API,因此需要在客户端加载而不是服务器上加载。我正在使用“两次渲染”,我在这里阅读了相关信息:https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57 我试图弄清楚为什么当next.js页面状态中的“ssrDone”状态更改时,整个<Layout>组件都会不必要地重新渲染,其中包括页面的页头、页脚等。
我已经阅读过 React.memo() 以及利用 shouldComponentUpdate(),但似乎无法阻止它重新渲染<Layout>组件。
我的控制台日志消息<Layout>会触发两次,但<ThirdPartyComponent>的控制台消息只会触发一次,就像预期的那样。这是一个问题吗?还是React足够聪明,不会实际更新DOM,所以我甚至不需要担心这个问题。看起来很愚蠢,没有理由重新渲染我的页面页头和页脚。
在控制台中,输出为:
Layout rendered
Layout rendered
3rd party component rendered

index.js(next.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>;
}

Layout.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>
  );
}
3个回答

4
你可以定义一个新的组件,代码如下:
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组件。


这似乎是正确的方法,但我运行了你的代码,<Layout>内部的console.log仍然运行两次(渲染两次?),就像我的示例一样? - Taylor A. Leach
@TaylorA.Leach 我编辑了我的代码,我忘记在Home组件中删除旧代码了。 - yachaka
1
啊,对了,将状态从页面中移除并将其传递给高阶组件可以防止页面重新渲染整个<Layout>,而是正确地只渲染<ThirdPartyComponent>。我知道这就是答案,只是我无法完全理解。谢谢! - Taylor A. Leach

1
我最近遇到了类似的问题,您可以使用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>
);
}

这是一个动画按钮组件,可以在头部组件(父级)中切换侧边栏。
之前我将侧边栏状态存储在头部组件中,当其改变时,所有头部都必须重新渲染,导致按钮动画出现问题。
相反,我需要所有的头部、按钮状态和侧边栏在导航期间保持持久,并且能够与它们进行交互而不会重新渲染。
我想现在状态不再在组件中,而是在“上面”,所以下一个状态不会重新渲染。(我可能对这一部分有所误解,但它看起来是这样的)
请注意,toggleNav在头部中定义并作为属性传递,因为我还需要在其他组件中使用它。以下是它的样子:
 const toggleNav = () => {
    dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action

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

1

由于其 children 属性更改,Layout 组件进行重新渲染。首先是 <div> ...loading</div>(当 ssrDone = false 时),然后是 <ThirdPartyComponent />(当 ssrDone = true 时)


但我的问题是,有没有办法防止不必要的重新渲染,因为唯一会改变的是ThirdPartyComponent。 - Taylor A. Leach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接