stayjs 是一个书写声明式代码,却以命令式的运作方式的高性能UI编写库。没有VDOM,也没有JSX,没有生命周期,组件就是原始的HTMLElement元素,完美融合所有 vanilla.js 库的同时有着简洁、丰富的表达能力。

我会在此文简要描述,如何使用 stayjs,如何创建工程、组件、使用state、传递props、管理全局状态、使用 map渲染/更新list,使用类似 useMemo 的方式拦截不必要的更新,以及如何使用路由、动画、如何使用css。是的,stayjs 的表达力之强,仅需要一片文章就可以描述完以上feature。

看看我是怎么做到的。

ymzuiku/stayjs
Only a library, Use library create web application, We don’t need a framework - ymzuiku/stayjs

stayjs

Only a library, Use library create web application, We don't need a framework.

We love sevlte's design, bu we love JS/TS more, not .sevlte.

stayjs is just stay in javascript/typescript, we didn't need JSX, use javascript create like XML's dom tree; we didn't need VDO(like sevlte), Observer/Subject update dom.

Faster!!!

stayjs Size: no gizp 2k.

And like sevlte, no need diff VDOM, we only use Observer/Subject update dom. you can go back native js create all your feature.

Install

We can use React-cli: create-react-app, or vue-cli, or customer create any npm project. Now we use create-react-app.

create-react-app hello-stayjs --typescript

Install stayjs library, and start project:

$ npm install --save stayjs
$ npm run start

Use

Simple:

A element only is HTMLElement, we are vanilla.js!

import { El } from "stayjs";

const button = El("button", ["touch me"]); // button only a HTMLButtonElement

document.body.append(button);

DOM Tree and Component:

A Component only is ()=> HTMLElement, we are vanilla.js!

import { El } from "stayjs";

function Box() {
  return El("div", [
    El("h1", { textContent: "hello" }),
    El("h2", { textContent: "world" }),
    El("button", ["touch me"]),
  ]);
}

document.body.append(Box());

Set State:

El type like: El(Element|string, Props|className|children, children|()=>children)=>Element

We use State:

$bind:[state]: subject state's update, if element is remove, auto unsubject.

state.update: update all subject's element

import { El, State } from "stayjs";

function Box() {
  const state = State({
    name: "Alex",
    age: 10,
  });

  return El("div", [
    El("h1", { $bind: [state], textContent: () => state.val.name }),
    El("h2", { $bind: [state], textContent: () => state.val.age }),
    El("button", { onClick: () => state.update((val) => val.age++) }, [
      "touch me, change age",
    ]),
  ]);
}

document.body.append(Box());

Double bind value:

import { El, State } from "stayjs";

function Box() {
  const state = State({
    text: "Please input the <input />",
  });

  return El("div", [
    El("p", { $bind: [state], textContent: () => state.val.text }),
    El("input", {
      oninput: (e) => state.update((val) => (val.text = e.target.value)),
    }),
  ]);
}

document.body.append(Box());

Set Props

Use El(<Component>, ....) change component top level tree props

import { El, State } from "stayjs";

// Top level element don't need props
function BigButton() {
  return El("button", {
    style: { fontSize: "30px", textContent: "no props button" },
  });
}

function Box() {
  const state = State(["dog", "cat", "fish"]);

  return El("div", [
    El("p", { textContent: "hello button:" }),
    BigButton(),
    El(BigButton, {
      textContent: "change props's button",
      style: { background: "#f33" },
    }),
  ]);
}

document.body.append(Box());

Use Component(props) change component sub level tree props

import { El, State } from "stayjs";

function BigButton({ fontSize = "30px", background, color }) {
  return El("div", {style:{color}} [
    El("button", {
      style: { fontSize, color, textContent: "no props button" },
    }),
  ]);
}

function Box() {
  const state = State(["dog", "cat", "fish"]);

  return El("div", [
    El("p", { textContent: "hello button:" }),
    BigButton({fontSize: '50px', background:'#f55', color:'#fff'})
  ]);
}

document.body.append(Box());

Map Render List

We can add $append: () => [state.val.length] in Element, when state change and $append callback's value change, stayjs can help you update list.

When stayjs update list, stayjs only update new item, or delete item. There have hight performent at big list screen.

and children we need use ()=>[Element, Element] replace [Element, Element]

