basyura's blog

あしたになったらほんきだす。

inkdrop - sidetoc plugin v1.5.0 release

inkdrop でサイドバーにヘッダーを表示するプラグインをアップデート。 主な内容はヘッダ移動するコマンドを二つ追加したこと ( sidetoc:jump-prevsidetoc:jump-next )。
一つのノート の記載量が多くてもサイドバーでアウトラインを確認しつつヘッダジャンプもサクサクできるので便利。vim plugin も入れているのでカーソル移動がいろいろ捗る。

keymap.cson 抜粋

'.CodeMirror.vim-mode:not(.insert-mode):not(.key-buffering) textarea':
    'ctrl-n': 'sidetoc:jump-next'
    'ctrl-p': 'sidetoc:jump-prev'

また、あまり分かってなかったので気軽にイベント登録してたのをちゃんと解除するように見直した (componentWillUnmount だいじ)。

vim plugin がプレビュー表示でもキー操作できるようになったので、sidetoc もヘッダのハイライトを連動したり対象ヘッダに移動したりをしたいのだけどやり方がまだよく分からない。

inkdrop - SideToc Plugin でヘッダ行にジャンプ

最近はてなブログでエントリを書こうとすると未ログイン状態になってて、ログインしようとするとパスワードが違うとい割れるけどログインしようとしている Id でログインしてる。自動ログインのチェックボックスを解除しても自動ログインを始めるからエンドレスループ。いつのまにかちゃんと?ログインしたことになってたり、ログアウトしてもログイン状態になってるしでタイムラグが有る感じ。書く気力が削がれていく辛い。

"use babel";
import { Dispatcher } from "flux";
export default new Dispatcher();

inkdrop plugin の機能追加中にどうしたら実現できるんだろうと詰まったのだけど、結果 flux を利用して実現できた。flux よく分からんなと思っていたけど connecting dots したかも?

表示する React.Component が持っている状態を使ってイベントを発生させたいケース。具体的には特定の行にジャンプさせたい。

f:id:basyura:20200506210742p:plain:w500

他にも componentWillUnmount でイベントを解除しないと行けないとか、更新検知が微妙だったあたりも見直せたので GW の活動は効果あったようだ。ほぼほぼできたと思ってたけど、挙動を見ていくと気になるところがぽろぽろ出てくる。

React - Hooks を使って ToDo アプリ

GW 中は React の勉強するかと思い立ったもののまだまだ道半ば (ゴールが見えない)。その自分用の記録。

構成を考える

Web 層は Go + echo でキーワード検索した結果を JSON で返すだけ。CL 層は React で受け取った JSON の内容を表示する単純構成。Web はすぐに完成。CL 層もボタンを押したタイミングで一覧を出せるところまでは React のチュートリアルを見ながらで到達。

ここからが長かった。

Flux

React で状態管理をしたくなるが、複数 Component で構築する際に状態をどこに持って管理して、どう伝搬させたらいいのか分からなくてすぐに詰まる。Facebook 推奨の Flux を使えばいいらしいことが分かるので入門記事を漁る。

pub/sub 方式により Action でイベントを発行して Dispatcher が振り分ける。
Store を更新して View が反映する。

連携 クラス図
f:id:basyura:20200505143037p:plain f:id:basyura:20200505143217p:plain

TodoStore.js

class TodoStore extends EventEmitter {
    createTodo(text) {
        // ・・・ 略 ・・・ //
        this.emit("change")
    }
  
  handleActions(action) {  switch(action.type) {
      case "CREATE_TODO": {
        this.createTodo(action.text);
      }
  }
  
    // ・・・ 略 ・・・ //
}

const todoStore = new TodoStore();
dispatcher.register(todoStore.handleActions.bind(todoStore));

Todos.js

TodoStore.on("change", () => {
    this.setState({
        todos: TodoStore.getAll()
    })
})

f:id:basyura:20200505144448p:plain:w600

dispatcher と emitter の二つが出てくる。使い分けはなんとなく感じるけど漠然としてる。
これでもいいのだけど状態管理するのに分かりづらいので他の方法が無いかを探し始める。
とくに状態を伝搬させていくのが辛い。

Redux

すぐに Redux がヒットして、説明を読む分にはこれがいいのではないかと入門を漁る。

Redux の Store の特徴として、flux とは異なり、一つの Store のみ存在することです。すべての状態データを一箇所で管理することになるのでシンプル性を維持することができます。画面及びアプリケーションの初期化は Store から始まります。

