可重用 Alpine.js 组件?

Reusable Alpine.js components?

如何使用 Alpine.js 创建可重用组件并显示它?例如,也许我想定义一个通用的 Alpine.js 按钮组件,它根据参数更改文本和颜色,然后让我的 Alpine.js 导航栏组件使用按钮组件显示登录按钮。

我可以在纯客户端代码中执行此操作,而不依赖服务器模板化所有按钮 HTML 在所有使用按钮组件的地方吗?

Alpine.js 贡献者@ryangjchandler 评论说可重用模板超出了 Alpine.js:

的范围

The proposed [Alpine.js version 3] x-component directive will NOT have anything to do with templating or the markup for your component. Instead it will provide a way of writing more immediately reusable data sets & functions, whilst reducing the amount of directives you need to define in your markup.

If you need re-usable templates, I would consider using a server-side template engine or a more monolithic front end framework such as Vue or React. (link)

The functionality you are looking for is far out of the scope of Alpine. It's designed to work alongside your existing markup from the server or static files, not replace / component-ise your markup. (link)

Can I do this in pure client-side code, without relying on a server templating?

是的,你可以。

Alpine.js 总是会尝试 persuade 您使用服务器端模板引擎。

但就像你一样,我不让自己被说服:

<template x-component="dropdown">
    <div x-data="{ ...dropdown(), ...$el.parentElement.data() }">
        <button x-on:click="open">Open</button>

        <div x-show="isOpen()" x-on:click.away="close" x-text="content"></div>
    </div>
</template>

<x-dropdown content="Content for my first dropdown"></x-dropdown>

<div> Random stuff... </div>

<x-dropdown content="Content for my second dropdown"></x-dropdown>

<x-dropdown></x-dropdown>

<script>
    function dropdown() {
        return {
            show: false,
            open() { this.show = true },
            close() { this.show = false },
            isOpen() { return this.show === true },
            content: 'Default content'
        }
    }

    // The pure client-side code
    document.querySelectorAll('[x-component]').forEach(component => {
        const componentName = `x-${component.getAttribute('x-component')}`
        class Component extends HTMLElement {
            connectedCallback() {
                this.append(component.content.cloneNode(true))
            }
 
            data() {
                const attributes = this.getAttributeNames()
                const data = {}
                attributes.forEach(attribute => {
                    data[attribute] = this.getAttribute(attribute)
                })
                return data
            }
        }
        customElements.define(componentName, Component)
    })
</script>

使用 Alpine.js v3 和全局 Alpine 组件,您可以使用 Alpine.component() 来封装此功能。

<div x-data="dropdown">
...
</div>

<script>
    Alpine.component('dropdown', () => ({
        open: false,
        toggle() { this.open = !this.open }
    }))
</script>

使用alpinejs-component

cdn 同一页面:

<div
  x-data="{
    people: [
      { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
      { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
    ]
  }"
>
  <ul>
    <template x-for="person in people">
      <!-- use the person template to find the <template id="person"> element. -->
      <x-component-wrapper x-component template="person" x-data="{ item: person }"></x-component-wrapper>
    </template>
  </ul>
</div>

<template id="person">
  <li class="user-card">
    <h2 x-text="item.name"></h2>
    <p x-text="item.age"></p>
    <ul>
      <template x-for="skill in item.skills">
        <li x-text="skill"></li>
      </template>
    </ul>
  </li>
</template>

<script src="https://unpkg.com/alpinejs-component@1.x.x/dist/component.min.js"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>

使用url导入html模板:

<div
  x-data="{
    people: [
      { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
      { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
    ]
  }"
>
  <ul>
    <template x-for="person in people">
      <x-component-wrapper x-component url="/public/person.html" x-data="{ item: person }"></x-component-wrapper>
    </template>
  </ul>
</div>

<script src="https://unpkg.com/alpinejs-component@1.x.x/dist/component.min.js"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>

person.html:

<li class="user-card">
  <h2 x-text="item.name"></h2>

  <p x-text="item.age"></p>

  <ul>
    <template x-for="skill in item.skills">
      <li x-text="skill"></li>
    </template>
  </ul>
</li>

通过 npm 安装:

npm i -D alpinejs-component

yarn add -D alpinejs-component

注册插件:

import Alpine from "alpinejs";
import component from "alpinejs-component";

Alpine.plugin(component);

window.Alpine = Alpine;

Alpine.start();

或在浏览器中使用模块:

<x-component-wrapper x-component template="dropdown" x-data="dropdown"></x-component-wrapper>
<x-component-wrapper x-component template="dropdown" x-data="dropdown"></x-component-wrapper>

<template id="dropdown">
  <div @click="close" class="dropdown-toggle">
    <button x-on:click="open">Open</button>
    <div x-show="show" x-text="content"></div>
  </div>
</template>

<script type="module">
  import { default as Alpine } from 'https://cdn.skypack.dev/alpinejs'
  import alpinejsComponent from 'https://cdn.skypack.dev/alpinejs-component'
  function dropdown() {
    return {
      show: false,
      open() {
        console.log('open')
        this.show = true
        console.log(this.show)
      },
      close(event) {
        const button = this.$el.querySelector('button')
        const target = event.target
        if (this.$el.contains(target) && !button.contains(target)) {
          this.show = false
        }
      },
      get isOpen() {
        return this.show === true
      },
      content: 'Default content',
      init() {
        console.log(this.$el.parentElement)
        console.log('dropdown --- init')
      },
    }
  }
  Alpine.data('dropdown', dropdown)
  Alpine.plugin(alpinejsComponent)
  Alpine.start()
</script>

工作顺利。

more info alpinejs-component