fuzzy study

仕事・趣味で勉強したことのメモ

web worker, service worker, worklet

w3schoolsのHTMLチュートリアルを流し読みして、 HTML5 APIについて学んでいたら、web workerというものを知った。

service workerは聞いたことがあったのだけど何が違うのかよくわかっていなかったので、調べた。
ほぼ以下の記事のまとめ。

bitsofco.de

似たような概念に、web worker、service worker、workletの3つがあるという。

  • 共通
    • バックグラウンドで別jsのプロセスが動く。
    • DOMにはアクセスできない。
  • web worker
    • どんな用途にも使える。
    • 基本的には、重くてUXに支障をきたす処理をオフロードするのに使う(画像処理など)。
    • DOMで必要な情報がある場合はpostMessageで渡す。
  • service worker
    • networkとcacheへのプロキシの役割を持つプロセスとして使う。
    • ブラウザからのネットワーク要求を受けてキャッシュからデータを返せるので、webアプリがオフラインでも使えるようにできる。
  • worklet

CSSのレイアウト flexbox について

fuzzy0.hatenablog.com

以前、上の記事でCSSのレイアウトがわかってないなという話を書いたが、 CSSを一通りざっと勉強して、(古いブラウザのことを無視すれば)flexboxだけわかってれば 大体のレイアウトは組めそうなことがわかった。

flexbox

flexbox自体の説明はググればたくさんあるのでしないとして、 設計方針としては、基本的には、

  • display: flexとするだけで横並びになるので、行単位で設計する
  • 子要素がフォームやボタンのような部品系なら、
    • 子要素の並びは
      justify-content(子要素が並ぶ方向)と
      align-items(子要素が並ぶのと垂直方向)で決める
  • 子要素がレイアウトのためのコンテナ要素なら、
    • 子要素の幅はflex-containerのwidthを%指定して調整
    • 等幅に並べるならwidthは不要で、子要素全てflex-basis: autoとして自動で引き延ばしてもらえばOK
      • その上で中身同士の比率の指定をするなら、中身のflex-growで調整(ただし注意点あり。後述)

でなんとなくイメージが形になる。

自分としてはまだCSSに不慣れなので、 各要素のwidth/heightの値を指定してあげないと意図したとおりにレイアウトされない部分がいろいろあるのに注意が必要な状況。

迷ったこと

試している中で迷ったことが2つ。

行の中の部品のうち1つだけ右揃えにする方法

自分では float: right をつけるしかないのかな、そうするとflexboxとfloatが共存して気持ち悪いな、と思っていたのだけど、 「flexbox 右寄せ」でググったら、例えばココのように、marginを使った方法がたくさん紹介されてた。

サンプルは以下。
これですっきり実現できた。

See the Pen flexbox right side example by nippi (@nshinya) on CodePen.

flex-growの比率指定の意味

子要素にflex-growで数字を指定すれば、子要素同士の幅や高さの比率指定ができる。

ただし、この比率指定は 余白部分の割り当て比率 であるため、 子要素がもともと持っている幅(内部のテキストの幅など)があると、 行や列全体に対する割合にはならないことに注意が必要。

厳密に全体に対する割合を指定したいときは、widthで%指定を使う。

See the Pen flexbox grow example by nippi (@nshinya) on CodePen.

レイアウト例

2 column layout

See the Pen flexbox example (2 columns) by nippi (@nshinya) on CodePen.

grid

See the Pen flexbox example (grid) by nippi (@nshinya) on CodePen.

row/column layout

See the Pen flexbox row-column example by nippi (@nshinya) on CodePen.

Reactに入門してみた

趣味でフロントエンドにゆるゆるとキャッチアップしたく、HTML/CSS/JavaScriptを復習しながらReactにも入門してみた。

公式サイトのチュートリアルを、最後の練習問題まで一通りやってみたものがこちら。

※Window幅が狭いとレイアウトが崩れるので、.border-rowCSSdisplay:flexに変更している。

