React (next.js) SSR 二次渲染如何防止父组件重新渲染?
How to prevent parent component from re-rendering with React (next.js) SSR two-pass rendering?
所以我有一个使用 Next.js 的 SSR 应用程序。我正在使用利用 WEB API 的第 3 方组件,因此它需要加载到客户端而不是服务器上。我正在使用我在此处阅读的 'two-pass' 渲染来执行此操作:https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57
我想弄清楚为什么当 'ssrDone' 状态在 next.js 页面状态发生变化时,整个 <Layout>
组件不必要地重新呈现,其中包括页面的页眉、页脚、等等
我已经阅读了有关 React.memo()
以及利用 shouldComponentUpdate()
的内容,但我似乎无法阻止它重新呈现 <Layout>
组件。
我的 <Layout>
的 console.log 消息触发了两次,但 <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>
);
}
Layout
组件重新呈现,因为其 children
属性已更改。首先是 <div> ...loading</div>
(当 ssrDone = false
时)然后是 <ThirdPartyComponent />
(当 ssrDone = true
时)
您可以做的是定义一个新的 <ClientSideOnlyRenderer />
组件,它看起来像这样:
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
组件在初始安装后会重新呈现。
我最近遇到了类似的问题,你可以做的是使用 redux 来存储导致组件 re-render 的状态。
然后使用 useSelector 和 shallowEqual,您可以使用它并更改其值,而无需 re-render 组件。
这是一个例子
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>
);
}
这是一个切换侧边栏的动画按钮组件,全部包裹在 header 组件中 (parent)
在我将侧边栏状态存储在 header 之前,在其更改时所有 header 必须 re-render 导致按钮动画出现问题。
相反,我需要所有 header、按钮状态和侧边栏在导航期间保持不变,并且能够在没有任何 re-render 的情况下与它们交互。
我想现在状态不再在组件中,而是在它的“上方”,所以 next 不会启动 re-render。 (这部分我可能是错的,但它看起来像)
请注意,toggleNav 在 header 中定义并作为 prop 传递,因为我也需要在其他组件中使用它。这是它的样子:
const toggleNav = () => {
dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action
我正在使用 id 和 fn,因为我所有的布局都存储在 redux 中的一个数组中,但是您可以对这部分使用任何逻辑或解决方案。
所以我有一个使用 Next.js 的 SSR 应用程序。我正在使用利用 WEB API 的第 3 方组件,因此它需要加载到客户端而不是服务器上。我正在使用我在此处阅读的 'two-pass' 渲染来执行此操作:https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57
我想弄清楚为什么当 'ssrDone' 状态在 next.js 页面状态发生变化时,整个 <Layout>
组件不必要地重新呈现,其中包括页面的页眉、页脚、等等
我已经阅读了有关 React.memo()
以及利用 shouldComponentUpdate()
的内容,但我似乎无法阻止它重新呈现 <Layout>
组件。
我的 <Layout>
的 console.log 消息触发了两次,但 <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>
);
}
Layout
组件重新呈现,因为其 children
属性已更改。首先是 <div> ...loading</div>
(当 ssrDone = false
时)然后是 <ThirdPartyComponent />
(当 ssrDone = true
时)
您可以做的是定义一个新的 <ClientSideOnlyRenderer />
组件,它看起来像这样:
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
组件在初始安装后会重新呈现。
我最近遇到了类似的问题,你可以做的是使用 redux 来存储导致组件 re-render 的状态。
然后使用 useSelector 和 shallowEqual,您可以使用它并更改其值,而无需 re-render 组件。
这是一个例子
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>
);
}
这是一个切换侧边栏的动画按钮组件,全部包裹在 header 组件中 (parent)
在我将侧边栏状态存储在 header 之前,在其更改时所有 header 必须 re-render 导致按钮动画出现问题。
相反,我需要所有 header、按钮状态和侧边栏在导航期间保持不变,并且能够在没有任何 re-render 的情况下与它们交互。
我想现在状态不再在组件中,而是在它的“上方”,所以 next 不会启动 re-render。 (这部分我可能是错的,但它看起来像)
请注意,toggleNav 在 header 中定义并作为 prop 传递,因为我也需要在其他组件中使用它。这是它的样子:
const toggleNav = () => {
dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action
我正在使用 id 和 fn,因为我所有的布局都存储在 redux 中的一个数组中,但是您可以对这部分使用任何逻辑或解决方案。