はじめに
NuxtとTypeScriptは相性が悪いという話はよく聞くと思います。
とはいえ、TypeScriptの型システムは保守性を担保するためには捨てがたいものがあります
この記事では、私がNuxtで型セーフな設計をするために考えたことを整理も兼ねて
書いていこうと思います
前提
フロント: Nuxt.js × TypeScript
バックエンド: C#
API通信について
C#で作成したAPIのControllerに対して、
NSwagを用いてNuxt側にAPI通信をするためのSwagerをTypeScriptで自動生成しています
#コンポーネント設計
タイトルにある方セーフなコンポーネント設計を考えるために
まず、ベースとなる考え方としてAtomic Designの考え方を取り入れています
その上にTypeScriptによる型システムを乗っけることで
Nuxtで型セーフな設計をしようとしています
Atomic Designで各Lvの定義をする
ベースとなる考え方としてtomic Designの思想を一部取り入れています
ここでは説明は記載しませんので、詳細を知りたい方はこちらを読むと良いとおもいます
Atomic Designでは各Lv 何を入れるかといった明確な定義はないので
勝手に以下のように定義しました
-
atoms
使用しない -
molucules
表示または、フォームとしての責任を持つvue
このvue内ではCSSとロジックは記載しない、organismsからpropされたdataを表示
またはフォームに入力された値をorganismsにemitする -
organisms
主ビジネスロジックとして責任をもつvue
API通信はここでおこなう -
template (pages)
レイアウトについての責任をもつvue
ここではScoped CSSによりコンポネートの配置を調整する
(pages内にいるvue)
Atomsを使用していないのは、moluculesとの切り分けが難しかったからで、
迷うくらいなら使用しないというのが理由です
(とはいえ、後でatomsとmoluculesを分けたいと考えることも十分にあり得るので粒度の大きいmoluculesを使用しています)
このような構成にすることで、少なくとも機能単位でコンポーネントを切り出せており、
再利用しやすくなると思います
構成を図式すると以下のような感じ
ここに画像
C#でNuxt側で使用するmodelを定義する
Nuxt側独自のmodel定義はせず、NSwagによって生成された
型定義を利用しますので、基本的にNuxtで使うmodelはC#側で定義します
実装例
下記のような、HomeController.cs に
UserModelを受け取って、MessageModelを返すAPIを実装すると
NSwagはwebapi.tsのようなInterfaceを実装してくれます
※ 表示フラグのようなものはNuxt側で定義することもあります
[ApiController]
public class HomeController : ControllerBase
{
[HttpPost]
[Route("[controller]")]
public MessageModel Hello([FromBody] UserModel req)
{
return new MessageModel
{
Name = req.UserName,
Meesage = "Hello World!!"
};
}
}
public class MessageModel
{
public string Name { get; set; }
public string Meesage { get; set; }
}
public class UserModel
{
public long UserId { get; set; }
public string UserName { get; set; }
}
// NSwagで出力されたmodelはtypeではなく、Interfaceとして実装される!
export interface IMessageModel {
name?: string;
meesage?: string;
}
export interface IUserModel {
userId?: number;
userName?: string;
}
TypeScriptで型セーフなコンポーネント設計する
Atomic Desigの各Lvについて定義してどの粒度でコンポーネントを切り出すかを決めました
また、Nuxt内で使用する型定義ファイルはNSwagにお任せです
必要な部品は揃ったので
ここから、実際のコンポーネントを実装していきます
基本的な実装方針は以下です
- moleculesにNSwagから読ませたmodelをimportする
- IntefaceをTypeに変換、exportする
- propsには定義したmodel定義を使用する
- orgnismsにはmoleculesのvueとtypeをimportする
- orgnismsでAPI通信をおこなう
- pageではcomponentの呼び出しとscoped cssによるレイアウト調整をおこなう
順番に書いていきます
- moleculesにNSwagにより作成されたmodelをimportする
- IntefaceをTypeに変換、exportする
基本的に、APIから返される値はクライアント側で表示される値が多いと思います
また、値の表示する責任はmoleculesに任せていますので、
moleculesにNSwagにより作成されたmodelをimportしています
これよりも大きな粒度のcomponentsでは
moleculesよりTypeに変換されたmodelを使用します
InterfaceをTypeにしているのはInterfacceの拡張性を嫌ったからです
- propsには定義したmodel定義を使用する
- orgnismsにはmoleculesのvueとtypeをimportする
- orgnismsでAPI通信をおこなう
moleculesで実装したcomponentをimportしていきます
(ちなみに、ここで別のorgnismsをimportしても良いと思います)
moleculesにわたすのはvueと同時に typeで定義した型で渡します
これにより、propsの渡し忘れや、mocules内の仕様変更に対応しやすくなると考えています
orgnismsではAPI通信と取得結果をの処理をおこないます
(クラインと側のビジネスロジックはここに集約されると言い換えられる?)
このことによいり、ロジックをまとめて上げることができるのではないかと、
- pageではcomponentの呼び出しとscoped cssによるレイアウト調整をおこなう
pageでは極力ロジックは入れません
(URLを管理する都合で、URLパラメータをprops経由で渡すくらいはしますが)
ので、ここではcssによりかくcomponentのレイアウト調整をしていきます
現在、私がアサインされているサービスでは以上のような
設計思想で実装を進めています
サーバー側で定義したmodelを最も小さな粒度から大きな粒度のcomponentに流してあげ、
逆に、orgnismsで取得した値をそのmodelに当てはめてpropsで渡すことによって
仕様変更にある程度強いサービスができるのではないかと考えています
終わりに
実装するときに考えているコンポーネント設計を整理する意味を兼ねて記事を書きました
しかし、この実装方針ではまだ不都合があったりと完璧な設計とは言い難いです
ので、都度都度設計には手を入れていこうと考えています