  • reducer を作成する
  • store を作成する
  • action をdispatch する
  • 単一のreducer もしくは複数のreducer が単一のstore に対して処理を行う

middleware を使うと reducer の組み合わせで柔軟な処理を実現できるっぽい。

f:id:basyura:20200505145039p:plain:w500

ここまできて、大げさすぎるしよく分からんくなってきたのでもっと簡単にできないかと探し始める。

React Hooks

“接続する (hook into)” ための関数です。 フックは React をクラスなしに使うための機能ですので、クラス内では機能しません

useEffect

レンダリングの後に処理を動作させる

  • React は、副作用が実行される時点では DOM が正しく更新され終わっていることを保証します。 ( https://ja.reactjs.org/docs/hooks-effect.html )
  • 関数を返すと cleanup 関数とみなされて、useEffect 実行前に呼ばれる
const [count, setCount] = useState(0)

useEffect(() => {
  console.log('hello useEffect')
})

return (
  <>
    <p>You clicked {count} times</p>
    <button onClick={() => setCount(count + 1)}>
      Click me
    </button>
  </>
);

useContext

prop drilling 問題を解決する一案。

const Context = createContext()

const Mago = () => {
  const { money } = useContext(Context)
  return <p>{money}円</p>
}

const Kodomo = () => <Mago />

const Oya = () => {
  return (
    <Context.Provider value={{ money: 10000 }}>
      <Kodomo />
    </Context.Provider>
  )
}

useRef

const afterRef = useRef();

// useEffect の cleanup で参照する場合はコピーしないとだめ? (ワーニングが出る)
useEffect(() => {
  document.title = `You clicked ${count} times`;
  const copyRef = afterRef;
  return () => {
    copyRef.current.innerText = count;
  };
});

return (
  <span ref={afterRef}>{count}</span>
)

Hooksを使う上で絶対に守ること

コンポーネントの中で呼び出されるHooksはいつなんどきでも必ず同じ順番で同じ回数呼び出されること!

簡単にいうとつまり if や for の中に Hooks を入れて「場合によって Hooks の順番や実行回数が変わる」ことを禁止しています。また、早期 return による実行回数のズレにも注意です。 基本的には関数コンポーネントのトップレベルかつ最上部に Hooks を書き並べておけば大丈夫でしょう。

Reducer と useContext でオレオレ状態管理

Redux のあたりで出てきた Reducer と createContext を使えば手軽にできるじゃんと思い立つ。

  • Store (状態管理のオブジェクト) をトップレベルで作って Context で伝搬させる
  • 状態の変更は Reducer を通して通知する
function App() {
  const [store, dispatch] = useReducer(reducers, Store);

  return (
    <div className="App">
      <AppContext.Provider value={{ store, dispatch }}>
        <Header />
        <Content />
      </AppContext.Provider>
    </div>
  );
}
const ToDoPane = () => {
  const { store, dispatch } = useContext(AppContext);

  return (
    <div>
      {store.toDoList.map((todo) => (
        <ToDoItem
          key={todo.key}
          title={todo.title}
          description={todo.description}
          onClick={() => dispatch({ type: "remove", todo: todo })}
        />
      ))}
    </div>
  );
};
export default function reducer(store, action) {
    switch (action.type) {
    case "add":
      return store.add(action.todo);
    case "remove":
      return store.remove(action.todo);
    default:
  }
  return store;
}

いい感じにできた気がしたのだけど挫折

  • reducer が邪魔くさく感じる
  • App() が何回も呼ばれる
  • 描画ループに陥る

unstated-next

もっと簡単にやりたいので探し始める。オレオレでやりたかったことができるじゃんで飛びつく。

200 bytes to never think about React state management libraries ever again

  • React Hooks use them for all your state management.
  • ~200 bytes min+gz.
  • Familiar API just use React as intended.
  • Minimal API it takes 5 minutes to learn.
  • Written in TypeScript and will make it easier for you to type your React code.
ページ構成 クラス図
f:id:basyura:20200505150838p:plain f:id:basyura:20200505150925p:plain

react-modal でモーダル表示もしてみた。

f:id:basyura:20200505151026p:plain:w400

こねこねやった結果がこのあたり。
最初の描画でデータを取得して反映する方法がよくわからず (描画のループになるとか、描画中に反映できないエラーとか) Initializer Component をかますようにしてみた。正当な方法はどうやるんだろか。

その他 - オブジェクトの更新方法

hooks を使うときは新しいオブジェクトを返すお決まりなのでやり方。

配列に追加

[...state.values, newValue]

配列のある要素の変更

[
 ...state.slice(0, action.index),
 modifiedValue,
 ...state.slice(action.index + 1)
]

オブジェクトの変更

Object.assign({}, 
  state, 
  {
    completed: true,
    name: "new name"
  });

まとめ

パズルみたいでアレをやるためにはどうしたらいいのかを結構考えないといけなくて、ちょっとズレてると大失敗する印象。
いまどきのフロントエンド開発は大変だ。

本命のツールは作れずで今に至る。疲れた。 そもそも React である必要はなくて単純なページ更新方式でいいのだけど。

Switch 用のディスプレイ購入

↑ 新規購入

↑ もともと使っていたファミコンっぽい色のモニタ。気に入ってたけど 2 年ちょいでダメになったっぽい。Switch 用のモニタとして使っていたのだけど GW 初日に画面出力されなくなった。辛かった。PC に繋いだら出力されたので USB-C のコネクタか Dock が問題なんだろうと思って両方追加で買ってしまったのだけど結局モニタだった。HDMI - mini HDMI ケーブルが問題の可能性もあるけどこれ以上購入して検証するのもだるいので保留中。最悪、本体修理で 1ヶ月の可能性もありそうだったのでよかった。

新しいディスプレイは 11 inch → 13.3 inch で大きくなった上に表示も綺麗なので気に入っている。音量の変更ができないのかと思って 1 時間ぐらいガックリしていたけど (その発想はなかった) 、横のスイッチをぽちぽちしてたら変更できることが分かって一安心。

ある時期からでかい画面 (といっても 21 inch とか) を見るのがしんどくて、仕事中もノートpc の 13.3 inch ディスプレイを超えるとしんどくて見てられなくなる病なので (最近だいぶ良くなってきたけどつらい) 、ちょうどよいサイズ。このモバイルモニタと apple keyboard を使えばいい感じの環境になりそうな気がしているので家で試してみていい感じだったら会社でもスタンド付きで買ってもらいたいなぁ。

GitLab - Hashed Storage

v10.0 からリポジトリパス(/var/opt/gitlab/git-data/repositories 配下のパス)をハッシュ化する機能がリリースされていて、v12.0 でデフォルト ON に変更された。

プロジェクト名やグループを変更した際にディスクのパスが変更されていたけど、プロジェクトID を hash 化したパスを使うことでそれがなくなった。特定のグループに偏ることなくフラットに配置されるので色々とディスクに優しくなるらしい。

管理画面でプロジェクトのパスを確認できる。

To access the Projects page, go to Admin Area > Overview > Projects
and then open up the page for the project.
The “Gitaly relative path” is shown there, for example:
"@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git"

新規のプロジェクトを Hashed Storage にするかは管理画面で設定できる。

1. Go to Admin > Settings > Repository and expand the Repository Storage section.
2. Select the Use hashed storage paths for newly created and renamed projects checkbox.

ID 指定で migration できる。

sudo gitlab-rake gitlab:storage:migrate_to_hashed ID_FROM=50 ID_TO=100

逆に元のプロジェクト名のフォルダ構成に戻す方法もある

sudo gitlab-rake gitlab:storage:rollback_to_legacy

Rails console から該当プロジェクトを引く方法もある。

Project.find(16).disk_path
ProjectRepository.find_by(disk_path: '@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9').project

いろいろ機能追加されていってて便利に。

dokuwiki で plantuml

dokuwiki に前からある plantuml plugin はそのままじゃ動かなくなってて、代わりに plantumlparser ができてた。 ただ、サーバーを指定できなくて plantuml 公式の方にリクエストを投げるしかなかった。 せっかく社内に docker で plantuml サーバーを立てたのでこちらに投げたいと思っていたらちょうど MR が作成されてた。

MR がマージされたので、さっそくインストールしてセットアップ完了。

plantuml を動かせる環境が増えたのでまた一つ便利に。

docker x509: certificate has expired or is not yet valid

docker x509: certificate has expired or is not yet valid

windows に入れてた docker toolbox の証明書がいつの間にかきれてた。

You can attempt to regenerate them using 'docker-machine regenerate-certs [name]'.
Be advised that this will trigger a Docker daemon restart which will stop running containers.

docker-machine regenerate-certs を実行するとよいらしいのだけど実行しても更新されたふうに終わるものの改善しない。 --client-certs を付けるとさらによいらしいのだけど古すぎて指定できない。かといってアップデートするのも怖い。

コンテナで動かしてるサービスは動くからいいのだけどバックアップが取りたい (データの置き場を別にしとくべきだったんだけど)。

virtualbox を開いて直接コンソールを叩けることを確認。さらに git bash からも入れることを確認。

$ ssh localhost -p 3327 -l docker
$ docker-machine ssh

ssh で入ってコマンドを叩けることを確認。

$ docker-machine ssh default free

bat で git bash 経由でコマンドを実行できることを確認。

"C:\Program Files\Git\bin\sh.exe" --login -i -c "ls"

バックアップを生成する sh をコンテナ内に置いといてキックして結果をコピーしていくリレー方式でバックアップ取れるようにできた。

f:id:basyura:20200422225946p:plain

docker のバージョンを上げてきれいに作り直したい。