如何为你的React项目添加一个完美的数据库钩子

329 阅读12分钟

简介

React是创建有状态界面的最佳库之一,也是我们所知的互联网的重要组成部分。

包括Twitter、Facebook、Instagram和Airbnb在内的许多网络应用都依赖于这个库,以向数十亿 的用户提供有状态的、跨平台的应用。它仍然是同类产品中最受欢迎的库之一。

React Hooks--改变游戏规则

React 16.8引入了一种新的开发模式,称为钩子。这个新功能把库带到了一个全新的地方,使人们比以往任何时候都更容易在函数的背景下编写和理解组件,而不是类。

看看这个Gif,从开发者的角度来看,这些功能组件的效率会提高很多:

推特。Pavel @prchdk

React中有各种类型的钩子,这是有原因的。不同的任务需要不同的钩子,从存储变量到备忘函数。

许多钩子的特点是所谓的依赖阵列。钩子观察数组中变量的变化,如果观察到任何变化,它就会重新运行。

useEffect 钩子为例。这个钩子在一个组件第一次安装时运行,并且在这个依赖性数组中的有状态变量发生变化时运行:

const [count, setCount] = useState(0);

useEffect(() => {
  console.log(count); 
}, [count]);

在上面的代码片段中,count 将在两种情况下被记录:

  1. 当组件首次挂载时
  2. 当你使用setCount 来改变 "D "的值时。count

尽管还有很多关于钩子的内容需要深入研究,但理解上面的概念对本文的其余部分至关重要。这是因为在这篇文章中,我们将演示你如何使用这个依赖数组来自动重新获取你的中心数据,类似于它重新运行useEffect 钩子。

数据库设置

几乎每一个React应用程序的生产实例都会在某些时候使用数据库,无论是用于存储用户信息、业务信息还是API数据。

在你的React或React Native应用程序中实现数据库有很多方法,但有一些特定的方法与React编程模式,特别是钩子,结合得非常好。

在为你的React应用提供的各种托管解决方案中,你会发现使用无服务器架构的好处最大。我可以写出无服务器架构的所有好处,但这也可能是一篇独立的文章。这里只列举几个:

  • 按需自动扩展
  • 超级容易部署
  • 忘记服务器的管理和维护
  • 有更多时间用于UI/UX
  • 成本开销降至0

下面演示的方法结合了无服务器应用开发的所有好处,以及与React编程生态系统完美配合的定制钩子。

对于那些想知道的人来说,下面介绍的库也能与React Native一起使用,适合移动优先的开发者。

我们最终会有一个名为useReturn 的钩子,它将始终返回一个给定查询的新实例。它看起来会像下面这样:

const [minRating, setMinRating] = useState(0);
const { frame } = useReturn(() => /* Your query */, [minRating])

return <div>{frame.map(ele => <Card {...ele} />)}</div>

不要担心现在这个不连贯。几分钟后你就能完美地适应你的用例。

请注意,在这个例子中,frame 实例,也就是你数据库中的记录数组,将在两种情况下被更新:

  1. minRating (或依赖数组中的任何东西)变化
  2. 数据库的另一个实例 (db) 创建、更新或删除了数据

React设置

本节将简要地演示如何创建一个React项目。如果你已经很熟悉了,可以随意跳到下一部分。

React的开发团队创建了一个易于使用的脚本,名为create-react-app 。唯一的前提是你的机器已经安装了nodenpm ,无论如何你都会需要。

所以,如果你还没有这些软件包,请按照这里的说明快速安装。

在你想放置新项目的目录中打开命令提示符或终端,运行以下命令:

# npx create-react-app serverless-app

该过程完成后,进入serverless-app 目录,像这样启动该项目:

# cd serverless-app
# npm run start

这将创建一个你的应用程序的本地实例,当你位于src/ 文件夹中的文件被编辑时,它会自动重新加载(称为热加载)。一个浏览器窗口应该自动弹出。如果没有,请打开你的网络浏览器,并进入http://localhost:3000

Easybase设置

让我们通过进入我们的项目目录并执行npm install easybase-react 来安装一个叫做easybase-react 的库。这是我们在这个演示中唯一需要的依赖。

接下来,在easybase.io创建一个账户(你可以使用免费层)。

一旦你登录,使用"+创建"按钮来创建一个新表。让我们把它命名为MY TABLE,并给它三列:评级(数字),海报(图片),和标题(字符串):

