javascriptを書いていて、requireの書き方で、ちょっとハマったのでメモ。
次のような要件を実装していくとします。
- 子要素を生成することができるclass Person{}をつくる。
- 要素を生成する機能は、class外に別途factory()メソッドを用意する。
- Personにはchild()関数があり、factory()を実行して、子要素を作れる。
この仕組みを1ファイルですべて記述すると成功するのだけど、
ファイルを分割して、双方の関数をお互いがrequireしあうようにすると、
書き方によっては、そんな関数無いですよ!?と怒られます。
TypeError: factory is not a function
では。そのコードを紹介します。
続きを読む »
一つ前のポストで、useMemoがうまく動作しないっていう症状について書きましたが、
複雑な要件ではなく、コンポーネントの値をsort、sliceし描画するだけであれば、特に問題はおこらないです。
正確には、問題は問題だけど一応動作する。というのが正式な回答かな。
結局使いまわししたいということに尽きるので、特定の責務に特化したカスタムフックを作り、値を受け渡すことで、さまざまな場面で使い回せるようにするのが良いと思っています。
さて、今回はカスタムフックの組み立て方について思うことを書き残します。
責務を考える
基本は、OOPの特定の責務のクラスを連携してモジュールを作っていく感じですけど、ファサードに当たる部分だったりを、カスタムフックに担わせて、必要最低限のインターフェースをコンポーネント側に提供するっていう感覚を自分は持っています。
コンポーネント間でロジックも分離されることになるので、カスタムフック自体が複雑な大きなボリュームになるとは想定しにくいですし、そういう複雑な処理は別途ライブラリを作るなり読み込んで使うなりするとおもうので、カスタムフックの範疇からはそれてくるのかな?という印象。
Real world
ページネーションの実装を元に考えてみたいと思います。
先のポストで、immutableな関数を通した値の加工に触れましたが、
sortやslice処理後のオブジェクトの受け渡しもここで、発生するので、あわせてみてください。
さて、ページネーションを実装していくにあたり、様々なケースで使い回せるような設計にするためには、リストアイテムを引数に渡すことで、うまく加工されるという2つのカスタムhookを作ることが考えられます。
- const {sortedItems} = useSort(items):並び替えするカスタムフック
- const {filteredItems} = useFilter(items):件数を絞り込むカスタムフック
もう少し考えると、Filterする際に、何ページ目なのか、合計何ページあるのかといったインターフェースも提供したいので、useFilterではなく、usePaginationとするほうがベターのようにも思います。
- const {filteredItems, paginationElement} = usePagination(items):件数を絞り込むみ、ページネーションを生成するカスタムフック
Sample
では、以上を元に、コードにおとすだけですね。
codesandboxに、App.jsの1枚にまとめましたのでみてください。
ただ、このままではページネーションとしては動きませんので、あくまでも参考と慣れば幸いです。
そっくりそのままおいておきます。
今日はこの辺で。
import React from "react";
import { useCallback, useState, useMemo } from "react";
export default function App() {
const [items, setItems] = useState([1, 2, 3, 4, 5, 6]);
const { sortedItems, toggleSort } = useSort(items);
const { filteredItems } = usePagination(sortedItems);
return (
<div>
This is sample
<button onClick={() => {
toggleSort();
}}
>
トグル
</button>
<ul>
{filteredItems.map((val, index) => {
return
<li key={index}>{val}</li>
;
})}
</ul>
</div>
);
}
//並び替えするカスタムフック
const useSort = (items) => {
const [isDesc, setIsDesc] = useState(false);
const sortedItems = useMemo(() => {
//悪い例
const badExampleItems = isDesc
? items.sort(_sortDesc)
: items.sort(_sortAsc);
//良い例
const goodExampleItems = isDesc
? [...items].sort(_sortDesc)
: [...items].sort(_sortAsc);
return goodExampleItems;
}, [items, isDesc]);
// DESC/ASCの切り替え
const toggleSort = useCallback(() => {
setIsDesc((prevState) => {
return !prevState;
});
}, [setIsDesc]);
return { sortedItems, toggleSort };
};
function _sortAsc(a, b) {
if (a < b) { return -1; } if (a > b) {
return 1;
}
return 0;
}
function _sortDesc(a, b) {
if (a < b) { return 1; } if (a > b) {
return -1;
}
return 0;
}
//フィルタするカスタムフック
const usePagination = (items) => {
const [limit, setLimit] = useState(3);
const [currentPage, setCurrentPage] = useState(1);
const filteredItems = useMemo(() => {
//悪い例
const badFilteredItems = items.slice(0, limit);
//良い例
const goodFilteredItems = [...items].slice(0, limit);
return goodFilteredItems;
}, [items, limit]);
const paginationElement = useMemo(() => {
// console.log(items, currentPage, limit);
let li_list;
// ページネーションのLiタグリストを生成する処理
// ...
return (
<ul>{li_list}</ul>
);
}, [items, currentPage, limit]);
return { filteredItems, paginationElement, setCurrentPage, setLimit };
};
汎用性を考える
責務を考えるにつながる話だけど、フック作るならば、極力汎用的なほうがよい。
たとえば、先の例でいうと、useSortというフックは、ページネーションの要素に関わらず、ありとあらゆるリストの並び替えに利用できるほうがよいし、useFilterも、ページネーション関わらず何らかの絞り込みができるとよい。
なおかつ、その結果をusePaginationと互換性がある形にしておけば、ページネーションにもスムーズに適応できるようになる。
こういう設計を考えた際には、カスタムフック内に、useContextなどグローバルで共有されるような仕組みを使って更新するという処理は向かず、フックの外からStateを更新するような処理を注入するしてあげるほうが、汎用性は上がりますね。
汎用性を必要としない要件(たとえば認証など)であれば、hook内で認証状態をcontextなどを使ってグローバル管理ちゃって良いと思います。
拡張方法を考える
完成形は用意していませんが、完成形としては、
などが実装できて完成だと思いますが、ページネーションの責務だと思いますので、usePaginationにそのインターフェースなども実装するのが良いと思います。
の要件を考えるとしたら、usePaginationの中に内包するよりかは、useSearchFilterなどを新たに用意して、キーワードを特定のキーで絞り込んだ結果をかえす処理をつくるのが汎用的に使いまわせそうに思いますね。
いろいろ試行錯誤して、自分なりのフックを作ってみてください。
Reactで処理が複雑になれば、useMemoやuseCallbackなどを使い、描画コストがかからないよう効率化していくわけでが、
ページネーションを作っていて、その際、useMemoの値が更新されない症状が発生したのでそのメモ。
Immutableであれ
結構、Reactなどで重要な、immutabuleであることがポイントで、破壊的な関数をつかってたのが原因。Immutableじゃないぞ!っていうエラーが出るわけもなく、ちょっとハマった。
immutableをわかったつもりでわかっていなかったんだと思う。
immutabuleについては、こちらの記事が、完結でわかりやすい。
https://noah.plus/blog/007/
配列/オブジェクトの正しい加工
具体的には、並び替えをする処理に、Array.sortやArray.sliceを、そのまま使って加工していた。
だめなコード例は、次のような感じ
items.sort(並び替えの関数);
items.slice(start,end);
次のように、加工前に、あらたなオブジェクト(配列)を作ってしまうことで、元のオブジェクトを壊さないで済む。
[...items].sort(並び替えの関数);
[...items].slice(start,end);
その他の例についても、先のページがやっぱりわかりやすいので、一読をオススメ。
https://noah.plus/blog/007/
実例はこちら
実際、この操作は、カスタムフックでページネーションを作っていて、この症状が発生したのだけど、
別エントリにしまとめてみたので、よかったら合わせてどうぞ。
カスタムフックの設計指針についてページネーションで考える
cssを書くとき、sass/scssを書きたくなる。
javascriptを書くとき、babelを使う必要が出てくる。
そんな時、おそらくGulpかWebpack、を使うことを強いられる。
最近は、フロントエンドの開発環境にWebpackを使うことが増えてきたけど、
そんな時は、Laravel Mixを使うとすごく便利。という話。
自分は、Laravel MixというWebpackのラッパーを使って楽している。
Reactでも、Vueでも、BabelでもSassでも、PostCSSでも思いつくことは基本的にできる。
Webpackって、設定ファイルを書くのが面倒。Webpackの設定ファイルはカオスになる。それをうまい事隠してくれる。
個人的に、過去、Gulp -> Webpack -> Laravel Mixと試してきて、ふと、Webpackに戻ろうかと思ったけど、結局Laravel Mixに戻ってきた。
(Gulpだけで良い時もあるので、適材適所)
本記事ではこれらを比較して見た記事。
続きを読む »
この記事は「[JS/CSS/SVG] Webとアニメーションの様々なありかた」という勉強会用の発表資料です。
アニメーションまわりのプラグインを紹介したものの、すごく駆け足だったので、あらためて、ご紹介します。
なお、ここで紹介するサンプルのデモは、githubにソース置いておきます。
(スライド無いです)
続きを読む »
多忙きわまりない2013年初夏でございます。
最近あんまりコードらしいもの書けてなくって、vagrantとかshefとかpjaxとかnodeとか理解するために時間つくりたい、コード書きたい!ってなってます。今回ちょっとajaxまわりのものの実装でjavascriptのテンプレートエンジンに調べる必要あったので、javascriptのテンプレートエンジンに触れてみようかなと。
phpのテンプレートだとsmartyだとか、rubyだとERBとかなんかそういうのですけど、javascriptのテンプレートエンジンについて知らなかったんで。
目次 – Table of Contents
Javascriptのテンプレートエンジンの候補
はじめに申し上げますが、いっぱいあります。ここみるといいです。
癖とかいろいろあって、今回選んだのは3つ(内2つはほとんど似ている)
- Microtemplating
(すごく軽くて、すごく手軽で、すごく早い。作者はjQueryのリードプログラマーだって。はい、すごい。)
- Mustache.js
(Mustacheの書き方を覚えたらたら、いろんな言語でMustache使えますよ。便利ですよっていうやつ。開発者はgithubのcofounder。はい、すごい、すごい。
ただ、Mustacheだとif文だとかロジックを入れたい場合には非力なため、Mustacheと互換性のあってロジック部分も強化できるhandlebars.jsというものを使う。)
その他候補にあがったのは、
- pure.js
(書き方に癖があるし、雛形となるDOMが生成してある必要があるっぽいので、Ajaxの動的生成には向かないのかな?ということで、今回パス)
- underscore.js
(Ruby使いな人にとってはunderscore.jsでよかったりするのかな。今回は手をだせなかった。)
- JsRender
(簡単そうでjQuery無しで動くけど30kと重い。殆どの場合jQuery導入してるから恩恵がない。ただ、jQueryの本命テンプレートエンジンとして開発されてるっぽいので、jQuery版リリースされたら導入を検討しようかと思ったり。)
(ごく数名の天才エンジニアが提供してくれてるライブラリのおかげで、ほとんどのwebサービスがつくられてるんだなと感じます。)
続きを読む »
[追記:2015/05] 2012年12月の投稿なので情報がかなり古いです。iOS7ではスクロールイベントが取りやすくなってます。とはいえ、スマホのパララックスは要注意。
こんにちは。Advent Calendar 2012 軽めのjQuery の17日目を担当します。
http://www.adventar.org/calendars/29
パララックスエフェクトを実装するためのイベントについて少し触れてみたいと思います。
パララックスエフェクトで考えること
パララックスエフェクトのような動きを考える時、いくつかの項目を考慮する必要があるとおもうんですが、例えば
- イベント発火のタイミング
- パララックスエフェクトの実装
- スマホで動作する際の妥協点。
- 如何に心地よくぬるぬると動かせれるか。
などなど。僕の場合このあたりを考えるんですけど、一番最後の、如何に心地よくぬるぬると動かせれるかが、パララックスのエフェクトを実装するなかでの決め手だと思っています。
実際の所写真などを多用すると、どうしても重くなりますし、いろいろ試行錯誤してみて、ある程度のところで妥協点を見つけるという感じになってしまいますが。
軽めのjQueryということで、イベントの発火のタイミングの一部と、心地よくアニメーションする方法の一部を急いで作ったDemoを見ていただきながら少し考えてみたいと思います。すこし重くなるかもしれません。
ここで使用しているDemoファイルは、githubにも置いておきましたので、ご自由にお使いください。
続きを読む »
VancouverのBCITで開催されたWordCamp Vancouver 2012に行ってきました。WordCamp東京でもWordCamp大阪でもありませんのであしからず。非力の英語力ながらもレポを残します。
WordCamp Vancouver 2012
http://2012.vancouver.wordcamp.org/
案の定、英語が難しい。
ボイレコ持参し、自宅で聞き直してみても、やっぱり難しい。現地にいるときより難しい。勉強不足だね。
というわけで、まとめます。ボイレコの内容しっかり把握できたら書き直します。
2部屋で行われ、全体の参加者は300人前後ぐらいかなと。
続きを読む »