Make組ブログ

Python、Webアプリや製品・サービス開発についてhirokikyが書きます。

仕事の精度、家政の精度。なぜ核家族の育児は大変なのか

家事、育児の話題続きで申し訳ないです。 今僕が育児休暇中なので考える機会が多いのです。

ですがこういう課題の多い分野ほどビジネスチャンスが大きいと思うので、記録として残しておきます。

仕事の"精度"と家政の"精度"は大きく違う

仕事と家政の違いは何でしょうか?働きに外に出る人と専業主夫/婦の揉める種として以下のような話を聞きます。

  • 私の外仕事のほうが大変だ
  • 私は毎日24時間仕事してるようなものだ

僕は仕事と家政の違いは以下のように分けて考えるべきだと思います

  • 一般的な仕事: 80%〜120%の精度の仕事を、週に5回、8時間働く
  • (自宅における)家政、育児: 40%〜60%の精度の仕事を、週に7回、12時間働く

一般的に仕事というのはまず100%が当たり前という世界です。 手順があるのであれば、その手順を間違えないことが大切になってきます。そのうえでプラスアルファを出せればより良いと捉えられます (もちろんクリエイティブな仕事はこの限りではありませんが、基本にある考え方は理解してもらえると思います)。

一方、自宅における家政というのはかなり精度が低くて良いです。 四角い角と丸く掃いたところで、売上が落ちるようなことはありません。 一日トイレ掃除をサボっても、まぁ生きてはいけるでしょう。お客様商売のようなシビアさはありません (気を遣う必要がある親族と同居している、配偶者が細かい点に厳しい場合などは精度が求められますが、これも基本的な部分は理解してもらえると思います)。

育児の大変さは、精度を高めようとすることだ

育児が大変なのは、その家政において精度が求められる(求めてしまう)ことにあります。 内的な要因としても「命を預かる以上」という責任感が産まれますし、「よく育てたい」という気持ちや、親戚の期待、子供への社会の厳しさ(泣き声に苦情がくる)もあって仕事に高精度を求められがちです。 ですが前述したようにこの仕事は言ってしまえば7D24Hに近い仕事です。そこで100%以上の精度を常に出していると燃え尽きてしまいます。

たしかに「子供が泣かないようにしないと」「ちゃんと消毒しないと」「ミルクをあげてちゃんと体重を増やさないと」など気にすべきことは多いです。ですがこれらすべてに100%の精度を求めると仕事が多すぎます。 「まぁ泣いてもいいや」「サカザキ菌なんてそうそういないやろ」「それなりに体重増えてるし大丈夫やろ」くらいの雑な気持ちでいかないといけません。

なので 育児は仕事ができる人ほど辛い ものだと思います。 予測不能で、論理的に解釈不可能で、膨大な仕事があるからです。でも、雑で良いんです。

育児は核家族には不可能。でもなぜ外注しにくいのか

育児をしてみて感じたのは、人類の子供というものが核家族で育てるようにできていないことです。 子供の世話をしながら家事も仕事もするというのは不可能です。現代においては社会保障制度がある程度充実しているので誤魔化しながら運用されていますが、そもそも不可能と言うべきです。

とはいえ親族に頼れというのも前時代的です。 では「代行」を使うのはなぜ難しいのでしょう。

それは「家事・育児代行」とすると「一般的な仕事」になり、80%〜120%の精度が求められるようになるから です。 とくに育児であれば命を預かる仕事なので、精度は100%以上を求められるでしょう。それを発注するとなると自分でやるより割高になるのは無理ありません。

100%の仕事として24時間の仕事を依頼するとなると、もちろん数万円単位の支払いとなります。 家政として自分でやったり親戚に任せるのであれば、50%くらいの精度ですが、それほど高額な仕事にはなりません(一日子供を親に預けていて、5万円、10万円を支払うことはないでしょう)。

だからといって「50%の精度の育児」を買う人はいないでしょう。命を預ける仕事だからです。 この 仕事に期待する精度の意識差 が、核家族を追い込むことになっています。

