StencilでlocalStorage利用のTODOアプリを写経してみる
Stencilとは、Webコンポーネントを作成するためのツール(コンパイラ)だそうです。
?
何を言っているのか自分でもよく分かりませんが、特定のフレームワークに依存せずに、フォームやナビゲーションバーなどの、『再利用可能なWebページ作成用の部品』を作成するためのツールだそうです。ただ、AngularやVue.jsなどのJavaScriptフレームワークのように使用することもできます。
@stencil/coreが2019年6月にVersion1.0.0になり、ようやく使用できるようになったようです。2019年6月以前のチュートリアルをそのまま写経しても、うまくいかないことがあります。そのため、Stencil.jsの解説を読むのであれば、2019年7月以降のものを選んで読むのがおすすめです。
今回は、以下のサイトを写経して、TODOアプリを作ってみたいと思います。
https://www.joshmorony.com/building-a-notepad-application-from-scratch-with-ionic-and-stencil-js/
- 1. ソースコードとDEMOサイト
- 2. 開発環境
- 3. 新規Stencilアプリ作成
- 4. Stencil.jsのionic-pwaのファイル構造
- 5. 必要なコンポーネント用ファイルとサービス用ファイルの作成
- 6. ルーティングの設定
- 7. app-home.tsxの編集
- 8. TODO追加 を実装
- 9. Interfaceインターフェース の作成
- 10. src/services/storage.tsの作成
- 11. src/services/todo.tsの作成
- 12. app-home.tsxを修正してaddTodo()を完成させる
- 13. app-detailページの実装
- 14. @stateとcomponentDidLoad()
- 15. app-detail.cssの編集
- 16. ソースコードとDEMOサイト(不完全)
- 17. Netlifyにデプロイする際の設定
ソースコードとDEMOサイト
(作成中)
ソースコード
DEMOサイト
開発環境
Windows 10 Pro (1803)
VisualStudioCode 1.37.1
git version 2.20.1.windows.1
nvm-windows 1.1.7
node 12.2.0
npm 6.9.0
yarn 1.16.0
新規Stencilアプリ作成
C:/stencil/ フォルダ内に、stencil-todo-localStorage/ という名前のアプリを作成することにします。
C:/stencil/ フォルダを右クリックして、『Open with Code』をクリックして、VisualStudioCodeで開き、Ctrl+@でターミナル画面を出します。次に、以下を入力して新規stencilアプリを作成します。
npm init stencil
// ionic-pwaを選択して、Enterを押す
// Project name は stencil-todo-localStorage と入力して、Enterを押す
// Confirm? はそのままEnterを押す
以下のようになります。(この時点では、node_modules/フォルダは作成されていません。)
とりあえず、開発サーバを起動してみます。言われるがままに、以下のように入力すると、以下のようにエラーが出ます。
cd stencil-todo-localstorage
npm start
言われたとおりにやっているのにエラーが出るのでびっくりしたのですが、よく見ると、src/ フォルダしかなく、node_modules/ フォルダが無いので、以下のように入力してみます。
npm install
npm start
うまくいったようです。
開発サーバを停止するときは、"Ctrl+C" => y + Enter で停止できるようです。
Stencil.jsのionic-pwaのファイル構造
とりあえず、この時点でのファイル構造を見てみます。
(Windows のコマンドプロンプトで、 “tree /f" とすると、見ることができます。)
src/components/ フォルダには、以下の3つのフォルダあり、それぞれ4つのファイルが入っており、その中の、app-xxx.tsxファイルとapp-xxx.cssファイルがセットになって、コンポーネントを形成しています。
- app-root
- app-home
- app-profile
今回は、トップページは、app-homeコンポーネントが表示されています。app-homeコンポーントの中身は、
src/components/app-home/app-home.tsx
の中の、render() { return [] } の中で指定しているようです。こちらの書き方は、Ionic4 (Angular)に慣れていれば、見慣れている書き方となります。
app-rootコンポーネントは特別で、ルーティングrouting(URLの値によって、表示するコンポーネントを振り分ける)を示しているだけのようです。具体的には、以下のようになっています。
- ”/” (トップページ)は、app-home.tsx を表示する
- ”/profile/:name” は、app-profile.tsx を表示する
- (URLから、@Propとしてnameを受け取っています。)
URLから、@Propとしてnameを受け取り、たとえば、<ion-title>のところで、Profile: {this.name} と記載することにより、nameの内容(今回は"ionic")を表示しています。
なんとなく、構造が分かった気になったところで、次に進みます。
ここまでのソースコード
https://github.com/adash333/stencil-todo-localstorage/tree/3cb8fd6f0a726762126908b166cc49cbdc0b70f8
必要なコンポーネント用ファイルとサービス用ファイルの作成
今回作成するのは、ホームページ(app-home component)にTODOリストが表示され、それおぞれのTODOをクリックすると、詳細ページ(detail component)が表示されるようなアプリです。
また、以下の2つのサービスも作成します。
- todo.ts (TODOをCRUD(create, read, update, delete)するサービス)
- storage.ts (TODOリストをlocalStorageに書き換えるサービス)
具体的には、
- src/components/app-profile/ フォルダをフォルダごと削除
したのち、以下の4つのファイルを作成します。(4つとも、最初は中身が空のファイルを作成しておきます。)
- src/components/app-detail/app-detail.tsx
- src/components/app-detail/app-detail.css
- src/services/todos.ts
- src/services/storage.ts
ルーティングの設定
ルーティング(URLにより、表示するページを振り分けること)の設定を行います。
src/components/app-root/app-root.tsx を編集することにより、以下のようにURLによって表示するページを振り分けます。
- “/" または “/notes" => app-home.tsx を表示
- “/todos/2" など => app-detail.tsx を表示(2番目のtodo)
// (変更前)
<ion-route url="/" component="app-home" />
<ion-route url="/profile/:name" component="app-profile" />
// (変更後)
<ion-route url="/" component="app-home" />
<ion-route url="/todos" component="app-home" />
<ion-route url="/todos/:id" component="app-detail" />
(変更前)
(変更後)
参考: https://ionicframework.com/jp/docs/api/router
app-home.tsxの編集
写経元サイトを参考に、トップページを編集し、TODOリストを表示と、TODOの追加をできるようにします。
src/components/app-home/app-home.tsx
(変更前)
(変更後)
Ionicの使い方については、公式Documentが参考になります。
https://ionicframework.com/jp/docs/api/toolbar
TODO追加 を実装
TODOを追加する際に、今回は、ion-alert-controller を利用してみます。そのためには、まず、
src/components/app-root/app-root.tsx
に<ion-alert-controller/ > を 追加します。
次に、
src/components/app-home/app-home.tsx
に、以下のコードを追加します。
componentDidLoad() {}
addNote() {
const alertCtrl = document.querySelector("ion-alert-controller");
console.log(alertCtrl);
}
// ion-button のところに
<ion-button onClick={() => this.addNote()}>
// ion-list のところに
<ion-list>
{this.notes.map(note => (
<ion-item button detail href={`/notes/${note.id}`} routerDirection="forward">
<ion-label>{note.title}</ion-label>
</ion-item>
))}
</ion-list>
今は画面が真っ白になっていますが、
- 画面右上のボタンをクリックするとaddTodo() 関数が発動して、Alert画面でTodoを追加する
- todos 配列(Todoリストを格納)
をこれから実装していきます。
参考: https://ionicframework.com/jp/docs/api/alert
Interfaceインターフェース の作成
Todosサービスを作成する前に、Todoの定義であるtodoインターフェースを作成します。id, title, contentとなります。
src/interfaces/note.ts (新規作成)
export interface Note {
id: string;
title: string;
content: string;
}
src/services/storage.tsの作成
データをlocalStorageに保存したりするためのコードを、
src/services/storage.ts
に記載します。以下の3つの関数を定義します。
- get(key: string)
- set(key: string, value: any)
- remove(key: string)
参考1:ブラウザにデータを保存するlocalStorage(ローカルストレージ)の使い方 Posted by NAGAYA on Oct 26th, 2017
参考2:JavaScriptのLocalStrage(ローカルストレージ) の使い方を現役エンジニアが解説【初心者向け】2018/2/21
src/services/todo.tsの作成
前述のservices/storage.ts を利用して、Todoリストの書き込み、削除などのサービスを、
src/services/todo.ts
に記載します。以下の6個の関数(load、saveとCRUD)を定義します。
- load()
- save()
- getTodo(id)
- createTodo(title)
- updateTodo(Todo, content)
- deleteTodo(Todo)
そして、最後に、全部を、
export const todosService = new todosServiceController();
としてexportしています。
app-home.tsxを修正してaddTodo()を完成させる
src/components/app-home/app-home.tsx
を以下のように編集して、Todoの追加の実装を完成させます。
// 最初に以下を追加して、State(from @stencil/core)と、todoインターフェースとtodosサービスをインポート
import { Component, State, h } from '@stencil/core';
import { Todo } from "../../interfaces/todo";
import { TodosService } from "../../services/todos";
// export class AppHome { の直後に以下を追加
@State() todos: Todo[] = [];
async componentDidLoad() {
this.todos = await TodosService.load();
}
async addTodo() {
const alertCtrl = document.querySelector("ion-alert-controller");
let alert = await alertCtrl.create({
header: "New Note",
message: "What should the title of this todo be?",
inputs: [
{
type: "text",
name: "title"
}
],
buttons: [
{
text: "Cancel"
},
{
text: "Save",
handler: async data => {
TodosService.createTodo(data.title);
this.todos = [...(await TodosService.load())];
}
}
]
});
alert.present();
}
これでやっと、Todoの追加(Titleのみ)を実装することができました。
参考: https://ionicframework.com/jp/docs/api/alert
ここまでのソースコード
https://github.com/adash333/stencil-todo-localstorage/tree/58edf280fd52fe2f3ed3e96d2f49ee81c051cb51
app-detailページの実装
app-homeページのそれぞれのTodo(のtitle)をクリックすると、対応するapp-detailページへ移動し、
Todoのtitle, detailの表示
detailの編集、削除
ができるようにします。
- componentDidLoad() 関数の中で、初期化を行う
- noteChanged(ev) 関数
- deleteTodo() 関数
src/components/app-detail/app-detail.tsx
ターミナル画面に、
『@Prop() id: string; のところは、本当は、"id"という文字列は使ってほしくないので、別の文字列に変更することをお勧めします。』
と出ていますが、今回は無視します。
削除しても、リロードしないと削除されないなど、なんか微妙に挙動がおかしいです。
src/components/app-home/app-home.tsx
に、以下のコードを追加します。(理由はよくわかりません。)
public navCtrl = document.querySelector("ion-router");
この状況下でも、app-detailページでTodoを削除しても、リロードしない限りは、app-homeページで、直前に削除したTodoのTitleが表示されたままでした。リロードすると消えるので、localStorageからは削除されているようですが、app-homeページが自動ではページを更新してくれないようです。
そこで、
src/components/app-home/app-home.tsx
の15行目を以下のように変更します。
// (変更前)
this.todos = await TodosService.load();
// (変更後)
this.todos = [...(await TodosService.load())];
この変更を行っても、Todoを削除したときに、すぐにTodoリストから削除されませんでした。理由はわかりません。残念。。。
@stateとcomponentDidLoad()
State Decorator(公式ドキュメント) によると、内容が変化する可能性のあるデータには、 @state()デコレータをつけるそうです。
@State() propertyが変更されるたびに、コンポーネントのrender関数が呼び出される(再描画される)。
とのことなのですが、今回は、app-detail.tsxでデータを削除しても、app-home.tsxに自動的に反映するためには、app-home.tsxに以下のように記載する必要があるそうです。
@State() todos: Todo[] = [];
public navCtrl = document.querySelector("ion-router");
async componentDidLoad() {
// URLが変化したら、notesをNoteServiceからloadする
this.navCtrl.addEventListener("ionRouteDidChange", async () => {
this.todos = [...(await TodosService.load())];
});
}
(変更前)
(変更後)
しかし、これでも、app-detail.tsxで、特定のTodoを削除しても、その直後のapp-home.tsxでは直前に削除したTodoが表示されたままで、更新ボタンを押すと、消えました。。。原因はわかりません。がっくしです。
Stencilの@propと@state に少しだけまとめてみましたので、もしよろしければご覧ください。
app-detail.cssの編集
app-detailのところのテキストボックスの大きさがすごく変なので、app-detail.css を編集します。
(変更前)
(変更後)
うまくいきません。Ctrl + Shift + I で見てみると、
<ion-textarea class="native-textarea sc-ion-textarea-md" ...>
</ion-textarea>
の中に、
<textarea class="native-textarea sc-ion-textarea-md" ...>
(書き込んだ内容)
</textarea class=">
が入ってしまっています。
途中。。。
不完全ですが、この時点でのソースコードとDEMOサイトを以下に記載しておきます。
ソースコードとDEMOサイト(不完全)
ソースコード
https://github.com/adash333/stencil-todo-localstorage/tree/ee509427375c887b9903eb8622ed72f537dad6c4
DEMOサイト
https://nostalgic-feynman-6f201d.netlify.com
Netlifyにデプロイする際の設定
作成中
ディスカッション
ピンバック & トラックバック一覧
[…] https://i-doctor.sakura.ne.jp/font/?p=40983 […]