mrmapleleaf
@mrmapleleaf (Kaede Maruyama)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Vue.jsのDataオプションに指定したプロパティを更新できない

解決したいこと

APIから取得したデータを画面表示できない。

Vue.jsとJavaを使って、urlにアクセスした際にDBに格納されている名前の一覧を返す
小さな練習用アプリケーションを作成しています。
Vue.jsのMountedオプションを使ってAPIを呼び出し、データを取得して、
画面に表示する流れなのですが、データ取得後、dataオプション内に指定した
データ格納用のプロパティの値が更新されず、一覧表示されない事象が発生しています。
値の詰め方やライフサイクルフックの問題かと予想していますが、答えが掴めずにおります。

ご助力頂けますと幸いです。

発生している問題・エラー

APIの実行は行われて、、レスポンスは返って来ているが、dataプロパティへの格納が出来ていない様子
スクリーンショット 2023-10-20 12.25.33.png

環境

API

  • Java17
  • Spring boot 3.1.4
  • h2Database
  • tomcat10.1.13

フロントエンド

  • Vue3
  • axios

本事象に関連すると思われるソースコード

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="/css/main.css" />
  </head>
  <body>
    <div id="app">
      <ul>
        <li v-for="member in items.memberList">
        	{{ member.name }}
        </li>
      </ul>
    </div>
    <script src="https://unpkg.com/vue@3.1.5"></script>
    <script src="https://unpkg.com/axios@0.21.1/dist/axios.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.js"></script>
    <script src="/js/main.js"></script>
  </body>
</html>


Vue.js
const app = Vue.createApp({
  data: () => ({
    items:''
  }),
  watch: {},
  mounted: function () {
    console.log('hello');
    this.getMemberList();
    console.log(this.items)
  },
  methods: {
    getMemberList: function () {

      axios
        .get('http://localhost:8080/sample/index')
        .then(function (response) {
          console.log('response');
          console.log(response)
          this.items = response.data;
        })
        .catch(function (error) {
          console.log('error' + error);
        })
        .finally(function () {
          console.log('API finished');
        });
    },
  },
});

app.mount('#app');

//エンティティクラス
package com.example.demo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "member01")
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class Member {

	@Id
	private int id;
	
	private String name;
	
}

//レスポンスクラス
package com.example.demo.response;

import java.util.List;

import com.example.demo.entity.Member;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties({"hibernateLazyInitializer"})
@Getter
public class JavaAndVueResponse {
	
	List<Member> memberList;
	
}

上記以外に必要な情報ありましたら、お声掛けいただけますと幸いです。
宜しくお願い致します。

0

3Answer

画像を適切にトリミングして貼り直してください。つぶれて読めない。


【追記】