解決策は?

僕が考えるなかでは、信頼をベースにした仕事の依頼しかないと感じています。 お金が動くと100%精度の仕事を求められるので、贈与経済のような信頼を元にしたやり取りがうまくいくかもしれません。 信頼し合える人たち同士が、お互いに50%の仕事を担保し合えるような関係です。 今の社会では親族くらいしかありませんが、そういったコミュニティーを形成するサービスというのは考えられます(田舎の閉鎖的になったコミュニティーにも問題はあるので)。

ともあれ、局所最適化した都市型経済というのは、東京で毎日通勤電車に乗る単身者に向いたものなのだなと感じています。

皆さんのアイディアで、ぜひ日本の核家族(そして僕)を救ってください。

育児が大変なのは基準や業務の曖昧さからくる?

免責: この記事は個人の戯言です この記事に政治、宗教的な立場はありません。また、何かを他人に訴える文章でもありません。 僕がただ感じたことを書いているだけです。

本文

育児において何が苦労するのか。作業自体の労力もちろんですが、僕は情報の曖昧さ(基準や業務内容の曖昧さ)からくる余分な疲れというのも多いのではないか?と思っています。 「どういうこと?」「基準?業務?」と思う人にほど読んで欲しいところです。育児や家政には基準、業務設計や正解がないから大変だという話をします。

(これは早朝に、ミルクをあげるブサイクな僕です)

例えば新生児を育てたことがある人なら、誰しも以下のような疑問を持ったことだと思います

  • 授乳間隔はどれほど守るべきなのか
  • 授乳の時間はどれくらいかけて良いものなのか
  • なぜ毎度沸騰して70℃以上にしたお湯でミルクを作るべきなのか
  • 哺乳瓶を洗う洗剤は、必ず乳幼児用を使うべきなのか

色々な情報を調べたり教えて貰えるので知ることはできるでしょう。産科の看護師さんに教えてもらったり、親に教えてもらったり、役所の勉強会で知ったり、ネットで調べて知ったりできます。でも情報のソースが多くて混乱することもあることと思います。

僕が腑に落ちないと言っている点は何でしょうか。それは「基準」がないことです。調べたり教えて貰っても、明確な答えが常に100%あるわけではありません(知っているものは後述します)。 義務教育やプロトコル、規格のような基準、ある種の「バイブル」はなく、あくまでも個々人として対応する必要があります。

情報がどれも曖昧な理由は2つ考えられます

  1. 安全性の絶対的な基準がないこと
  2. 子供による個体差の違いが大きいこと

言ってしまえば、育児(や家政)は業務設計や組織管理ができていない職場です。 そのゴール設定、ルール、手順、定款、労基を自分で作る必要がある場だとすら言えます。

育児においてその基準があれば親のストレスは随分軽減されると感じています。 例えば調乳や授乳の基準があれば「これだけやっとけば大丈夫。それ以上は趣味範囲の頑張り」というのが分かって気持ちも楽になるでしょう。 例えば赤ちゃんが泣いたときも、「100dBまではご近所さんからも許容されている」と分かっていれば「泣かせておけばいいや」と思えるかもしれません。

何が言いたいかというと、僕は今世界で一番、世の親というものに共感しているということです。

1. 安全性の基準はどこに

まず目的が「子供の健康的な発育」ということで、安全性をどこまで求めるべきかという基準はそもそも存在しません(究極はもちろん個人の自由になります)。 大昔の人類であれば滅菌も殺菌も気にすること無く生きてきたことと思いますが、それに併せて疾病や死亡リスクも高かったんだろうと想像できます。だが現代ではどこまで求めれば良いのでしょうか?

現代においては知られているリスクをもとに、なるべくリスクを減らすようにしたいことと思います。後述するように比較的信頼できる基準はあるので、それを参考にするのが良いでしょう。

でも世の情報にはお気持ちレベルのものも多いです。例えば哺乳瓶などの滅菌をする「ミルトン」は水で洗い流す必要はないとメーカーが表示していますが、ネットで調べると「いちおう水で洗い流しています」という情報も見つけられます。それはもはや神経質すぎではないでしょうか。

個人がそうなるのも理解できます。基準がないがゆえ、どこまで「リスクを許容したうえで楽をできる」かが分からないからです。 信頼できる基準があって、それを信頼すれば、もう少し楽な気持ちで育児に望めるのではないでしょうか。

調乳のガイドライン

安全性の基準は探せばあります。もちろんメーカーの製品が書いてあることは、個人の想像よりは信頼できるでしょう。 また、例えば調乳については、WHOとFAOの出したガイドラインを訳したものを厚生労働省が公開しています。

www.mhlw.go.jp

これについては、以下のように明確に書かれており分かりやすです

  • 前提として無菌状態の調整粉乳を作ることは難しい
  • E Sakazaki、Salmonellaの感染リスクを下げたい
  • 以下の手順をガイドラインとして推奨
    • 哺乳瓶などは滅菌すること
    • 一度沸騰させたお湯で調乳すること
    • 70℃以上のお湯で調乳すること
    • 調乳後2時間以内に使用しなかったミルクは捨てること

また、誕生後6ヶ月までは母乳を推奨するとも書かれています。 これは原典が2007年のガイドラインなので古い情報かもしれませんが、無いよりは良い情報です。 これで「70℃以上で調乳」の根拠も分かります。

2.個体差

ここまで基準やなんやと書いていますが、正直、基準を用意するなど無理なことも理解しています。 それは子供の個体差が大きいからです。

もちろん調乳やら疾病リスクについては基準は設けられるでしょう。 でも「どういう姿勢で授乳すべきか」「どのくらいの感覚で授乳すべきか」「洗剤には何を使うべきか」「なぜ泣くのか」「便の頻度はどのくらいか」などは個体差が大きすぎて基準が設けられません。 例えばウチの娘はそれほど泣かなきません(でしたが、一ヶ月くらいになる頃によく泣くようになりました)。ミルクは飲みますが、飲んでるときの休憩はよくするなどあります。僕の赤ちゃん時代は石鹸に弱く沐浴剤を使っていたそうですし、子供によってはベビーオイルで肌荒れするなど個体差が多くあります。

よくよく考えてみれば、大人も自分自身も個体差があり、自分の健康にすらもよく分かっていません。 健康についても数多くの宗教がありますし、どれが正解かも分かりません。僕はボディーソープはダブが良いですし、皆、気がつけば低糖質ダイエットにハマっています。だがそれの恒久的な正解はなく個人の好みの問題です。

育児が難しい理由は、それら「宗教の選択」が自己責任の範疇を少し超えていることです。 あくまでも子供という他人を面倒みないといけないのに、自己責任の範疇で面倒を見る必要があることです。 これが育児ストレスやノイローゼの大きな一因になっていると思います。マジメな人ほど、100%の安全に寄せようとして心労を重ねているように見えます。

言ってしまえば誰しもが素人ながらルールもなく、絶対の正解もなく、あいまいな成果を求められている状態です。管理体制のない組織のようですね。 管理視点での仕事において大切なのは、業務設計と、ゴールの基準と、評価制度 だ(と僕個人は考えている)。育児や家政にはそれがなく、自分や家庭内で策定する必要があります。 実に自主性を求められる職場です。もはや小さな起業ではないでしょうか?

まとめ

僕が何をあなたに伝えたいか、ということはありません。 新生児の育児は大変だねという共感です。 強いて言うなら、もう少し業務設計やマネジメント的な視点を持ち込むと良いかもしれません。 以下の本や僕のブロク記事をオススメします。

気が向けば、「家政のための業務設計入門」というブログ記事でも書こうと思います。

はじめの一歩を踏み出そう―成功する人たちの起業術

はじめの一歩を踏み出そう―成功する人たちの起業術

blog.hirokiky.org

blog.hirokiky.org

highlight.jsで行ごとに区切りつつハイライトする

現在のhighlight.jsではこのstateを引き回す書き方は非推奨となっており、今後廃止される予定です。

