備忘録集[Vue.js+Laravel+docker]

一つの記事に書く程ではないけれども、
ハマった所を今後同じ所でハマった同志のため、

また物忘れが最近酷いので再度自分がハマらないため

自分が躓いた所を備忘録集として残します。
基本的だけれども見落としやすい所が中心です。

備忘録なので書きなぐりの文章が多いです。
また間違い等があるかもしれないので、
都度修正していきます。

# Javascript関連：VueJS,typescript,axios等
### 1.axiosのdeleteの方法

postとは異なる方法で設定する必要がある。

```javascript
axios.delete('url',
             params: { foo: 'bar' }
             ).then(
             //以下省略
             );
```

###2.[Vue.js]async,awaitでreturnの結果としてaxiosでgetしたデータを取得する。
下の例はupdateDate関数でaxiosを使っている。
このupdateDate関数は外部から呼び出され、this.Dataの値を更新している。
しかしthisを使用しているので、thisに依存している。
依存を解消するなら、axiosの結果をupdateDataの外で受け取るべき。
しかしaxiosはPromise型なので、thenの内部でreturnを書いても値を返さない。

```javascript:改善前
methods:{
    updateData(url) {
      //データ取得
      axios
        .get(
          url
        )
        .then(response => {
          this.Data=response;
          //下のように書いてもresponseを返さない
          //return respose;
        })
        .catch(error => {
          console.log(error);
        })
    },
}
```
そこでasync,awaitを利用し以下のように書き直す。
then以降まで書かないでもaxios.get(url)までで,
結果を返してくれる。

参考:https://github.com/axios/axios


```javascript:例1.thisをupdateDataの外部に出すことができた.
  mounted: async function() {
    let url = "どこかのurl";
    try {
      this.Data = await this.updateTable(url);
    } catch (err) {
      console.log(err);
    }
  },
  methods: {
    async updateData(url) {
      //axiosでデータ取得.get以降にthenを書かない。
      return axios.get(url);
    }
``` 

```javascript:例2
    async function updateData2(url) {
        try {
          const response = await axios.get(url);
          return response;
        } catch (error) {
          console.error(error);
          return error;
        }
      }
```

注意:axiosをVue.jsのmounted内部で使っている。
Axiosは例外処理も含めて多用する可能性が高いので、
慣れてきたらaxios等の非同期処理を担うクラスを作成したほうがコードが散乱しないので良い。

###3.tr等に普通にtransition-groupしてもアニメーションはつかない

参考：https://jp.vuejs.org/v2/guide/components.html#%E5%8B%95%E7%9A%84%E3%81%AA%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88

htmlの仕様によりliやtr等のタグはtableタグ直下等の特殊な条件でしか処理されない。
なので、tableとtrの間に普通にtransition-groupしてもtr以下が表示されなくなる。

```html:失敗例
<tbody name="table-row">
<transition-group>
<tr v-for="data in showData" class="table-row-item" :key="data.id">  
<!--ここは表示されない.transition-groupをdiv等にしても同じ挙動になる.-->
</tr>
</transition-group>
</tbody>
```

対策の一例は、tbodyにis="transition-group"をつけること。

```html:tbodyにisをつける回避策
<tbody name="table-row" is="transition-group">
<tr v-for="data in showData.data.data" class="table-row-item" :key="data.title">  
```

他の対策も複数あるので、必要な時は参考urlを参照。

###4.アップロードされた画像ファイルのプレビュー表示方法 + URL.createObjectURLの仕様
```html:画像の出力
    <div class="col-sm-1">
      <img class="icon-image border border-dark" :src="iconimg" />
    </div>
```
ファイルのアップロードを入力するhtmlタグは省略。
ファイルが入力された後、下のselectedFile関数を通して画像を格納する。