See the Pen Tic Tac Toe by nippi (@nshinya) on CodePen.

GitHub - nshinya/react-tutorial

以前Flutterに入門したとき、「Reactをリスペクトした」とあったが、Reactを学んでみると確かにFlutterはReactの書き方や仕組みをかなり真似ていることがわかった。 仮想DOMとか、setStateとか。

エンジニアとして生きるにはいろいろ間違えてきたなあという反省と、今後への決意

最近キャリアとスキルを振り返って思ったこと。

これまで自分は以下のような行動をとっており、人一倍技術への興味は強いと思って生きてきた。

  • 寝食も忘れて技術情報漁り
  • 業務でわからないことはもちろん、業務外で興味を持ったことはなんでも理解できるまで調べる
  • 他人と話すとき、わからないことがあったら次からはわかるように調べる

興味の強さは今でも疑ってはいない。
でも、人に誇れる技術は何も身につかないまま何年も過ごしてしまった、と思う。

原因を考えてみると、結局のところ、たいへん一般的に言われているNGパターンに帰結した。

多くの分野に手を広げすぎる

聞いたことのない単語が出てきたとき、片っ端から調べていたら、インプットだけで日が暮れてしまうわけですよ。 分野を絞っていればいいけど、フロントエンドからハードウェア、プロジェクトマネジメントまで全部にその姿勢でぶつかっていたら それは人間としては絶対的時間が足りない。 ましてや動きの早いIT業界においては致命的遅さとなる。

まあ悪いことばかりではなく、かなり幅広い視野が身につくことは確かなのだけど。。
いかんせん基礎的なことばかりが断片的に頭に入っていくので、どうも積み重なっていく感じは薄くなる。

新しいことばかりに取り組む

何を勘違いしたか、新しいことに取り組めばレベルが上がると思っていた節があった。 振り返ってみれば、新しいことっていうのはドキュメントや参考情報が少なく整ってもいないので 勉強効率も悪いし、そもそも基本がわかってないと身につきにくいものが多い。 新しいことばかり追いかけないで、基本的なことを着実に身に着けるべきだった。

本ではなくWeb等のリソース中心で学ぶ

新しいことに目が行っていると本が少ないので本ではなくweb中心でインプットが続く。 すると自分の使ってきた時間に何をしていたのか、まとまった単位で記録されないので 最終的に身についたもの、を形に表現しにくくなる。 (たとえ身についていたとしても。)

アウトプットしない

すでに世の中に存在する内容の記事を書く意義が見いだせず、 かついろんなことの基礎ばかりをやってきたために突っ込んだ内容の記事は書けず。

実際、基礎的なことでも「自分の言葉で説明する」練習だと思ってアウトプットを続けていれば、 それなりの形になるものもあったと思う。 やはりアウトプットしないというのはもったいなかった。

形になる前に他のことに手を出してしまう

なにか新しいことに取り組んだ時、最初のうちは楽しくて、こんなもの作ってみたい、とか考えながらやるけど、 本業とつながりがないから、という意識がどこかで働いて、最後まで作りきらずに他のことに興味が移ってしまう。 そんなことがよくあった。 ある程度形にしてPublicにする姿勢が必要だった。

外部の勉強会に参加して人脈を広げることをあまりしなかった

セミナとかは結構行っていたけど、身近にはほとんどエンジニア気質な人がいなかったので、 一人で行って一人で帰ってくる。つまらないけど仕方ない、くらいに思っていた。

もっと勉強会みたいなのに出てコミュニティに入っていけるコミュ力があったらよかった。 (いや、たいていの人は人に誘われて行ってんじゃないの、と思わなくもないんだけど。)

そして何より、技術を身につけるということのコストを甘く見すぎていたんだと思う。 やればできる、という奢りが、1つのことをとことんやるという姿勢を阻害していたように思う。

これからエンジニアを目指す学生~新卒くらいの若い方々は、ぜひ上記とは真逆のスタンスをもって動くといいと思う。