highlight.js というコードハイライトをするJavaScriptのライブラリーがあります。

highlightjs.org

このライブラリーで、「行ごと」にHTML要素を分割しつつハイライトする方法を説明します。

highilght.jsの基本的な使い方をおさらい

highlight.jsは基本的に関数を呼ぶだけでコードハイライトができるので便利です。 以下の例では "# Heading 1" という文字列をMarkdownとしてハイライトしています。

import hljs from 'highlight.js'

let result = hljs.highlight("markdown", "# Heading1")
console.log(result.value)  // '<span class="hljs-section"># Heading 1</span>'

highlight.js は '<span class="hljs-section"># Heading 1</span>' のようなハイライト用のHTMLタグが埋められた結果を返します。 .hljs-section クラスにCSSを当てることでハイライトができるというものです。

失敗例: 行を分けてからハイライトするとうまくいかない

画面の仕様により、表示するハイライト済みのコードを行ごとに区切りたいことがあります。 例えば以下のように、行番号を表示しつつコードを表示したいときなどがあります。

<table>
  <tr>
    <td>1</td>
    <td><span class="hljs-section"># Heading1</span></td>
  </tr>
  <tr>
    <td>2</td>
    <td>This is body</td>
  </tr>
</table>

もちろん行番号を表示するコードの実装方法も様々ですが、画面や動作の都合上 <table> で実装するとします。 このとき、肝心のコードの内容は # Heading 1This is body に行ごとに区切る必要があります。

以下のように行ごとにハイライトするのは良くありません。

import hljs from 'highlight.js'

let body = `# Heading 1
This is body

\`\`\`python
import this
\`\`\`
`

for (let row of body.split("\n")) {
  let result = hljs.highlight("markdown", row)
  console.log(result.value)
}

理由は、複数行にまたがるシンタックスMarkdownならコードブロックなど)に対応できないからです。 上記の例だと```pythonimport this 、 ``` が別々に解釈されるので、サブブロック(import this)のハイライトが効かなくなります。

回答: 状態を共有しながらハイライトする

highlight.jsの .highilght(...) 関数は第4引数に状態を渡せます。 ハイライトした結果の状態を引き継いで渡すことで、複数行にまたがるシンタックスでもうまく解釈されます。

function lineByLineHighilght (body) {
  let state = null
  return body.split("\n").map(function (row) {
    let result = hljs.highlight('markdown', row, true, state);
    state = result.top  // result.topの状態を次に受け渡す
    return result.value + "<br/>"
  })
}

こうすることで行ごとに区切りつつ、行ごとにハイライトが効くようになります (行ごとに毎度 <span class="...">...</span> は正しく閉じられます)。

8月23日のAWSの大規模障害でMultiAZでもALB(ELB)が特定条件で500エラーを返すことがあったという話

このブログ記事で 「MultiAZ」にしていたら何事も全て大丈夫という認識を変えられると嬉しいです (当該の時点で障害起こした人はちゃんとMultiAZにしてなかったんでしょ?という人の認識も変えられると嬉しいです)。

MultiAZにしておくことは基本 です。 その上でも、 安心しきらずに監視は必要 という話をしています。

  • MultiAZ構成にしておきましょう
  • そのうえで監視、検知、トレーサビリティを大切にしましょう

MultiAZ要らないという見当外れの解釈はしないでください (一部、間違えた解釈をしてるコメントも見受けられましたが、大いに違います)。

前提

2019-08-23、AWSで大規模な障害が起こりました。 障害の一般的な内容は以下のとおりです。

私は、AWSの障害については仕方のないものだと思っています。 このブログ記事はAWSの障害や対応について論じたり、補償などを求めるものではありません。誤解なきようお願いします。

8月29日追記: AWSからこの記事での報告と一致する説明が追記されました

www.publickey1.jp

tech.nikkeibp.co.jp