```javascript
    selectedFile(event) {
      //必要に応じてkeyは変更
      this.iconfile = event.target.files[0];
      //プレビュー用のイメージ格納
      if (this.iconfile.type.startsWith("image/")) {
      //ObjectURLを生成
      this.iconimg = window.URL.createObjectURL(this.iconfile);
      }
    },
```
以下はjavascriptのcreateObjectURLの仕様の話。

`createObjectURL(object)`は、
与えられたobjectに対してアクセス可能となるURLを生成している。
このURLはクライアントのブラウザのメモリ上に保存されているblob(いわゆる生のデータ)を参照する。
アップロードされたファイルはクライアント上ではFileオブジェクトで保存されているが、
File自体がblobを継承しているため、createObjectURLでのURL生成が可能となる。
なので、createObjectURL関数の引数となるobjectはfile,blob,MediaSourceのどれかでなくてはならない。
生成されたブラウザを閉じるまでURLは有効。
ただ一度ブラウザに読み込まれればURLは必要ないので、可能ならば削除したほうが良い。
`window.URL.revokeObjectURL(file);`
でURLの削除が可能。

参考：
https://developer.mozilla.org/ja/docs/Web/API/File
https://qiita.com/azu369yu/items/8998e1e1536a5acfb7b3
https://qiita.com/iLLviA/items/c24f385ca3334c05a682

### 5.[typescript+Vue.js]子コンポーネントにて、propsを定義する際にはundefinedに注意
参考：https://qiita.com/kyokoshimizu/items/ee6c6e6b905b8aa101fa
参考https://tech-up.hatenablog.com/entry/2019/03/15/152258

<b>前提:vue-property-decoratorをimportしている。</b>

```typescript
@Prop() public name:string;
```

のようにVue.jsのpropsをtypescriptで書くと

```
Property '----' has no initializer and is not definitely assigned in the constructor.
```

というエラーが出て怒られ、コンパイルができないケースがある。
しかし

```typescript
@Prop() public name:string = "";
```
と初期化したらしたで、コンパイルも稼働はするが、コンソールには

```
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "propsValue"
```

とエラーが出てしまう。
この場合、前者はtypescriptのエラーで後者がVue.jsのエラーになる。

<b>原因</b>
typescriptの設定でstrictPropertyInitializationという設定がオンになっているためである。
strictPropertyInitializationは、「undefinedの許容されてない型を初期化させる」という設定になる。
要するに変数に初期値設定しろって設定。
最初に定義したPropsはstring型であるが、何も定義されていないのでundefinedが代入される。
string型にはundefinedが許容されていないため、strictPropertyInitializationによりエラーが発生した。
２回目のPropsのように初期化すればstrictPropertyInitializationによるエラーは解決しコンパイルは可能になるが、
これは子コンポーネントでpropsを書き換えているので、今後はVue.jsがエラーを発生させた。

<b>対策</b>

四苦八苦した過程が残るけど
自分のお勧めは４番

1.tsconfig.jsonに以下を記述しstrictPropertyInitializationの設定をオフにする

```
strictPropertyInitialization:false
```

但し全体に影響する。

2.any型やstringならばUnion型に変更しundefinedで対応できるようにする。

```typescript
    @Prop() public name:string|undefined;
```

ただし、number型の場合はundefinedまたはnullとunion型にできないので、
この解決はできない。

```typescript
    @Prop() public name:any;
```
anyはどの型でも機能する。但しanyになるので型の意味があまりなくなる。
stringやnumberのような複雑じゃない型の時までanyにするのは悲しい。

3.そもそも@component以下に書く

```javascript
@Component(
    {
        props:{
            placeholder:String,
        }
    }
)
```
但しexport以下で呼び出せなくなる。

<b>4.型宣言の際に！をつける、または？をつける</b>
参考：https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#strict-class-initialization
参考：https://dev.classmethod.jp/server-side/typescript-assertions/