(でも100%自分が悪いということもないと思っている。 もともとソフトウェアアーキテクチャが好きでそういう方向の仕事がしたかった。 でも仕事はインフラだった。インフラで頑張っていたけどやっぱりソフトウェアのほうが好きで 両方の勉強をしていた。結果が上記のような状況だった。 環境が不利だった、という言い訳くらいはしたい。その環境を選んだのは結局自分だけどね。。)


じゃあこれからどうするか。

キャリアアンカー診断というのをやってみた結果。

  • 22.8% : 専門・職能別コンピタンス
  • 18.5% : 生活様式
  • 15.4% : 純粋な挑戦

特徴 一言で言えば あなたは何事も専門的な立場を重視して判断し行動する。専門家タイプです。

全くその通りです。

今までは、そんな専門性を身に着け活かせる環境を探すことばかりに時間を費やしてしまったような感じ。

やっぱり、今からでも専門性を身に着けたい。

それに技術への興味は捨てたくないので、技術的専門性を身に着けるために、基本からやり直すしかない。

方向性を決めて、人一倍、コツコツとやるしかない。

青春全部賭けたって強くなれない、なんて、賭けてからじゃないと言っちゃいけないから。

スマホアプリ開発初心者がFlutterのチュートリアルを読んでできるようになったこと

以前よりスマホアプリ開発に興味があったのですが、最近クロスプラットフォームスマホアプリ開発フレームワークのFlutterが話題なので、 チュートリアルを読んで入門してみました。

Flutterとは

FlutterGoogle製のフレームワークで、開発言語はDartです。
昨年登場し、一気に流行しはじめました。

もともとスマホアプリは作ってみたいと思っていたのですがなかなか手が出なかったので、 今回のFlutter流行に乗っかって今度こそと思い、始めてみました。

実際使ってみたところ、少なくとも簡単なアプリならとてもお手軽に作れるので、 初心者にもオススメなのではないかと思います。
私としては、Flutterがスマホアプリ開発の最初の選択肢になるくらい流行ったらいいなあと思います。

チュートリアルを読んでできたこと

  • Flutter開発環境の構築
  • Widgetの理解
  • UIレイアウト方法の理解
  • イベントの理解
  • 状態管理の設計指針の概要理解
  • 画面遷移方法の理解
  • 簡単なアプリの作成
  • Dartの基礎文法理解

本記事では自分が学んだことを簡単にメモしていこうと思います。

※注)自分はAndroidしか持っていないので、AndroidアプリONLYの内容になっている箇所もあるかもしれません。。

Flutter開発環境の構築

環境構築はとても簡単で、いくつも参考記事があるので割愛します。

自分はVSCodeでの開発を選びました。好みだと思いますが、VSCodeは軽量なので、それほど複雑ではないアプリを作る程度であればよい選択肢なのではないかと思います。

flutter.io

Widgetの理解

Flutterでは画面に配置するものをはじめ、ほとんどすべての要素がWidgetというものでできています。
基本はStatelessWIdgetとStatefulWidgetで*1、以下のような性質を持ちます。

  • StatelessWidget : 自分自身は状態を保持しない。親Widgetからもらったパラメータと定数に従って描画される。
  • StatefulWidget : 状態を持ち、ボタンタップやタイマー等のイベント契機で状態を変更し、再描画できる。

StatefulWidfetの子にStatelessWidgetを置き、StatefulWidgetが持つ状態に応じてStatelessWidgetを再作成することでも画面全体としては再描画できます。
このときFlutter内部では、StatelessWidgetはすべてが再作成されるわけではなく、変更があった箇所のみが再作成されるようになっている、らしいです。

Bringing it all together

When the parent receives the onCartChanged callback, the parent updates its internal state, which triggers the parent to rebuild and create a new instance of ShoppingListItem with the new inCart value. Although the parent creates a new instance of ShoppingListItem when it rebuilds, that operation is cheap because the framework compares the newly built widgets with the previously built widgets and applies only the differences to the underlying RenderObject.

