Aline.js x-data 不响应

问题描述 投票:0回答:1

我正在使用 Alpine.js 通过 API 渲染视频列表。进行 API 调用后,响应数据成功填充我的视频数组,但页面未更新以显示新数据。

以下是所发生事件的详细说明:

我正在发出 Axios API 请求并将生成的视频推送到视频数组。 我已通过控制台日志确认视频填充了正确的数据。 尽管如此,模板不会渲染更新后的数组。

谢谢!

  <template x-for="video in videos" :key="video.id">
    <!--video content-->
    <div>Test Video</div>
  </template>
      const app = {
            triggerElement: null,
            page: 1,
            lastPage: 24,
            itemsPerPage: 24,
            observer: null,
            isObserverPolyfilled: false,
            videos: [],
            debug: true,
            loaded: false,

            init: function () {

                window.addEventListener('urlChange', () => {
                    app.getItems();
                });

                app.triggerElement = document.querySelector('#infinite-scroll-trigger');

                document.addEventListener('DOMContentLoaded', function () {
                  //  app.getItems();
                    app.loaded = true;
                });

                app.infiniteScroll();

                (app.debug) ? console.log('init' )  : '';


            },
            infiniteScroll: function () {

                (app.debug) ? console.log('infiniteScroll' )  : '';

                // Check if browser can use IntersectionObserver which is waaaay more performant
                if (!('IntersectionObserver' in window) ||
                    !('IntersectionObserverEntry' in window) ||
                    !('isIntersecting' in window.IntersectionObserverEntry.prototype) ||
                    !('intersectionRatio' in window.IntersectionObserverEntry.prototype)) {
                    // Loading polyfill since IntersectionObserver is not available
                    this.isObserverPolyfilled = true

                    // Storing function in window so we can wipe it when reached last page
                    window.alpineInfiniteScroll = {
                        scrollFunc() {
                            var position = app.triggerElement.getBoundingClientRect()

                            if (position.top < window.innerHeight && position.bottom >= 0) {
                                if (app.loaded) {
                                    (app.debug) ? console.log('getItems 1' )  : '';

                                    app.getItems();
                                }

                            }
                        }
                    }

                    window.addEventListener('scroll', window.alpineInfiniteScroll.scrollFunc)
                } else {
                    // We can access IntersectionObserver
                    this.observer = new IntersectionObserver(function (entries) {
                        if (entries[0].isIntersecting === true) {
                            if (app.loaded) {
                                (app.debug) ? console.log('getItems 2' )  : '';

                                app.getItems();
                            }
                        }
                    }, {threshold: [0]})

                    this.observer.observe(this.triggerElement)
                }
            },
            getItems: function () {
                // TODO: Do fetch here for the content and concat it to populated items
                // TODO: Set last page from API call - ceil it

                let currentURL = new URL(window.location.href);
                currentURL.hostname = 'api.' + currentURL.hostname;


                axios.post(currentURL, {
                    page: this.page,
                    perPage: this.itemsPerPage,

                })
                    .then(response => {
                        const data = response.data;
                        //  this.lastPage = data.total_pages;
                        app.videos = data.data.videos;
                        //  this.page++;
                        (app.debug) ? console.log(response)  : '';

                    })
                    .catch(error => {
                        console.error('Error loading videos:', error);
                        (app.debug) ? console.log(error)  : '';
                        this.loading = false;
                    });


                // Next page
                this.page++

                // We have shown the last page - clean up
                if (this.lastPage && this.page > this.lastPage) {
                    if (this.isObserverPolyfilled) {
                        window.removeEventListener('scroll', window.alpineInfiniteScroll.scrollFunc)
                        (app.debug) ? console.log('alpineInfiniteScroll')  : '';

                    } else if (this.observer && this.triggerElement) {
                        try {
                            this.observer.unobserve(this.triggerElement);
                        } catch (e) {
                            console.error('Failed to unobserve element:', e);
                        }
                    }

                    if (this.triggerElement && this.triggerElement.parentNode) {
                        this.triggerElement.parentNode.removeChild(this.triggerElement);
                        this.triggerElement = null; // Prevent further access to the removed element
                    }

                }
            }
        };

更新视频后使用 this.$nextTick() 以确保 Alpine.js 更新。 手动触发 Alpine.js 反应性。 直接操作 DOM 作为临时解决方法(并不理想)。 这些方法都没有解决问题。如果您了解 Alpine.js 可能不重新渲染更新后的数据或任何替代解决方案的原因,我将不胜感激。

javascript axios alpine.js
1个回答
0
投票

我尝试在本地计算机上重现您的问题,并最终编写了一个完整的工作示例。我希望它能帮助您与实际代码库进行比较,看看有什么问题:

index.html

确保将所有内容都用

div
x-data
包裹到
x-init
中。 您还会注意到,我创建了一个函数,该函数返回一个应用程序对象,并对您的代码进行了一些更改,如下面的 js 示例所示。通过在组件中定义 init 并通过 x-init 调用它,您可以确保您的设置代码在组件初始化时运行。

<div x-data="app()" x-init="init">
  <!-- Video List -->
  <div id="video-list">
    <template x-for="video in videos" :key="video.id">
      <div class="video">
        <h3 x-text="video.title"></h3>
        <p x-text="video.description"></p>
      </div>
    </template>
  </div>

  <!-- Infinite Scroll Trigger -->
  <div id="infinite-scroll-trigger"></div>
</div>

app.js

function app() {
  return {
    // Data properties
    triggerElement: null,
    page: 1,
    lastPage: null,
    itemsPerPage: 10,
    observer: null,
    isObserverPolyfilled: false,
    videos: [],
    debug: true,
    loading: false,

    // Initialization method
    init() {
      this.triggerElement = document.querySelector("#infinite-scroll-trigger");
      this.infiniteScroll();

      if (this.debug) console.log("Initialization complete.");
    },

    // Method to set up infinite scroll
    infiniteScroll() {
      if (this.debug) console.log("Setting up infinite scroll.");

      const supportsIntersectionObserver = "IntersectionObserver" in window;

      if (supportsIntersectionObserver) {
        // Use IntersectionObserver for better performance
        this.observer = new IntersectionObserver(
          this.handleIntersection.bind(this),
          {
            threshold: 0,
          }
        );
        this.observer.observe(this.triggerElement);
      } else {
        // Fallback for browsers without IntersectionObserver support
        this.isObserverPolyfilled = true;
        this.scrollFunc = this.handleScroll.bind(this);
        window.addEventListener("scroll", this.scrollFunc);
      }
    },

    // Handler for IntersectionObserver
    handleIntersection(entries) {
      if (entries[0].isIntersecting && !this.loading) {
        if (this.debug)
          console.log("Trigger element intersected. Loading items...");
        this.getItems();
      }
    },

    // Handler for scroll event (polyfill)
    handleScroll() {
      const position = this.triggerElement.getBoundingClientRect();

      if (
        position.top < window.innerHeight &&
        position.bottom >= 0 &&
        !this.loading
      ) {
        if (this.debug)
          console.log("Trigger element visible. Loading items...");
        this.getItems();
      }
    },

    // Method to fetch items from the mock API
    async getItems() {
      if (this.loading) return; // Prevent multiple calls
      this.loading = true;

      try {
        const response = await axios.get("http://localhost:3000/videos", {
          params: {
            _page: this.page,
            _limit: this.itemsPerPage,
          },
        });

        const totalItems = response.headers["x-total-count"];
        this.lastPage = Math.ceil(totalItems / this.itemsPerPage);

        this.videos = this.videos.concat(response.data);

        if (this.debug) console.log(`Loaded page ${this.page}.`, response.data);

        this.page++;

        // Check if we've reached the last page
        if (this.page > this.lastPage) {
          this.cleanup();
        }
      } catch (error) {
        console.error("Error loading videos:", error);
      } finally {
        this.loading = false;
      }
    },

    // Cleanup method to remove observers and event listeners
    cleanup() {
      if (this.isObserverPolyfilled && this.scrollFunc) {
        window.removeEventListener("scroll", this.scrollFunc);
        if (this.debug) console.log("Removed scroll event listener.");
      }

      if (this.observer) {
        this.observer.disconnect();
        if (this.debug) console.log("Disconnected IntersectionObserver.");
      }

      if (this.triggerElement) {
        this.triggerElement.remove();
        this.triggerElement = null;
        if (this.debug) console.log("Removed trigger element.");
      }
    },
  };
}

JavaScript 文件的关键更新:

  • 通过函数实例化app对象,并通过
    this
    语句而不是
    app
    引用属性和方法。在 Alpine 组件的方法中, this 指的是组件的数据对象。使用它,您可以访问和修改反应属性。
© www.soinside.com 2019 - 2024. All rights reserved.