【问题标题】:How manage global state using context API in React js如何在 React js 中使用上下文 API 管理全局状态
【发布时间】:2021-12-20 12:35:49
【问题描述】:

我在使用 useContext 管理导航栏的状态时遇到问题。 Atm 我的应用程序在菜单切换后立即呈现菜单项。我希望此事件仅在单击时发生,并且该按钮也不记录 console.log 消息,它仅在我直接单击链接项 ex:home 时才有效。 所以我有2个问题。如何管理我的导航栏状态以显示如何隐藏菜单项而无需为其创建新组件? 如何修复我的点击事件,因为它在菜单按钮本身或/和菜单项上触发? 下面您将为 App.js、Layout.js、ThemeContext.js、useTheme.js、useToggle.js、ToggleContext.js 和使用切换上下文的导航栏编写 sn-ps。

我真的很想在这里得到一些帮助,我是初级,真的有点卡在这里。 在此先感谢大家。

狮子座

App.js

//import { data } from '../../SkillData';
import Header from './Header';
import Navbar from './Navbar';
import Skills from './Skills';
import Layout from './Layout';

function App () {

    return (
        <Layout startingTheme="light" startingToggle={"show"}>
        <div>
        <Navbar />
        <Header />
        <Skills />
        </div>
        </Layout>
    );
}

export default App;

布局.js

import React, { useContext } from "react";
import { ThemeContext, ThemeProvider } from "../contexts/ThemeContext";
import { ToggleContext, ToggleProvider } from "../contexts/ToggleContext";
function Layout ({startingTheme, startingToggle, children}) { 
    return (
        <>
        <ThemeProvider startingTheme={startingTheme} >
            <ToggleProvider startingToggle={startingToggle}>
                <LayoutNoToggleProvider>
                </LayoutNoToggleProvider> 
            </ToggleProvider>
            <LayoutNoThemeProvider >{children}</LayoutNoThemeProvider>
        </ThemeProvider>        
        </>
        
    );
}

function LayoutNoToggleProvider ({children}) {
    const  toggle = useContext(ToggleContext);

    return (
        <div className={            
        toggle === false ? "navbar navbar-collapsed" : "navbar navbar-collapse show"
        }> 
        {children}     
        </div>
    )
}

function LayoutNoThemeProvider ({ children }) {
    const {theme} = useContext(ThemeContext);

    return (
        
        <div className={
            theme === "light" ? 
            "container-fluid bg-white" :
            "container-fluid bg-dark"  
        }>
        {children}
        </div>
    
    );
}
export default Layout;

主题上下文

import React, { createContext} from "react";
import useTheme from "../hooks/useTheme";

export const ThemeContext = createContext(); 

function ThemeProvider ({children, startingTheme}) {
    const { theme, setTheme } = useTheme(startingTheme);

    return (
        <ThemeContext.Provider value={
            {theme, setTheme}
        }>    
        {children}    
        </ThemeContext.Provider>
    );

}

export { ThemeProvider };

useTheme.js

import { useState } from "react";

function useTheme (startingTheme ="light") {

    const [theme, setTheme] = useState(startingTheme);

    function validateTheme (themeValue) {
        if (themeValue === "dark") {
            setTheme("dark");
        } else {
            setTheme("light");
        }
    }    

    return {
        theme,
        setTheme: validateTheme,
    }
}

export default useTheme;

ToggleContext.js

import React, { createContext } from "react";
import useToggle from "../hooks/useToggle";

export const ToggleContext = createContext();

function ToggleProvider({ children, startingToggle }) {
  const { toggle, setToggle } = useToggle(startingToggle);

  return (
    <ToggleContext.Provider value={{ toggle, setToggle }}>
      {children}
    </ToggleContext.Provider>
  );
}

export { ToggleProvider };

useToggle.js

import { useState } from "react";

function useToggle (startingToggle = false) {
    const [toggle, setToggle] = useState(startingToggle);

    function validateShowSidebar (showSidebarValue) {
        if (showSidebarValue === "show")  {
            setToggle("show");
        } else {
            setToggle("");
        }
    }
    return {
        toggle,
        setToggle: validateShowSidebar,
    }
}

export default useToggle;

Navbar.js

import Image from "next/image";
import styles from "../../styles/Home.module.scss"
import Logo  from "../../public/Knowledge Memo.svg"
import { useContext } from "react";
import { ThemeContext } from "../contexts/ThemeContext";
import { ToggleContext } from "../contexts/ToggleContext";
import Link from 'next/link';
import { useState } from "react";


const navbarData  = [
    {   id: "1",
        title: "home",
        ref: "#home"
    },
    {   id:"2",
        title: "Skills",
        ref: "#skills"
    },
    {   id:"3",
        title: "The List",
        ref: "#theList"
    },
    {   id: "4",
        title: "Team",
        ref: "#team"
    },
    {   id: "5",
        title: "Contact",
        ref: "#contact"
    },
];

