响应式

声明状态

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";

export default class NameComponent extends Component {
  name = "张三";

  <template>
    <h1>你好 {{this.name}}</h1>
  </template>
}

Alpine 标志 Alpine

<h1 x-data="{ name: '张三' }" x-text="name"></h1>

更新状态

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

export default class CounterComponent extends Component {
  @tracked name = "张三";

  constructor(owner, args) {
    super(owner, args);

    this.name = "李四";
  }

  <template>
    <h1>你好 {{this.name}}</h1>
  </template>
}

Alpine 标志 Alpine

<h1 x-data="{ name: '张三' }" x-init="name = '李四'" x-text="name"></h1>

计算属性

Ember Polaris 标志 Ember Polaris

import Component, { tracked } from "@glimmer/component";

export default class DoubleCount extends Component {
  @tracked count = 10;

  get doubleCount() {
    return this.count * 2;
  }

  <template>
    <div>{{this.doubleCount}}</div>
  </template>
}

Alpine 标志 Alpine

<h1
  x-data="{
  count : 10,
  get doubleCount() { return this.count * 2 }
}"
  x-text="doubleCount"
></h1>

模板语法

最简模板

Ember Polaris 标志 Ember Polaris

<template>
  <h1>你好,世界</h1>
</template>

Alpine 标志 Alpine

<h1>你好,世界</h1>

样式

Ember Polaris 标志 Ember Polaris

<template>
  <h1 class="title">我是红色的</h1>
  <button style="font-size: 10rem;">我是一个按钮</button>

  <style>
    .title {
      color: red;
    }
  </style>
</template>

Alpine 标志 Alpine

<h1 class="title">我是红色的</h1>
<button style="font-size: 10rem">我是一个按钮</button>

<style>
  .title {
    color: red;
  }
</style>

DOM循环

Ember Polaris 标志 Ember Polaris

const colors = ["红", "绿", "蓝"];

