ワイのメモ帳なんやで...

主にWebプログラミングのお話をします。

Anaconda3 on pyenvとVSCodeでJupyterプラグインを使った環境構築

VSCodeのJupyterプラグインが便利なので。

前提条件

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G4015

% brew --version
Homebrew 1.8.5
Homebrew/homebrew-core (git revision b2d3; last commit 2018-12-13)
Homebrew/homebrew-cask (git revision bca77d; last commit 2018-12-13)

pyenvをインストール

WebサイトからインストールしたAnacondaをお持ちの方は、アンインストール Anaconda3をアンインストールする

% brew install pyenv

インストールのログの最後に設定が出てくるのでそれに従って、環境変数を設定する

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

Anaconda3をインストール

% pyenv install --list
...
anaconda3-5.3.0
...

# 時間がかかるので、タバコに行ってくる
% pyenv install anaconda3-5.3.0
% pyenv global anaconda3-5.3.0

VSCodeのセットアップ

VSCodeが入っていない場合
Caskで導入します。(Webからのインストールはこちら)

% brew cask install visual-studio-code

インストールしたら起動して、拡張機能のタブで「jupyter」と入れて検索&インストール image.png

サンプルプロジェクトを作成

% mkdir -p /path/to/sample
% code /path/to/sample

左下のPythonバージョンっぽい文字をクリックするとインストールされてるインタプリタが見えるのでPyenvのものを選択する image.png

サンプルコードを作成し、上記の様に#%%の上にRun cellが表示されていることを確認する 一番右側のRun cellをクリックして Start a new notebookをクリック image.png

画像のように実行できていれば大丈夫です。

React componentDidCatchでcatch出来ない?

タイトル通り

React16から追加された componentDidCatch メソッドがコールされなかったの。

公式と同じErrorBoundaryのComponentを定義したのにそんなことあるぅ?

先に言っておくと公式キチンと読んでいないだけです。

ErrorBoundaryのComponent
import React from "react";

export default class ErrorBoundary extends React.Component {
  state = { hasError: false };

  componentDidCatch(error, errorInfo) {
    this.setState({ hasError: true });
  }

  render() {
    return this.state.hasError ? (
      <h1>エラーキャッチしたんじゃ</h1>
    ) : (
      this.props.children
    );
  }
}
 
throwするComponent
import React, { Component } from "react";

class MethodThrow extends Component {
  throwError() {
    throw new Error("error!!!");
  }

  render() {
    return (
      <button type="button" onClick={this.throwError}>
        method throw
      </button>
    );
  }
}

export default MethodThrow;

ボタン押したらthrowが走って「エラーキャッチしたんじゃ」が表示され

ないんですよ。マスオさんもびっくり。

 

正解はこちら
import React, { Component } from "react";

class LifecycleThrow extends Component {
  constructor(props) {
    super(props);
    this.state = { error: false };
    this.throwError = this.throwError.bind(this);
  }

  throwError() {
    this.setState({ error: true });
  }

  render() {
    if (this.state.error) {
      throw new Error("error!");
    }
    return (
      <button type="button" onClick={this.throwError}>
        lifecycle throw
      </button>
    );
  }
}

export default LifecycleThrow;

throwをReactのライフサイクルメソッド内で発火するように変更。

これで無事エラーキャッチしたんじゃ。

 

公式を見ると

Note

Error boundaries do not catch errors for:

  • Event handlers (learn more)
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server side rendering
  • Errors thrown in the error boundary itself (rather than its children)

 

Error boundaries do not catch errors for:

Event handlers

Event handlers

Event handlers

あっはーい。

DOMへのイベントハンドラのバインドを終えたら次のState更新があるまでComponentの役目は終わっているもんね。。。

 

ということでSandbox。

Vue.js エクセルのセルみたいなラベル

エクセルのセルみたいなラベル

ちょっと何言ってるかわかんないっす。
ってなると思いますが、アレですよアレアレ。Excel2013?くらいから導入された、セルの変更時に変更したセルの値を参照しているセルが同時に変更されて、古い値が下に消えて新しい値が上から降ってくるあのアニメーション。
実案件でExcelみたいな表を実装することになりまして、

「あーあんな感じのアニメーションできたらいいなぁ」