点击下一步,完成你的下一个表的创建。它将自动弹出,但你也可以展开左侧抽屉中的按钮,在那里选择它:

为了演示的目的,让我们添加一个例子行,这样我们就可以在我们的React应用程序中显示它。使用表格左上方的**'+**'按钮来添加一个新行:

我的例子将以电影数据为特色,但请自由使用最适合你的应用程序的任何类型的数据。

在我们回到代码之前的最后一步是在Easybase界面创建一个新的项目 。这将给我们一个配置文件,使我们的应用程序可以安全地访问数据库。保持这个配置文件的私密性,因为它包含可以用来访问你的数据的凭证。

在左边的抽屉里,前往"项目>创建项目":

转到权限。点击你的表的名字并启用"未登录用户>读、写":

不要忘记点击'保存'。

最后,转到项目令牌标签,下载你的自定义配置令牌:

把这个令牌放在你的React项目中的App.js 旁边,这样结构看起来就像下面这样:

├ ...
├ ebconfig.js
├ App.css
├ App.js
├ index.js
└ ...

现在让我们回到代码上。在你的React项目中打开src/index.js 文件。首先,从我们之前安装的easybase-react 包中导入EasybaseProvider 我们自定义的ebconfig.js token。然后,用<EasybaseProvider ebconfig={ebconfig} > 包裹<App />

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import ebconfig from './ebconfig'; // Add this
import { EasybaseProvider } from 'easybase-react'; // Add this

ReactDOM.render(
  <React.StrictMode>
    <EasybaseProvider ebconfig={ebconfig}> {/* <-- */}
      <App />
    </EasybaseProvider> {/* <-- */}
  </React.StrictMode>,
  document.getElementById('root')
);

不要忘记在ebconfig道具中传递你的配置文件

这种设置被称为提供者模式,使我们React项目中的所有组件都能有效地访问该提供者的上下文,它是在你的组件中组织状态的最佳方式(另外它是原生支持的)。在我们的例子中,那是一个叫做useEasybase 的钩子。

useEasybase钩子

在这一点上,项目配置已经完成。前往src/App.js ,删除导入和App 函数内的一切。

