响应式

声明状态

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { track } from "ripple";

export default function Name() {
  let &[name] = track("张三");

  return <h1>"你好 "{name}</h1>;
}

更新状态

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { track } from "ripple";

export default function Name() {
  let &[name] = track("张三");
  name = "李四";

  return <h1>"你好 "{name}</h1>;
}

计算属性

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { track } from "ripple";

export default function DoubleCount() {
  let &[count] = track(10);
  let &[doubleCount] = track(() => count * 2);

  return <div>{doubleCount}</div>;
}

模板语法

最简模板

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

export default function HelloWorld() {
  return <h1>"你好,世界"</h1>;
}

样式

Ember Octane 标志 Ember Octane

/* using: https://github.com/salsify/ember-css-modules */
.title {
  color: red;
}

Ripple 标志 Ripple

export default function CssStyle() {
  return <>
    <h1 class="title">"我是红色的"</h1>
    <button style={{ fontSize: "10rem" }}>"我是一个按钮"</button>

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

DOM循环

Ember Octane 标志 Ember Octane

<ul>
  {{#each (array "红" "绿" "蓝") as |color|}}
    <li>{{color}}</li>
  {{/each}}
</ul>

Ripple 标志 Ripple

export default function Colors() {
  const colors = ["红", "绿", "蓝"];

  return <ul>
    for (const color of colors; key color) {
      <li>{color}</li>
    }
  </ul>;
}

点击事件

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { track } from "ripple";

export default function Counter() {
  let &[count] = track(0);

  function incrementCount() {
    count++;
  }

  return <>
    <p>"计数器: "{count}</p>
    <button onClick={incrementCount}>"+1"</button>
  </>;
}

DOM引用

Ember Octane 标志 Ember Octane

<input {{this.autofocus}} />

Ripple 标志 Ripple

export default function InputFocused() {
  function autofocus(element) {
    element.focus();
  }

  return <input ref={autofocus} />;
}

条件渲染

Ember Octane 标志 Ember Octane

<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>

Ripple 标志 Ripple

import { track } from "ripple";

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

export default function TrafficLight() {
  let &[lightIndex] = track(0);

  let &[light] = track(() => TRAFFIC_LIGHTS[lightIndex]);

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

  return <>
    <button onClick={nextLight}>"下一个灯"</button>
    <p>"现在亮着的是: "{light}</p>
    <p>
      "你应该 "
      if (light === "红灯") {
        <span>"停下"</span>
      } else if (light === "黄灯") {
        <span>"慢行"</span>
      } else if (light === "绿灯") {
        <span>"赶紧走"</span>
      }
    </p>
  </>;
}

生命周期

组件加载时

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { effect, track } from "ripple";

export default function PageTitle() {
  let &[pageTitle] = track("");

  effect(() => {
    pageTitle = document.title;
  });

  return <h1>"页面标题: "{pageTitle}</h1>;
}

组件卸载时

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { effect, track } from "ripple";

export default function Time() {
  let &[time] = track(new Date().toLocaleTimeString());

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

    return () => clearInterval(timer);
  });

  return <h1>"当前时间: "{time}</h1>;
}

组件组合

组件Props传参

Ember Octane 标志 Ember Octane

<UserProfile
  @name="张三"
  @age={{20}}
  @favouriteColors={{array "green" "blue" "red"}}
  @isAvailable={{true}}
/>

Ripple 标志 Ripple

import { UserProfile } from "./UserProfile.tsrx";

export function App() {
  return <UserProfile
    name="张三"
    age={20}
    favouriteColors={["绿", "蓝", "红"]}
    isAvailable
  />;
}

向父组件emit事件

Ember Octane 标志 Ember Octane

<p>你快乐吗?</p>
<AnswerButton @onYes={{this.handleYes}} @onNo={{this.handleNo}} />
<p style="font-size: 50px;">{{if this.isHappy "😀" "😥"}}</p>

Ripple 标志 Ripple

import { track } from "ripple";

import { AnswerButton } from "./AnswerButton.tsrx";

export function App() {
  let &[isHappy] = track(true);

  function onAnswerNo() {
    isHappy = false;
  }

  function onAnswerYes() {
    isHappy = true;
  }

  return <>
    <p>"你快乐吗?"</p>
    <AnswerButton onYes={onAnswerYes} onNo={onAnswerNo} />
    <p style={{ fontSize: "50px" }}>{isHappy ? "😀" : "😥"}</p>
  </>;
}

slot插槽

Ember Octane 标志 Ember Octane

<FunnyButton> 点我! </FunnyButton>

Ripple 标志 Ripple

import { FunnyButton } from "./FunnyButton.tsrx";

export function App() {
  return <FunnyButton>"点我!"</FunnyButton>;
}

slot插槽内容

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { FunnyButton } from "./FunnyButton.tsrx";

export function App() {
  return <>
    <FunnyButton />
    <FunnyButton>"这是传到slot的内容!"</FunnyButton>
  </>;
}

上下文

Ember Octane 标志 Ember Octane

<UserProfile />

Ripple 标志 Ripple

import { RippleObject } from "ripple";

import { UserContext } from "./UserContext.tsrx";
import { UserProfile } from "./UserProfile.tsrx";

export function App() {
  const user = new RippleObject({
    id: 1,
    username: "abcdefg",
    email: "[email protected]",
  });

  UserContext.set(user);

  return <>
    <h1>"欢迎回来, "{user.username}</h1>
    <UserProfile />
  </>;
}

表单输入

输入框

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { bindValue, track } from "ripple";

export default function InputHello() {
  let &[text, textTracked] = track("你好,世界");

  return <>
    <p>{text}</p>
    <input ref={bindValue(textTracked)} />
  </>;
}

复选框

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { bindChecked, track } from "ripple";

export default function IsAvailable() {
  let &[isAvailable, isAvailableTracked] = track(false);

  return <>
    <input
      id="is-available"
      type="checkbox"
      ref={bindChecked(isAvailableTracked)}
    />
    <label for="is-available">"这是一个checkbox"</label>
  </>;
}

单选框

Ember Octane 标志 Ember Octane

<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>

Ripple 标志 Ripple

import { bindGroup, track } from "ripple";

export default function PickPill() {
  let &[picked, pickedTracked] = track("red");

  return <>
    <div>"你选择了: "{picked}</div>

    <input
      id="blue-pill"
      type="radio"
      value="blue"
      ref={bindGroup(pickedTracked)}
    />
    <label for="blue-pill">"蓝色"</label>

    <input
      id="red-pill"
      type="radio"
      value="red"
      ref={bindGroup(pickedTracked)}
    />
    <label for="red-pill">"红色"</label>
  </>;
}

选择器

Ember Octane 标志 Ember Octane

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

Ripple 标志 Ripple

import { bindValue, track } from "ripple";

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

export default function ColorSelect() {
  let &[selectedColorId, selectedColorIdTracked] = track(2);

  return <select ref={bindValue(selectedColorIdTracked)}>
    for (const color of colors; key color.id) {
      <option value={color.id} disabled={color.isDisabled}>
        {color.text}
      </option>
    }
  </select>;
}

Web应用特性

渲染应用

Ember Octane 标志 Ember Octane

缺少代码片段 帮助我们改进 Component Party Component Party 标志

Ripple 标志 Ripple

<!doctype html>
<html>
  <body>
    <div id="app"></div>
    <script type="module" src="./main.js"></script>
  </body>
</html>

fetch获取数据

Ember Octane 标志 Ember Octane

{{#let (this.fetchUsers) as |request|}}
  {{#if request.isLoading}}

    <p>正在获取用户信息……</p>

  {{else if request.error}}

    <p>获取用户信息出错</p>

  {{else}}

    <ul>
      {{#each request.users as |user|}}
        <li>
          <img src={{user.picture.thumbnail}} alt="user" />
          <p>{{user.name.first}} {{user.name.last}}</p>
        </li>
      {{/each}}
    </ul>

  {{/if}}
{{/let}}

Ripple 标志 Ripple

import { useFetchUsers } from "./useFetchUsers.tsrx";

export function App() {
  const data = useFetchUsers();

  return <>
    if (data.isLoading) {
      <p>"正在获取用户信息……"</p>
    } else if (data.error) {
      <p>"获取用户信息出错"</p>
    } else if (data.users) {
      <ul>
        for (const user of data.users; key user.login.uuid) {
          <li>
            <img src={user.picture.thumbnail} alt="用户" />
            <p>{user.name.first}" "{user.name.last}</p>
          </li>
        }
      </ul>
    }
  </>;
}