FicusJSを触ってみた

株式会社ココロファン - エンジニアリング事業部所属のSassyです。  
普段は業務委託で仕事をしながら、自社プロダクトの企画/開発をしています。

今回はJSer.infoに取り上げられていたFicusJSというのを調べてみました。  

https://jser.info/2021/04/06/typescript-4.3-beta-hls.js-v1.0.0apple-low-latency-hls-storybook-6.2/

## FicusJSとは

https://docs.ficusjs.org/

FicusJSはWebコンポーネントを使ってアプリケーション開発するための軽量なライブラリです。

「Ficus」ってなんて読むの？って疑問になりますね。
調べてみると何通りか読み方出ました「ファイカス」「フィークス」「フィカス」。。
多分。。「ファイカスジェイエス」です。

ここら辺読み方間違ってると恥ずかしいから、そのライブラリについて語る前にしっかりと読み方は押さえておきたいものですね。

FicusJSはESModulesを使って書くのでIEでは動かないみたいですが、レガシーブラウザの対応サンプルがあるのでここを見れば良さそうです。

https://github.com/ficusjs/ficusjs/tree/master/examples/ie11

また、FicusJSはビルド不要のようです。

## FicusJSの売り

- 関数型プログラミングパターン
- 宣言型コンポーネントの作成
- リアクティブコンポーネント
- 独自のレンダラーを選択
- 関数を使用してコンポーネントを拡張する
    - ストアでの作業
    - イベントバスでの作業
    - State transactionを処理する
    - CSSを操作する
- ストアでのアプリケーション状態管理
- アプリケーションイベントバス
- 軽量
    - すべての機能-4.12KBをgzipで圧縮
    - コンポーネント-2.01KB gzip圧縮
    - ストア-1.22KB gzip圧縮
    - イベントバス-274B gzip圧縮
- 機能は個別にロードすることも、すべての機能バンドルを使用することも可能
- 依存関係なし
- すべてのサーバー側およびクライアント側のフレームワークで動作可能

## 実際に触ってみる

実査に触ってみないとよくわからないので触って行きます！

### コンポーネントの作成

HTMLで使用するコンポーネントを作成するためにはcreateComponent関数を使用します。

とりあえずは参考のソースコードを貼り付けてブラウザに表示してみます。

```javascript
<hello-world></hello-world>

<script type="module">
import { html, renderer } from 'https://cdn.skypack.dev/@ficusjs/renderers/lit-html'
import { createComponent } from 'https://cdn.skypack.dev/ficusjs/component'

createComponent('hello-world', {
  renderer,
  handleClick (e) {
    window.alert('Hello to you!')
  },
  render () {
    return html`
        <div>
        <p>Test component</p>
        <button type="button" @click="${this.handleClick}">Click me!</button>
        </div>
    `
  }
})
</script>
```

![スクリーンショット 2021-04-07 20.22.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128033/aa045e12-fa46-2e1d-39fe-ee70a47c3a95.png)

ボタンをクリックしてみると

![スクリーンショット 2021-04-07 20.22.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128033/e51348b2-84bd-1599-1fa5-33288abfcc57.png)

アラートが出ることを確認しました。


createComponent関数は引数を２つとります。
第一引数はタグ名、第二引数はオブジェクトです。
第二引数のオブジェクトのプロパティに色々な設定を書いていきます。

どういうプロパティを設定できるのでしょうか。

| プロパティ | 必須 | タイプ | 説明 |
| -------- | ---- | ----- | ---- |
|renderer|●|function|render関数から返却されるものをレンダリングする|
|render|●|function|renderer関数に渡せるものを返さないといけない|
|root||string|コンポーネントのルート定義を行う|
|props||object|１つ以上の定義を記述できる。コンポーネントの使用時に設定できる属性です|
|computed||object||
|state||function|初期状態を返す関数。コンポーネントの内部変数|
|*||function|アクションのロジックに役立つ|
|created||function|コンポーネントが作成された時、またはDOMに接続する前に呼び出されるライフサイクル|
|mounted||function|コンポーネントが最初にDOMに接続された時に呼び出されるライフサイクル|
|updated||function|コンポーネントがDOMに移動または接続された時に呼び出されるライフサイクル|
|removed||function|コンポーネントがODMから接続されるたびに呼び出されるライフサイクル|