<template>
  <ul>
    {{#each colors as |color|}}
      <li>{{color}}</li>
    {{/each}}
  </ul>
</template>

Alpine 标志 Alpine

<ul x-data="{ colors: ['红', '绿', '蓝'] }">
  <template x-for="color in colors">
    <li x-text="color"></li>
  </template>
</ul>

点击事件

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

export default class Counter extends Component {
  @tracked count = 0;

  incrementCount = () => this.count++;

  <template>
    <p>计数器: {{this.count}}</p>
    <button {{on "click" this.incrementCount}}>+1</button>
  </template>
}

Alpine 标志 Alpine

<div x-data="{ count: 0 }">
  <p>计数器: <span x-text="count"></span></p>
  <button x-on:click="count++">+1</button>
</div>

DOM引用

Ember Polaris 标志 Ember Polaris

import { modifier } from "ember-modifier";

const autofocus = modifier((element) => element.focus());

<template>
  <input {{autofocus}} />
</template>

Alpine 标志 Alpine

<input x-init="$el.focus();" />

条件渲染

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

const TRAFFIC_LIGHTS = ["红灯", "黄灯", "绿灯"];

export default class TrafficLight extends Component {
  @tracked lightIndex = 0;

  get light() {
    return TRAFFIC_LIGHTS[this.lightIndex];
  }

  nextLight = () => {
    this.lightIndex = (this.lightIndex + 1) % TRAFFIC_LIGHTS.length;
  };

  <template>
    <button {{on "click" this.nextLight}}>下一个灯</button>
    <p>现在亮着的是: {{this.light}}</p>
    <p>
      你应该
      {{#if (eq this.light "红灯")}}
        停下
      {{else if (eq this.light "黄灯")}}
        慢行
      {{else if (eq this.light "绿灯")}}
        赶紧走
      {{/if}}
    </p>
  </template>
}

Alpine 标志 Alpine

<div
  x-data="{
  TRAFFIC_LIGHTS: ['红灯', '黄灯', '绿灯'],
  lightIndex: 0,
  get light() { return this.TRAFFIC_LIGHTS[this.lightIndex] },
  nextLight() {
    this.lightIndex = (this.lightIndex + 1) % this.TRAFFIC_LIGHTS.length;
  }
}"
>
  <button x-on:click="nextLight">下一个灯</button>
  <p>现在亮着的是: <span x-text="light"></span></p>
  <p>
    你应该
    <span x-show="light === '红灯'">停下</span>
    <span x-show="light === '黄灯'">慢行</span>
    <span x-show="light === '绿灯'">赶紧走</span>
  </p>
</div>

生命周期

组件加载时

Ember Polaris 标志 Ember Polaris

const pageTitle = () => document.title;

<template>
  <p>页面标题: {{(pageTitle)}}</p>
</template>

Alpine 标志 Alpine

<p
  x-data="{ pageTitle: '' }"
  x-init="$nextTick(() => { pageTitle = document.title })"
>
  页面标题: <span x-text="pageTitle"></span>
</p>

组件卸载时

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { registerDestructor } from "@ember/destroyable";

export default class Time extends Component {
  @tracked time = new Date().toLocaleTimeString();

  constructor(owner, args) {
    super(owner, args);

    let timer = setInterval(() => {
      this.time = new Date().toLocaleTimeString();
    }, 1000);

    registerDestructor(this, () => clearInterval(timer));
  }

  <template>
    <p>当前时间: {{this.time}}</p>
  </template>
}

Alpine 标志 Alpine

<p
  x-data="{
  time: new Date().toLocaleTimeString(),
  timer: null,
  init() { this.timer = setInterval(() => (this.time = new Date().toLocaleTimeString()), 1000) },
  destroy() { clearInterval(this.timer) }
}"
>
  当前时间: <span x-text="time"></span>
</p>

组件组合

组件Props传参

Ember Polaris 标志 Ember Polaris

import UserProfile from "./user-profile.gjs";

const favoriteColors = ["green", "blue", "red"];

<template>
  <UserProfile
    @name="张三"
    @age={{20}}
    @favouriteColors={{favoriteColors}}
    @isAvailable={{true}}
  />
</template>

Alpine 标志 Alpine

<!--Alpine JS suggests using a server-side templating engine or another frontend framework in conjunction with Alpine to do this-->

<div
  x-data="{
  name: '张三',
  age: 20,
  favouriteColors: ['green', 'blue', 'red'],
  isAvailable: true
}"
>
  <p>我的名字是 <span x-text="张三"></span></p>
  <p>My age is <span x-text="age"></span></p>
  <p>
    My favourite colors are <span x-text="favouriteColors.join(', ')"></span>
  </p>
  <p>I am <span x-text="isAvailable ? 'available' : 'not available'"></span></p>
</div>

向父组件emit事件

Ember Polaris 标志 Ember Polaris

<template>
  <button {{on "click" @onYes}}> 是的! </button>
  <button {{on "click" @onNo}}> 不是! </button>
</template>

Alpine 标志 Alpine

<div
  x-data="{ isHappy: true }"
  x-on:yes="isHappy = true"
  x-on:no="isHappy = false"
>
  <p>你快乐吗?</p>
  <div>
    <button x-on:click="$dispatch('yes')">是的!</button>
    <button x-on:click="$dispatch('no')">不是!</button>
  </div>
  <p style="font-size: 50px" x-text="isHappy ? '😀' : '😥'"></p>
</div>

slot插槽

Ember Polaris 标志 Ember Polaris

import FunnyButton from "./funny-button";

<template>
  <FunnyButton>点我!</FunnyButton>
</template>

Alpine 标志 Alpine

<!--Alpine JS suggests using a server-side templating engine or another frontend framework in conjunction with Alpine to do this-->

<button
  x-data
  x-text="'点我!'"
  style="
    background: rgba(0, 0, 0, 0.4);
    color: #fff;
    padding: 10px 20px;
    font-size: 30px;
    border: 2px solid #fff;
    margin: 8px;
    transform: scale(0.9);
    box-shadow: 4px 4px rgba(0, 0, 0, 0.4);
    transition: transform 0.2s cubic-bezier(0.34, 1.65, 0.88, 0.925) 0s;
    outline: 0;
  "
>
  <span>默认slot内容</span>
</button>

slot插槽内容

Ember Polaris 标志 Ember Polaris

import FunnyButton from "./funny-button";

<template>
  <FunnyButton />
  <FunnyButton>这是传到slot的内容!</FunnyButton>
</template>

Alpine 标志 Alpine

<!--Alpine JS suggests using a server-side templating engine or another frontend framework in conjunction with Alpine to do this-->

<button
  x-data
  style="
    background: rgba(0, 0, 0, 0.4);
    color: #fff;
    padding: 10px 20px;
    font-size: 30px;
    border: 2px solid #fff;
    margin: 8px;
    transform: scale(0.9);
    box-shadow: 4px 4px rgba(0, 0, 0, 0.4);
    transition: transform 0.2s cubic-bezier(0.34, 1.65, 0.88, 0.925) 0s;
    outline: 0;
  "
>
  <span>默认slot内容</span>
</button>

<button
  x-data
  x-text="'这是传到slot的内容!'"
  style="
    background: rgba(0, 0, 0, 0.4);
    color: #fff;
    padding: 10px 20px;
    font-size: 30px;
    border: 2px solid #fff;
    margin: 8px;
    transform: scale(0.9);
    box-shadow: 4px 4px rgba(0, 0, 0, 0.4);
    transition: transform 0.2s cubic-bezier(0.34, 1.65, 0.88, 0.925) 0s;
    outline: 0;
  "
>
  <span>默认slot内容</span>
</button>

上下文

Ember Polaris 标志 Ember Polaris

import UserProfile from "./user-profile";

<template>
    <UserProfile />
</template>

Alpine 标志 Alpine

<!-- x-text 必须用在带有 x-data 的元素或其子元素上。 -->
<!-- 这两个元素可以共用一个带有 x-data 的父元素,也可以像下面这样分别添加 x-data: -->

<h1 x-data>
  欢迎回来, <span x-text="$store.user.username">未知用户</span>
</h1>

<div x-data>
  <h2>我的简介</h2>
  <p>用户名: <span x-text="$store.user.username"></span></p>
  <p>邮箱: <span x-text="$store.user.email"></span></p>
  <button @click="$store.user.updateUsername('李四')">
    更新用户名为 李四
  </button>
</div>

<script>
  document.addEventListener("alpine:init", () => {
    Alpine.store("user", {
      id: 1,
      username: "abcdefg",
      email: "[email protected]",

      updateUsername(newUsername) {
        this.username = newUsername;
      },
    });
  });
</script>

表单输入

输入框

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

export default class InputHello extends Component {
  @tracked text = "你好世界";

  handleInput = (event) => (this.text = event.target.value);

  <template>
    <p>{{this.text}}</p>
    <input value={{this.text}} {{on "input" this.handleInput}} />
  </template>
}

Alpine 标志 Alpine

<div x-data="{ text: '你好世界' }">
  <p x-text="text"></p>
  <input x-model="text" />
</div>

复选框

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

export default class InputHello extends Component {
  @tracked isAvailable = false;

  handleChange = (event) => (this.isAvailable = event.target.checked);

  <template>
    <input
      id="is-available"
      type="checkbox"
      checked={{this.isAvailable}}
      {{on "change" this.handleChange}}
    />
    <label for="is-available">这是一个checkbox</label>
  </template>
}

Alpine 标志 Alpine

<div x-data="{ isAvailable: true }">
  <input id="is-available" x-model="isAvailable" type="checkbox" />
  <label for="is-available">这是一个checkbox</label>
</div>

单选框

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

export default class PickPill extends Component {
  @tracked picked = "red";

  handleChange = (event) => (this.picked = event.target.value);

  <template>
    <div>你选择了: {{this.picked}}</div>

    <input
      id="blue-pill"
      type="radio"
      value="blue"
      checked={{eq this.picked "blue"}}
      {{on "change" this.handleChange}}
    />
    <label htmlFor="blue-pill">蓝色</label>

    <input
      id="red-pill"
      type="radio"
      value="red"
      checked={{eq this.picked "red"}}
      {{on "change" this.handleChange}}
    />
    <label htmlFor="red-pill">红色</label>
  </template>
}

Alpine 标志 Alpine

<div x-data="{ picked: 'red' }">
  <div>你选择了: <span x-text="picked"></span></div>

  <input id="blue-pill" x-model="picked" type="radio" value="blue" />
  <label for="blue-pill">蓝色</label>

  <input id="red-pill" x-model="picked" type="radio" value="red" />
  <label for="red-pill">红色</label>
</div>

选择器

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

export default class ColorSelect extends Component {
  @tracked selectedColorId = 2;

  select = (event) => (this.selectedColorId = event.target.value);

  isSelected = (colorId) => this.selectedColorId === colorId;

  colors = [
    { id: 1, text: "红" },
    { id: 2, text: "蓝" },
    { id: 3, text: "绿" },
    { id: 4, text: "灰", isDisabled: true },
  ];

  <template>
    <select {{on "change" this.select}}>
      {{#each this.colors as |color|}}
        <option
          value={{color.id}}
          disabled={{color.isDisabled}}
          selected={{this.isSelected color.id}}
        >
          {{color.text}}
        </option>
      {{/each}}
    </select>
  </template>
}

Alpine 标志 Alpine

<div
  x-data="{
  selectedColorId: 2,
  colors: [
    { id: 1, text: '红' },
    { id: 2, text: '蓝' },
    { id: 3, text: '绿' },
    { id: 4, text: '灰', isDisabled: true }
  ]
}"
>
  <select x-model.number="selectedColorId">
    <template x-for="color in colors" x-bind:key="color.id">
      <option
        x-text="color.text"
        x-bind:value="color.id"
        x-bind:disabled="!!color.isDisabled"
        x-bind:selected="color.id === selectedColorId"
      ></option>
    </template>
  </select>
</div>

Web应用特性

渲染应用

Ember Polaris 标志 Ember Polaris

<!doctype html>
<html>
  <body>
    <script type="module" src="./index.js"></script>
  </body>
</html>

Alpine 标志 Alpine

<h1>你好,世界</h1>

fetch获取数据

Ember Polaris 标志 Ember Polaris

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

class State {
  @tracked isLoading = false;
  @tracked error = null;
  @tracked data = null;
}

function fetchUsers() {
  let state = new State();
  
  async function fetchData() {
    try {
      let response = await fetch("https://randomuser.me/api/?results=3");
      let { results: users } = await response.json();
      state.data = users;
      state.error = null;
    } catch (err) {
      state.data = null;
      state.error = err;
    }
    state.isLoading = false;
  }
  
  fetchData();
  return state;
}

export default class App extends Component {
  <template>
    {{#let (this.fetchUsers) as |request|}}
      {{#if request.isLoading}}
        <p>正在获取用户信息……</p>
      {{else if request.error}}
        <p>获取用户信息出错</p>
      {{else}}
        <ul>
          {{#each request.data as |user|}}
            <li>
              <img src={{user.picture.thumbnail}} alt="user" />
              <p>{{user.name.first}} {{user.name.last}}</p>
            </li>
          {{/each}}
        </ul>
      {{/if}}
    {{/let}}
  </template>
}

Alpine 标志 Alpine

<div
  x-data="
		function fetchUsers() {
			return {
				users: null,
				isLoading: false,
				error: null,
				async init() {
					this.isLoading = true;
					try {
						this.users = (await (await fetch('https://randomuser.me/api/?results=3')).json()).results;
					} catch (err) {
						this.users = [];
						this.error = err
					}
					this.isLoading = false;
				},
			};
		}
	"
>
  <template x-if="isLoading">
    <p>正在获取用户信息……</p>
  </template>

  <template x-if="error">
    <p>获取用户信息出错</p>
  </template>
  <template x-if="!error">
    <ul>
      <template x-for="user in users">
        <li>
          <img
            :src="user.picture.thumbnail"
            :alt="`picture of ${user.name.first} ${user.name.last}`"
          />
          <p x-text="`${user.name.first} ${user.name.last}`"></p>
        </li>
      </template>
    </ul>
  </template>
</div>