JavaScript軽量フレームワークxattoのcontextについて


[JavaScript軽量フレームワークxatto触って見る](https://qiita.com/jhonyspicy/items/26018185dcb9c6baec3a)を書いてからデモのリポジトリが更新されていました。

フレームワーク本体 https://github.com/atomita/xatto
デモ https://github.com/atomita/xatto-starter-kit

# xattoのデモが更新されていましたので一緒にcontextについて説明します
大きく変わったのは`src/app.jsx`だけです。

```jsx:src/app.jsx
import { x, atto, Context } from 'xatto';

import { Counter } from './components/Counter';

const view = (props, children, context) => (
  <div>
    <Context slice="counters.0"><Counter /></Context>
    <Context slice="counters.1"><Counter /></Context>

    <h1>{context.counters.reduce((acc, v) => acc + v.count, 0)}</h1>
  </div>
);

atto(view, document.getElementById('app'))({
  counters: [{ count: 0 }, { count: 10 }],
});
```

ポイントとなるのは下記の変更で、 **`context`の抜粋方法** が更新されています。
(書き方が違うだけでやっていることは同じです。)
`xatto`を使う上でこの`context`の理解が大事になります。

```jsx
- <Counter xa={{ slice: 'counters.0' }} />
+ <Context slice="counters.0"><Counter /></Context>
```

# xattoでのcontextとは
`context`はアプリ全体で必要になる(状態管理の？)データで、特に何もしなければルートのコンポーネントから末端のコンポーネントまで同一の`context`が渡されてきます。
ただ、今回のカウンターの例では`Counter`コンポーネントは自身の`count`の値以外には興味がありません。
そんな時に`Context`コンポーネントが役にたちます。`Context`コンポーネントは`context`の一部だけを切り取って、その抜粋した値を以降の子孫に渡しています。
`Context`コンポーネントの子孫のコンポートは渡された`context`が抜粋されたものであることを意識する必要はありません。

逆にいうと抜粋された部分以外の`context`を参照できなくなってしまうのですが、そんな時には`extra`にメソッドを追加して、コンポーネントからそのメソッドを実行することで回避できます。

## contextの更新方法 - イベントハンドラー編
まずは`Counter`コンポーネントをみてください。

```jsx:src/components/Counter/view.jsx
import { x } from 'xatto';

/*
 * この down / up イベントハンドラーの返り値でcontextを更新します。
 * <Context slice="counters.0"><Counter /></Context>
 * の場合ですとフルパス(?)でいうところの
 * context.counters[0].count
 * を更新することになります。
 * 
 * Contextコンポーネントの子孫はcontextが抜粋されていることを
 * 意識しなくてもうまいこと処理してくれます。
 */
const down = context => ({ count: context.count - 1 });
const up = context => ({ count: context.count + 1 });

export const CounterView = (props, children, context) => (
  <div class="c-counter">
    <h1>{context.count}</h1>
    <button onclick={down}>-</button>
    <button onclick={up}>+</button>
  </div>
);
```

このクリックイベントのようにイベントハンドラーの関数の戻り値で`context`が更新できます。そして再レンダリングが走ります。
この`Counter`コンポーネントは`Context`コンポーネントによって`context`が抜粋されていることを意識する必要はありません。

## contextの更新方法 - mutate編
もう一つ更新する方法があります。
今回の`src/app.jsx`を少し書き換えてみましょう。

```jsx:src/app.jsx
import { x, atto, Context } from 'xatto';

import { Counter } from './components/Counter';

const view = (props, children, context, extra) => (
  <div
    xa={{
      extra: {
        test: () => {
          return context
        }
      }
    }}>
    <Context slice="counters.0"><Counter /></Context>
    <Context slice="counters.1"><Counter /></Context>

    <h1>{context.counters.reduce((acc, v) => acc + v.count, 0)}</h1>
  </div>
);

/*
 * atto()を実行するとmutateが返ってきます。
 * このmutateにオブジェクトを渡して実行すると
 * その値をcontextとして再レンダリングしてくれます。
 */
const mutate = atto(view, document.getElementById('app'));

// 新しいcontextで再レンダリング
mutate({
  counters: [{ count: 0 }, { count: 10 }],
});

setTimeout(() => {
  // contextの一部を更新したい場合は第2引数で指定します。
  mutate({ count: 11 }, 'counters.0')
}, 1000)
```

このようにアプリの初期化に使われた`atto`関数の戻り値は実行可能で、これにオブジェクト形式のデータを渡して実行するとそのデータを`context`としてレンダリングしてくれます。また`context`の一部を更新したい場合は第2引数にパスを指定することもできます。

## 関連
[JavaScript軽量フレームワークxatto触って見る](https://qiita.com/jhonyspicy/items/26018185dcb9c6baec3a)