```typescript
    @Prop() public name!:any;
```
!はコンパイラにこの型はnon-nullですよと伝える意味がある。
初期化されてないが実際には外部で変数が代入されている（丁度今回みたいな）場合に使える。
ちなみに `@Prop()` の中身にはoptionが設定できる。

```typescript:ソースより
export interface PropOptions<T=any> {
  type?: PropType<T>;
  required?: boolean;
  default?: T | null | undefined | (() => T | null | undefined);
  validator?(value: T): boolean;
}
```

defaultとか設定したいときは次のような感じ

```typescript
@Prop({default:"text"}) public type!:string;
```

ちなみに上のソースコードでも出現しているが、!の代わりに?をつけても解決する。
?は代入されてなくても構わない、という意味になる。

### 6.[Vue.js]vue-routerのto属性に対してparamを設定しpropを渡したい時、pathではなくnameでurlを呼び出さないといけない

```html
<router-link
:to="{name:'hoge',params:{huga: 'hage'}}"
>
```

でないといけない。router.jsの方でnameで設定しないとparamsが有効にならない。
pathを設定するとurlに直接paramsを組み込まなければならない。

#Laravel

###1.モデルでSELECTする時にidを取得しないと、そのインスタンスにsaveもupdateもできない

大ハマり

参考：https://qiita.com/moimoinon/items/5adb4b179e3c25d189a2

```php:悪い例
$data = Article::select('title','updated_at')->with('hashtags:name')->get();
```
例のようにモデルのidを取得していないインスタンスにsaveメソッドを使用して更新しようとした場合、
saveの結果としてtrueが返されてもデータベース上では更新されない。
この後の例等、予期せぬ挙動を生むことが多いので、selectする時は基本的にidは取得したほうが良い。


###2.EagerLoadを使う場合、SELECTでidを取らないとwithで参照してくれない
参考：https://mseeeen.msen.jp/laravel-5-5-get-specified-column-with-with/

relationのwithも、idを軸にリレーション先のデータを取得している。
よって、もしSELECT元でidが選択されていない場合、withの参照先のデータも取得されない。

```php:良い例
//もしidのカラムを取得していない場合はhashtagsが空になってしまう
$data = Article::select('title','updated_at','id')->with('hashtags:name')->get();
```

###3.belongsToの場合のモデル名は単数。データ取得の際も関数でなくプロパティを通して取得。
HasManyと同じつもりで複数形にすると、参照先モデルの取得が正しくされない。

```php:悪い例
    public function admins(){
        return $this->belongsTo(Admin::class);
    }
```

belongsToの参照先は１対多の１側なので、単数にしなければならない。

```php:良い例
    public function admin(){
        return $this->belongsTo(Admin::class);
    }
```

リレーション先のデータを取得する際も単数形で指示する。
<b>プロパティでアクセスできる</b>のが大きな違い。

```php:例
    //adminに対して()をつけずにプロパティでアクセスできる。
    public function scopeGetAdminName($query,$id){
        return $query->find($id)->admin->name;
    }

    //EagerLoad,withのadminが単数形になるのがhasManyの時との違い
    public function scopeWithAdmin($query){
        return $query->with('admin:id,name');
    }

```
#その他

###1,[docker] php:7.4.2-apacheのdockerイメージでドキュメントルートを変更する方法
/etc/apache2/sites-available/000-default.confのドキュメントルートを変更

```conf:000-default.conf
<VirtualHost *:80>
	# The ServerName directive sets the request scheme, hostname and port that
	# the server uses to identify itself. This is used when creating
	# redirection URLs. In the context of virtual hosts, the ServerName
	# specifies what hostname must appear in the request's Host: header to
	# match this virtual host. For the default virtual host (this file) this
	# value is not decisive as it is used as a last resort host regardless.
	# However, you must set it for any further virtual host explicitly.
	#ServerName www.example.com

	ServerAdmin webmaster@localhost
        #ここを変更
	DocumentRoot /var/www/html/
```