以前のレポートではALBについての説明などありませんでしたが、8月29日時点で追記が確認されました。 やはり、他のマネージドサービスでも影響があり、 ALBでも一部の構成の場合エラーが発生していたと説明されています 。 その構成の場合はMultiAZ構成でも障害の影響を受けていたということです。

ALB(ELB)がたまに500を返すようになる

23日午後6時30分ごろから、ロードバランサーが500を返すようになりました。 障害が主に発生していたのは午後1時か、2時ごろだったので、後になってからALBが500を返すようになったという状態です。

  • ALB自体が500を返す(バックエンドからの500ではない)
  • バックエンドのサーバーは異常なし
  • ブラウザーでアクセスしても問題ないが、 特定の別サーバーからアクセスするとたまに500になる

昼過ぎのころもインスタンスが勝手に死んだりしていましたが、分散した構成でしたので大規模な問題にはなりませんでした。 「特定のAvailabilityZoneで障害なんだな、MultiAZにしていて良かった」と僕はそのときは思っていました。

が、そのあとロードバランサーが突然500を返すようになります。 最初はアプリの問題かと色々調べたのですが、結局アプリサーバーは正常に稼働しており、ロードバランサー自体が500を返していると特定できました。

後になっての知識ですが、同時刻に冷却システムの復旧とインスタンスの復旧、一部のインスタンスで(ハードウェアの限界による)リタイアがあったようなので、その当たりが関係しているのかもしれません。

ALBのデプロイをするAZで、特定のAZを使わなくすると解決

ALBを利用させているAZから、特定のAZを消すと問題なく動作するようになりました。 ALBの設定で配置する対象のサブネットを変更しました(VPCのサブネットはAvailabilityZoneに紐付いています)。

もちろん「特定のAZで障害があった」とは私も認識していましたが、ALBの設定(ALBをデプロイするAZの設定)まで変えようとは思っていませんでした。

f:id:hirokiky:20190823200044p:plain
事態の収束を告げるグラフ

これはALB自体のモニタリングのグラフです。ロードバランサー自体が返している5xxエラーの数です。ロードバランサーは通常、正常に稼働しているインスタンスが1つもない場合などに5xx系のエラーを返しますが、このときはALB以下には正常なインスタンスが紐付いていました。自動で治る雰囲気が無かったのでALBのAZの設定を変えると、グラフ右端のように問題は解決しました。