質問者さん @mrmapleleaf が書いた回答へのコメントにも書きましたが、質問のコードの以下の部分の this.items = response.data; の this は、Vue.js のオブジェクトではなく、window オブジェクトを指すので、<li v-for="member in items.memberList"> の items にデータを渡せなかった(結果メンバー一覧を表示できなかった)ということだったようです。

  methods: {
    getMemberList: function () {

      axios
        .get('http://localhost:8080/sample/index')
        .then(function (response) {
          console.log('response');
          console.log(response)
          this.items = response.data;
        })

自分の環境で検証してみました。

以下のコードで試してみると、function を使った場合(コメントアウトとした方)は this は window に、アロー関数を使った場合 this は Vue.js が生成する Proxy になります。

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <div id="app">
      <ul>
        <li v-for="member in items.memberList">
        	{{ member.name }}
        </li>
      </ul>
    </div>
    <script src="https://unpkg.com/vue@3.1.5"></script>
    <script src="https://unpkg.com/axios@0.21.1/dist/axios.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.js"></script>
    <script type="text/javascript">
        const app = Vue.createApp({
            data: () => ({
                items: ''
            }),
            watch: {},
            mounted: function () {
                this.getMemberList(); 
            },
            methods: {
                getMemberList: function () {
                    axios
                        .get('https://localhost:44331/handler3.ashx')
                        //.then(function (response) {
                        //    this.items = response.data;

                        //    console.log(response);
                        //    console.log(this.items);
                        //})
                        .then(response => {                            
                            this.items = response.data;

                            console.log(response);
                            console.log(this.items);
                        })
                }
            }
        });

        app.mount('#app');
    </script>  
</body>
</html>

上のコードの実行結果は以下のようになります。

result.jpg

1Like

Comments

  1. @mrmapleleaf

    Questioner

    @SurferOnWwwさん
    画像再掲載致しました。文字のつぶれ、なくなりましたでしょうか?

  2. 画像の修正をありがとうございました。

    開発者ツールの「ソース」タブでステップ実行していったときに、

    methods: {
    getMemberList: function () {

    メソッドの中の

    this.items = response.data;

    には期待した結果は得られているのでしょうか?

    期待した結果は得られているのに、その上のコードの、

    this.getMemberList();
    console.log(this.items)

    console.log(this.items) では質問に貼られた画像のように何も出力されないというのが問題なのでしょうか?

    であれば、getMemberList は非同期メソッドなので、axios.get の応答が返ってくる前に次の行に進んで console.log(this.items) が実行されてしまい、その時点では this.items には応答が取得できてないというのが問題ではないのですか?

  3. @mrmapleleaf

    Questioner

    @SurferOnWwwさん

    開発者ツールの「ソース」タブでステップ実行していったときに、
    methods: {
    getMemberList: function () {
    メソッドの中の
    this.items = response.data;
    には期待した結果は得られているのでしょうか?

    確認したところ、~this.items = response.data;~の時点で、期待した結果は取得できているように見えました。(下記画像参照)
    スクリーンショット 2023-10-20 14.39.19.png

    console.log(this.items) では質問に貼られた画像のように何も出力されないというのが問題なのでしょうか?
    今回問題に上げさせていただいたのは「APIからデータを取得し、Dataオプションのitemsプロパティにデータを格納しているはずなのに、index.htmlのv-forを使って一覧表示できていない」という点です。

    ※1ステップずつの動きを見て、getMemberList()が非同期で動いていること確認しました。ありがとうございます。getMemberList()が完了する前に、画面が表示されてしまい、一覧が表示できていないのでしょうか?

  4. 今回問題に上げさせていただいたのは「APIからデータを取得し、Dataオプションのitemsプロパティにデータを格納しているはずなのに、index.htmlのv-forを使って一覧表示できていない」という点です。

    たぶん、「index.htmlのv-forを使って一覧表示」するタイミングでは、「Dataオプションのitemsプロパティにデータを格納」(コードの this.items = response.data;)ができてないからダメということではないかと思います。

    Java, Spring boot, vue.js 的にどうやるのが正解なのかはそのあたりの知識がない自分には分かりませんが、間違いなくできる方法としては、API から応答が返ってきて .then の中のコールバック function が呼ばれてから response の中のデータを JavaScript で ul 要素の中に追加してやることだと思います。

    具体例は以下の記事の「クライアント側 (MVC の View)」のコードを見てください。

    Web API に CORS 実装 (CORE)
    http://surferonwww.info/BlogEngine/post/2023/01/21/aspnet-core-web-api-with-cors-middle-ware.aspx

    抜粋して説明すると、html の <ul id="heroes"></ul> 要素の中に、JavaScript オブジェクトの配列 data を書き込んでいます。結果はその記事の画像のようになります。

    const elem = document.querySelector("#heroes");
    
    // ・・・中略・・・
    elem.innerHTML = "";
    for (let i = 0; i < data.length; i++) {
        elem.insertAdjacentHTML("beforeend", 
            `<li>${data[i].id}: ${data[i].name}</li>`);
    }
    
  5. @mrmapleleaf

    Questioner

    参考サイトまで掲示いただきありがとうございます!
    関数の同期、非同期やライフサイクルフックなどの理解が甘いと痛感しました。
    改めて、方法考えてみます。

一旦データをもらう視点を予測できるように非同期の処理を同期に変えた方がいいと思います。
また、今のコードを見るとDom(現在ページのHTML構成)が作られる前にデータをもらって「items」に入れた方がいいと思うので、ロジックを「mounted」ではなくて「create」で変えてみました。

参考になると幸いです。

const app = Vue.createApp({
	data: () => ({
	  items:''
	}),
	watch: {},
	create: function () {
	  	this.getMemberList();
		console.log(this.items) ;
	},
	methods: {
	  getMemberList: async function () {
		try {
			const response = await axios.get('http://localhost:8080/sample/index') ;
			this.items = response.data.memberList ;
		} catch (error) {
			console.log(error) ;
		} finally {
			console.log('API finished');
		}
	  },
	},
});
  
app.mount('#app');

「mounted」と「create」の違いは下のURLをご参考ください。

1Like

Comments

  1. @mrmapleleaf

    Questioner

    @heesukim998 さん
    回答頂きありがとうございます。
    サンプルコードまで掲載頂き、感謝です...mm
    そもそもの関数の同期、非同期処理やDOM、ライフサイクルフックの理解がかなり甘かったです。
    共有いただいた資料もとに、理解深めようと思います。
    回答ありがとうございました!

以下、2点の変更を行なった結果、v-forでの一覧表示ができました。
①thenメソッドの中でデータを受け取る際に、dataではなく、memberListを渡す。
②thenメソッド後に{}で記載していた処理を、thenメソッドの()の中に{}を記述し、アロー関数でresponseを渡す。

修正の参考にしたページ:https://qiita.com/Takanori30907/items/26e0244d983123f900c1

①の変更を加えても、一覧表示はできなかった為、②の変更によりitemsに正しくAPIのレスポンスから取得したデータが格納できたのだと思います。
理解も納得もあまり出来ていないので、さらに勉強していきます。

Vue.js
const app = Vue.createApp({
  data: () => ({
    items:''
  }),
  watch: {},
  mounted: function () {
    console.log('hello');
    this.getMemberList();
    console.log(this.items)
  },
  methods: {
    getMemberList: function () {

      axios
        .get('http://localhost:8080/sample/index')
        //変更②
        .then(response => {
          console.log('response');
          //変更①
          this.items = response.data.memberList;
          console.log(this.items)
        })
        .catch(function (error) {
          console.log('error' + error);
        })
        .finally(function () {
          console.log('API finished');
        });
    },
  },
});

app.mount('#app');
1Like

Comments

  1. 以下、2点の変更を行なった結果、v-forでの一覧表示ができました。
    ①thenメソッドの中でデータを受け取る際に、dataではなく、memberListを渡す。
    ②thenメソッド後に{}で記載していた処理を、thenメソッドの()の中に{}を記述し、アロー関数でresponseを渡す。

    それで解決できるとは思えませんが?

    ① については、最初の質問と時と response の中身が変わっていなければ、data は以下のような JavaScript オブジェクト(連想配列)のはずです。

    console.jpg

    一方、v-for のところは以下のようになっているそうなので、最初の質問に書いてあった this.items = response.data; で良いのでは?

    <li v-for="member in items.memberList">
        {{ member.name }}
    </li>
    

    ② については、アロー関数式は従来の function の簡潔な書き方に過ぎず、最初の質問にあった、

    function (response) { ... }

    をアロー関数式で書くと、

    response => { ... }

    となりますが、機能的には同じです。

    いろいろ試しているうちに最初の話と変わってきたということはありませんか?

  2. 自分の環境で検証してみました。

    ②thenメソッド後に{}で記載していた処理を、thenメソッドの()の中に{}を記述し、アロー関数でresponseを渡す。

    というのは、function を使った場合とアロー関数を使った場合では、その中で使っている this が異なるということで結果に違いが出たようです。

    以下のコードで試してみると、function を使った場合(コメントアウトとした方)は this は window を、アロー関数を使った場合 this は Vue.js が生成する Proxy を指します。

    <!DOCTYPE html>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title></title>
    </head>
    <body>
        <div id="app">
          <ul>
            <li v-for="member in items.memberList">
            	{{ member.name }}
            </li>
          </ul>
        </div>
        <script src="https://unpkg.com/vue@3.1.5"></script>
        <script src="https://unpkg.com/axios@0.21.1/dist/axios.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.js"></script>
        <script type="text/javascript">
            const app = Vue.createApp({
                data: () => ({
                    items: ''
                }),
                watch: {},
                mounted: function () {
                    this.getMemberList(); 
                },
                methods: {
                    getMemberList: function () {
                        axios
                            .get('https://localhost:44331/handler3.ashx')
                            //.then(function (response) {
                            //    this.items = response.data;
    
                            //    console.log(response);
                            //    console.log(this.items);
                            //})
                            .then(response => {                            
                                this.items = response.data;
    
                                console.log(response);
                                console.log(this.items);
                            })
                    }
                }
            });
    
            app.mount('#app');
        </script>  
    </body>
    </html>
    

    なので、function を使った場合は this.items = response.data; で v-for が使う items にデータを渡せないということがメンバー一覧を表示できなかった原因だったようです。

    ①thenメソッドの中でデータを受け取る際に、dataではなく、memberListを渡す。

    これについては、最初に質問のコードの通り this.items = response.data; でよさそうです。上のサンプルでもそうしており、結果は以下の通りとなります。

    result.jpg

    ということで、結局は、function を使った場合の this は window を指すということが問題だったようです。

  3. @mrmapleleaf

    Questioner

    @SurferOnWwwさん
    検証、解説までして頂きありがとうございます!
    アロー関数とfunctionでthisが指すものが変わるのですね。

Your answer might help someone💌