reactのstateがごちゃつき始めるくらいまで使ってみた。
stateがごちゃついてなんかつらいなこれ…ってなり始めるくらいまでreact.jsを使ってみました。 初心者なりにつらかったところを記録しておきます。 react.jsの環境構築や使い方については、初心者向けの記事は良い記事がたくさんあるので、そちらにお任せします。
つくったもの
ソートするやつをつくりました。 0~99までの100個の数値を、バブルソートでソートします。
もっとちゃんとしたやつをどこかで見たことがるって?はい、私も見たことがあります。いいのです。学習用なので、これでいいのです。
herokuにデプロイしてみました。
https://evening-brushlands-42735.herokuapp.com/
https://github.com/ytnk531/react-tutor
ソースコードも公開しています。ただしフロントエンドド素人が作ったものであるため、くれぐれもお手本にはしないように( ´∀` )
reactのコンポーネントで状態管理するのはつらい?
下記の記事では、reactのコンポーネントの状態管理はreduxに任せてしまおう、と言っています。
qiita.com
reactのコンポーネントはstateと呼ばれるプロパティを持っていて、通常はこのstateプロパティを変化させてコンポーネントの状態を変更します。
reduxは、コンポーネントごとに状態を持つのはやめて、reduxを使うことで一か所で管理してしまおう、というライブラリです。こういったライブラリが推奨されるということは、コンポーネントで状態管理することに辛さがある、ということが示唆されているわけですが、いまいちそのつらみが理解できませんでした。
つらくなるまでやってみた
reactを使うならreduxと一緒に使いなさい!と教えていただきましたが、いまいち嬉しさがわかりませんでした。reduxで状態管理する嬉しさがわからないというよりは、reactで提供されているstateを使って状態管理する困難さがわからなかったのです。
ということで、ある程度つらくなるまで使ってみました。
フロントエンドは経験も知識ありませんでしたが、react自体は難しい物じゃないのでわりとすんなりと使えるようになりました。
作るのに必要だった知識
子要素は筆者の事前の理解度や利用経験。
- HTML
- JavaScript
- 基本的なシンタックスとconsole.logとアロー関数がわかる程度。
- Greasemonkeyで簡単な自動入力スクリプトを書いたことがある
- SVG
- ベクターの画像だよね?
stateで状態管理するつらさ
stateの更新がめんどくさい
stateを操作するときは、以下のように記述します。
// value1を1増やす this.setState(prevState => ({ value1: prevState.value1 + 1 });
状態を変えるたびにこれをやるのはしんどいってもんですね。
stateの反映されるタイミングが謎
setState()は非同期で呼ばれます。いつstateが更新されるかは定かではないのです。
例えば、下記のコードは無限ループします。
'''javascript
// finishの初期状態はfalse
while(this.state.finish) {
this.setState({finish: true});
}
'''
やっぱり散らばるのはよくない
コンポーネント間で共有したい値があったりします。 stateを外部から参照する方法があるにはあるのですが、とてもやりづらいうえに非推奨だそうです。
各コンポーネントの状態が、変更されているかわかりにくくなってしまうのは、確かに悪いことに感じます。 状態がいつ変更されているのかを追跡する手段が用意されていなければ、バグを見つけにくくなってしまうでしょう。 一つのクラスの中にstateが留められていたとしても、それはやはり同じでしょう。
結局つらかったか
すみません、stateを持ったコンポーネントを一つしか作らなかったので、そんなにつらくなかったです。
というか、stateもったコンポーネント同士で親子関係を作ると値の変更がしにくいので、子コンポーネントは状態を持たずにpropsで渡した値が状態になるような作りにするほかなかったです。
つまるところ、規模が大きくなってくると、一つのコンポーネントでたくさんの状態を管理したり、コンポーネント間で値が伝搬していくように作らなければならないように思います。
これがコンポーネントで状態を管理するつらさ…で…いいのか…な…。
つまづいたところ
コンポーネントをループで生成する h3poteto.hatenablog.com
Docker ToolBoxのDocker環境をWindows 10 HomeのBash On Windowsから使う
結論
docker-machine.exe start default docker.exe $(docker-machine.exe config default) run hello-world
はじめに
Windows 10 HomeでDocker Toolboxで入れたDockerをBash On Windowsを使う方法を説明します。 Docker for Windows + Bash on Windowsだったり、Dockerを入れたVM + Bash on Windowsとはちょっと違う環境の話です。 Docker for Windows使えればよかったんですけどねぇ。
注意
本ページに登場するコマンドの.exe
は省略しないでください。
省略すると、コマンドが見つからずエラーになるか、Linux Subsystemの方にインストールされたコマンドが実行されてしまいます。
環境
Windows 10 HomeではDocker for Windowsが利用できないので、Docker Toolboxを使います。Docker ToolboxとBash on Windowsはインストール済みという前提で説明していきます。
BoWからdocker.exeはうまくいかない
BoWからDocker Toolboxでインストールされたdocker.exeを実行すると次のようなエラーになります。
$ docker.exe ps error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.31/containers/json: open //./pipe/docker_engine: The system cannot find the file specified. In the default daemon configuration on Windows, the docker client must be run elevated to connect. This error may also indicate that the docker daemon is not running.
docker.exeはDockerのクライアントですから、docker daemonというdockerのコンテナを実際に動作させるサーバと接続しなければなりません。 BoWからdocker.exeを起動した場合、接続先の設定がされていないためエラーになります。 Docker Quick Start Terminalでは起動時にこの設定を行ってくれるようです。以下では、Docker Quick Start Terminalに頼らず接続設定を行う方法を説明していきます。
docker-machine.exeでdocker-daemonを起動する
Docker Toolboxで使えるようになるDocker daemonは、実はdocker-machineで作られたVM上にあるので、docker-machine.exeを使って確認できます。
$ docker-machine.exe ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default - virtualbox Stopped Unknown
defaultという名前のdocker machine(docker daemonを動作させるVM)があるのがわかりますね。Docker Quick Start Terminalではこのdocker machineを利用しています。STATEがStoppedなので、まずは起動しましょう。私の環境では起動に3分くらいかかりました。
$ docker-machine.exe start default Starting "default"... (default) Check network to re-create if needed... (default) Waiting for an IP... Machine "default" was started. Waiting for SSH to be available... Detecting the provisioner... Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.
Docker Quick Start Terminalは、起動時このdocker machineの立ち上げも自動で行っていたというわけです。
Dockerを使う
起動したdocker machineを介してdockerを操作するには、下記のコマンドを使います。
docker.exe $(docker-machine.exe config default) [dockerコマンド]
[dockerコマンド]
にはrun mysql
やps
など、docker操作用のコマンドを入れてください。docker-machine.exe config default
でdefault(Docker Machine)に接続するためのdocker.exeのためのオプションが生成されるので、これを評価したものを引数にしています。
では実行してみます。
$ docker.exe $(docker-machine.exe config default) run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 9bb5a5d4561a: Pulling fs layer 9bb5a5d4561a: Verifying Checksum 9bb5a5d4561a: Download complete 9bb5a5d4561a: Pull complete Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/engine/userguide/
BoWからDocker ToolboxでインストールしたDockerを操作することができました。
おまけ: もうちょっと楽にする
docker machineの起動と、docker操作用のコマンドにaliasを張っておきます。
alias dmsd='docker-machine start default' alias docker='docker.exe $(docker-machine.exe config default)'
docker machineの起動は/etc/rc.local
に書いておけばうまくいくでしょうか?
すみません、試してないのでわかる方いらっしゃいましたらコメントか何かで教えてください😷
余談
Docker Toolboxのほうでは、おそらくdocker-machine.exe env default --shell
で生成される設定スクリプトを起動時に実行して環境変数を設定しているのですが、これと同じ方法はBoWでは使えません。というのも、docker.exeはBoWで設定された環境変数を読んでくれないからです。
ならば、Windowsにインストールしたdocker.exeでなく、BoWにインストールしたdockerクライアントを実行すれば環境変数を読めるのでうまく動きそうです。結果は、これもダメでした。docker-machine.exe env default --shell
で設定した環境変数では、pemファイルのパスがWindowsの形式(C:\foo\bar\的な)で設定されてしまい、BoWからはそのパスをたどれないからです。
【メモ】DDD実践ドメイン駆動設計 第1章と第2章
Angularプロジェクトをコピーしたらビルドできなかった
Angular CLIで作ったプロジェクトをコピーしたらビルドがコケました。
隠しファイル(ドットファイル)をコピーし損ねてたただけでした(汗)
> cp -r angular-app/* . > ng build Cannot read property 'config' of null TypeError: Cannot read property 'config' of null at Class.run (/home/tanaka/scala/play/play-scala-seed/src/node_modules/@angular/cli/tasks/build.js:15:56) at Class.run (/home/tanaka/scala/play/play-scala-seed/src/node_modules/@angular/cli/commands/build.js:216:26) at resolve (/home/tanaka/scala/play/play-scala-seed/src/node_modules/@angular/cli/ember-cli/lib/models/command.js:261:20) at new Promise (<anonymous>) at Class.validateAndRun (/home/tanaka/scala/play/play-scala-seed/src/node_modules/@angular/cli/ember-cli/lib/models/command.js:240:12) at Promise.resolve.then.then (/home/tanaka/scala/play/play-scala-seed/src/node_modules/@angular/cli/ember-cli/lib/cli/cli.js:140:24) at <anonymous>
エラーメッセージで検索すると、Angular-CLIのissueにヒントがありました。
ardiadrianadri commented on Feb 17 I know why it is happening but I don't know how to solved. The problem is that the transpiled JS code remove the "." at the beginning of the string ".angular-cli.json", so angular-cli cannot find the configuration file because it is looking for a file that doesn't exist. Let me show you:
This is the content of packages/@angular/cli/models/config.ts:
(...) import * as chalk from 'chalk'; import * as fs from 'fs'; import * as path from 'path';
export const CLI_CONFIG_FILE_NAME = '.angular-cli.json'; const CLI_CONFIG_FILE_NAME_ALT = 'angular-cli.json'; (..)
以前はトランスパイルするとファイル名の頭のドットが取れてしまうバグがあったようですね。 リンク先の問題とは違いますが、私の環境では".angular-cli.json"がコピーできていなかったのでコケていました。
cp -r ~/myapp/.angular-cli.json ~/myapp/.editorconfig .
これでビルドが成功するようになりました。
Spring + JSP + JQueryでポチポチしてフォームを増やしたい。
ポチポチしたい
フォームをネストして、サブフォームをいっぱい持つParentForm
package myspring.web; import java.io.Serializable; import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class ParentForm implements Serializable{ private static final long serialVersionUID = 1L; private List<ChildForm> children; @NotNull @Size(min = 1, max = 20) private String name; /** * */ public ParentForm() { super(); } /** * @return the children */ public List<ChildForm> getChildren() { return this.children; } /** * @param children the children to set */ public void setChildren(List<ChildForm> children) { this.children = children; } /** * @return the name */ public String getName() { return this.name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } }
いっぱい持たれるChildForm
package myspring.web; public class ChildForm { private String name; private String group; /** * */ public ChildForm() { super(); } /** * @return the name */ public String getName() { return this.name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the group */ public String getGroup() { return this.group; } /** * @param group the group to set */ public void setGroup(String group) { this.group = group; } }
Controller
package myspring.web; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class WelcomeController { @RequestMapping("/") public String fuck(Model model) { return "welcome"; } @RequestMapping("/me") public String me(ParentForm form, Model model) { form.setChildren(Arrays.asList(new ChildForm(), new ChildForm())); model.addAttribute("form", form); return "fuckYou"; } @RequestMapping("/new") public String rec(@Valid ParentForm form, BindingResult result, Model model) { if (form.getChildren() != null) { form.getChildren().stream() .filter(Objects::nonNull) .forEach(child -> System.out.println("forEach" + child.getName())); List<ChildForm> childforms = form.getChildren().stream() .filter(Objects::nonNull) .filter(child -> child.getName() != null) .collect(Collectors.toList()); form.setChildren(childforms); } model.addAttribute("form", form); if (result.hasErrors()) { return "fuckYou"; } return "fuckMe"; } }
JSPは、JQueryを使ったけど、たいしたことしてないので使わないほうがいいかもしれない。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Sub Form Sample</title> <script src="/webjars/jquery/2.2.4/jquery.min.js"></script> </head> <body> <c:set var="count" value="0"/> <h2>Sub Form Sample</h2> <form:form modelAttribute="form" action="/new"> <table> <tr> <td colspan="4"> <button type="button" id="add">Add</button> </td> </tr> <c:forEach items="${form.children}" varStatus="rowS" var="item"> <tr id="subform${rowS.index}"> <td>Name</td> <td><form:input path="children[${rowS.index}].name" /></td> <td>Group</td> <td><form:input path="children[${rowS.index}].group" /></td> <td><button type="button" class="remove" onclick="removeForm(${rowS.index})">Remove</button></td> </tr> <c:set var="count" value="${rowS.index + 1}" /> </c:forEach> <tr id="subformanker"><td></td></tr> <tr> <td colspan="4"><input type="submit" value="Save Changes" /></td> </tr> </table> </form:form> <script> var count = ${count}; function removeForm(index) { $("#subform" + index).remove(); console.log(count); } $(document).ready(function () { $("#add").click(function (event) { $('<tr id=subform' + count + '>').append("<td>Name</td>") .append('<td><input id="children' + count + '.name" name="children[' + count + '].name" type="text" value=""/></td>') .append('<td>Group</td>') .append('<td><input id="children' + count + '.group" name="children[' + count + '].group" type="text" value=""/></td>') .append('<td><button type="button" class="remove" onClick="removeForm(' + count + ')">Remove</button></td>') .insertBefore("#subformanker"); console.log(count); count += 1; }); }); </script> </body> </html>