UIレイアウト方法

Widgetをネストさせてレイアウトを作ります。

runApp()で指定したWidgetが最初に起動されます。
基本的には、いろいろな便利機能を使うため、MaterialAppをルートにするのが吉のようです。

import 'package:flutter/material.dart';
    
void main() {
  runApp(MaterialApp(home: MyHomePage()));
}

多くの種類のWidgetが標準で用意されており、公式のカタログで確認できます。

簡単なアプリを作るにあたってよく使うであろうWidgetはそれほど多くないので、メモしておきます。

基本レイアウト

  • Row, Column
    • children: に指定したWidgetを横・縦に並べる。これをネストしていくだけで大体のレイアウトは作れる。
      レイアウトの仕方は公式チュートリアルの図がめっちゃわかりやすい。
    • mainAxisAlignment: で主軸方向(Rowなら横、Columnなら縦)方向のWidgetの並べ方を指定できる。以下の記事がとてもわかりやすい。
      medium.com
  • Stack
    • 並べるのではなく、重ねたい場合に使う。
  • ListView
    • スクロールできるRow、Columnのイメージ。
    • 横スクロールのListViewも可能。
    • 無限スクロールにはListView.builderコンストラクタのitemBuilder:を使う。 itemBuilterには(context, index){}形式の関数を与える。indexがListViewのアイテムのインデックスになり、関数が返したWidgetが描画される。 詳細はGetStartedにある。

配置、装飾

  • Center
    • 子を真ん中に配置する。
  • Expanded
    • 子を画面内に配置できる全体に広げる。
  • SizedBox
    • 子のサイズを決め打ちする。
  • Container
    • margin, padding, width, height, color, などなど、やりたいことは大体入っているコンテナ。
    • 細かいことをしたいときはdecoration:にBoxDecorationをつけてやればいい。
  • SingleChildScrollView
    • child:に与えたWidgetがスクロールできるようになる。
    • 配置するWidgetは、ExpandやContainerで描画サイズを決めてあげることで、その枠内でスクロール可能な動きになる。

部品系

  • Text, Icon, Image, RaisedButton, IconButton, TextField, Checkboxなどなど
  • カタログを見ながら、かなり直感的に使えます。

MaterialComponents

  • あらかじめMaterialDesignの標準的なレイアウトを簡単に実現できるように用意されているWidgetがあり、簡単なアプリを作る際には便利。
  • Scaffold, AppBar, TabBar, Drawer, SnackBarなどなど

イベント(ジェスチャ)ハンドリング

RaisedButtonやIconButtonなど、はじめからonPressed:などのプロパティを持っているWidgetはそれらにイベント用関数を与えればOKです。

そうでないWidgetの場合は、GestureDetectorを使います。
GestureDetectorのonTap:などに与えた関数は、child:に与えたWidgetに対するイベントとして登録されます。

状態管理の設計指針の概要

アプリの状態を表すパラメータをどこで管理するべきか、についての設計指針について チュートリアル内にあった説明を簡単にまとめます。

  • 状態をどこで管理するか
  • 大まかな指針としては、
  • Widget単位で見ると以下の3通りがある
    • 全状態を親に置いて、StatelessWidgetにする
    • 全状態を自分の中に閉じて、StatefulWidgetにする
    • 親に置くものと自分で閉じるものを組み合わせて、StatefulWidgetにする
  • 親が管理したいと思うであろうプロパティを親に置くと良い

という感じのようです。

状態変更はStatefulWidgetのsetState()で行うのが基本ですが、複雑なアプリになると 他の方法(後述)も知っておく必要があるようです。

リソース(画像等)の使い方

pubspec.yamlのassets: にファイルのパス(ディレクトリの場合は配下のファイル全て)が プログラム中からリソースとして利用できるようになります。

assetsディレクトリ配下全てを使うなら以下のようにします。

flutter:
  assets:
    - assets/

ロードは以下のようにします。

文字列

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
    
