簡體   English   中英

用於設置文檔標題的反應掛鈎在 ssr 上不起作用

[英]react hook for setting the document title does not work on ssr

我有以下鈎子用於設置最初在服務器上呈現的反應網站的文檔標題:

import { useEffect } from 'react';

export const useDocumentTitle = (title: string): void => {
  useEffect(() => {
    document.title = title;
  }, [title]);
};

這不在服務器上運行,僅在瀏覽器中運行。 有沒有辦法讓這個鈎子在服務器上運行?

document.title只能應用於client side rendering ,因為您需要訪問 dom 才能更新標題。 在服務器端渲染上,您無權訪問文檔或 window,因此需要為 html 提供每個頁面或路線的元標記

使用服務器端渲染處理document.title有幾種解決方案

  • 你可以利用現有的庫(如果你願意使用一個),比如react-helmet ,它可以讓你更好地控制元標記

示例用法

const App= ({title, children}) => {
   return (
    <React.Fragment>
       <Helmet>
          <title>{title}</title>
        </Helmet>
        {/* Other logic here */ }
     </React.Fragment>
   )
}

現在如果你想實現一個通用組件,你可以編寫一個像 HOC 這樣的組件

const TitleWrapper = ({title, children}) => {
   return (
    <React.Fragment>
       <Helmet>
          <title>{title}</title>
        </Helmet>
        {children}
     </React.Fragment>
   )
}

現在您可以將它與您的組件一起使用,例如

const App = () => (
   <TitleWrapper title={"Title page"}>
       {/* Some components here */}
  </TitleWrapper>
)

現在在渲染時你可以這樣寫

app.get('/*', (req, res) => {
  const app = renderToString(<App />);
  const helmet = Helmet.renderStatic();

  res.send(formatHTML(app, helmet));
});

app.listen(8000);

function formatHTML(appStr, helmet) {
  return `
    <!DOCTYPE html>
    <html lang="en">
      <head>
        ${helmet.title.toString()}
      </head>
      <body>
        <div id="root">
          ${ appStr }
        </div>
        <script src="./bundle.js"></script>
      </body>
    </html>
  `
}
  • 現在考慮到僅更新document.title的庫可能過於依賴。 這里的另一個解決方案是編寫自己的組件來處理服務器端呈現,但也允許您在客戶端的文檔中添加標題。

在編寫這樣的自定義組件之前,您必須注意您可能需要在同一視圖中處理組件的多個實例。 到 go 使用最簡單的方法,您可以選擇獲取最后一個活動實例來呈現文檔標題

您還必須注意,到目前為止,這里不能替代 serverSide 上的componentWillMount

下面的代碼受@DanAbramov 的react-document-title啟發

class AddDocumentTitle extends React.Component {
   static mountedInstances = []; // keep track of mounted instances. Being a prototype this won't be reinitializd for each instance

   static renderStatic() {
       // Will be used on serverSide
        const inst = AddDocumentTitle.getCurrentActiveInstance();
        AddDocumentTitle.mountedInstances.splice(0)// remove all instance since on serverside componentWillUnmount won't run
        if(inst) {
            return inst.props.title; // return the title props
        }
   }

   static getCurrentActiveInstance() {
      const length = AddDocumentTitle.mountedInstances.length;
      if (length > 0) {
        return AddDocumentTitle.mountedInstances[length - 1];
      }
      return null;
   }


   static updateDocumentTitle() {
      if (typeof document === 'undefined') {
        // for serverside this will be undefined
        return;
      }
      // code that will only run on clientSide
      const activeInstance = AddDocumentTitle.getActiveInstance();
      if (activeInstance) {
        document.title = activeInstance.props.title;
      }
    }

    static defaultProps = {
        title: ''
    }

    constructor(props) {
         super(props);
         AddDocumentTitle.mountedInstances.push(this); // Add instance to mounted instance
         AddDocumentTitle.updateDocumentTitle(); // This will update it on serverSide
    }

    componentDidUpdate(prevProps) {
      if (this.isActive() && prevProps.title !== this.props.title) {
         // Update title if this instance is still the active instance and props title changed
         AddDocumentTitle.updateDocumentTitle();
      }
    }

    componentWillUnmount() {
       // Remove this instance from instance list and update documentTitle according to the now active instance
       const index = AddDocumentTitle.mountedInstances.indexOf(this);
       AddDocumentTitle.mountedInstances.splice(index, 1);
       AddDocumentTitle.updateDocumentTitle();
    }

    isActive() {
       return this === DocumentTitle.getActiveInstance();
    }

    render() {
       if (this.props.children) {
           return React.Children.only(this.props.children);
       } else {
           return null;
       }
    }
}

現在我們已經編寫了 class 組件,我們可以按以下方式使用它

const App = () => (
   <AddDocumentTitle title={"Title page"}>
       {/* Some components here */}
  </AddDocumentTitle >
)

現在在服務器renderToStatic serverSide這個獲得的標題嵌入到 HTML 頁面模板中,就像我們對 Helmet 所做的那樣

app.get('/*', (req, res) => {
  const app = renderToString(<App />);
  const title= AddDocumentTitle.renderStatic();

  res.send(formatHTML(app, title));
});

app.listen(8000);

function formatHTML(appStr, title) {
  return `
    <!DOCTYPE html>
    <html lang="en">
      <head>
        ${title}
      </head>
      <body>
        <div id="root">
          ${ appStr }
        </div>
        <script src="./bundle.js"></script>
      </body>
    </html>
  `
}

我希望這些信息對你有幫助

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM