如何使用 React.js 构建实时电影投票系统

2025-05-25

如何使用 React.js 构建实时电影投票系统

我们将要建造什么?

首先,我们将尝试构建一个简单的(实际上并不那么简单)电影评论应用。用户可以在其中对电影进行投票、发表评论,甚至可以添加电影。

然后,在第二部分中,我们可以更深入地为电影添加一个预订系统,并拥有多个影院。我们将使用Rocketgraph来实现这一点,因为它提供了一个完整的后端,带有身份验证和数据库,所以我们不必担心这一点。

为此,您需要定义用户、电影和存储它们的位置:

  1. 身份验证:您需要将用户存储在 Postgres DB 的表中。

  2. 实时:您需要从数据库直接获取实时评论和点赞,并将其发送到前端供用户使用。

  3. 数据库:您可以在其中将用户映射到电影,将用户和电影映射到喜欢。

💡要阅读本文,您需要对 React.js 有基本的了解

Rocketgraph:一个超越 Firebase 的完整后端,并且是开源的

简单介绍一下背景。Rocketgraph 提供了完整的后端。它配备了 Postgres 数据库、Hasura 控制台(用于管理 Postgres 并为数据添加 GraphQL 层)、身份验证和无服务器功能。

总而言之,我们为您的 Web 应用提供身份验证,为消息/通知/评论等实时事务提供 GraphQL,并为您想要卸载的任何内容提供无服务器函数。我们的无服务器 Github 应用可将您的 Github 代码自动编译为 AWS Lambda 函数。

那么 GraphQL 到底是什么?

GraphQL 是 Meta 制定的一种语言规范,它通过精确请求所需内容来实现实时数据查询。这与传统的 API 方法不同,传统的 API 方法将查询代码写入后端,而前端几乎无法控制请求的数据内容和方式。

可以把它想象成一个 JSON 查询。你在类似 JSON 的查询中请求你想要的数据,它会准确返回这些字段。

在本文中,我们将利用 GraphQL、React Apollo 和 Hasura 的强大功能,构建一个用于电影评分和评论的实时系统。我们也可以使用这个系统来预订电影票。

TLDR 版本

如果您只想查看代码,这里是本文的代码库。您可以在这里查看更多示例。这是Rocketgraph 背后的开源软件。

继续阅读

继续阅读 gif

太棒了,让我们从基础开始

创建一个 React 项目并开发前端。后端暂时先不用管,我们稍后再添加。

mkdir movie-voting
cd movie-voting
Enter fullscreen mode Exit fullscreen mode

接下来搭建一个基本的反应应用程序。

npx create-react-app ./
Enter fullscreen mode Exit fullscreen mode

安装React Router以便能够在页面之间导航。安装 React-apollo 和 graphql 以实现如上所述的实时功能。

yarn add react-router-dom
yarn add @apollo/client graphql
Enter fullscreen mode Exit fullscreen mode

删除项目里的logo等冗余文件,并更新index.js文件如下:

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
      <Router>
          <Routes>
            <Route path="/login" />
            <Route path="/signup" />
            <Route path="/" element={<App />} />
          </Routes>
      </Router>
  </React.StrictMode>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

现在我们需要添加 App.js 和登录/注册组件。很简单。

App.js

import logo from './logo.svg';
import './App.css';


const movies = [
  {
    name: 'Snatch',
    img: 'https://occ-0-3934-3211.1.nflxso.net/dnm/api/v6/E8vDc_W8CLv7-yMQu8KMEC7Rrr8/AAAABVJgO06RKuruJpcyezdM43Ai2ZjvNDmtbnwUXVtvXVhhvpL0tvhr4s9e3j8UojFCLao5a7v8Dg5kti1vFKcA0ldZXWnnC03nBRIt.jpg?r=cbf',
    likes: 10,
    state: true,
  }
];

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <p>
          Movies list
        </p>
      </header>
      {
        movies.map(movie => {
          return (
              <div className="movie-box">
                <div className="movie-box-header">
                </div>
                <div className="movie-box-body">
                  <img alt={movie.name} className="movie-image" src={movie.img} />
                </div>
                <div className="movie-box-footer">
                  {movie.name}
                  <div className="like-button"><i class="fa fa-heart" style={{"color": "red"}}aria-hidden="true"></i></div>
                </div>
              </div>
          )
        })
      }
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

现在我们已经完成了基本的主页设计。接下来我们来创建登录和注册页面。

signup.js

import React, { useState } from "react";
import { useNavigate } from 'react-router-dom';

