如何在 React js 中使用上下文 API 管理全局状态
How manage global state using context API in React js
我在使用 useContext 管理导航栏状态时遇到问题。 Atm 我的应用程序在菜单切换后立即呈现菜单项。我希望此事件仅在单击时发生,而且按钮不记录 console.log 消息,它仅在我直接单击 link 项 ex:home 时才有效。
所以我有两个问题。我如何管理我的导航栏状态以显示如何隐藏菜单项而无需为其创建新组件?
我如何修复我的点击事件,因为它会在菜单按钮本身 or/and 菜单项上触发?
下面您将为 App.js、Layout.js、ThemeContext.js、useTheme.js、useToggle.js、ToggleContext.js 和使用切换上下文的导航栏编写代码片段.
我真的很感谢这里的一些帮助,我是初级的,真的有点被困在这里。
在此先感谢大家。
狮子座
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;
Layout.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;
您可以使用 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 来处理状态变化
在此我使用本地存储来保持持久状态,因为在页面重新加载时 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;
这是我的代码LINK如果你想看结构Github
我在使用 useContext 管理导航栏状态时遇到问题。 Atm 我的应用程序在菜单切换后立即呈现菜单项。我希望此事件仅在单击时发生,而且按钮不记录 console.log 消息,它仅在我直接单击 link 项 ex:home 时才有效。 所以我有两个问题。我如何管理我的导航栏状态以显示如何隐藏菜单项而无需为其创建新组件? 我如何修复我的点击事件,因为它会在菜单按钮本身 or/and 菜单项上触发? 下面您将为 App.js、Layout.js、ThemeContext.js、useTheme.js、useToggle.js、ToggleContext.js 和使用切换上下文的导航栏编写代码片段.
我真的很感谢这里的一些帮助,我是初级的,真的有点被困在这里。 在此先感谢大家。
狮子座
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;
Layout.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;
您可以使用 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 来处理状态变化
在此我使用本地存储来保持持久状态,因为在页面重新加载时 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;
这是我的代码LINK如果你想看结构Github