Future<String> loadAsset() async {
  return await rootBundle.loadString('assets/config.json');
}

Futureで受け取ることになるので、FutureBuilderを使ってWidgetを作るなどして使用します。
以下の例では、ファイルのロードが完了するまでは"loading..."、完了したらファイルの内容を表示するText Widgetを作ります。

Text TextFromFile(){
  return FutureBuilder(
    future: loadAsset(), // ここにFutureを返す関数を指定
    builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
      // snapshot.dataにFutureで受け取る結果が格納される
      if (snapshot.hasData) {
            return Text(snapshot.data);
      } else {
        // snapshot.hasDataがfalseならまだ処理が終わっていない
        return Text("loading...");
      }
    },
  );
}

画像

Image Widgetとしてロードするなら以下のようにします。

Image loadImage() {
  return Image(image: AssetImage("assets/my_icon.jpg"));
}

同名の画像ファイルを、2.0xや3.0xのような名前のディレクトリに配置すると、MipMap として使えます。

assets/
├── 2.0x
│   └── my_icon.jpg
├── 3.0x
│   └── my_icon.jpg
├── 4.0x
│   └── my_icon.jpg
└── my_icon.jpg

上記の場合、ロードはassets/my_icon.jpgのみででき、利用時もAssetImage("assets/my_icon.jpg")とするだけで、 現在の画面の解像度に合った画像がロードされるようにできます。

ディレクトリ名の数字はdart:uiパッケージにあるwindow.devicePixelRatioの値を指しており、 最も近い数字のディレクトリ内の画像が使われます。
devicePixelRatioの値は約96dpiで1.0のようです。

画面遷移方法

画面遷移はNavigatorを使います。

画面もWidgetなので、Widget1からWidget2へ遷移したいとします。

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => Widget2()),
);

Widget2から戻るときは、

Navigator.pop(context);

です。

MaterialPageRouteは、MaterialAppの子孫になっているWidgetでないと使えません。

次に、パラメータを渡す・受け取る場合。

  • 遷移元では、Navigator,pushの引数に与える遷移先Widgetのコンストラクタにパラメータを渡します。
  • 遷移先では、Navigator.popの引数に返したいパラメータを与えます。
  • 遷移元での値受取は、Future型でNavigator.pushの戻り値として受け取ります。
// Widget2に10を渡す
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => Widget2(param: 10)),
);
// 100を返す
Navigator.pop(context, 100);

簡単なアプリの作成

簡単なアプリということで、マインスイーパを作ってみました。

github.com

設計的には、RowとColumnで画面レイアウトを作り、 Mineが隠されているマスをStatelessWidget、盤面全体をStatefulWidgetとして作りました。
マスの状態も全体でリストとして管理する形です。

Flutterでマインスイーパを作ってみたという記事はいくつかあり、自分は以下の記事を参考にした上で イチから作成しました。

medium.com

www.youtube.com

後者の動画内で紹介されている内容で役に立ったものについていくつか紹介します。

stopwatch

dart:coreにStopwatchクラスがあります。 時間を測るのに非常に便利です。
使い方も以下の要領で、簡単です。

import "dart:async"; // Future.delayedを使ってスリープさせるためだけに利用

void main() async {
  Stopwatch stopwatch = Stopwatch();

  print("is running? ${stopwatch.isRunning}"); // is running? false 

  stopwatch.start();

  print("is running? ${stopwatch.isRunning}"); // is running? false 

  await Future.delayed(new Duration(seconds: 3)); 3秒スリープ

  stopwatch.stop();

  print(stopwatch.elapsedMilliseconds); // 3005
  print("is running? ${stopwatch.isRunning}"); // is running? false 

  stopwatch.reset();

  print(stopwatch.elapsedMilliseconds); // 0
}

Timer

定期的な処理を実行するときに使います。 import 'dart:async';が必要です。

以下の例では、1秒ごとに定期的な画面更新をする場合のコードです。

// Widget内で
timer = Timer.periodic(Duration(seconds: 1), (t) => setState(() {}));

