摘要:在本教程中,您将学习 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 = 'http://localhost: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 = 'http://localhost: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 = `http://localhost: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 = `http://localhost: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 应用程序中共享状态。