「*」というプロパティが設定できるんですか。。よくわからない。。

rootプロパティには「standard」「shadow」「shadow:closed」が設定できるみたい

ShadowDOMについてはMDN参照しましょう。
https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_shadow_DOM


### Propsを渡してみる

Propsを渡せるようにしてみます。

nameのプロパティを定義して、nameに渡した値でアラートに出力する「~you」の部分を書き換えるようにしてみました。

```javascript
<hello-world></hello-world>
<hello-world name="sassy"></hello-world>

<script type="module">
import { html, renderer } from 'https://cdn.skypack.dev/@ficusjs/renderers/lit-html'
import { createComponent } from 'https://cdn.skypack.dev/ficusjs/component'

createComponent('hello-world', {
  renderer,
  handleClick (e) {
    window.alert(`Hello to ${this.props.name}!`)
  },
  props: {
      name: {
        type: String,
        default: 'you',
        required: false,
        observed: false
      }, 
  },
  render () {
    return html`
        <div>
        <p>Test component</p>
        <button type="button" @click="${this.handleClick}">Click me!</button>
        </div>
    `
  }
})
</script>
```

２つ目のボタンを押下すると「Hello to sassy!」と表示されているのがわかります。

![スクリーンショット 2021-04-07 21.01.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1128033/099a48f6-4507-f690-5915-d2067025ff75.png)


### computedを使ってみる

computedはメモ化された関数を定義できます。
状態が変わると再計算され、変わらない限りはキャッシュした結果を返します。

```javascript
<hello-world></hello-world>
<hello-world name="sassy"></hello-world>


<script type="module">
import { html, renderer } from 'https://cdn.skypack.dev/@ficusjs/renderers/lit-html'
import { createComponent } from 'https://cdn.skypack.dev/ficusjs/component'

createComponent('hello-world', {
  renderer,
  handleClick (e) {
    window.alert(`Hello to ${this.props.name}!`)
  },
  computed: {
    greeting () {
      return `Hi, ${this.props.name}, Click me!`
    }
  },
  props: {
      name: {
        type: String,
        default: 'hoge',
        required: false,
        observed: false
      }, 
  },
  render () {
    return html`
        <div>
        <p>Test component</p>
        <button type="button" @click="${this.handleClick}">${this.greeting}</button>
        </div>
    `
  }
})
</script>
```

### stateで状態を管理してみる

stateは関数として定義します。

以下公式ドキュメントのサンプルコードを抜粋します。

```javascript
state () {
    return {
      count: 0,
      isEven: false,
      color: 'secondary'
    }
  },
```

状態の更新にはsetStateメソッドを使うか「this.state.name = '[値]'」のように直接書き換えることができます。  

setStateを使う場合は以下のように書きます。

```javascript
increment () {
  this.setState(
    (state) => {
      const count = state.count + 1
      const isEven = count % 2 === 0
      return {
        count,
        isEven,
        color: isEven ? 'success' : 'info'
      }
    },
    () => console.log('Component did render!')
  )
}
```

setStateを使う利点は、１つのアクションで複数のstateの値を更新することができます。  
またsetStateを使って複数stateを書き換えた場合はレンダリングが１回で済むので２個以上ある場合はsetStateを使った方が良さそうですね。  

## ドキュメントを眺めてみた

とりあえず基本的なところは実際に動かしてみたので、複雑そうなところはドキュメントを見て調べていこうと思います。  

### ライフサイクルフックについて

FicusJSではコンポーネントの接続または解除タイミングや状態の変更タイミングによってコールバック関数を実行できます。
そのために用意されているのが「created」「mounted」「updated」「removed」です。
これらはstateと同じように関数で定義します。

```javascript
{
  created () {
    // TODO
  },
  mounted () {
    // TODO
  },
  updated () {
    // TODO
  },
  removed () {
    // TODO
  }
}
```

### コンポーネントを拡張について

コンポーネントを拡張するための4つの関数が用意されています。

