摘要:在本教程中,您将学习 React 上下文以及如何使用它在整个 React 应用程序中共享状态。
React 上下文简介
React 上下文是 React 中的一项功能,它允许您在整个 React 应用程序(或其一部分)中共享 状态,而无需通过组件树的每一层传递 props。
当许多位于不同嵌套级别的组件必须访问状态的一部分时,React 上下文很有用。
以下是如何实现 React 上下文的逐步指南。
步骤 1. 创建一个上下文
首先,使用 react 库中的 createContext() 函数创建一个上下文。
// MyContext.js
import { createContext } fronm 'react';
const MyContext = createContext();Code language: JavaScript (javascript)步骤 2. 创建一个 Provider 组件
Provider 组件将使用上下文对象并将共享状态提供给其子组件。
// MyProvider.js
import { useState } from 'react';
import MyContext from './MyContext';
const MyProvider = ({ children }) => {
const [state, setState] = useState();
const shared = {state, setState };
return (
<MyContext.Provider value={shared}>
{children}
</MyContext.Provider>
);
};
export default MyProvider;Code language: JavaScript (javascript)在此示例中,Provider 组件将共享包含 state 和 setState 函数的对象(shared)与其子组件。
除了 state 和 setState 函数之外,您还可以通过将它们放在共享对象中来共享任何函数。
步骤 3. 在您的应用程序中使用 Provider 组件
将组件树包装在 Provider 组件中,以便所有子组件都可以访问上下文对象。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import MyProvider from './MyProvider';
const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);
root.render(
<MyProvider>
<App />
</MyProvider>
);Code language: JavaScript (javascript)在此示例中,App 组件及其所有子组件将能够访问 Provider 组件共享的 MyContext 对象。
步骤 4. 在组件中使用上下文
以下是说明如何从 Provider 组件的子组件中使用(或使用或访问)MyContext 对象的方法。
import { useContext } from 'react';
import MyContext from './MyContext';
const MyComponent = () => {
const { state, setState } = useContext(MyContext);
return (
<div>
<p>{state.someValue}</p>
<button onClick={() => setState({ ...state, someValue: 'New Value' })}>
Update Value
</button>
</div>
);
};
export default MyComponent;Code language: JavaScript (javascript)使用上下文重构 Todo 应用程序
让我们重构 Todo 应用程序并使用 React 上下文。
在此图中
- App 组件将从上下文中访问
getTodos函数以获取所有待办事项。 - TodoList 组件将访问
todos状态以显示所有待办事项。 - TodoShow 组件将访问
changeTodo和removeTodo函数。 - TodoCreate 组件将访问
createTodo函数以创建一个新的待办事项。
1) 创建 React 上下文
首先,在 src 目录中创建一个名为 context 的新目录。
其次,创建一个 todos.js 文件,该文件将存储上下文对象和 Provider 组件。
import { createContext, useState } from 'react';
const TodosContext = createContext();
const Provider = ({ children }) => {
const [todos, setTodos] = useState([]);
const getTodos = async () => {
const url = 'https://:5000/todos';
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const storedTodos = await response.json();
// Update the state
if (storedTodos) setTodos(storedTodos);
} catch (error) {
console.error('Error during GET request:', error);
}
};
const createTodo = async (title) => {
// call an API to create a new todo
const url = 'https://:5000/todos';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: title,
completed: false,
}),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const responseData = await response.json();
const newTodo = { ...responseData };
// add the new todo to the list
const updatedTodos = [...todos, newTodo];
setTodos(updatedTodos);
} catch (error) {
console.error('Error creating a todo:', error);
}
};
const removeTodo = async (id) => {
// Delete the todo
const url = `https://:5000/todos/${id}`;
try {
const response = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
// Update the state
const updatedTodos = todos.filter((todo) => todo.id !== id);
setTodos(updatedTodos);
} catch (error) {
console.error('Error during DELETE request:', error);
throw error;
}
};
const changeTodo = async (id, newTitle, completed = false) => {
// Update todo
const url = `https://:5000/todos/${id}`;
const data = { title: newTitle, completed };
try {
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const responseData = await response.json();
// Update the state
const updatedTodos = todos.map((todo) => {
if (todo.id === id) {
return { ...todo, ...responseData };
}
return todo;
});
setTodos(updatedTodos);
} catch (error) {
console.error('Error during PUT request:', error);
throw error;
}
};
const shared = { todos, getTodos, createTodo, removeTodo, changeTodo };
return (
<TodosContext.Provider value={shared}>{children}</TodosContext.Provider>
);
};
export default TodosContext;
export { Provider };Code language: JavaScript (javascript)它是如何工作的。
首先,从 react 库中导入 createContext 和 useState 函数。
import { createContext, useState } from 'react';Code language: JavaScript (javascript)其次,使用 createContext 函数创建一个名为 TodosContext 的新 Context 对象。
const TodosContext = createContext();Code language: JavaScript (javascript)第三,定义 Provider 组件。
const Provider = ({ children }) => {
// ...
}Code language: JavaScript (javascript)第四,使用 useState 函数为 Provider 组件创建一个状态。
const [todos, setTodos] = useState([]);Code language: JavaScript (javascript)第五,将 getTodos、createTodo、removeTodo 和 changeTodo 函数从 App 组件移动到 Provider 组件。
第六,定义一个要在跨组件共享状态和函数的对象。
const shared = { todos, getTodos, createTodo, removeTodo, changeTodo };Code language: JavaScript (javascript)第七,将共享对象传递给 Provider 组件的 value 属性,并返回 Provider 组件。
return (
<TodosContext.Provider value={shared}>{children}</TodosContext.Provider>
);Code language: JavaScript (javascript)最后,使用默认导出导出 TodosContext,并使用命名导出导出 Provider 组件。
export default TodosContext;
export { Provider };Code language: JavaScript (javascript)使用 Provider 组件
修改 index.js,在 index.js 文件中使用 Provider 组件包装 App 组件。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from './context/todos';
const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);
root.render(
<Provider>
<App />
</Provider>
);Code language: JavaScript (javascript)它是如何工作的。
首先,从上下文中导入 Provider 组件。
import { Provider } from './context/todos';Code language: JavaScript (javascript)其次,使用 Provider 组件包装 App 组件。
root.render(
<Provider>
<App />
</Provider>
);Code language: JavaScript (javascript)App 组件及其所有子组件将能够访问 Provider 组件提供的 TodosContext 对象。
修改 App 组件
将 App 组件更改为以下内容。
import TodoList from './components/TodoList';
import TodoCreate from './components/TodoCreate';
import { useEffect, useContext } from 'react';
import TodosContext from './context/todos';
import './App.css';
const App = () => {
const { getTodos } = useContext(TodosContext);
useEffect(() => {
getTodos();
}, []);
return (
<main className="main">
<h1>
React Todo <span>Streamline Your Day, the React Way!</span>
</h1>
<TodoList />
<TodoCreate />
</main>
);
};
export default App;
Code language: JavaScript (javascript)它是如何工作的。
首先,导入 TodosContext 对象。
import TodosContext from './context/todos';Code language: JavaScript (javascript)其次,使用 useContext() 函数从 TodosContext 中访问 getTodos 函数。
const { getTodos } = useContext(TodosContext);Code language: JavaScript (javascript)第三,在 useEffect 钩子中调用 getTodos 函数。
useEffect(() => {
getTodos();
}, []);Code language: JavaScript (javascript)修改 TodoList 组件
修改使用 TodosContext 的 TodoList 组件。
import TodoShow from './TodoShow';
import TodosContext from '../context/todos';
import { useContext } from 'react';
const TodoList = () => {
const { todos } = useContext(TodosContext);
const renderedTodos = todos.map((todo) => {
return <TodoShow key={todo.id} todo={todo} />;
});
return <ul className="todo-list">{renderedTodos}</ul>;
};
export default TodoList;Code language: JavaScript (javascript)它是如何工作的。
首先,导入 TodosContext。
import TodosContext from '../context/todos';Code language: JavaScript (javascript)其次,导入 useContext 函数。
import { useContext } from 'react';Code language: JavaScript (javascript)第三,从 TodosContext 中访问 todos 数组。
const { todos } = useContext(TodosContext);Code language: JavaScript (javascript)最后,将每个待办事项传递给 TodoShow 组件。
const renderedTodos = todos.map((todo) => {
return <TodoShow key={todo.id} todo={todo} />;
});Code language: JavaScript (javascript)修改 TodoShow 组件
更改 TodoShow.js 组件以使用 TodosContext。
import { useState } from 'react';
import TodoEdit from './TodoEdit';
import EditIcon from '../edit.svg';
import DeleteIcon from '../delete.svg';
import { useContext } from 'react';
import TodosContext from '../context/todos';
const TodoShow = ({ todo }) => {
const [showEdit, setShowEdit] = useState(false);
const { removeTodo, changeTodo } = useContext(TodosContext);
const handleDelete = (e) => {
removeTodo(todo.id);
};
const handleEdit = (e) => {
setShowEdit(!showEdit);
};
const handleSubmit = (id, title) => {
changeTodo(id, title);
setShowEdit(false);
};
const handleDoubleClick = (e) => {
e.preventDefault();
changeTodo(todo.id, todo.title, !todo.completed);
setShowEdit(false);
};
if (showEdit)
return (
<li className="todo">
<TodoEdit todo={todo} onSubmit={handleSubmit} />
</li>
);
else {
return (
<li className="todo" onDoubleClick={handleDoubleClick}>
<p className={todo.completed ? 'completed' : 'open'}>{todo.title}</p>
<div className="actions">
<button onClick={handleDelete}>
<img src={DeleteIcon} title="Delete" />
</button>
<button onClick={handleEdit}>
<img src={EditIcon} title="Edit" />
</button>
</div>
</li>
);
}
};
export default TodoShow;Code language: JavaScript (javascript)它是如何工作的。
首先,从 react 库中导入 useContext 函数,并从 todos.js 模块中导入 TodosContext 对象。
import { useContext } from 'react';
import TodosContext from '../context/todos';Code language: JavaScript (javascript)其次,从上下文中访问 removeTodo 和 changeTodo 函数。
const { removeTodo, changeTodo } = useContext(TodosContext);Code language: JavaScript (javascript)修改 TodoCreate 组件
修改 TodoCreate 组件以使用 TodosContext 对象。
import { useState, useContext } from 'react';
import TodosContext from '../context/todos';
const TodoCreate = () => {
const [todo, setTodo] = useState('');
const { createTodo } = useContext(TodosContext);
const handleSubmit = (e) => {
e.preventDefault();
createTodo(todo);
setTodo('');
};
const handleChange = (e) => {
setTodo(e.target.value);
};
return (
<form onSubmit={handleSubmit} className="todo-create">
<input
type="text"
name="todo"
id="todo"
placeholder="Enter a todo"
value={todo}
onChange={handleChange}
/>
</form>
);
};
export default TodoCreate;Code language: JavaScript (javascript)它是如何工作的。
首先,导入 TodosContext 对象。
import TodosContext from './context/todos';Code language: JavaScript (javascript)其次,使用 useContext() 函数从 TodosContext 中访问 createTodo 函数。
const { createTodo } = useContext(TodosContext);Code language: JavaScript (javascript)第三,调用 createTodo 函数以创建一个新的待办事项。
const handleSubmit = (e) => {
e.preventDefault();
createTodo(todo);
setTodo('');
};Code language: JavaScript (javascript)下载项目源代码
总结
- 使用 React 上下文在整个 React 应用程序中共享状态。