👑
NimのフレームワークKaraxを試してみる
概要
初めてzennに記事を投稿させていただきます
初心者ゆえ至らない所も数々あると思いますので、ご指摘いただけると幸いです
NimのSPAフレームワークであるKaraxを使って、簡単な三目並べを作成します
Karaxのリポジトリ
要点
- NimのKaraxというフレームワークを使えば、Reactのような仮想DOMを用いたSPAが作れる
- Nimの簡潔な記法で書けるので楽
- コンポーネント間の情報の受け渡しを気にしなくていいので、(開発に向いているかは別として)楽に書くことができる
動作環境
Nim Compiler Version 1.0.6
nimble v0.11.0
Karax 1.1.3
Karaxをインストールする
前提
- OSにNimがインストールされている
- Nimに付属のパッケージマネージャーNimbleがインストールされている
手順
- 適当なディレクトリを用意する。この時のディレクトリ名がプロジェクトの名前になる
- そのディレクトリに移動する
$ nimble init
-
nimble init
はnimのプロジェクトを作成するコマンド
・karaxをインストールする。
$ nimble install karax
- ホームディレクトリ下の.nimbleにパッケージをインストールする
Karaxを動かしてみる
index.html
- src内に、以下のようなhtmlファイルを作成する。ファイル名は何でもいいので、とりあえずindex.htmlとしておく
index.html
index.html
<!DOCTYPE html>
<html>
<head>
<title>HelloWorld</title>
<link rel="stylesheet" href="./index.css">
<link rel="icon" href="/favicon.ico">
</head>
<body>
<div id="ROOT" />
<script type="text/javascript" src="./helloworld.js"></script>
</body>
</html>
-
body
タグ内の#ROOT
に、nimファイル中Karaxを用いて記述した仮想DOMが追加される -
script
には、helloworld.nimからトランスパイルされるjsのファイルを指定する
helloworld.nim
・Karaxライブラリを用いてHello Worldと表示するためのコードを書く
・index.htmlと同じく、src内にhelloworld.nimを作る
helloworld.nim
include karax/prelude
proc createDom(): VNode =
return buildHtml(tdiv):
text "Hello World!"
setRenderer createDom
- helloworld.nimをJavaScriptにトランスパイル。src内にhelloworld.jsというファイルが出力される
$ nim js src/helloworld.nim
後は……
-
ブラウザでindex.htmlを開く
-
Hello World! と表示されていたら成功!
三目並べを作ってみる
Reactのチュートリアルなどでおなじみの三目並べを作ってみましょう
今回作るのは次の機能を揃えたものです
- ボタンを押すとxかoが表示される
- ボタンを押すたびに、次に入力されるのがxかoかが入れ替わる
- 次にxが入力されるのか、oが入力されるのかを表示する
- 一直線にxかoか並んだらそれ以上入力を受け付けなくする
- 一直線に並んだ時に、勝者を表示する
tictactoe.nim
完成形のコードは次のようになります
tictactoe.nim
include karax/prelude
import algorithm
var
status: array[0..8,kstring]
xIsNext: bool = true
winner: kstring = kstring""
status.fill(kstring"")
proc calculateWinner(): kstring =
const lines = @[
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for i in lines:
var a, b, c: int
(a, b, c) = i
if status[a] != kstring"" and status[a] == status[b] and status[a] == status[c]:
return status[a]
return ""
proc square(m: int): VNode =
proc squareOnClick(a: int): proc() =
return proc() =
if winner != kstring"": return
status[a] = if xIsNext: kstring"x" else:kstring"o"
xIsNext = not xIsNext
winner = calculateWinner()
return buildHTML(tdiv):
button(class = "square",onclick = squareOnClick(m)):
text status[m]
proc board(): VNode =
return buildHtml(tdiv):
for i in 0..2:
tdiv(class = "board-row"):
for j in 0..2:
square(i*3+j)
proc createDom(): VNode =
return buildHtml(tdiv):
board()
tdiv(class = "container"):
tdiv:
if winner == "":
text "Next: " & (if xIsNext:"x" else:"o")
else:
text "Winner: " & winner
setRenderer createDom
NimとKaraxに独特の記法について軽く言及していきましょう
変数宣言
var
status: array[0..8,kstring]
xIsNext: bool = true
winner: kstring = kstring""
status.fill(kstring"")
- VueやReactのように、コンポーネントごとに状態を管理する変数を用意する必要がない
- グローバルスコープで定義した変数は、どの仮想DOMからもアクセスできるので、要素間の値の受け渡しが楽
- 変数宣言を、varのコードブロックを作りまとめて行えるのはNimの機能
- Nimで他の言語にトランスパイルするときに、string型ではなくてkstring型を用いる必要がある
board
proc board(): VNode =
return buildHtml(tdiv):
for i in 0..2:
tdiv(class = "board-row"):
for j in 0..2:
square(i*3+j)
- ある程度まとまった部分は、関数型コンポーネントのように実装することになる
- 仮想DOMはVNodeという型を持つ
- buildHtml:のブロックに、HTMLタグに対応したブロックを作っていき、仮想DOMを作る
- divがNimの組み込みにあるため、divタグは
tdiv
と表記される[1]
-
<タグ名>(属性=値,属性=値,属性=値,...):
という形で属性を設定することができる - タグに文章を設定するのは、
text string
(text(string)
でも同じ)という風に行う - Htmlタグのブロック中で、for文を使うことができる
- これらの構文はNimのマクロ機能で実装されていて、トランスパイル前に書き換えが行われる
square
proc square(m: int): VNode =
proc squareOnClick(a: int): proc() =
return proc() =
if winner != kstring"": return
status[a] = if xIsNext: kstring"x" else:kstring"o"
xIsNext = not xIsNext
winner = calculateWinner()
return buildHTML(tdiv):
button(class = "square",onclick = squareOnClick(m)):
text status[m]
- 引数を取るプロシージャで仮想DOMを返す
-
イベント名 = proc{...}
の形でイベントを登録することができる - 引数とEventを紐づけたい場合は、間にプロシージャを返すプロシージャを噛ます必要がある
- 例えば、
button(class="square",onclick = proc() = status[a] = if xIsNext: kstring"x" else:kstring"o"):
としても上手く動いてくれない
- 例えば、
createDom + Setrenderer
proc createDom(): VNode =
return buildHtml(tdiv):
board()
tdiv(class = "container"):
tdiv:
if winner == "":
text "Next: " & (if xIsNext:"x" else:"o")
else:
text "Winner: " & winner
setRenderer createDom
- for文が使えるので、やはりif文も使える
- 最期に
setRenderer 仮想DOM
してあげる必要がある
実行
$ nim js src/tictactoe.nim
でjsにトランスパイルしたら、src内にtictactoe.jsというファイルができるはずですので、それに合わせてindex.htmlを書き換えます。また、cssも追加してあげましょう。
index.html
<!DOCTYPE html>
<html>
<head>
<title>○×ゲーム</title>
<link rel="stylesheet" href="./index.css">
<link rel="icon" href="/favicon.ico">
</head>
<body id="body">
<div id="ROOT" />
<script type="text/javascript" src="./tictactoe.js"></script>
</body>
</html>
index.css
.board-row{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.square{
width:50px;
height:50px;
margin:5px;
border:1px solid #00CCFF;
font-size:30px;
}
これでindex.htmlファイルをブラウザで開いてあげましょう。
これで完成です!
まとめ(再掲)
- NimのKaraxというフレームワークを使えば、Reactのような仮想DOMを用いたSPAが作れる
- Nimの簡潔な記法で書けるので楽
- コンポーネント間の情報の受け渡しを気にしなくていいので、(開発に向いているかは別として)楽に書くことができる
参考
おまけ
Google検索でtictactoeと調べると……
-
同じような例として、iタグはitalic、bタグはbold、 uタグはunderlinedと書く必要がある ↩︎
Discussion