f:id:hirokiky:20190826134101p:plain
全体像(時刻はUTC

今回はすぐに検知して、調査、対応できましたが、 大きな問題でなく「静かに少し死ぬ」、そして影響範囲は大きいというのは大変でした。検知やトレーサビリティーの大切さを思い知りました

今回の問題について

今回の障害について、「MultiAZ構成だったから大丈夫だった」というような声をSNSなどでも見聞きしましたが、今回発生したすべての問題をそれだけで解決できるとは思いません(障害を起こしたサービスに対して安易な批判をするのは控えたほうが良いでしょう)。 MultiAZは当たり前として、監視や復旧のための準備が必要 です(このALBの問題もそうですし、RDSの応答がなくなる問題や、コンソール画面を操作できなくなる問題、AMIがビルドできない問題もあったと話に聞いています)。 AWSのマネージドサービス全体に関わる問題ですし、システム構成、アプリケーションの作り、時の運に影響して問題は発生し得る状況でした。

障害がどう影響したかなどは詳しくは分かりません。もちろんアプリサーバー側が古いコネクションを持っており問題を抱えたALBにアクセスしていたのか?なども考えられますが、ALBの設定を変更すればすぐに治ったので決定的にそれが原因だとも言えない状態です。

ともかく安易に「コレをしていたから大丈夫」、「コレをしないのが悪いんだ」と思わずに、今一度問題が起こっていないか、今後何をできるかを考えたほうが良いでしょう。 特定のAZでインスタンスが落ちちゃうくらいならMultiAZで対応できますが、ロードバランサーで問題が発生する & 自動でキレイに元通りにならないと対応は簡単ではなくなります。

検知の重要性

インフラを信じる、お金を使うということは大事ですが、今回のようにクラウドサービス自体が予期しない動作になることもあると勉強になりました。

「MultiAZだから大丈夫」というのも、そもそもクラウドサービス自体がすべて正常に動作しているから言えることです。予期しない状況ですべてのシステム、アプリがうまく動作するとも限りません。足がかりにしている地盤そのものに問題が発生する場合どう検知して対応したら良いでしょうか?

インフラ環境を信じすぎず、自分のアプリも信じすぎず、問題が発生する前提でちゃんと監視、検知、トレースすることは重要だと勉強になりました。

関連する他の記事

似た事象にあったBoardさんのとても誠実でまとまった報告

the-board.jp

今回の私が取った対応の具体的な操作方法

dev.classmethod.jp

他にオススメの記事

blog.hirokiky.org

blog.hirokiky.org

Djangoでdjango-hijack-adminをカスタムユーザーモデルと使うときのハマりポイント

django-hijack-adminDjangoのカスタムユーザーを併せて使うとき、Adminサイトへの登録でハマるので書いておきます。

django-hijackとは、Admin画面から別ユーザーのセッションを乗っ取れるライブラリーです。 個別のユーザーで問題が発生しているときに状態を確認したり、ローカルで動作確認するときに複数アカウントを切り替えやすくなったりで便利になります。

github.com

django-hijack-adminはdjango-hijackの機能をAdmin画面から使いやすくしてくれるものです。 かなり小さい実装なので、ライブラリー自体は参考実装にして自分で書いてしまっても十分なものです。

カスタムユーザーをAdminに登録するときにハマる

カスタムユーザーモデルを作って拡張したModelAdminを登録しようとするときに、django-hijack-adminをINSTALLED_APPに入れているとハマります。 具体的にはカスタムユーザーモデル用のModelAdminの内容がAdmin画面に反映されなくなります。

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as AuthAdmin

from . import models


@admin.register(models.User)
class UserAdmin(AuthAdmin):
    # デフォルトのUserAdminに独自の拡張をする
    fieldsets = AuthAdmin.fieldsets + (("Additional", {"fields": ("icon",)}),)

ここでは fieldsetsicon というフィールドを追加していますが、Admin画面に反映されなくなります。

django-hijack-adminは独自のUserAdminを登録する

原因は django-hijack-admin も独自のUserAdminを登録しようとすることです。 django-hijack-adminは「Hijack」するためのボタンを追加するために、 auth.admin.UserAdmin を拡張したクラスをAdminに登録します。 Userモデルに対応するModelAdminをunregisterして登録するので、カスタムユーザー用に自身のUserAdminを登録しても無駄になります。

if hijack_admin_settings.HIJACK_REGISTER_ADMIN:
    UserModel = get_user_model()
    admin.site.unregister(UserModel)
    admin.site.register(UserModel, HijackUserAdmin)

github.com

解決策

以下の用に3つ設定します

  1. settingsに HIJACK_REGISTER_ADMIN = False を設定する
  2. UserAdminでHijackUserAdminMixin をMixinする
  3. list_displayに 'hijack_field' を足す

このようになります。

from hijack_admin.admin import HijackUserAdminMixin


@admin.register(models.User)
class UserAdmin(AuthAdmin):
    ...
    list_display = AuthAdmin.list_display + ('hijack_field',)

カスタムユーザーを使うときの設定方法は READMEに書かれています 。 ですがdjango-hijack-adminがデフォルトで、元の動作を音もなく書き換えるので少し気づきにくいと思います。

もしdjango-hijack-adminを使っていて「おかしいな」というときは疑ってみてください。

他のおすすめ記事

blog.hirokiky.org

Vue.js+VueRouterでページの離脱、再読込、別ルートへの移動時に警告を表示する

ページの離脱時に警告を表示するには、 beforeunload イベントを使えば簡単にできます。 ですが、Vue.jsでVueRouterを使っている場合、ページの移動で beforeunload イベントは発生しません。 理由はブラウザーの画面自体が切り替わっていないからです(ページの遷移はVueRouter, つまりJavaScriptが同一の画面内で制御している)。

WordPressやDropboxPaperも、ページ離脱時には confirm() やモーダル表示を使ってページ遷移時に警告を表示しています (もちろんbeforeunload時の警告も併用しています)。

Vue.js + VueRouterでページ離脱、再読込時に警告を表示する

以下のように beforeunload を使えば良いです(クロスブラウザー対応どうこうはうまく書き換えてください)。

  methods: {
    handler (event) {
      event.returnValue = "Data you've inputted won't be synced"
    }
  },
  created () {
    window.addEventListener("beforeunload", this.handler)
  },
  destroyed () {
    window.removeEventListener("beforeunload", this.handler)
  }

vue-prevent-unload というライブラリーもありますが、小さすぎる実装なので参考にするだけで良いでしょう。 必要なコンポーネントに処理を書くか、独自のvue-prevent-unloadのようなコンポーネントをプロジェクト以下に置いておけば十分です。 例えば以下のようなVueコンポーネントStopUnload.js としておくなどです(あくまで参考実装です)。

export default {
  name: 'StopUnload',
  props: ["stop"],
  render: () => null,
  methods: {
    handler (event) {
      if (this.stop) {
        event.returnValue = "Data you've inputted won't be synced"
      }
    }
  },
  created () {
    window.addEventListener("beforeunload", this.handler)
  },
  destroyed () {
    window.removeEventListener("beforeunload", this.handler)
  }
}

ページ遷移時にも警告をする

ただこれだけではVueRouterによるページの遷移時に警告が表示されません。 以下のようにVueコンポーネント内に beforeRouteLeave を書けばVueRouterでのページ移動を検知できます。

beforeRouteLeave (to, from, next) {
  let answer = window.confirm("Data you've inputted won't be synced, OK?")
  if (answer) {
    next()
  } else {
    next(false)
  }
}

router.vuejs.org

ただしこの場合、VueRouterの routes に登録されているVueコンポーネントに上記の処理を書いてください(Viewとしてのコンポーネントに書く)。 VueRouterに直接関係しないコンポーネントでは beforeRouteLeave は呼び出されません。なので上記で例示したStopUnoadコンポーネントbeforeRouteLeave を書いても機能しません。

windowのbeforeunloadと、beforeRouteLeaveを使うことで、Vue.js+VueRouterでSPAを作っているときもページの離脱時に警告を表示できます。

他のオススメ記事

blog.hirokiky.org

Vue.jsでAPIにないフィールドはモデルにも作らないプラクティス - モデルとViewModelの区別の仕方

前回の記事はこちらです。先に読まれておくことをオススメします。

blog.hirokiky.org

モデルに置く値、ViewModelに置く値の区別をつけよう

Vue.jsで開発するときに、モデルに置くべき値とViewModelに置くべき値を区別できればかなりキレイに設計できます。 区別の判断は少し難しいですが、「バックエンドのAPIで保存・読み込みする値のみモデルで管理する」と考えてみると勘所が分かりやすいです。 バックエンドのAPIなどが無い場合は、例えばlocalStorageに保存する値をモデルとすると良いでしょう。

前提: 記事の読み込み、保存する画面で説明します

説明のためのプロジェクトの説明をします。

記事(Post)の編集をする画面をVue.jsで作り、バックエンドのAPIからデータを取得、保存するとしましょう。 ここではAPIから以下のレスポンスがあると想定します。

{
    "id": 1,
    "title": "タイトル",
    "body": "本文"
}

この場合、APIのレスポンスはモデルで管理すべきです(参考: JavaScript (ES6) でAPIから受け取ったデータをモデルに入れるプラクティス - Make組ブログ

export class Post {
  constructor (id, title, body="") {
    this.id = id
    this.title = title
    this.body = body
  }
}

読み込み、保存する処理は以下のようにします

import axios from 'axios'

import { Post } from './api'


export async function getPost (id) {
  let res = axios.get(`/posts/${id}/`)
  return new Post(res.data.id, res.data.title, res.data.body)
}


export async function patchPost (post) {
  let res = axios.patch(`/posts/${post.id}/`, {body: post.body})
  post.body = res.data.body
  return post}

単純にVueコンポーネントを作る

さて、ここでどのようにVueコンポーネントを書くべきでしょうか? 一番単純に作ると以下のようになります。

<template>
  <div v-if="post">
    <textarea v-model="post.body"></textarea>
    <button @click="save">Save</button>
  </div>
</template>

<script>
import * as api from './api'


export default {
  name: 'Post',
  data () { return {
    post: null
  } },
  methods: {
    async save () {
      await api.patchPost(this.post)
    }
  },
  async mounted () {
    this.post = await api.getPost(1)
  }
}
</script>

モデルに書く?

ここで「値が変更されていないときは Save ボタンを disabled にしたい」としましょう。 この場合、値が変更されたことを検知するのはどこが良いでしょうか?

せっかくJavaScriptclass が使えるので、以下のように getter, setter で書くとカッコイイ気がします。 が、結論から言うとあまりオススメしません。

export class Post {
  constructor (id, title, body="") {
    this.id = id
    this.title = title
    this._body = body
    this.changed = false
  }

  get body  () {
    return this._body
  }

  set body (value) {
    this._body = value
    this.changed = true 
  }
}

この場合、Vueコンポーネントは以下のようになるでしょう。 changed = false にする処理や、 :disabled="!post.changed" が足されています。

<template>
  <div v-if="post">
    <textarea v-model="post.body"></textarea>
    <button @click="save" :disabled="!post.changed">Save</button>
  </div>
</template>

<script>
import * as api from './api'


export default {
  name: 'Post',
  data () { return {
    post: null
  } },
  methods: {
    async save () {
      await api.patchPost(this.post)
      post.changed = false
    }
  },
  async mounted () {
    this.post = await api.getPost(1)
  }
}
</script>

この程度ではあまり問題になりませんが、 1つのコンポーネントでしか使わないような処理や状態をモデルに入れるとモデルが肥大化していきます 。 変更の検知をする部分はVueコンポーネントに書くほうが良いです。

changedAPI(データの読み込み・保存)には関係しない。モデルに書くべきでは無いかも?」と考えてみてください。

Vueコンポーネントに書こう

モデルの定義は元に戻して(getter, setterを消して)、Vueコンポーネントを以下のように書いてみましょう。 「変更されたかどうか」という状態をVueコンポーネントに持つようになっています。

<template>
  <div v-if="post">
    <textarea v-model="body"></textarea>
    <button @click="save" :disabled="!changed">Save</button>
  </div>
</template>

<script>
import * as api from './api'


export default {
  name: 'Post',
  data () { return {
    post: null,
    body: ""
  } },
  methods: {
    async save () {
      this.post.body = body
      await api.patchPost(this.post)
    }
  },
  computed: {
    changed () {
      return this.body != this.post.body
    }
  },
  async mounted () {
    this.post = await api.getPost(1)
    this.body = this.post.body
  }
}
</script>

textarea ではVueコンポーネントbody を編集するようにして、 post.body は触れないようにしています。 「変更されたかどうか」は changed というcomputedで計算しています。

changeddata で管理するフラグにしても良いです。

  methods: {
    update (value) {
      this.body = value
      this.changed = true
    }
  }

こう変更することで、モデルとしたPostクラスでは changed という実装が無くなりました。 「記事」として管理すべき値はモデルに、「編集する」という責任範囲で管理すべき値はコンポーネントに置きましょう。 そうすることで、モデルの実装を減らして、細かく分けられたVueコンポーネントに実装を分散できます。

もし複数コンポーネントで状態を共有するのであれば、Vuexを使えば済みます。 もちろん今回の説明も「こっちが正解」というわけではありません。ですが、「全てモデルに書く」、「全てViewコンポーネントに書く」のでなく、適宜コンポーネントを分離して値を管理する場所を分けることで、より良い設計が自然とできるようになるでしょう。

他のオススメ記事

blog.hirokiky.org