export default function Login(props) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();
    navigate("/");
  }

  return (
    <div>
      <h1>Signup</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button>Signup</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

让我们看看是否有效

yarn start
Enter fullscreen mode Exit fullscreen mode

恭喜🥂,您刚刚创建了 Web 应用程序的框架。现在我们只需填写数据、身份验证和实时信息。

进入Rocketgraph。如何创建具有身份验证和无服务器功能的后端。

只需注册并单击仪表板中的创建项目:

创建项目

接下来我们将了解一些令人惊叹的功能,这些功能将利用 GraphQL 的强大功能为您神奇地构建后端。

项目启动后,您将获得一个 Hasura 控制台和一个 Postgres 数据库,如下所示。请等待服务启动。这可能需要大约 3-5 分钟。

Hasura 是什么?

Hasura 是一款出色的开源工具,它可以将你的 Postgres 数据库转换为 GraphQL 格式。这意味着你的数据仍然在 Postgres 数据库中,但你却能享受 GraphQL 的强大功能。它还包含一个编辑器,可以根据你的 Postgres 表自动生成 GraphQL 查询。

返回 Rocketgraph

当您的项目启动时,您会在这里获得一个 Hasura 链接:

链接到 hasura

打开 Hasura,现在我们可以开始为我们的数据库创建表。

我们需要一个电影表,如下所示:

电影表

我们还需要让用户访问它。Rocketgraph 中user有一个经过身份验证的角色,我们的 JS SDK 会将 JWT 随您的请求一起发送,这样您就无需亲自操作了。

转到电影的权限选项卡并添加以下权限:

对于插入放置权限如下:

插入 1

插入 2

对于选择来说,也是一样的:

选择

使用 react-apollo 和 graphql 包来开启 GraphQL 之旅。Apollo 让你能够更轻松地直接从 React 查询 GraphQL,并提供了一些强大的功能useSubscription,我们将在稍后讨论。

让我们安装它们。

yarn add @apollo/client graphql
Enter fullscreen mode Exit fullscreen mode

我们还需要一些定制的 JS 库来使身份验证正常工作。

yarn add @rocketgraphql/react-apollo @rocketgraphql/rocketgraph-js-sdk
Enter fullscreen mode Exit fullscreen mode

现在我们将使用RApolloProvider提供的@rocketgraphql/react-apollo

首先创建一个名为的文件夹utils,然后config.js在其中创建以下内容:

import { createClient } from "@rocketgraphql/rocketgraph-js-sdk";
import Cookies from 'js-cookie';

const config = {
  baseURL: "https://backend-REPLACE",
};

const { auth } = createClient(config);

export { auth };

Enter fullscreen mode Exit fullscreen mode

将上述内容替换https://backend-REPLACE为 Rocketgraph 仪表板中的后端 URL:

后端 URL

您会在Auth参考资料 部分找到它。

将代码更改为index.js

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import App from "./App";
import Signup from "./components/login";
import { RApolloProvider } from "@rocketgraphql/react-apollo";
import { auth } from "./utils/config";

ReactDOM.render(
  <React.StrictMode>
      <RApolloProvider auth={auth} gqlEndpoint="https://gqlEndpoint/v1/graphql">
        <Router>
            <Routes>
              <Route path="/login" element={<Signup />}/>
              <Route path="/signup" />
              <Route path="/" element={<App />} />
            </Routes>
        </Router>
      </RApolloProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

Enter fullscreen mode Exit fullscreen mode

将上述内容更改https://gqlEndpoint/v1/graphql为 Hasura 控制台中的 graphql 端点:

点

接下来我们将添加授权。

login.js

import React, { useState } from "react";
import { useNavigate } from 'react-router-dom';
import { auth } from "../utils/config";

export default function Login(props) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();

    // login
    try {
      await auth.signIn({email, password, provider: "local"});
    } catch (error) {
      alert("error logging in");
      console.error(error);
      return;
    }

    navigate("/");
  }

  return (
    <div>
      <h1>Login</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button>Login</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

signup.js

import React, { useState } from "react";
import { useNavigate } from 'react-router-dom';
import { auth } from "../utils/config";


export default function Login(props) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();

    // login
    try {
      await auth.register({email, password});
    } catch (error) {
      alert("error logging in");
      console.error(error);
      return;
    }

    navigate("/");
  }

  return (
    <div>
      <h1>Signup</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <input
          type="password"
          placeholder="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <button>Login</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

就这样。Rocketgraph 会处理剩下的事情。用户信息将被填充到用户数据库中。

TJ

您可以通过注册并检查用户是否已创建来测试这一点。

让我们构建更多功能

输入 react-apollo。

App.js

import './App.css';
import { gql, useSubscription } from "@apollo/client";

const GET_MOVIES = gql`
  subscription {
    movies {
      id
      created_at
      name
      image
    }
  }
`;

function App() {
  const { data, loading } = useSubscription(GET_MOVIES);
  if (loading) {
    return <div>Loading</div>;
  }
  return (
    <div className="App">
      <header className="App-header">
        <p>
          Movies list
        </p>
      </header>
      {
        data && data.movies && data.movies.length ?
        data.movies.map((movie, index) => {
          return (
              <div className="movie-box" key={index}>
                <div className="movie-box-header">
                </div>
                <div className="movie-box-body">
                  <img alt={movie.name} className="movie-image" src={movie.image} />
                </div>
                <div className="movie-box-footer">
                  {movie.name}
                  <div className="like-button"><i className="fa fa-heart" style={{"color": "red"}} aria-hidden="true"></i></div>
                </div>
              </div>
          )
        }) : "No movies"
      }
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

就是这样,只需在您的数据库中添加记录,您就可以在这里实时看到它。

太棒了😎 现在我们终于可以添加点赞按钮了

重要部分(用户 ID)

首先在 Hasura 中创建带有 id、movie_id 和 user_id 的 likes 表,如下所示

创建喜欢表

我们必须从 JWT 令牌本身中提取这个 User-Id。
为此,

步骤 1

创建一个名为的新角色user并单击Insert以编辑其权限

权限步骤 1

第 2 步

允许用户角色修改所有内容。勾选以下复选框

调整

第 3 步 - 最重要的

自动设置用户 ID

点击列预设并选择用户 ID。从 X-Hasura-user-id 设置。

会议

然后点击“保存”。现在我们已经准备好了表格来保存点赞/投票。

输入聚合(点赞)

components在named中创建新文件
likeCount.js

import React, { useState } from "react";
import { gql, useSubscription, useMutation } from "@apollo/client";

const likes = (movie_id) => gql`
  subscription {
    likes(where: {movie_id: {_eq: "${movie_id}"}}) {
        id
        user_id
    }
  }
`;

const LIKE = gql`
  mutation like($movie_id: uuid!) {
    insert_likes(objects: {movie_id: $movie_id}) {
        affected_rows
    }
  }
`;

const UNLIKE = gql`
    mutation unlike($movie_id: uuid!) {
        delete_likes(where: {movie_id: {_eq: $movie_id}}) {
            affected_rows
        }
    }
`;

function Component({movie}) {
  const LIKE_COUNT = likes(movie.id);
  const [addLike, { like_data, like_loading, error }] = useMutation(LIKE);
  const [unLike, _] = useMutation(UNLIKE);

  const [isRed, setIsRed] = useState(false);
  const { data, loading } = useSubscription(LIKE_COUNT);
  console.log(data, movie);
  if (loading) {
    return <div>Loading</div>;
  }
  const likeThis = () => {
    setIsRed(!isRed);
    if (isRed) {
        unLike({variables: {movie_id: movie.id}});
    } else {
        addLike({ variables: { movie_id: movie.id }});
    }
  }
  return (
    <span>
        {data.likes.length}
        <i className="fa fa-heart" style={{"color": isRed ? "red" : "gray"}} aria-hidden="true" onClick={likeThis}></i>
    </span>
  );
}

export default Component;

Enter fullscreen mode Exit fullscreen mode

哦,等等!这会删除表中的所有“赞”。所以,让我们通过以下方式保护表:

删除权限

并将其导入App.js如下:

import './App.css';
import { gql, useSubscription } from "@apollo/client";
import LikeCountComponent from "./components/likeCount";

const GET_MOVIES = gql`
  subscription {
    movies {
      id
      created_at
      name
      image
    }
  }
`;



function App() {
  const { data, loading } = useSubscription(GET_MOVIES);
  if (loading) {
    return <div>Loading</div>;
  }
  return (
    <div className="App">
      <header className="App-header">
        <p>
          Movies list
        </p>
      </header>
      {
        data && data.movies && data.movies.length ?
        data.movies.map((movie, index) => {
          return (
              <div className="movie-box" key={index}>
                <div className="movie-box-header">
                </div>
                <div className="movie-box-body">
                  <img alt={movie.name} className="movie-image" src={movie.image} />
                </div>
                <div className="movie-box-footer">
                  {movie.name}
                  <div className="like-button"><LikeCountComponent movie={movie} /></div>
                </div>
              </div>
          )
        }) : "No movies"
      }
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

好了!恭喜,你成功制作了一个电影投票应用。

再见 gif

文章来源:https://dev.to/kaushik94/how-to-build-a-real-time-movie-voting-system-using-reactjs-3nfa
PREV
你应该写这篇博文的5个理由:1. 你的经历独一无二 2. 有助于为他人提供视角 3. 为自己树立名声 4. 一段编程历程 5. 你做得太棒了。展现出来吧!
NEXT
新手指南:socket.IO