マインスイーパのタイマーの表示を1秒ごとに更新するために使いました。

まだ理解不十分なこと

状態やデータ管理のアーキテクチャ

状態管理をsetStateでやっていて、簡単なアプリを作っている間はそれほど気になりませんが、 規模が大きくなったり、サーバサイドでDBを使ったりするともっと考慮が必要になると思います。

状態管理の方法としては、以下の記事で挙げられているものとしても4つあります。

  • setState
  • ScopedModel
  • BLoC
  • Redux

medium.com

状態管理は奥の深いトピックで、まだまだ全然理解が追い付いていないので、個人的に今後の課題です。
まずは公式チュートリアルを読むところからですね。。

データベース

以下の記事で紹介されている通り、sqfliteパッケージを使うとsqliteを使えるようです。

medium.com

また、Firebaseの機能は一通り使えるようなのでそのほうがいいかもです。

アニメーション

自分はあまりアニメーションに凝ったアプリを作るつもりがなかったのでまだ見れていませんが、 今後見ていけたらと思います。

flutter.io

Flutterに関するまとめ系リソース

このへんを漁りまくったら身につきそうです。

*1:ほかにもInheritedWidgetなどもあるようですが、簡単なアプリならこの2つの理解でひとまず十分のようです。

Flutter開発の時に知っておくとよさそうなDart文法のメモ

Dart

DartはもともとJavaScriptを代替する目的で作られた言語で、何年も前からありますがあまり流行ってはいませんでした。 昨年、Dart2としてFlutterとともに話題再燃したようです。
私自身Dartに触れて一か月程度の身なのでまだよくわかっていないのですが、Dart2は個人的にかなり好きになりそうな言語です。

感触としてはこんな感じです。

Dartの今後に期待です。

以下、Dartの文法について、Flutter開発の時に知っておくとよさそうなもののメモ書きです。

名前付きコンストラクタ(Named constructor)

ListView(
  children: <Widget>[
    Text("a"),
    Text("b"),
    Text("c"),
  ]
);

が普通のコンストラクタなのに対し、名前付きコンストラクタは

ListView.builder(
  itemBuilder: (context, index) => Text("$index")
);

のように、.のあとに名前を続け、いろいろな種類のインスタンスを返すコンストラクタを呼び分けられます。

Listにも

  • List.filled
  • List.from
  • List.generate

などの名前付きコンストラクタがあり、それぞれ異なった方法でリストを初期化できるようになっています。

よく使うのはList.generateで、

List.generate(10, (index) => "item $index");

のように、indexを引数とした関数の返す値に従って初期化されたListを返します。

関数の引数

普通に定義した関数では、引数の区別は位置で判断されます。

int add(int a, int b){
  return a + b;
}

add(1, 3); // 4

定義時に{}で括った引数はOptionalとなります。関数を呼ぶ際には位置ではなく名前で区別されるため、名前の指定が必須となります。

int add(int a, int b, {int c}){
  if(c == null){
    return a + b;
  }else{
    return a + b + c;
  }
}

add(1, 3); // 4
add(1, 3, c: 5); // 9
//add(1, 3, 5); // Error

名前つき引数に対する値の指定方法はコロン:区切りです。
変わってるな*1、と思いましたが、flutterのようにコンストラクタを多用する記法をしていると、 引数をコロン区切りで指定するのはJavaScriptでMap型(or Hash型 or dict型)を関数の引数に渡しているのと似たような記法となり、 むしろ意味合い的にわかりやすくなったように感じました。

(=で区切る場合、その値に無名関数を与えるときになんとなく不自然に感じるのは自分だけ?)

ちなみにOptional引数は、省略した際に使うデフォルト値を指定できます。 指定されていない引数について省略すると値はnullとなります。(明示的にnullをデフォルト値として指定すると警告が出る。)

int add(int a, int b, {int c: 100}){
  if(c == null){
    return a + b;
  }else{
    return a + b + c;
  }
}