import { El, State } from "stayjs";

function Box() {
  const state = State(["dog", "cat", "fish"]);

  return El("div", { $append: () => state.val.length }, () => [
    El("p", { textContent: "input change list:" }),
    El("button", { onclick: () => state.update(() => (state.val = [])) }, [
      "Clear list",
    ]),
    El("input", {
      oninput: (e) => state.update((val) => val.push(e.target.value)),
    }),
    ...state.val.map((label) => El("p", [label])),
  ]);
}

document.body.append(Box());

Application's global state/store

Global state is same to Component state, just move out state to Component

import { El, State } from "stayjs";

const state = State({
  text: "Please input the <input />",
});

function PageA() {
  return El("div", [
    El("p", { $bind: [state], textContent: () => state.val.text }),
    El("input", {
      oninput: (e) => state.update((val) => (val.text = e.target.value)),
    }),
  ]);
}

function PageB() {
  return El("div", [
    El("p", { $bind: [state], textContent: () => state.val.text }),
    El("input", {
      oninput: (e) => state.update((val) => (val.text = e.target.value)),
    }),
  ]);
}

document.body.append(PageA(), PageB());

Like react useMemo

stayjs is high performance,we can controll $bind's state. and we can use $memo intercept detail update.

This example, h1 is no rerender, because it $memo's data no change.

import { El, State } from "stayjs";

function Box() {
  const state = State({
    name: "Alex",
    age: 10,
  });

  return El("div", [
    El("h1", {
      $bind: [state],
      $memo: (val) => [val.name],
      textContent: () => state.val.name,
    }),
    El("h2", {
      $bind: [state],
      $memo: (val) => [val.age],
      textContent: () => state.val.age,
    }),
    El("button", { onClick: () => state.update((val) => val.age++) }, [
      "touch me, change age",
    ]),
  ]);
}

document.body.append(Box());

Ecology

Router

We are vanilla.js, so we can easy use all vanilla.js library.

Here have a simple route: npm i vanilla-route, vanilla-route like react-route, can splice pages, and can prefetch page.

import route from "vanilla-route";
import { El, State } from "stayjs";

const state = State({
  text: "Please input the <input />",
});

function PageA() {
  return El("div", [
    El("p", { $bind: [state], textContent: () => state.val.text }),
    El("input", {
      oninput: (e) => state.update((val) => (val.text = e.target.value)),
    }),
  ]);
}

function PageB() {
  return El("div", [
    El("p", { $bind: [state], textContent: () => state.val.text }),
    El("input", {
      oninput: (e) => state.update((val) => (val.text = e.target.value)),
    }),
  ]);
}

route.use("/", PageA);
route.use("/page-b", PageB);

// sync load page-c.js at open '/page-c' url
route.use("/page-c", () => import("./PageC").then((v) => v.default));

// render route, need after all route.use.
route.render();
// render.target is HTMLDivElement, we need append in document rendered it.
document.body.append(route.target);

CSS

CSS feature is Project-cli's work. If we use create-react-app, we can use css or scss like in react:

index.module.css:

.box {
  background: #ff9;
}
.hello {
  background: #f55;
}
.world {
  background: #66f;
}

index.ts:

El type like: El(Element|string, Props|className|children, children|()=>children)=>Element

import { El } from "stayjs";
import css from "./index.module.css";

function Box() {
  return El("div", css.box, [
    El("h1", { className: css.hello, textContent: "hello" }),
    El("h2", { className: css.world, textContent: "world" }),
    El("button", ["touch me"]),
  ]);
}

document.body.append(Box());

We can use css-in-js also: npm install --save vanilla-css-js, and read it's README.md

Animation

Ok, we need show this again: We are vanilla.js, so we can easy use all vanilla.js library.

I love animejs:

npm install --save animejs

Example:

import { El } from "stayjs";
import anime from "animejs";

function Box() {
  const label = El("h1");

  return El("div", [
    El(label, { textContent: "hello" }),
    El("button", {
      onclick: () => {
        anime({ target: label, translateX: 100 });
      },
      textContent: "run anime",
    }),
  ]);
}

document.body.append(Box());

End

stayjs is vanilla.js library, so we can use all js library complate your project. and use javascript create like XML's dom tree.
enjoy stayjs, thx.