在react裡,元件間的資料主要是靠props來傳遞,然而,當元件越來越多的時候,這樣的傳遞就會過於複雜,這時候就可以利用context來分享資料。在Firebase Advanced Topics裡,我們介紹了ThemeProvider,其實,ThemeProvider就是利用context。當然,也可以使用更複雜的Redux/Recoil來達成這樣的效果,不過,多數的情況下,Context就能解決問題了。

假如我們想把登入狀況的資料放在Context裡,要怎麼處理?

createContext()

首先,先新增一個AuthContext.js,在AuthContext.js裡,先利用createContext,產生一個context物件。

/src/account/AuthContext.js

import React from 'react';
/*
status及setStatus在provider會被覆蓋
status為toSignIn 已註冊,將要登入
status為toSignOut 已登入,將要登出  
status為toSignUp 未註冊,將要註冊  
*/
export const STATUS = {
  toSignIn: 1,
  toSignOut: 2,
  toSignUp: 0,
};

export const AuthContext = React.createContext({
    status: STATUS.toSignIn
})

context.Provider

就像使用Theme一樣,我們想要讓所有元件都可以使用到AuthContext的內容,所以,我們加上Provider,並提供預設的值。

/src/App.js

import "./styles.css";
import { BrowserRouter as Router } from "react-router-dom";
//import { HashRouter as Router } from "react-router-dom";
import { Routes, Route } from "react-router-dom";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { green, purple } from "@mui/material/colors";
import Exam from "./ExamEnglish";
import Chemistry from "./Chemistry";
import Image from "./Image";
import Main from "./ui/Main";
**import { AuthContext, STATUS } from "./account/AuthContext";**

const theme = createTheme({
  palette: {
    primary: {
      main: purple[500]
    },
    secondary: {
      main: green[500]
    }
  }
});

export default function App() {
  return (
    **<AuthContext.Provider value={{ status: STATUS.toSignIn }}>**
      <ThemeProvider theme={theme}>
        <Router>
          <Routes>
            <Route path="/" element={<Main />} />
            <Route path="/exam" element={<Exam />} />
            <Route path="/chemistry" element={<Chemistry />} />
            <Route path="/image" element={<Image />} />
          </Routes>
        </Router>
      </ThemeProvider>
    **</AuthContext.Provider>**
  );
}

useContext

類似Theme的使用,透過useContext()取得status的值。

/src/ui/AppMenu.js

import React, **{ useContext }** from "react";

import { NavLink } from "react-router-dom";
import { AppBar, Button, Toolbar } from "@mui/material";
import { useTheme } from "@mui/material/styles";
**import { AuthContext, STATUS } from "/src/account/AuthContext";**

export default function AppMenu() {

  const theme = useTheme();
  **const authContext = useContext(AuthContext);**
  const activeStyle = {
    backgroundColor: theme.palette.secondary.main,
    color: "black"
  };
  const inActiveStyle = { backgroundColor: "inherit", color: "inherit" };
  return (
    <AppBar position="sticky">
      <Toolbar>
        <Button
          component={NavLink}
          to="/"
          style={({ isActive }) => (isActive ? activeStyle : inActiveStyle)}
        >
          Main
        </Button>
        <Button
          component={NavLink}
          to="/exam"
          style={({ isActive }) => (isActive ? activeStyle : inActiveStyle)}
        >
          Exam
        </Button>

        <Button
          component={NavLink}
          to="/chemistry"
          style={({ isActive }) => (isActive ? activeStyle : inActiveStyle)}
        >
          Chemistry
        </Button>
        <Button
          component={NavLink}
          to="/image"
          style={({ isActive }) => (isActive ? activeStyle : inActiveStyle)}
        >
          Image
        </Button>
        **{authContext.status === STATUS.toSignIn ? "未登入" : "已登入"}**
      </Toolbar>
    </AppBar>
  );
}

Untitled

更動context

在介紹Theme的時候,並沒有介紹如何去更動內容,在這裡,因為登入之後狀態必須被更新,要怎麼處理呢?

先在AuthContext留一個空的方法,其實這裡的變數值及方法都會在設定Provider時覆蓋,在這裡寫的好處是可以知道AuthContext會有status變數及set方法。

/src/account/AuthContext.js

import React from "react";
/*
status及setStatus在provider會被覆蓋
status為toSignIn 已註冊,將要登入
status為toSignOut 已登入,將要登出  
status為toSignUp 未註冊,將要註冊  
*/
export const STATUS = {
  toSignIn: 1,
  toSignOut: 2,
  toSignUp: 0
};

export const AuthContext = React.createContext({
  status: STATUS.toSignIn,
  **setStatus: (newStatus) => {
    this.status = newStatus;
  }**
});