と思って調べたけどなかったのでComponentを作ってみました。
結果だけ知りたいって人は下にSandboxの実行結果貼ってるから飛ばしちゃってください。

全体のソースこんな感じ

<template>
  <div class="parent">
    <label v-if="change" :class="updateing && 'rotation'">
      {{ oldValue }}
    </label>
    <label v-else :class="updateing && 'rotation'"> {{ value }} </label>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      required: true
    }
  },
  data: () => ({
    init: false,
    change: false,
    updateing: false,
    oldValue: null
  }),
  watch: {
    value: {
      handler(newValue) {
        // immediate fire
        if (!this.init) {
          this.init = true;
          this.oldValue = newValue;
          return;
        }
        this.updateing = true;
        this.change = true;
        setTimeout(() => {
          this.change = false;
          this.oldValue = newValue;
        }, 250);
setTimeout(() =>
{
this.updateing = false;
}, 500); }, immediate: true } } }; </script> <style scoped lang="scss"> div.parent { position: relative; height: inherit; width: inherit; label { position: absolute; top: 0; left: 0; width: 100%; height: 100%; &.rotation { animation-name: rotation; animation-duration: 0.5s; } } } @keyframes rotation { 0% { opacity: 1; } 33% { opacity: 0; transform: translateY(0.5rem); } 66% { opacity: 0; transform: translateY(-0.5rem); } 100% { opacity: 1; } } </style>

アニメーション初心者なこともあって、これ作るのに1日くらい費やしてしまった。
もうちょっとシンプルにできないかと思っていたのだが、なかなかに複雑に。。。
機能説明を付け加えておく。

まずテンプレート部分

  <div class="parent">
    <label v-if="change" :class="updateing && 'rotation'">
      {{ oldValue }}
    </label>
    <label v-else :class="updateing && 'rotation'"> {{ value }} </label>
  </div>

なんで if-else してんのって思われるかもしれませんが、僕の知識不足かもしれません。
アニメーションを実行する際に、下に落ちるアニメーションの時にすでに親側で変えられた値が表示されちゃうんですよね。
下に落ちるときは古い値、その後降ってくるときは新しい値になってほしかったの。

苦肉の策なの。

次にScript部分

export default {
  props: {
    value: {
      type: String,
      required: true
    }
  },
  data: () => ({
    init: false,
    change: false,
    updateing: false,
    oldValue: null
  }),
  watch: {
    value: {
      handler(newValue) {
        // immediate fire
        if (!this.init) {
          this.init = true;
          this.oldValue = newValue;
          return;
        }
        this.updateing = true;
        this.change = true;
        setTimeout(() => {
          this.change = false;
          this.oldValue = newValue;
        }, 250);
setTimeout(() => {
this.updateing = false;
}, 500); }, immediate: true } } };

主にWatchの所ですね。

mounted時にはアニメーションが走らないようにするためinit変数で管理
② アニメーションのclassを付与するためにupdateingフラグを立てて
③ 変更中はchangeフラグを立てて古い値を表示
アニメーションの後半になったら変更値を表示

Q. アニメーション表示のためにここまでいろいろ必要なんですか?
A. 僕の脳ミソだと必要です。

最後にcss

div.parent {
  position: relative;
  height: inherit;
  width: inherit;
  label {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    &.rotation {
      animation-name: rotation;
      animation-duration: 0.5s;
    }
  }
}
@keyframes rotation {
  0% {
    opacity: 1;
  }
  33% {
    opacity: 0;
    transform: translateY(0.5rem);
  }
  66% {
    opacity: 0;
    transform: translateY(-0.5rem);
  }
  100% {
    opacity: 1;
  }
}

すまん、scssだったは。

キモはrotationのkeyframesのとこっすね。
アニメーション0/3~1/3の間消しながら下に
アニメーション1/3~2/3の消えてる間に要素を上に持ってくる
アニメーション2/3~3/3で上から降らす

といった感じになってます。
ちなみにこのComponent、Componentを呼んでる親要素にheightがpxやremで設定がされてないと高さが取得できなくてうまくアニメーションしてくれないので注意な!

ベストなソリューションが知りたいです。
こっちのほうがいいよ!ってのがあれば教えていただけると幸いです。

ということでこのブログの恒例にしたい Sandboxの結果を貼っていくスタイルで締めたいと思います。

 

