我的 Serverless 实战 — 一小时完成表单类应用

我的 Serverless 实战 — 使用 formio 实现一个完整的项目

Formio 作为一个比较成熟的平台,在经过配置之后能够一定程度上的摆脱对程序员进行表单页面的实现。

在最近的开发中也确实体会到了对业务进行足够的拆分后,确实能够在一定程度上地对开发效率进行提升,甚至能够达到 pro-code 的程度。

只是,formio 的桎梏仍然是封装的太好,在业务较为复杂的情况下对表单的逻辑处理可能会有一定的问题。希望在最近的重构中能够提升 FP(函数式编程) 的比例,对现有的代码进行分割和封装,从而进一步提升效能吧。

本文内实现的应用带有状态管理(Redux)和路由(react-router)的实现,同时也会借助 formio 这个平台实现数据到数据库的上传和存储。

前情回顾

在上一篇 我的 Serverless 实战 — 使用 formio 快速搭建表单类应用 中简单的回顾了一下 formio 的优缺点,以及如何使用 React 接入一个 formio 的表单。

通过平台创建页面

创建表单的方式是非常简单的,页面上有各种组件:

components

组件本身也对验证、显示等信息进行了封装:

validation

通过将组件拖拽到右边的展示页,再勾选好合适的配置后,就能够生成一个可应用的页面:

submission-form

这个表单创建的目的是为了能够接受邮件,用户通过输入自己的姓(Last Name),名(First Name),邮件地址(Email),内容(Message),就能够通过这个应用将消息发送到指定的邮件地址去。

以下是收到的邮件:

inbox

和平台上数据库的信息:

db-info

RESTful 表单

使用平台创建的表单是有对应的、自动生成的 API 的:

API

而使用生成的 API 就可以完成 CRUD 操作:

RESTful

通过 [base-url]/api-name/submission 以及合适的 HTTP 请求,即可以实现增删改查。

在框架中对接表单

在 React 中渲染表单页是非常简单的:

import { Form } from "react-formio";

const RenderedForm = () => {
  <Form src={"some-url-string"} />;
};

react-formio 的库对 onChange, onSubmit, onSubmitDone 等 actions 已经进行了封装,对于一些简易的表单结构,例如说用户调研,之类的数据收集类表单,基本无需通过 React 对标单进行处理。

至此,一个单独的页面就已经在 React 项目中被渲染出来了。

生成一个完整的项目

仅有一个页面是无法被称之为一个项目的,接下来的步骤就是结合自己这半年多的开发经验讲一下如何在 React 之中使用 Formio,并且完成一个实现登录和发送邮件功能的小项目。

starter kit 的结构

|- src
|  |- common
|  |  |- 一干共用组件,包括header, footer, cloading, modal 等
|  |- components
|  |- modules
|  |  |- 一干模块化组件,包括auth使用的配置,forms和submissions,
        这部分的模块应该是与 react-formio 中的modulse相对应的
|  |- App.js
|  |- config.js 重要的配置文件
|  |- index.js
|  |- 还有其他一干来自于CRA的文件

这个结构相对而言还是比较清晰的,用途也是很明确的,接下来就按照 formio starter kit 中有的结构进行补充,让程序运行起来。

redux 的配置

react-formio 实际上是对 formio 已经封装好的一个状态管理工具,在 React 的项目中,formio 使用了 Redux 去做状态管理。

也因此,如果没有配置 Redux,那么就无法发送(dispatch)对应的 actions,也就无法获取和存储数据。

Redux 的基础部分可以看之前做的笔记:一文快速上手 Redux,这里简单的带一下配置的部分,不会过多深入讲解。

鉴于 formio 自身的状态管理树已经被封装好了,所以初始项目中的结构非常简单,只需要一个 rootReducer 去合并封装好的状态树即可。

redux 的文件夹在 src 的根目录下,其结构为:

|- src
|  |- redux
|  |  |- rootRedeucer.js
|  |  |- store.js

关于 Redux 的结构,因为没有其他的组件,目前只是沿用了最简单的部分。

rootReducer

主要用途还是将 react-formio 部分的 reducer 结合到状态树上。

其中:

  • auth 负责的是登录验证部分
  • form 是表单的结构——毕竟 formio 的结构是以 json 实现的
  • submission 是提交的表单信息,也就是会存到数据库上的数据

rootReducer.js 代码:

import { combineReducers } from "redux";
import { auth, form, forms, submission, submissions } from "react-formio";

export default combineReducers({
  auth: auth(),
  form: form({ name: "form" }),
  forms: forms({ name: "forms" }),
  submission: submission({ name: "submission" }),
  submissions: submissions({ name: "submissions" }),
});
store

我这里配置了一下 redux-thunk 和 redux-logger,都是很老牌的 redux 辅助包,虽然 thunk 这里没用上:

store.js 代码:

import logger from "redux-logger";
import { configureStore } from "@reduxjs/toolkit";
import thunk from "redux-thunk";
import rootReducer from "./rootReducer";

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(logger, thunk),
});

router 的配置

router 的结构如下:

|- src
|  |- confit
|  |  |- routePaths.js
|  |  |- routes.js

其中,routePaths.js 作为路径的常量,routes.js 作为实际的路由。

routePaths

这一部分是路径的常量地址:

export const INDEX = "/";
export const DEMOGRAPHICS_FORM = "/demographics";
routes

这里是配置的路由,我是用了 react-loadables 作为配置的方法,因为 react-loadables 可以进行代码分割(code splitting),对于这个迷你项目来说不是必须。Loading 部分使用的是项目中原生带的 Loading 组件:

import React from "react";
import { Route, BrowserRouter as Router } from "react-router-dom";
import { Switch } from "react-router";
import Loadable from "react-loadable";
import * as routePaths from "./routePaths";
import Loading from "../common/Loading/Loading";