现在,让我们用easybase-react 包来设置我们的第一个组件。这个过程可以在你的项目中复制,适用于以下的任何属性 [useEasybase](https://easybase.io/docs/easybase-react/interfaces/types_types.contextvalue.html) (P.S. 有很多这样的属性)

首先,从那个easybase-react 包中导入useEasybase 。让我们像这样抓取useReturn,db, 和e

import { useEasybase } from 'easybase-react';

function App() {
  const { useReturn, db, e } = useEasybase();
  return (
  
  );
}

export default App;

你可能想知道--这些函数是什么?

db - 正如其名称所述,这个函数让我们访问我们的数据库,它像这样工作:

let records = await db('MY TABLE').return().all()

这是一个非常简单的例子,但db 函数是相当强大的。在这里阅读更多关于它的信息。

e - 这代表了表达式。在 函数中使用它来建立查询,在 的 函数中使用db db .where eq (等于)neq (不等于)lt (小于)or (OR语句)以及更多的 e.eq("column_name", value) 的形式。 这将查询列_名称 等于什么的记录。

现在我们可以使用表达式来做一个复合查询:

let records = await db('MY TABLE').return(e.avg('rating')).where(e.or(e.like('title', 'T%'), e.lt('rating', 80))).all();

// e.avg = Return the average of 'rating' where:
//   e.or = OR statement on: 
//     e.like = string matching pattern [title starts with 'T'] 
//     e.lt = less than [rating < 80]

还有很多操作可供你使用,包括强大的聚合器

useReturn - 最后,这就是前面提到的钩子。它通过包装 函数而工作。这个钩子会自动订阅 中的变化。最重要的是,它将使我们能够访问一个有状态的数据数组,称为 。db db frame

const { useReturn, db, e } = useEasybase();
const { frame } = useReturn(() => db().return()
  .where(e.gt('rating', minRating)) // Where rating > minRating     
  .limit(limit),                    // Limit query length 
[minRating, limit]); // Also returns some helpers: 
                     //   'error' - any
                     //   'loading' - boolean
                     //   'manualFetch' - async function
                     //   'unsubscribe' - function

不要在useReturn 钩子中使用.all.one ,这会被自动处理。欲了解更多信息,请看这里的文档

第一个组件

让我们在我们的空src/App.js 中使用这些函数,如下所示:

import { useEasybase } from "easybase-react";

function App() {
  const { useReturn, db, e } = useEasybase();
  const { frame } = useReturn(() => db("MY TABLE").return(), []);
  
  return (
    <div>{frame.map(ele => JSON.stringify(ele))}</div>
  );
}

export default App;

作为一个示范,这将简单地显示当前表中的一条记录的字符串表示:

**恭喜你,你的数据库已经上线运行了。**现在,让我们实现一个自定义组件,叫做<Card /> ,它将在用户界面中给我们的记录提供一些结构(请随意将这些组件放在单独的文件中进行组织):

function Card({ rating, poster, title, _key }) {
  const cardStyle = {
    display: "inline-block",
    margin: 10,
    padding: 10,
    borderRadius: 10,
    background: "#eaeaea",
    minWidth: 200,
  };

  return (
    <div style={cardStyle}>
      <img 
        src={poster} 
        style={{ height: 300, minWidth: 200 }} 
      />
      <h2>{title}</h2>
      <h4>Rating: {rating}</h4>
    </div>
  );
}

function App() {
  const { useReturn, db, e } = useEasybase();
  const { frame } = useReturn(() => db("MY TABLE").return(), []);

  return (
    <div style={{ textAlign: "center", display: "inline-block" }}>
      {frame.map(ele => <Card {...ele} />)}
    </div>
  );
}

这看起来好多了。为了简洁起见,我将保持我的造型简单。你可以自由地给这个项目赋予你自己的外观!

你可以看到,<Card /> 使用原始记录的所有属性作为它的道具,再加上一个叫做_key的道具。 _key是每个记录的唯一标识符,与其他属性一起返回。 这对于查询和更新特定记录非常有用。稍后会有更多介绍。

插入记录

现在,让我们快速实现一个向数据库添加新卡的方法。这也将展示useReturn 钩子如何在我们添加不同组件的记录时自动刷新。

在我们映射框架数组后,显示一个新的按钮:

// ...

function AddCardButton() {
  const addCardStyle = {
    background: "#ea55aa",
    display: "inline-block",
    width: 200,
    borderRadius: 10,
    cursor: "pointer",
  };

  return (
    <div style={addCardStyle}>
      <h2 style={{ color: "#fff" }}>Add Card</h2>
    </div>
  );
}

function App() {
  const { useReturn, db, e } = useEasybase();
  const { frame } = useReturn(() => db("MY TABLE").return(), []);

  return (
    <div style={{ textAlign: "center", display: "inline-block" }}>
      {frame.map(ele => <Card {...ele} />)}
      <AddCardButton /> {/* <- New button */}
    </div>
  );
}

在React或React Native应用程序中,有许多不同的方法来收集用户输入。在这个例子中,我将使用内置的 [prompt](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt)函数,但你也可以使用表单、对话框等。

一旦我们收集到了新的记录细节,就用db 函数上传它们。所以,让我们把那个 [useEasybase](https://easybase.io/docs/easybase-react/interfaces/types_types.contextvalue.html)钩子。而不是.return ,我们将使用 [.insert](https://easybase.github.io/EasyQB/docs/insert_queries.html#insert)(我们将在后面探讨上传图片)。

在代码中,这个的实现可以看起来像下面这样:

function AddCardButton() {
  // ...
  
  const { db } = useEasybase();
  async function addCardClick() {
    let title = prompt("Please enter a movie title");
    let rating = prompt("Please enter the rating for this movie");
    if (!rating || !title) {
      return;
    }

    db("MY TABLE")
      .insert({ title, rating: Number(rating) })
      .one();
  }

  return (
    <div style={addCardStyle} onClick={addCardClick}> {/* <- onClick */}
      <h2 style={{ color: "#fff" }}>Add Card</h2>
    </div>
  );
}

点击这个新按钮并输入一些数值:

这就是了,新的记录!

最后,让我们用 [setImage](https://easybase.io/docs/easybase-react/interfaces/types_types.contextvalue.html#setimage)函数,从useEasybase 。媒体(图片、视频、文件)的处理方式与其他值不同,需要上传,而不是插入

在这里,我们终于可以使用 _key 属性来唯一地识别当前记录。 该属性也常用于db.setdb.delete ,等等。

当用户点击图片(或空的图片空间)时,他们将能够上传一个新的图片。useReturn 将再次展示它自动刷新新的数据。

回到<Card /> 组件,并带入那个useEasybase 钩子。使用一个隐藏的输入是一个常见的技巧,使一个图像也显示为一个文件输入:

function Card({ rating, poster, title, _key }) {
  // ...

  const { setImage } = useEasybase();
  async function onFileChange(e) {
    if (e.target.files[0]) {
      await setImage(_key, "poster", e.target.files[0], "MY TABLE");
    }
  }

  return (
    <div style={cardStyle}>
      <input id={"fileInput" + _key} hidden type="file" onChange={onFileChange} />
      <img
        src={poster}
        style={{ height: 300, minWidth: 200 }}
        onClick={_ => document.getElementById("fileInput" + _key).click()}
      />
      <h2>{title}</h2>
      <h4>Rating: {rating}</h4>
    </div>
  );
}

现在,点击一个<Card /> 的图片会出现一个文件选择器。使用该选择器从你的机器上上传一张图片:

这很有效!上传的图片将通过Easybase CDN提供,并附在你的记录上。frame 应该自动显示它。

注意,这些变化也反映在Easybase的网络应用中。

查询

让我们再添加一个组件来演示如何使用useReturn 钩子的依赖阵列

作为一个示范,我将实现一个数字输入,当改变时,更新useReturn 钩子中使用的查询。

通常情况下,你会在db.where 函数中使用一个表达式来进行这些有状态的查询。下面是一个简单的例子,包裹了根<App /> ,并添加了一个受控输入。注意新的ratingMin变量。

import { useEasybase } from "easybase-react";

// ...

function App() {
  const [ratingMin, setRatingMin] = useState(0); // <- for new input
  const { useReturn, db, e } = useEasybase();
  const { frame } = useReturn(() => db("MY TABLE").return(), []);

  return (
    <div>
      <div style={{ textAlign: "center", display: "inline-block" }}>
        {frame.map(ele => <Card {...ele} />)}
        <AddCardButton />
      </div>
      <p>
        Rating filter:
        <input
          type="number"
          value={ratingMin} // controlled input
          onChange={e => setRatingMin(Number(e.target.value))}
        />
      </p>
    </div>
  );
}

剩下的就是在db 函数中使用ratingMin ,并把它放在依赖数组中。我们将使用e.gte('rating', ratingMin) 来查询 "评级"(列名)大于或等于ratingMin 的记录。

function App() {
  const [ratingMin, setRatingMin] = useState(0); // <- for new input
  const { useReturn, db, e } = useEasybase();
  const { frame } = useReturn(
    () => db("MY TABLE").return().where(e.gte("rating", ratingMin)),
    [ratingMin]
  );
  // ...
}

就这样,你的frame 响应状态变化并相应地更新查询:

你可以根据你的需要添加尽可能多的记录。

所有这些变化都将与你的远程数据库同步。专业提示:使用 [.limit](https://easybase.github.io/EasyQB/docs/select_queries.html#limit)[.offset](https://easybase.github.io/EasyQB/docs/select_queries.html#offset)来实现分页,如果你有几千甚至几万条记录。

结论

easybase-react包有很多有用的功能,你可能会觉得很有帮助,特别是在用户认证和数据库方面。

如果你想看看这个库在React和React Native中的所有功能,可以看看这个演练

这篇文章中的查询生成器的功能类似于Firebase数据库中使用的语法,而且相当灵活。例如,一个高级用例是用聚合器选择列,如 [e.min](https://easybase.github.io/EasyQB/docs/operations.html#minimum)[e.max](https://easybase.github.io/EasyQB/docs/operations.html#maximum).

此外,如果你的应用程序中有一些更复杂的业务逻辑,可以尝试使用 [dbEventListener](https://easybase.io/docs/easybase-react/interfaces/types_types.contextvalue.html#dbeventlistener)处理程序。这将在db 实例运行任何查询时运行一个回调函数。它也会从useEasybase 钩子中返回。

谢谢你的阅读!这是对React友好、有状态数据库钩子和无服务器编程的简单介绍,无服务器编程是个人和小型团队中流行的编程架构。

这种流行来自于没有传统的后台设置,而传统的后台设置有很多成本、时间和管理开销。

我希望这个演练能帮助那些有兴趣用Easybase的useReturn hook部署生产就绪的React/React Native应用的人熟悉。