・withStore
・withEventBus
・withStateTransactions
・withStyles

#### withStore関数
withStore関数はReduxのようなイメージを持っています。
withStore関数を使うことでそのコンポーネントに渡したstoreを接続して、そのstoreの値の変更を検知して何か処理したりなどもできるのかと思ってます。

#### withEventBus関数
https://docs.ficusjs.org/docs/composition/
これ公式ドキュメントを読んでもあまりピンとこなかったんですが、

```javascript
buttonClicked () {
  this.eventBus.publish('increment', undefined)
}
```

とした場合、ボタン押下時に`increment`とというイベントを発行するということなんでしょうか？。。
もう少し調べてみないとわからないですね。

#### withStateTransactions関数
トランザクションを使用するため、複数の状態変更を再レンダリングなしでできるようです。
例えば、以下のような書き方でもstateへの直接代入でもレンダリングは１回になります。

```javascript
this.state.count = this.state.count + 1
this.state.message = 'Thanks for liking this!'
```

setState使うよりも良さそうな感じしますね。

#### withStyles関数
style関数を定義できるようになります。
この関数が呼び出されると`<head>`タグに対して１度だけCSSを挿入することができるようです。

以下、公式ドキュメントから拝借します。

【文字列スタイル】

```javascript
{
  styles () {
    return `
      my-component button {
        background-color: yellow;
        color: black;
      }
    `
  }
}
```

【URLスタイル】

```javascript
{
  styles () {
    return 'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css'
  }
}
```

【文字列×URLスタイル】

```javascript
{
  styles () {
    return [
      'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css',
      `
        my-component button {
          background-color: yellow;
          color: black;
        }
      `
    ]
  }
}
```

### ストアについて

createStore関数を使ってstoreを定義します。

以下公式ドキュメントから拝借します。

```javascript
import { createStore } from 'https://cdn.skypack.dev/ficusjs'

const store = createStore('an.example.store', {
  ttl: 5,
  initialState: {
    count: 0
  },
  actions: {
    increment (context, payload) {
      context.commit('increment', payload)
    }
  },
  mutations: {
    increment (state, payload) {
      state.count = payload
      return state
    }
  },
  getters: {
    max (state) {
      return Math.max(state.count) * 1000
    }
  }
})
```

おそらく主に使用することになりそうなプロパティは以下ですかね。

「initialState」「actions」「mutations」「getters」

#### initialState
storeを作成した時に設定される初期値です。  
あとは`ttl`を設定した場合は指定の時間が過ぎた後にこの値が設定されます。  

#### actions
storeの状態を変更するためのアクションを定義します。

#### mutations
actionsではあくまで状態変更を通知するだけです。  
実際に値を変更するのは,ここに定義した関数内で行います。  
ドキュメントをみた感じですとactionsに定義した関数名と同名の関数を作ってあげれば、良さそうですかね？  
Reduxですとreducerに相当します。  

#### getters
これはstateの値をアプリケーション内で使う値に加工して返す関数を定義します。


### その他

・FicusJS routerを使えばクライアントにルーティングを実装することができるのでSPAもできそうですね。
　https://router.ficusjs.org/

・jsとhtmlに分離するには、HTMLファイル内に`<script type="module" src="./index.js"></script>`としてあげれば外部ファイルとして切り出したindex.jsを読み込ませることができます。


## 最後に

一通り触ったり、調べてみたところVue.jsに似ている感じがしました。
そして、確かにビルドせずにhtmlファイルをブラウザで開くだけで動かすことができし、修正してもリロードかければすぐ反映されるので、かなり楽な印象がありました。

Webpack周りの複雑な設定や専門知識からの解放はかなり良いのではないでしょうか？
しかし、ReactやVue、Angulerがメインとなっている現状どのようにFicusJSが成長していくのかが楽しみですね。

いつの日かこういうフレームワークが人気出てくる時期がくるのかわかりませんが、普段はReact漬けなので良い刺激になりました。

株式会社ココロファンでは自社プロダクトの開発も行なっており、
日々進化していく技術を常にキャッチアップしていけるように、
これからも積極的に技術記事をアップしていきたいと思います。

https://cocolofun.co.jp/