使用 Microsoft Graph 生成 React 单页应用

创建 React 单页应用

在此部分中,你将创建一个新的React应用。

  1. Open your command-line interface (CLI) , navigate to a directory where you have rights to create files, and run the following commands to create a new React app.

    yarn create react-app graph-tutorial --template typescript
    
  2. 命令完成后,在 CLI 中更改为 图形教程"**目录,并运行以下命令以启动本地 Web 服务器。

    yarn start
    

    备注

    如果尚未安装一个 "分 色",可以 npm start 改为使用。

默认浏览器将打开, https://localhost:3000/ 并包含默认React页面。 如果浏览器未打开,请打开它并浏览 https://localhost:3000/ 以验证新应用是否正常工作。

添加节点包

在继续之前,请安装一些你稍后将使用的其他程序包:

在 CLI 中运行以下命令。

yarn add react-router-dom@5.2.0 bootstrap@5.0.1 react-bootstrap@2.0.0-beta.4 windows-iana@5.0.2
yarn add date-fns@2.22.1 date-fns-tz@1.1.4 @azure/msal-react@1.0.1 @azure/msal-browser@2.16.1 @microsoft/microsoft-graph-client@3.0.0
yarn add -D @types/react-router-dom@5.1.8 @types/microsoft-graph

设计应用