add(1, 3); // 104
add(1, 3, c: 5); // 9

Listの生成

配列として使うList型ですが、Flutterでは各Widgetのコンストラクタの引数にListを指定したい場面が多くあります。
その場合、事前にfor文などでListを作るより、引数に渡すタイミングでListのオブジェクトを作るほうが良いケースもままあります。
そこで、Listを生成する方法を知っておくと役立ちます。

ちなみにListには固定長リストと可変長リストがあり、大抵のコンストラクタではgrowableパラメータにtrueを与えると可変長となります。 デフォルトでgrowableがtrueかfalseかはコンストラクタにより異なるので注意が必要です。
固定長リストに対してaddなどの要素追加や削除を伴うような操作を試みると、UnsupportedError例外が発生します。

// 空リスト(可変長)
var emptyList = List();
// または var emptyList = [];

// length指定(nullで初期化)(固定長)
var sizedList1 = List(3);

// length指定(nullで初期化)(可変長)
var sizedList2 = List()..length = 3;

// 同じ値で初期化(デフォルト固定長)
var list0 = List.filled(3, 1); // [1, 1, 1]
// list0.add(3); // UnsupportedError

// コピー(デフォルト可変長)
var list1 = List.from(list0); // [1, 1, 1]

// 関数を使って初期化(デフォルト可変長)
var list2 = List.generate(3, (i) => i * 2 + 1); // [1, 3, 5]

*1:Rubyも同様の記法のようですね。

DropboxPaperおすすめ

しばらくDropbox Paperを使っていて非常に気に入っているのでおすすめします。

なおDropbox Paperの使い方は公式ブログで細かく紹介されているので、まずはこちらを見てみるとイメージが湧くと思います。

気に入っている点

  • 見出し、箇条書き、太字、引用、コードブロック、リンクなど代表的なマークダウンに対応
    • かつ記入直後に整形される!
  • クリップボードの画像をコピペで挿入できる
  • チェックボックスハッシュタグ、絵文字、日付・時刻、他ドキュメント(Paper内)へのリンクも、マークダウンっぽく入力できる
  • LATEXの式も入れられる!

特に気に入っている機能

そもそも「マークダウンが記入直後に整形される」時点でかなり気に入っているのですが、それ以外で特に気に入っているのが以下2つです。

リンクの作り方

  1. リンク先URLをクリップボードにコピー
  2. リンクにしたいテキストを選択
  3. 貼り付け(Ctrl+v)

これでリンクになります!感動しました!

f:id:fuzzy0:20190121173748g:plain
リンク作成

表の編集

表を挿入した後の操作感がとてもよいです。行・列の追加削除、高さ・幅変更がとてもしやすい。かつコピーするとマークダウン形式で貼り付けられます。
センタリングとか細かいことはできないようですが、素のマークダウンで表を書くのってしんどいので、これでも重宝します。

f:id:fuzzy0:20190121173548g:plain
表の編集

その他

キーボードショートカット一覧はPaper編集画面の右下アイコンで開けるのでいちいち調べる必要がなく楽。

スマホアプリでも使い勝手はPC版とほぼ同等で、書きやすいです。 これが結構ポイント高くて、ちょっとPCから離れないといけなくなっても スマホですぐに続きの編集ができるので、ありがたいです。 目的にもよりますが、自分はちゃんと今後に残したいメモやブログの下書きに関しては他のアプリから乗り換えました。

今は個人で使っているだけですが、チームで使うにしてもToDoアサインとか特定ユーザ向けメンションもできるようなので便利そうです。

なお、個人的に不満な点は、

  • つけたことのあるハッシュタグの一覧を出せない?のでちょっと使いにくい
  • コピペしたとき、見出しや太字、リンクなど一部の書式はマークダウン形式でコピペされない
    • 箇条書きや表、画像のリンクはマークダウン形式でコピーされるのだけど

といったところです。

まだまだ使いこなせていないので、これからも機能ウォッチしていこうと思います。