function Navbar() {

    const theme = useContext(ThemeContext);
    const toggle  = useContext(ToggleContext);   


    return (
        <>  
            
            <nav className={
                theme === "light" ? 
                "navbar navbar-expand-lg navbar-dark fixed-top": 
                "navbar navbar-expand-lg navbar-dark bg-dark fixed-top id= mainNav"}>
                <div className="container d-flex flex justify-content-between">
                    <a className="navbar-brand h-50" href="#page-top">
                    <div className="navbar-brand"> 
                    <Image 
                    src={Logo} 
                    alt="..."                  
                    fill="#fff"
                    objectFit="contain"
                    className="h-50"                    
                    />
                    </div>
                    </a>                    
                    <button
                    onClick={ () => toggle === !toggle, console.log("clicked")}
                    className="navbar-toggler collapsed" 
                    type="button" 
                    data-bs-toggle="collapsed" 
                    data-bs-target="#navbarResponsive" 
                    aria-controls="navbarResponsive" 
                    aria-expanded="false" 
                    aria-label="Toggle navigation"
                    >
                        Menu
                    <i className="fa fa-bars ms-1 navbar-toggler" aria-hidden="true"></i>
                    </button>
                    {toggle ?
                    <div className="collapsed navbar-collapse mt-2 id=navbarResponsive">
                        <ul className="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
                            {navbarData.map((link,idx) => {

                                return (
                                    <li key={link.id}>
                                        <Link  href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
                                        <a className="nav-link">
                                        {link.title}
                                        </a>
                                        </Link>
                                    </li>

                                );
                            })}
                        </ul>
                    </div>
:                     <div className="collapse navbar-collapse show mt-2 id=navbarResponsive">
<ul className="navbar-nav show text-uppercase ms-auto py-4 py-lg-0">
    {navbarData.map((link,idx) => {

        return (
            <li key={link.id}>
                <Link  href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
                <a className="nav-link">
                {link.title}
                </a>
                </Link>
            </li>

        );
    })}
</ul>
</div>}
                </div>
            </nav>
        </>
    );
}

export default Navbar;

【问题讨论】:

    标签: reactjs navbar react-state-management use-context use-reducer


    【解决方案1】:

    您可以尝试使用 reducer 来实现此实现,以通过 localstorage 为您处理状态更改。这不是您的精确实现,但您可以看到流程

    在AppContext.jsx

    AppContext 保存应用程序的全局状态,因此可以更轻松地使用单个上下文提供程序并将操作分派给特定的 reducer 以处理状态更改,而无需提供许多提供程序。 combinedReducers 处理给定状态组件的 reducer 方法

    import { useReducer, createContext, useEffect } from "react";
    import userReducer from "./reducers/userReducer";
    import themeReducer from "./reducers/themeReducer";
    export const APP_NAME = "test_app";
    
    //Check the localstorage or set a default state
    const initialState = JSON.parse(localStorage.getItem(APP_NAME))
      ? JSON.parse(localStorage.getItem(APP_NAME))
      : {
          user: {
            username: "",
            email: "",
            isAdmin: false,
          },
          theme: { dark: false },
        };
    //Create your global context
    const AppContext = createContext(initialState);
    
    //Create combined reducers
    const combinedReducers = ({ user, theme }, action) => ({
      user: userReducer(user, action),
      theme: themeReducer(theme, action),
    });
    const AppState = ({ children }) => {
      //Making it to provider state
      const [state, dispatch] = useReducer(combinedReducers, initialState);
      useEffect(() => {
        localStorage.setItem(APP_NAME, JSON.stringify(state));
      }, [state]);
      return (
        <AppContext.Provider value={{ state, dispatch }}>
          {children}
        </AppContext.Provider>
      );
    };
    
    export default AppState;
    
    export { AppContext, AppState };
    

    上面的实现类似于redux,但是你将给定的状态解构到一个特定的reducer来处理状态变化

    在此我使用 localstorage 来保持持久状态,因为在页面重新加载时使用 context API 时状态会变为。使用来自react 的useEffect 钩子并在依赖数组中添加状态以确保您的状态同步

    在UserReducer.jsx

    const userReducer = (state, action) => {
      const { type, payload } = action;
      switch (type) {
        case "LOGIN":
          return { ...state, ...payload };
        case "LOGOUT":
          return {};
        default:
          return state;
      }
    };
    
    export default userReducer;
    

    在ThemeReducer.jsx

    const themeReducer = (state, action) => {
      const { type, payload } = action;
      switch (type) {
        case "DARK":
          return { ...payload };
        default:
          return state;
      }
    };
    
    export default themeReducer;
    

    使用index.jsx 中的单个提供程序包装整个应用程序

    import reactDom from "react-dom"
    import React from "react"
    import App from "./App"
    import "./index.css"
    import AppState from "./state/AppState"
    
    reactDom.render(
        <React.StrictMode>
            <AppState >
                <App />
            </AppState>
        </React.StrictMode>,
        document.getElementById("root")
    )
    

    从App.jsx访问上下文

    import { useContext } from "react";
    import { AppContext } from "./state/AppState";
    const App = () => {
      const { state, dispatch } = useContext(AppContext);
      const handleLogin = () => {
        dispatch({
          type: "LOGIN",
          payload: {
            username: "Mike",
            email: "mike@gmail.com",
            isAdmin: false,
          },
        });
      };
    
      const handleLogout = () => {
        dispatch({
          type: "LOGOUT",
          payload: {},
        });
      };
    
      return (
        <div className="main-container">
          <div className="container">
            <p>Username: {state.user.username ? state.user.username : "Unknown"}</p>
            <p>Email: {state.user.email ? state.user.email : "Unknown"}</p>
          </div>
          <button onClick={handleLogin}>Login</button>
          <button onClick={handleLogout} style={{ background: "red" }}>
            Login
          </button>
        </div>
      );
    };
    
    export default App;
    

    如果你想查看结构Github,这是我的代码链接

    【讨论】:

    • 谢谢@FelixOrinda。这个实现肯定会起作用,但我正在学习 useContext 以及如何使用它来管理整个应用程序的多个上下文。使用效果和 CRUD 是之后的下一步。出于我正在学习 atm 的目的,如果解决方案可以保留在上下文中,我将非常感激。再次感谢
    • 我发现context API 很方便,可以将它用于我所有的复杂状态。使用这种方法可以更轻松地处理多个状态
    猜你喜欢
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-26
    • 2021-09-29
    • 2021-12-15
    • 2015-06-23
    相关资源
    最近更新 更多