无法将属性从layout.tsx传递到page.tsx

问题描述 投票:0回答:1

我正在使用应用程序路由器构建一个 Next.js 应用程序,其中“layout.tsx”文件管理一个按钮状态,我想用它来渲染“page.tsx”中的不同内容。据我所知,“layout.tsx”充当“page.tsx”中内容的包装器,我需要将状态从布局传递到页面,以便它可以根据状态值确定要渲染哪个组件。但是,我不确定如何实现这一点。

由于我从layout.tsx 文件导出元数据,因此无法将其设为客户端渲染组件。因此,我明确创建了一个

RootLayoutClient.tsx
组件,它从父级 layout.tsx 接收子级。

这是目前的项目结构。

enter image description here

// layout.tsx
import type { Metadata } from "next";
import "./globals.css";
import RootLayoutClient from "./RootLayoutClient";
import { Open_Sans } from 'next/font/google';

const openSans = Open_Sans({
  subsets: ['latin'],
  display: 'swap',
});

export const metadata: Metadata = {
  title: "xxxx",
  description: "yyyy",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={openSans.className}>
        <RootLayoutClient>{children}</RootLayoutClient>
      </body>
    </html>
  );
}
// RootLayoutClient.tsx
'use client';

import Navbar from "./components/Navbar";
import { useState } from "react";

export default function RootLayoutClient({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const [selectedButton, setSelectedButton] = useState('today');
    console.log(children);
  return (
    <div className="main">
      <Navbar selectedButton={selectedButton} setSelectedButton={setSelectedButton} />
    </div>
  );
}
import Inbox from "./components/content/Inbox";
import Today from "./components/content/Today";
import Upcoming from "./components/content/Upcoming";
import FiltersAndLabels from "./components/content/FiltersAndLabels";

export default function Home({selectedButton}: {selectedButton: string}) {
  console.log(selectedButton);
  const renderContent = () => {
    switch (selectedButton) {
      case 'inbox':
        return <Inbox/>
      case 'today':
        return <Today/>
      case 'upcoming':
        return <Upcoming/>
      case 'filtersAndLabels':
        return <FiltersAndLabels/>
      default:
        return null;
    }
  }
  return (
    <main>
          {renderContent()}
    </main>
  );
}

有人可以帮我想办法解决这个问题吗?

next.js react-hooks react-props
1个回答
0
投票

布局,更一般地说是“包装器”,组件通常不会显式地将 props 传递给它们所包装的子组件。他们不知道要包装什么,也不知道具体要传递什么。

可以使用

Children
助手和
cloneElement
来注入额外的道具:

import { cloneElement, Children, useState } from "react";

export default function RootLayoutClient({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const [selectedButton, setSelectedButton] = useState('today');

  return (
    <div className="main">
      <Navbar
        selectedButton={selectedButton}
        setSelectedButton={setSelectedButton}
      />
      {Children.map(children, child => {
        return cloneElement(child, { selectedButton, setSelectedButton });
      })}
    </div>
  );
}

包装的子组件现在可以访问注入的

selectedButton
setSelectedButton
属性值。这通常在非 Typescript 项目中效果很好,但对于 TS,需要在包装的所有组件中了解这些附加属性,以便在这些组件的
props
接口中正确键入它们。

替代方案

另一种可能更可取的解决方案是通过 React 上下文提供程序在 ReactTree 中提供“props”值。

示例:

const SelectButtonContext = createContext<{
  selectedButton: string;
  setSelectedButton: React.Dispatch<React.SetStateAction<string>>;
}>({
  selectedButton: "";
  setSelectedButton: (value: string) => {};
});

const useSelectButtonContext = React.useContext(SelectButtonContext);
export default function RootLayoutClient({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const [selectedButton, setSelectedButton] = useState('today');

  return (
    <div className="main">
      <Navbar
        selectedButton={selectedButton}
        setSelectedButton={setSelectedButton}
      />
      <SelectButtonContext.Provider
        value={{ selectedButton, setSelectedButton }}
      >
        {children}
      </SelectButtonContext.Provider>
    </div>
  );
}
export default function Home() {
  const { selectedButton } = useSelectButtonContext();

  const renderContent = () => {
    switch (selectedButton) {
      case 'inbox':
        return <Inbox />;

      case 'today':
        return <Today />;

      case 'upcoming':
        return <Upcoming />;

      case 'filtersAndLabels':
        return <FiltersAndLabels />;

      default:
        return null;
    }
  }

  return (
    <main>
      {renderContent()}
    </main>
  );
}
© www.soinside.com 2019 - 2024. All rights reserved.