function logPageView() {
  window.scrollTo(0, 0);
}

const LoadingComp = () => <Loading />;
const Login = Loadable({
  loader: () => import("../containers/login"),
  loading: LoadingComp,
});
const DemographicsForm = Loadable({
  loader: () => import("../containers/demographicsForm"),
  loading: LoadingComp,
});

export default () => {
  return (
    <Router onUpdate={logPageView}>
      <Switch>
        <Route path={routePaths.INDEX} exact component={Login} />
        <Route
          path={routePaths.DEMOGRAPHICS_FORM}
          exact
          component={DemographicsForm}
        />
      </Switch>
    </Router>
  );
};
app.js

将 状态树 挂载到 app 上的同时,也将路由提供给 app

import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import ReactDOM from "react-dom";
import "./index.scss";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import "./config";
import { AuthProvider } from "./modules/auth";
import { initAuth } from "react-formio";

store.dispatch(initAuth());

ReactDOM.render(
  <Provider store={store}>
    <AuthProvider>
      <Router>
        <App />
      </Router>
    </AuthProvider>
  </Provider>,
  document.getElementById("root")
);

到这里本地运行的项目会出现报错,这是因为要渲染的页面还没有写好。接下来,就开始实现 登录页面(Login) 和 表单页面(DemographicsForm) 了。

页面的实现

为了能够即时的看到效果,可以先注释掉 表单页面(DemographicsForm),以实现登录页面为主。

实现登录(Login)

将以下的内容填充到登录页面中:

import { Form, setUser } from "react-formio";
import { formUrl } from "../../config";
import { useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import * as routePaths from "../../config/routePaths";

export function LoginComp() {
  const dispatch = useDispatch();
  const history = useHistory();
  return (
    <Form
      src={formUrl.login}
      onSubmitDone={function (submission) {
        // do something
        dispatch(setUser(submission));
        history.push(routePaths.DEMOGRAPHICS_FORM);
      }}
    />
  );
}

export default LoginComp;

简单地解释一下出现的陌生函数。

  • onSubmitDone 是 react-formio 自己封装的一个函数,只有当信息成功提交了——这里我的理解是状态码为 200——之后才会被调用。

  • setUser 函数是为了将用户信息存储到 Redux 之中。将它放在 onSubmitDone 自然是因为只有在用户成功登陆之后,才能够获得正确的用户信息。

  • history.push(routePaths.DEMOGRAPHICS_FORM); 是为了完成登陆之后的重定向

实现表单(DemographicsForm)
import React from "react";
import { formUrl } from "../../config";
import { Form } from "react-formio";
import { Footer, Header } from "../../common";
import "../../css/card.css";

const DemographicsForm = () => {
  return (
    <>
      <Header />
      <Form src={formUrl.demographics} />
      <Footer />
    </>
  );
};

export default DemographicsForm;

鉴于 formio 将大部分的功能都封装好了,这里又没有什么复杂的逻辑需要处理,只需要将组件渲染出来即可。

运行

配置上基本都完成了,那么是时候看一下页面的运行了。

先看一下页面的 redux 状态树,这也是为什么我加上 redux-logger 的原因,可以更加直观的看到状态树的改变:

state-tree

这一系列的变化都是由 store.dispatch(initAuth()); 引起的,它会重新设置用户的一些信息,包括是否在活跃状态(isActive)、用户信息(user)、是否验证(authenticated)等一系列的信息。

首页登录

加上 CSS 的一点细节之后,首页看起来是这样的:

login

因为使用的组件名称就是 Email,所以 formio 自身就会检测邮件名的合法性:

invalid-email

这也是封装带来的好处,formio 封装的另一个好处就在于它必须的验证,假设提供的用户名和密码是不对的:

wrong-email

当用户提供了正确的用户名和密码登录成功后,在登录页面中的 setUser 会调用对应的 reducer,将用户信息存储于 Redux 之中:

user-access

持久化的部分 react-formio 也已经实现了,传统的存在于 Redux 的问题——页面一旦刷新就会造成信息丢失——这一部分也被 react-formio 解决了。

鉴于这里是直接使用了 history.push(),当登陆成功后,就会自动重定向到要使用的表单上了。

表单功能使用及展示

表单的主体部分基本没怎么变:

form-body

主要的使用目的还是通过留姓名、邮件地址、信息从而实现一个自动发送邮件的功能。

这里通过页面再发送一封邮件:

successful

我本地是将发送的请求在命令行中打印出来了:

submission

其中,data 部分就是会被存储到数据库中的部分。除此之外,也可以在 network 上看到发送的请求:

req1

req2

表单在使用 RESTful APIs 发送请求也是使用 JWT token,这一点 formio 在 formiojs 这个库中进行了实现,也是不需要开发者再去实现一遍。

JWT token 默认有效时间是一个月,这个可以通过配置的方法去修改、使其过期。

应用

目前我们项目组是用表单去接受用户填写的数据,随后 Power BI 那边可以调用 RESTful APIs 去生成数据的总结,再生成报表、dashboard 之类的去进行可视化处理。

至于个人使用方面,我觉得白嫖的话其实蛮适合做个人博客的评论接入的。

免费版每个月的提交有 1000 条数据:

submissions

本身表单就支持做嵌入式应用,不用担心后台数据的接收问题,每个月 1000 条的评论,短期之内应该是够的了,毕竟像我这样的籍籍无名之人,又打算做的是个人博客,很长一段时间的数据都不太可能到一定程度。


【本文正在参与 “100%有奖 | 我的 Serverless 实战”征稿活动】

活动链接:https://marketing.csdn.net/p/15940c87f66c68188cfe5228cf4a0c3f

评论 57
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值