通过使用JSON Schema的功能,使React on Rails更安全
本篇文章是『Qiita公司Advent Calendar 2022』的第15天的文章。
作为一种用于Rails应用程序的React视图库,React on Rails是一个方便的库,广泛应用于Qiita公司的产品开发中。但是,由于跨越多种语言的特性,使用React on Rails进行交叉语言的操作验证等存在一些困难。
为了解决这个问题,我们将讨论在视图中使用JSON Schema为跨语言传递信息应用模式。
这个想法在Qiita公司的各种产品中实际使用,并以JsonSchemaView的形式作为开放源代码发布。如果您感兴趣,请务必参考一下。
在Rails上使用React的好处和挑战。
React on Rails 是一个库,可以让您在Rails视图中使用React组件。
一旦进行设置,您就可以使用以下代码在视图中轻松地使用React组件。
<%= react_component("HelloComponent", props: { name: "Taro" }) %>
const HelloComponent = ({ name }: { name: string }) => {
return (<div>Hello, {name}!</div>)
}
ReactOnRails.register({ HelloWorld })
这个库非常方便,但由于涉及多种语言,所以存在动作验证的困难。从Rails的角度来看,组件名称和属性只是字符串和哈希对象,Rails本身无法验证它们的正确性。
在Rails中,仅通过组件名称和组件属性来验证是否正确指定是困难的。
使用React作为前端框架,配合Rails作为后端框架的开发机制。
-
- Rails 側
react_component メソッドにより、埋め込む予定地に (利用したい React Component 名, その props) の情報を含む HTML タグを render する
JavaScript 側 (React 側)
埋め込めるようにしたい React Component を予め ReactOnRails.register で登録
React on Rails の JS 実装が、 react_component が render した HTML タグを発見し、それを対応した React Component に置き換え
现在的情况是这样的。
如果将在这里交流的信息进行超简化并以图形方式呈现,情况将如下所示。

在这里,从Rails端传递到JS端的信息是(所需使用的React组件名称,以及该组件的props)的组合。
然而,Rails端并不知道JS端有哪些组件(是否已注册)。只有在JavaScript端处理,才能确认指定的React组件名称和props是否正确。
而且,经常会出现React组件名称和props传递出现错误的情况。如组件名称的拼写错误,React端的props变更,视图序列化处理的变更等等,无限多种情况可能会发生。
有几种方法可以用于这些验证,例如采用服务器端渲染(Server Side Rendering)或使用浏览器测试如特性规范(Feature Spec)和系统规范(System Spec)。然而,一个缺点是实施和测试可能会变得过于复杂和繁琐。
给组件和其 props 使用 JSON Schema 提供模式
这里的问题是,对于定义了什么样的React组件以及对它的props有何期望值的信息,它都被JavaScript(TypeScript)端所封闭。
为了解决这个问题,考虑使用 JSON Schema 来定义 Rails 传递给 JavaScript 的信息(包括React组件名称和其属性)的方法。
这是因为根据 JSON Schema 可以生成类型定义和验证数据,从而减少了信息传播中跨语言失败的可能性,并能够独立验证各自职责中的正确性。

