我正在使用函数和类组件创建一个基本的待办事项列表应用程序。我使用了react-router来链接到包含待办事项列表代码的相应代码,其中包含功能/类组件。我还在探索 useContext 在这些页面上启用亮/暗模式。我成功地做到了这一点。此刻的光明/黑暗正在普遍发挥作用。因此,无论我在一个页面中启用它,它都会在另一个页面中显示相同的主题。
现在,我想以这样的方式启用它,即每个页面的主题切换都是单独的。因此,无论我在FunctionalToDo代码中启用深色模式,它在ClassToDo代码中仍应保持浅色模式。默认为灯光模式。如何做到这一点?
FunctionalToDo.jsx
// FunctionalToDo.jsx
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import {
Button, Checkbox, TextField, List, ListItem, ListItemText, IconButton, Typography, Container, CssBaseline, Box
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import { useTheme } from '../components/themeContext';
import '../components/styles.css';
const FunctionalToDo = () => {
const task = useRef('');
const [tasks, setTasks] = useState([]);
const [isError, setIsError] = useState(false);
const inputRef = useRef(null);
const editIndexRef = useRef(-1);
const { theme } = useTheme();
useEffect(() => {
(async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
if (!response.ok) {
throw new Error('Functional: Network response was not ok');
}
const data = await response.json();
const initialTasks = data.slice(0, 25).map(todo => ({
id: crypto.randomUUID(),
text: todo.title,
completed: todo.completed,
}));
setTasks(initialTasks);
console.log('Functional: Data fetched successfully:', initialTasks);
} catch (error) {
console.error('Functional: Error fetching data:', error);
setIsError(true);
}
})();
console.log('Functional: Component did mount');
}, []);
useEffect(() => {
console.log('Functional: Tasks state updated');
}, [tasks]);
const handleTask = useCallback(() => {
if (task.current) {
setTasks(prevTasks => {
if (editIndexRef.current !== -1) {
const updatedTasks = [...prevTasks];
updatedTasks[editIndexRef.current].text = task.current;
return updatedTasks;
} else {
const newTask = { id: crypto.randomUUID(), text: task.current, completed: false };
return [...prevTasks, newTask];
}
});
editIndexRef.current = -1;
task.current = '';
inputRef.current.value = '';
}
}, []);
const handleClearTasks = useCallback(() => {
setTasks([]);
}, []);
const taskList = useMemo(() => tasks.map((item) => (
<ListItem key={item.id} style={{ display: 'flex', alignItems: 'center' }}>
<Checkbox
id={item.id}
checked={item.completed}
onChange={() => {
const updatedTasks = tasks.map(task => (
task.id === item.id ? { ...task, completed: !task.completed } : task
));
setTasks(updatedTasks);
}}
/>
<ListItemText
primary={item.text}
style={{ textDecoration: item.completed ? 'line-through' : 'none' }}
/>
<IconButton onClick={() => {
const taskToEdit = tasks.find(task => task.id === item.id);
task.current = taskToEdit.text;
editIndexRef.current = tasks.findIndex(task => task.id === item.id);
inputRef.current.value = taskToEdit.text;
inputRef.current.focus();
}}>
<EditIcon />
</IconButton>
<IconButton onClick={() => {
const updatedTasks = tasks.filter(task => task.id !== item.id);
setTasks(updatedTasks);
}}>
<DeleteIcon />
</IconButton>
</ListItem>
)), [tasks]);
return (
<>
<CssBaseline />
<Container
className={`container ${theme.functional === 'dark' ? 'dark-mode' : 'light-mode'}`}
>
<Typography variant="h3" align="center" gutterBottom>
ToDo App - Functional Component
</Typography>
{isError && (
<Typography variant="h6" align="center" color="error">
Error fetching data. Please try again later.
</Typography>
)}
<TextField
label="Enter task"
variant="outlined"
fullWidth
style={{ marginBottom: '20px' }}
inputRef={inputRef}
onChange={(e) => { task.current = e.target.value }}
/>
<Box textAlign="center">
<Button
variant="contained"
color="primary"
onClick={handleTask}
style={{ marginBottom: '20px', marginRight: '10px' }}
>
{editIndexRef.current !== -1 ? 'Update Task' : 'Add Task'}
</Button>
<Button
variant="contained"
color="secondary"
onClick={handleClearTasks}
style={{ marginBottom: '20px' }}
>
Clear All Tasks
</Button>
</Box>
<List>{taskList}</List>
</Container>
</>
);
};
export default FunctionalToDo;
ClassToDo.jsx
// ClassToDo.jsx
import React from 'react';
import {
Button, Checkbox, TextField, List, ListItem, ListItemText, IconButton, Typography, Container, CssBaseline, Box
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import { useTheme } from '../components/themeContext';
import '../components/styles.css';
class ClassToDo extends React.Component {
constructor(props) {
super(props);
this.state = {
task: '',
tasks: [],
editIndex: -1,
isError: false,
};
this.inputRef = React.createRef();
}
componentDidMount() {
(async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
if (!response.ok) {
throw new Error('Class: Network response was not ok');
}
const data = await response.json();
const initialTasks = data.slice(0, 25).map(todo => ({
id: crypto.randomUUID(),
text: todo.title,
completed: todo.completed,
}));
this.setState({ tasks: initialTasks });
console.log('Class: Data fetched successfully:', initialTasks);
} catch (error) {
console.error('Class: Error fetching data:', error);
this.setState({ isError: true });
}
})();
console.log('Class: Component did mount');
}
componentDidUpdate(prevProps, prevState) {
console.log('Class: ComponentDidUpdate: Component has updated.');
if (prevState.tasks !== this.state.tasks) {
console.log('Class: Tasks state updated');
}
}
componentWillUnmount() {
console.log('Class: ComponentWillUnmount: Component is about to be unmounted.');
}
handleAddTask = () => {
const { task, tasks, editIndex } = this.state;
if (task) {
if (editIndex !== -1) {
const updatedTasks = [...tasks];
updatedTasks[editIndex].text = task;
this.setState({ tasks: updatedTasks, editIndex: -1, task: '' });
} else {
const newTask = { id: crypto.randomUUID(), text: task, completed: false };
this.setState({ tasks: [...tasks, newTask], task: '' });
}
}
}
handleClearTasks = () => {
this.setState({ tasks: [] });
}
renderItem = (item, index) => (
<ListItem key={item.id} style={{ display: 'flex', alignItems: 'center' }}>
<Checkbox
id={item.id}
checked={item.completed}
onChange={() => {
const updatedTasks = this.state.tasks.map(task => (
task.id === item.id ? { ...task, completed: !task.completed } : task
));
this.setState({ tasks: updatedTasks });
}}
/>
<ListItemText
primary={item.text}
style={{ textDecoration: item.completed ? 'line-through' : 'none' }}
/>
<IconButton onClick={() => {
const taskToEdit = this.state.tasks[index];
this.setState({ task: taskToEdit.text, editIndex: index });
this.inputRef.current.focus(); // Focus on the input field when editing
}}>
<EditIcon />
</IconButton>
<IconButton onClick={() => {
const updatedTasks = this.state.tasks.filter(task => task.id !== item.id);
this.setState({ tasks: updatedTasks });
}}>
<DeleteIcon />
</IconButton>
</ListItem>
);
render() {
const { task, tasks, isError } = this.state;
const { theme } = this.props;
return (
<>
<CssBaseline />
<Container
className={`container ${theme === 'dark' ? 'dark-mode' : 'light-mode'}`}
>
<Typography variant="h3" align="center" gutterBottom>
ToDo App - Class Component
</Typography>
{isError && (
<Typography variant="h6" align="center" color="error">
Error fetching data. Please try again later.
</Typography>
)}
<TextField
label="Enter task"
variant="outlined"
value={task}
onChange={(e) => this.setState({ task: e.target.value })}
fullWidth
style={{ marginBottom: '20px' }}
inputRef={this.inputRef}
/>
<Box textAlign="center">
<Button
variant="contained"
color="primary"
onClick={this.handleAddTask}
style={{ marginBottom: '20px', marginRight: '10px' }}
>
{this.state.editIndex !== -1 ? 'Update Task' : 'Add Task'}
</Button>
<Button
variant="contained"
color="secondary"
onClick={this.handleClearTasks}
style={{ marginBottom: '20px' }}
>
Clear All Tasks
</Button>
</Box>
<List>{tasks.map((item, index) => this.renderItem(item, index))}</List>
</Container>
</>
);
}
}
const WrappedClassToDo = (props) => (
<ClassToDo {...props} theme={useTheme().theme.class} />
);
export default WrappedClassToDo;
index.jsx
//index.jsx
import React from "react";
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import App from './App';
import { createRoot } from 'react-dom/client';
import { ThemeProvider } from './components/themeContext';
import './components/styles.css';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<ThemeProvider>
<App />
</ThemeProvider>
);
应用程序.jsx
//App.jsx
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom';
import { AppBar, Toolbar, Typography, Switch, Button, Container, Box } from '@mui/material';
import FunctionalToDo from './pages/FunctionalToDo';
import ClassToDo from './pages/ClassToDo';
import { useTheme } from './components/themeContext';
import './components/styles.css';
const TopBar = () => {
const { theme, toggleTheme } = useTheme();
const navigate = useNavigate();
return (
<AppBar position="static">
<Toolbar>
<Typography variant="h6" style={{ flexGrow: 1 }}>
ToDo App
</Typography>
<Button color="inherit" onClick={() => navigate('/functional')}>
Functional
</Button>
<Button color="inherit" onClick={() => navigate('/class')}>
Class
</Button>
<Box display="flex" alignItems="center">
<Typography variant="body1" style={{ marginRight: 8 }}>
Dark Mode
</Typography>
<Switch
checked={theme === 'dark'}
onChange={toggleTheme}
/>
</Box>
</Toolbar>
</AppBar>
);
};
const App = () => {
const { theme } = useTheme();
useEffect(() => {
document.body.className = theme === 'dark' ? 'dark-mode' : 'light-mode';
}, [theme]);
return (
<Router>
<TopBar />
<Container maxWidth="sm" className="container">
<Routes>
<Route path="/" />
<Route path="/functional" element={<FunctionalToDo />} />
<Route path="/class" element={<ClassToDo />} />
</Routes>
</Container>
</Router>
);
};
export default App;
样式.css
/* styles.css */
/* Reset styles */
html, body, #root {
height: 100%;
margin: 0;
transition: background-color 0.3s, color 0.3s;
}
/* Light mode styles */
body.light-mode {
background-color: #ffffff;
color: #000000;
}
/* Dark mode styles */
body.dark-mode {
background-color: #333333;
color: #ffffff;
}
/* Additional styling */
.container {
padding: 20px;
}
/* Dark and light mode styles for specific components can also be added */
.container.dark-mode {
background-color: inherit;
color: inherit;
}
themeContext.jsx
//themeContext.jsx
import React, { createContext, useState, useContext } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
您可以通过使用页面名称作为键将主题值存储为对象而不是将其存储为字符串来实现此目的。
themeContext.jsx
//themeContext.jsx
import React, { createContext, useState, useContext } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState({classToDo:'light', functionalToDo:'light'});
const toggleTheme = (page) => {
setTheme(prevTheme => ({
...prevTheme,
[page]:prevTheme[page] === 'light'? 'dark' : 'light'
}));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
因此,只要您使用
theme
中的 useTheme
值来确定当前是亮还是暗,您都需要根据页面将其更新为 theme.classToDO
或 theme.functionalToDo
。