Vue.js v-modelなComponent

Vue.jsを使っていると

子に渡したpropsの値を変更したら親側の値も変更されるようにしたい!

というシーンに割とよく直面する。

propsの値はそのままv-modelでは使用できないという制約もあり、回避策の知識がないと割と困ってしまうものである。(公式に書いてあるけどというツッコミは無しな!)

なので今回は子のComponentで変更された値を親に反映する方法を紹介します。

実行結果だけ知りたい人はSandboxが一番下にあるのでスクロールしてね☆

modelプロパティを指定する

Vue.js 2.2.0 からmodelプロパティが追加された。

詳しくは公式にかかれているが、正直わかりにくかったのでここでも解説する。

 

子のComponentで以下の宣言をする

export default {
  model: {
    prop: 'propData',
    event: 'input'
  },
  props: {
    propData: String
  }
}

すると親側でpropDataのbindをv-modelから行えるようになる

<template>
  <div>
    <input type="text" v-model="hoge" />
  </div>
</template>

 しかしこれだけだと子の変更を親に返却する処理が書かれていないので意味がない。

modelのeventで指定されたイベントを$emitしてやらないといけない。

以下はその返却方法を考えてみる。

 

アンチパターン watchして$emit

僕が最初に書いていた方法

mountedを通らないと親の値を子が受け取れないし、方法ですらない。

 皆は真似しないでね!

<template>
  <div>
    <label>Sample1&nbsp;</label>
    <input type="text" v-model="inputData" />
  </div>
</template>

<script>
export default {
  name: 'Sample1',
  model: {
    prop: 'propData',
    event: 'input'
  },
  props: {
    propData: String
  },
  data () {
    return {
      inputData: null
    }
  },
  mounted () {
    if (this.propData) {
      this.inputData = this.propData
    }
  },
  watch: {
    inputData (nextValue) {
      this.$emit('input', nextValue)
    }
  }
}
</script>

 

computedを利用する

いやぁ、最初に見たときは感動したね。

<template>
  <div>
    <label>Sample2&nbsp;</label>
    <input type="text" v-model="inputData" />
  </div>
</template>

<script>
export default {
  name: 'Sample2',
  model: {
    prop: 'propData',
    event: 'input'
  },
  props: {
    propData: String
  },
  computed: {
    inputData: {
      get () {
        return this.propData
      },
      set (value) {
        this.$emit('input', value)
      }
    }
  }
}
</script>

porpsを直接v-modelに指定できないから一旦computedを間に通してしまおうという魂胆。

v-modelの変更はset関数に入ってくるので、その値を親に返却。

美しく且つクールな書き方。

 

余談 v-modelを複数指定したい場合

v-modelは一つのパラメータしか指定できない。

複数の値を指定したい場合は Vue.js 2.3.0 以降でのみ使用可能なsync修飾子を使用する。

 

<template>
  <div id="app">
    <sample3 :foo.sync="sample3_1" :bar.sync="sample3_2" />
  </div>
</template>

 

<template>
  <div>
    <label>Sample3&nbsp;</label>
    <input type="text" v-model="inputData1" />
    <input type="text" v-model="inputData2" class="ml-1"/>
  </div>
</template>

<script>
export default {
  name: 'Sample3',
  props: {
    foo: String,
    bar: String
  },
  computed: {
    inputData1: {
      get () {
        return this.foo
      },
      set (value) {
        this.$emit('update:foo', value)
      }
    },
    inputData2: {
      get () {
        return this.bar
      },
      set (value) {
        this.$emit('update:bar', value)
      }
    }
  }
}
</script>

<style scoped>
.ml-1 {
  margin-left: .1rem;
}
</style>

v-bindで.syncを指定すると 子側の$emitでupdateイベントに変数名を追記することで親で指定されたプロパティにたいして値の更新を行えるようになる。

 

 

各実行結果はこちら

 

ということで今回はここらへんで失礼します。

このブログについて

どうも、メモ帳を書いているワイです。

主にWebプログラミングのお話をします。
中の人は2人いて、ふたりともJavaScriptが好きです。

一人はセミコロン絶対つけるマンで、
もうひとりはセミコロン絶対ぶっ◯すマンです。

ゆる〜く読みやすくを意識して書いていけたらなと思っています。

console.log('よろしくね!')