React 上下文

摘要:在本教程中,您将学习 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 组件将共享包含 statesetState 函数的对象(shared)与其子组件。

除了 statesetState 函数之外,您还可以通过将它们放在共享对象中来共享任何函数。

步骤 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 上下文。

React Context

在此图中

  • App 组件将从上下文中访问 getTodos 函数以获取所有待办事项。
  • TodoList 组件将访问 todos 状态以显示所有待办事项。
  • TodoShow 组件将访问 changeTodoremoveTodo 函数。
  • 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 库中导入 createContextuseState 函数。

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)

第五,将 getTodoscreateTodoremoveTodochangeTodo 函数从 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 组件

修改使用 TodosContextTodoList 组件。

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)

其次,从上下文中访问 removeTodochangeTodo 函数。

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 Todo 应用程序

总结

  • 使用 React 上下文在整个 React 应用程序中共享状态。
本教程是否有用?