※ 什么是JSON Schema?
JSON Schema是一种可以定义JSON格式数据或文档的模式(数据结构)的模式描述语言。
JSON Schema的优势是可以利用各种实现方式。我们可以利用JSON Schema来生成验证器、生成类型定义、生成React表单定义、生成测试对象,以及通过Language Server来实现JSON / YAML文件的补全等等。丰富的工具使我们能够在各种场景下灵活运用JSON Schema。顺便提一下,OpenAPI作为REST API的描述格式,也是基于JSON Schema进行扩展定义的。
在Qiita上也使用了JSON Schema,并且提供了Qiita API v2的JSON Schema(http://qiita.com/api/v2/schema),基于这个进行了API文档的生成。
这些技术细节已经在以下的文章中总结了。
为在View中传播的信息提供架构的是JsonSchemaView。
所以,为了实现使用JSON Schema在视图之间进行协作,我们开发了一个名为JsonSchemaView的开源软件。
在JsonSchemaView中,您可以使用以下Ruby代码来描述Component及其props的JSON Schema。
# クラス名は Component 名と同じになるようにする
class HelloComponent < JsonSchemaView::Component
renderer_class(:react_on_rails)
props_class do
property(:name, type: String)
attr_reader :name
def initialize(name:)
@name = name
end
end
end
用于定义 JSON Schema 的 DSL 是 JSONWorld 或其扩展。在编写时,可以使用 JsonWorld 从模型生成 JSON Schema 并作为参考。
我们可以将定义的组件传递给Rails的render方法,以替代使用react_component方法。
render HelloComponent.new(props: { name: "Taro" }) # react_component("HelloComponent", props: { name: "Taro" })
在进行渲染时,系统会自动验证其是否符合该模式,如果不符合,则会抛出异常。这样,Rails 就能够防止传播与模式不匹配的数据。
render HelloComponent.new(props: { name: "Taro" }) # valid
render HelloComponent.new(props: { naem: "Taro" }) # invalid なので、 Rails 側の render 時に例外が投げられる
此外,您还可以使用 json-schema-to-typescript 从该 JSON Schema 中生成 TypeScript 类型。通过使用它,Rails 和 React (TypeScript 部分) 的定义将通过 JSON Schema 保持一致。
import { HelloComponentProps } from "./generated-types-from-json-schema/HelloComponent"
const HelloComponent = ({ name }: HelloComponentProps) => {
return (<div>Hello, {name}!</div>)
}
ReactOnRails.register({ HelloWorld })
通过这一切可以在每种语言中进行自身处理的验证,使得更容易进行操作验证,从而更容易且安全地使用 React on Rails。
※设计与实施策略
将 View 转化为 React 组件,方便进行操作验证
我会简要提及这个设计的意图所在。
为了使使用更加方便,Qiita Team正在推动界面的更新,包括主页的重新设计。与此同时,我们也在推动前端的React化。以前,视图部分有些是用Rails(Slim)编写的,有些是用React编写的,现在我们正在努力将大部分内容替换为React。在此过程中,我们使用了React On Rails来委托渲染给React。
在那个时候,最困扰我的是从Rails(React On Rails)到JavaScript(React)传递数据的不明确性。当使用React On Rails将数据传递给JavaScript时,我们使用类似以下的字符串、哈希对象指定数据,但无法保证这与React方面期望的值一致,有时候(实际上)会有偏差,传递的数据也不明确。
<%= react_component("HelloComponent", props: { name: "Taro" }) %>

因为这种情况,
-
- フロントエンドで定義している型が信用できない
-
- テストをするために、 Rails, JavaScript 両方を実行するようなテスト (Feature Spec, System Spec, Server Side Rendering を利用する) が必要になる
React を使用する箇所で全てこれになると、テストが非常に複雑化する
在这个开发过程中存在问题。
因此,作为改进,我们采取的方法是在传播所介绍的 React On Rails 的信息中,使用 JSON Schema 提供模式。

通过使用JSON Schema来确保传递的信息的准确性,我们不再需要为Rails和JavaScript分别准备处理系统,而是可以在各自系统中检查其准确性。此外,由于可以从Schema生成类型,类型也变得更可信。
通过这一举措,即使迁移到React,测试也不会变得复杂,并且代码类型也变得可靠,使得替换为React并进行开发变得非常容易。
通过结合现有技术和经验,有效地提升体验的改善。
在开发和引入JsonSchemaView时,我们非常重视与Qiita公司内部的技术和开发经验的连续性,以降低团队成员学习和掌握它的难度,让他们能够尽快感受到新工作流的好处,并能够以较低的成本逐步进行开发。
由于使用Schema的开发工作流可能出现各种各样的工具和学习成本较高的问题,我认为特别需要注意。
举例来说,Qiita公司使用GraphQL Ruby和JsonWorld等工具,并具有通过所谓的代码优先方式定义模式的经验。
此外,他们还尝试通过自动生成TypeScript类型定义来建立GraphQL API,并在其他API的某些部分试验性地使用JSON Schema进行生成。
可以渲染出来的对象是参考了ViewComponent的思路,但在下面的文章中,我们介绍了该方法和优点,以及与其组合的技术和方法,而且这些技术和方法都是我们以前见过或使用过的。
我们采用将它们组合起来并附上一些额外的改进来进行导入和开发的方式。
采取这种导入方法是因为在开发这个功能的时候,我们已经以外包的形式参与其中,并且在导入过程中很难花费大量时间来支持。但是通过采取这种方法,我们在内部也能够感受到方便性,并成功地增加了用户数量。
总结
本文介绍了通过将JSON Schema和React on Rails结合使用,以便更容易进行操作验证的方法,以及实现这一方法的JsonSchemaView。
现代Rails应用的前端情况十分复杂且各不相同,因此直接利用本次方法可能会有一定困难,但是使用架构开发具有更广泛的应用范围,如果能够熟练掌握,可以发挥巨大的价值。希望这种方法和引入方式对你有所帮助。
如果你对这方面的话题感兴趣并希望了解更多,我也发布了有关Dev的讨论。无论是在线还是线下,都可以交流。所以请务必点击下方的“想要交流”按钮,与我们讨论吧。