繁体   English   中英

Next.js 使用 _document.js 在正文上自定义类

[英]Next.js custom class on body using _document.js

我在这里使用示例

https://github.com/zeit/next.js#custom-document

我想更改 body 标签上的 custom_class

我试过在调用组件时传递道具,但没有任何效果。 我想根据某些条件添加一个“暗”类,但我不知道如何更改它。

编辑:

我不确定这是可能的。 从 nextjs 松弛频道获得帮助后,我被告知

"Pages get rendered client side with next/link
And body is not touched after server side rendering"

我将尝试将内容包装在我生成的另一个标签中,然后尝试更改它。

我发现的最干净的解决方案不是声明性的,但效果很好:

import Head from "next/head"

class HeadElement extends React.Component {
    componentDidMount() {
        const {bodyClass} = this.props
        document.querySelector("body").classList.add(bodyClass || "light")
    }

    render() {
        return <Head>
            {/* Whatever other stuff you're using in Head */}
        </Head>
    }
}

export default HeadElement

使用此 Head 组件,您将传入“dark”或“light”(遵循浅色/深色主题的问题示例)作为页面中的 bodyClass 道具。

从当前 Next (10) 和 React (17) 版本开始,如果您想从页面更改 body 类,可以这样做:

// only example: maybe you'll want some logic before, 
// and maybe pass a variable to classList.add()
useEffect( () => { document.querySelector("body").classList.add("home") } );

请注意 useEffect 是一个 Hook,因此它只能在现代函数组件中使用,而不能在类组件中使用。

可以使用 useEffect Hook 代替“旧”componentDidMount LifeCycle。

https://reactjs.org/docs/hooks-effect.html https://reactjs.org/docs/hooks-overview.html https://reactjs.org/docs/components-and-props.html

对于我不需要很多独特主体类的情况,我想出的解决方案是创建一个名为 BodyClass.js 的组件并将该组件导入到我的 Layout.js 组件中。

BodyClass.js

import  { Component } from 'react';

class BodyClass extends Component {

    componentDidMount() {

        if (window.location.pathname == '/') {
            this.setBodyClass('home');
            
        } else if (window.location.pathname == '/locations') {
            this.setBodyClass('locations');
        } 
    }

    setBodyClass(className) {
        // remove other classes
        document.body.className ='';

        // assign new class
        document.body.classList.add(className);
    }

    render() { 
        return null;
    }

}
 
export default BodyClass;

布局.js

import Header from './Header';
import Footer from './Footer';
import BodyClass from '../BodyClass';

const Layout = ({ children }) => {

    return (

        <React.Fragment>

            <BodyClass />

            <Header />

            {children}
            
            <Footer />

        </React.Fragment>

    )

}


export default Layout;

直接访问 Next js 上的body标签的唯一方法是通过_document.js文件,但该文件在服务器端呈现,如文档中所述。

我建议的解决方法是直接从组件访问body标记。 例子:

const handleClick = (e) => {
    document.querySelector('body').classList.toggle('dark')
}

<div onClick={handleClick}>Toggle</div>

这并不完全是您正在寻找的答案,但我能够通过将一个 prop 传递给组件来在功能上完成同样的事情,该组件然后更新顶级元素上的一个类,该类包含我的应用程序中的所有内容并位于 . 这样,每个页面都可以拥有自己独特的类,并以与使用正文类相同的方式进行操作。

这是我通过道具的地方

 <Layout page_title={meta_title} page_desc={meta_desc} main_class={'contact_page'}>

这是我在 Layout 组件中使用它的地方:

 <main className={props.main_class}> <Header page_title={props.page_title} page_desc={props.page_desc} /> {props.children} <Footer /> </main>

直接,这是不可能的。 但是用另一种方式,您可以使用像 tailwind 这样的框架并将类直接插入到您的 css 中。
这是使用顺风的示例:

 .dark { background-color: black; color: white; } body { @apply dark }

这是我的CSS 唯一解决方案

(我首先查看了https://stackoverflow.com/a/66358460/729221 ,但感觉太复杂了,无法更改一些样式。)

(这使用了 Tailwind CSS,但不需要。)

索引.css:

body:has(#__next .set-bg-indigo-50-on-body) {
      @apply bg-indigo-50;
}

布局.tsx:

  //…
  return (
    <div className="set-bg-indigo-50-on-body">{children}</div>
  )

这将起作用,只要该div<div id="__next">的直接父级。 否则你需要更新 css :has -rule。

使用React Helmet而不是 next/head。

React Helmet 允许您在<body>元素上指定属性,例如类、样式等。

要设置您的pages/_document.js以使用 React Helment,这是您需要做的。 首先在布局组件之一的某处添加带有<body className="hello"> <Helmet>标记:

const title = "hello";
const bodyClass = "world";
return (
    <React.Fragment>
      <Helmet>
        <title>{ title }</title>
        <body className={ bodyClass } />
      </Helmet>
    </React.Fragment>
)

现在,配置您的pages/_document.js以从 Helmet 读取属性并将它们传递给 Next.js:

import Document, { Html, Head, Main, NextScript } from 'next/document';
import { Helmet } from 'react-helmet';

export default class MyDocument extends Document {
    static async getInitialProps(ctx) {
        const initialProps = await Document.getInitialProps(ctx);
        // see https://github.com/nfl/react-helmet#server-usage for more information
        // 'head' is used by 'renderPage().head', we cannot use it
        return { ...initialProps, helmet: Helmet.renderStatic() };
    }

    // should render on <html>
    get helmetHtmlAttrComponents() {
        return this.props.helmet.htmlAttributes.toComponent();
    }

    // should render on <body>
    get helmetBodyAttrComponents() {
        return this.props.helmet.bodyAttributes.toComponent();
    }

    // should render on <head>
    get helmetHeadComponents() {
        return Object.keys(this.props.helmet)
            .filter((el) => el !== 'htmlAttributes' && el !== 'bodyAttributes')
            .map((el) => this.props.helmet[el].toComponent());
    }

    render() {
        // if you don't like Helmet but you still want to set properties on body use this
        // const pageProps = _.get(this.props, '__NEXT_DATA__.props.pageProps');
        return (
            <Html {...this.helmetHtmlAttrComponents}>
                <Head>{this.helmetHeadComponents}</Head>
                <body {...this.helmetBodyAttrComponents}>
                    <Main />
                    <NextScript />
                </body>
            </Html>
        );
    }
}

注意:如果你不喜欢 Helmet 但你仍然想从传递到你的页面的 props 中设置属性,你可以从this.props.__NEXT_DATA__.props.pageProps读取它们但是它对我来说感觉很hacky 并且不稳定 :)

    render() {
        const pageProps = _.get(this.props, '__NEXT_DATA__.props.pageProps');
        return (
            <Html>
                <Head/>
                <body className={ pageProps.bodyClass }>
                    <Main />
                    <NextScript />
                </body>
            </Html>
        );
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM