繁体   English   中英

可重用的 Alpine.js 组件?

[英]Reusable Alpine.js components?

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

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

我可以在不依赖服务器模板的情况下使用纯客户端代码执行此操作吗?

是的你可以。

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

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

<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 贡献者@ryangjchandler 评论说,Alpine.js 的可重用模板超出了 scope:

提议的 [Alpine.js version 3] x-component 指令与组件的模板或标记没有任何关系。 相反,它将提供一种编写更立即可重用数据集和函数的方法,同时减少您需要在标记中定义的指令数量。

如果您需要可重复使用的模板,我会考虑使用服务器端模板引擎或更单一的前端框架,例如 Vue 或 React。 链接

您正在寻找的功能远远超出了 Alpine 的 scope。 它旨在与服务器或 static 文件中的现有标记一起工作,而不是替换/组件化您的标记。 链接

使用 Alpine.js v3 和 Global Alpine Components,您可以使用 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>

工作得很好。

更多信息 alpinejs 组件

您可以使用Alpine.data和记录的方法来使用x-bind封装指令 诀窍是绑定x-html指令。 在您的 HTML 中执行此操作:

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

在您的 Javascript 中:

document.addEventListener('alpine:init', () => {
    Alpine.data('dropdown', () => ({
        show: false,

        bind: {
            ['x-html']() { return `
                <button @click="show = !show">Click me!</button>
                <div x-show="show">Hello World</div>
            `},
        },
    }));
})

JSFiddle 在这里

这有点 hacky,因为您将所有嵌套内容封装在x-html指令中绑定的多行 HTML 字符串中(尽管可能不比到处克隆模板的替代方案更 hacky)。 确保您没有在内容中使用反引号字符。 然而,内容可以嵌套到你喜欢的深度,并且可以包含 Alpine.js 指令。 您可以通过声明参数并将参数传递给Alpine.data来初始化您的组件。 您还可以绑定x-modelable以将组件的任何属性公开为输出。

如果您更喜欢使用模板,可能是因为当标记未嵌入字符串时您的编辑器可以更好地突出显示语法,您可以将此方法与模板结合使用。 这是一个演示x-modelable和模板使用的示例。 实际上,Alpine 会为您克隆模板。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js"></script>

</head>

<body>

  <div x-data="{clicked: false}">

    <div>Clicked is <span x-text="clicked"></span></div>
  
    <div x-data="dropdown" x-bind="bind" x-model="clicked"></div>

  </div>
 
  <template id="dropdown">
    <button @click="show = !show">Click me!</button>
    <div x-show="show">Hello World</div>      
  </template>

</body>

<script type="text/javascript">

document.addEventListener('alpine:init', () => {
    Alpine.data('dropdown', () => ({
      show: false,

      bind: {
        ['x-modelable']: 'show',
        ['x-html']() { return document.querySelector('#dropdown').innerHTML},
      },
    }));
})

</script>

</html>

JSFiddle 在这里

Vimesh UI ( https://github.com/vimeshjs/vimesh-ui ) 中带有原生自定义元素的 x-component 是一个更完整的可重用组件实现:

 <head> <script src="https://unpkg.com/@vimesh/style" defer></script> <script src="https://unpkg.com/@vimesh/ui"></script> <script src="https://unpkg.com/alpinejs" defer></script> </head> <body x-cloak class="p-2" x-data="{name: 'Counter to rename', winner: 'Jacky'}"> Rename the 2nd counter: <input type="text" x-model="name" class="rounded-md border-2 border-blue-500"> <vui-counter x-data="{step: 1}":primary="true" title="First" x-init="console.log('This is the first one')" owner-name="Tom"></vui-counter> <vui-counter x-data="{step: 5}":title="name + ' @ ' + $prop('owner-name')" owner-name="Frank"></vui-counter> <vui-counter x-data="{step: 10, value: 1000}":owner-name="winner"> <vui-counter-trigger></vui-counter-trigger> </vui-counter> <template x-component.unwrap="counter":class="$prop('primary')? 'text-red-500': 'text-blue-500'" x-data="{ step: 1, value: 0}" x-init="$api.init && $api.init()" title="Counter" owner-name="nobody"> <div> <span x-text="$prop('title')"></span><br> Owner: <span x-text="$prop('owner-name')"></span><br> Step: <span x-text="step"></span><br> Value: <span x-text="value"></span><br> <button @click="$api.increase()" class="inline-block rounded-lg bg-indigo-600 px-4 py-1.5 text-white shadow ring-1 ring-indigo-600 hover:bg-indigo-700 hover:ring-indigo-700"> Increase </button> <slot></slot> </div> <script> return { init() { console.log(`Value: ${this.value}, Step: ${this.step}`) }, increase() { this.value += this.step } } </script> </template> <template x-component="counter-trigger"> <button @click="$api.of('counter').increase()" class="inline-block rounded-lg mt-2 bg-green-600 px-4 py-1.5 text-white shadow ring-1 ring-green-600 hover:bg-green-700 hover:ring-green-700"> Tigger from child element</button> </template> </body>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM