JavaScript軽量フレームワークxatto触って見るを書いてからデモのリポジトリが更新されていました。
フレームワーク本体 https://github.com/atomita/xatto
デモ https://github.com/atomita/xatto-starter-kit
xattoのデモが更新されていましたので一緒にcontextについて説明します
大きく変わったのは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
の理解が大事になります。
- <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
コンポーネントをみてください。
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
を少し書き換えてみましょう。
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引数にパスを指定することもできます。