首先为 应用创建 上下文。

  1. 在名为 AppContext.tsx./src 目录中创建新文件并添加以下 import 语句。

    import React, {
      useContext,
      createContext,
      useState,
      MouseEventHandler,
      useEffect} from 'react';
    
    import { AuthCodeMSALBrowserAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser';
    import { InteractionType, PublicClientApplication } from '@azure/msal-browser';
    import { useMsal } from '@azure/msal-react';
    
  2. 添加以下代码。

  3. ./src/AppContext.tsx 的末尾添加以下函数

    function useProvideAppContext() {
      const [user, setUser] = useState<AppUser | undefined>(undefined);
      const [error, setError] = useState<AppError | undefined>(undefined);
    
      const displayError = (message: string, debug?: string) => {
        setError({message, debug});
      }
    
      const clearError = () => {
        setError(undefined);
      }
    
      const authProvider = undefined;
    
      const signIn = async () => {
        // TODO
      };
    
      const signOut = async () => {
        // TODO
      };
    
      return {
        user,
        error,
        signIn,
        signOut,
        displayError,
        clearError,
        authProvider
      };
    }
    

    你将在稍后部分中完成此上下文的实现。

  4. 为应用创建导航栏。 在目录中新建一个名为 ./src 的文件 NavBar.tsx 并添加以下代码。

    import React from 'react';
    import { NavLink as RouterNavLink } from 'react-router-dom';
    import {
      Button,
      Collapse,
      Container,
      Navbar,
      NavbarToggler,
      NavbarBrand,
      Nav,
      NavItem,
      NavLink,
      UncontrolledDropdown,
      DropdownToggle,
      DropdownMenu,
      DropdownItem
    } from 'reactstrap';
    import '@fortawesome/fontawesome-free/css/all.css';
    
    interface NavBarProps {
      isAuthenticated: boolean;
      authButtonMethod: any;
      user: any;
    }
    
    interface NavBarState {
      isOpen: boolean;
    }
    
    function UserAvatar(props: any) {
      // If a user avatar is available, return an img tag with the pic
      if (props.user.avatar) {
        return <img
          src={props.user.avatar} alt="user"
          className="rounded-circle align-self-center mr-2"
          style={{ width: '32px' }}></img>;
      }
    
      // No avatar available, return a default icon
      return <i
        className="far fa-user-circle fa-lg rounded-circle align-self-center mr-2"
        style={{ width: '32px' }}></i>;
    }
    
    function AuthNavItem(props: NavBarProps) {
      // If authenticated, return a dropdown with the user's info and a
      // sign out button
      if (props.isAuthenticated) {
        return (
          <UncontrolledDropdown>
            <DropdownToggle nav caret>
              <UserAvatar user={props.user} />
            </DropdownToggle>
            <DropdownMenu right>
              <h5 className="dropdown-item-text mb-0">{props.user.displayName}</h5>
              <p className="dropdown-item-text text-muted mb-0">{props.user.email}</p>
              <DropdownItem divider />
              <DropdownItem onClick={props.authButtonMethod}>Sign Out</DropdownItem>
            </DropdownMenu>
          </UncontrolledDropdown>
        );
      }
    
      // Not authenticated, return a sign in link
      return (
        <NavItem>
          <Button
            onClick={props.authButtonMethod}
            className="btn-link nav-link border-0"
            color="link">Sign In</Button>
        </NavItem>
      );
    }
    
    export default class NavBar extends React.Component<NavBarProps, NavBarState> {
      constructor(props: NavBarProps) {
        super(props);
    
        this.toggle = this.toggle.bind(this);
        this.state = {
          isOpen: false
        };
      }
    
      toggle() {
        this.setState({
          isOpen: !this.state.isOpen
        });
      }
    
      render() {
        // Only show calendar nav item if logged in
        let calendarLink = null;
        if (this.props.isAuthenticated) {
          calendarLink = (
            <NavItem>
              <RouterNavLink to="/calendar" className="nav-link" exact>Calendar</RouterNavLink>
            </NavItem>
          );
        }
    
        return (
          <div>
            <Navbar color="dark" dark expand="md" fixed="top">
              <Container>
                <NavbarBrand href="/">React Graph Tutorial</NavbarBrand>
                <NavbarToggler onClick={this.toggle} />
                <Collapse isOpen={this.state.isOpen} navbar>
                  <Nav className="mr-auto" navbar>
                    <NavItem>
                      <RouterNavLink to="/" className="nav-link" exact>Home</RouterNavLink>
                    </NavItem>
                    {calendarLink}
                  </Nav>
                  <Nav className="justify-content-end" navbar>
                    <NavItem>
                      <NavLink href="https://developer.microsoft.com/graph/docs/concepts/overview" target="_blank">
                        <i className="fas fa-external-link-alt mr-1"></i>
                        Docs
                      </NavLink>
                    </NavItem>
                    <AuthNavItem
                      isAuthenticated={this.props.isAuthenticated}
                      authButtonMethod={this.props.authButtonMethod}
                      user={this.props.user} />
                  </Nav>
                </Collapse>
              </Container>
            </Navbar>
          </div>
        );
      }
    }
    
  5. 为应用创建主页。 在目录中新建一个名为 ./src 的文件 Welcome.tsx 并添加以下代码。

    import React from 'react';
    import {
      Button,
      Jumbotron
    } from 'reactstrap';
    
    interface WelcomeProps {
      isAuthenticated: boolean;
      authButtonMethod: any;
      user: any;
    }
    
    interface WelcomeState {
      isOpen: boolean;
    }
    
    function WelcomeContent(props: WelcomeProps) {
      // If authenticated, greet the user
      if (props.isAuthenticated) {
        return (
          <div>
            <h4>Welcome {props.user.displayName}!</h4>
            <p>Use the navigation bar at the top of the page to get started.</p>
          </div>
        );
      }
    
      // Not authenticated, present a sign in button
      return <Button color="primary" onClick={props.authButtonMethod}>Click here to sign in</Button>;
    }
    
    export default class Welcome extends React.Component<WelcomeProps, WelcomeState> {
      render() {
        return (
          <Jumbotron>
            <h1>React Graph Tutorial</h1>
            <p className="lead">
              This sample app shows how to use the Microsoft Graph API to access Outlook and OneDrive data from React
            </p>
            <WelcomeContent
              isAuthenticated={this.props.isAuthenticated}
              user={this.props.user}
              authButtonMethod={this.props.authButtonMethod} />
          </Jumbotron>
        );
      }
    }
    
  6. 创建错误消息显示,向用户显示消息。 在目录中新建一个名为 ./src 的文件 ErrorMessage.tsx 并添加以下代码。

    import React from 'react';
    import { Alert } from 'reactstrap';
    
    interface ErrorMessageProps {
      debug: string;
      message: string;
    }
    
    export default class ErrorMessage extends React.Component<ErrorMessageProps> {
      render() {
        let debug = null;
        if (this.props.debug) {
          debug = <pre className="alert-pre border bg-light p-2"><code>{this.props.debug}</code></pre>;
        }
        return (
          <Alert color="danger">
            <p className="mb-3">{this.props.message}</p>
            {debug}
          </Alert>
        );
      }
    }
    
  7. 打开 ./src/index.css 文件,并将其全部内容替换为以下内容。

    body {
      padding-top: 4.5rem;
    }
    
    .alert-pre {
      word-wrap: break-word;
      word-break: break-all;
      white-space: pre-wrap;
    }
    
  8. 打开 ./src/App.tsx 并将其全部内容替换为以下内容。

    import { BrowserRouter as Router, Route } from 'react-router-dom';
    import { Container } from 'react-bootstrap';
    import { MsalProvider } from '@azure/msal-react'
    import { IPublicClientApplication } from '@azure/msal-browser';
    
    import ProvideAppContext from './AppContext';
    import ErrorMessage from './ErrorMessage';
    import NavBar from './NavBar';
    import Welcome from './Welcome';
    import 'bootstrap/dist/css/bootstrap.css';
    
    export default function App() {
      return(
        <ProvideAppContext>
          <Router>
            <NavBar />
            <Container>
              <ErrorMessage />
              <Route exact path="/"
                render={(props) =>
                  <Welcome {...props} />
                } />
            </Container>
          </Router>
        </ProvideAppContext>
      );
    }
    
  9. ./public/images 目录中 添加no-profile-photo.png名称的图像文件。 当用户在 Microsoft Graph 中没有照片时,此图像将用作用户Graph。

  10. 保存全部更改并重新启动该应用程序。 现在,应用看起来应该非常不同。

    重新设计的主页的屏幕截图