看一眼前端框架DLight

121 阅读6分钟

最近陆陆续续有人开始关注DLight了,github小星星也在一颗颗的涨起来了。所以我感觉还是有必要在中文社区写点文章来介绍一下DLight。之所以我前面没那么想宣传DLight,是因为在国外社区里和别人讨论,除了个别很感兴趣的人,剩下都是一群只写过react,然后看到不是functional component,不是jsx,就开喷的人。所以感觉它只是个符合小众taste的东西,自己爽就完了,跟别人解释设计原理难道还要让人家把web ios android几十个框架都用一遍然后有自己的思考再来讨论?

但看最近来讨论DLight的人越来越多了,大家还是聪明又友善的,所以我觉得还是有必要写点东西来介绍一下。打算写个小系列,从语法到feature到render表现,大致的过一下。当然,只讲现象,不讲原理。这篇文章呢就先从语法以及对比开始。然后我nicely please一眼class不行的人请划走,too tired to explain。

什么是DLight.js

我一直称DLight.js是一个DX-first的渲染库。因为写react真写累了,看vue文档也真看累了,然后我到现在都不知道solid传prop该传getter还是value,也不知道svelte4的两套reactivity到底为什么能这么抽象(哦,但是svelte5变成solid了)。所以就想写个框架,只要你会写js,然后看一遍文档,就能构建出性能好的、易开发的app。对于DLight,性能好那确实,DLight和solid和svelte5速度差不多,内存占用要比他们小30%左右(去看current benchmark)。易开发这点见仁见智吧,把这篇文章看完看看你觉得好不好用。

Fake FP vs. Real OOP

很多人觉得FP写的爽,但你js的functional component能叫FP吗!对于个signal-based的MVVM框架,你function只跑一次啊,你function只跑一次啊,跑完了你component数据存哪啊?跑到不能自动回收的closure里了吧。那为什么不主动建一个view-model来hold这个数据呢。。

咱都MVVM了,就别假FP了,试试真OOP构建ui呢。

语法对比

我也就不说什么废话了,直接show you the code。由于90%的人都用react,这边就以react为对比(这边对比会用useCallback,useMemo,memo来包,因为我们是要写real world的web出来用的,不能让原本就差的性能更差了,别说我cherry-pick捏)。

React:

function Counter() {
  const [count, setCount] = useState(10);
  const doubleCount = useMemo(() => count * 2, [count]);
  const increment = useCallback(() => {
    setCount(count + 1)
  }, [count]);

  return (
    <>
      <div>{doubleCount}</div>
      <button onClick={increment}>Increment</button>
    </>
  )
}

DLight:

@View
class Counter {
  count = 10;
  doubleCount = this.count * 2;
  increment() {
    this.count++;
  }

  View() {
    div(this.doubleCount)
    button("Increment")
      .onClick(this.increment)
  }
}

所以我们看看DLight的这个Counter干了啥:

  1. @View声明了一个DLight component (DLight里面decorators都是只用了语法手动解析的假decorator,所以不存在decorator的性能和experimental的问题),我觉得很符合直觉。
  2. 声明了一坨property和method。诶?好奇怪哦,怎么我普通class怎么定义函数变量在DLight component里就怎么定义咧?咋没有各种奇奇怪怪的函数捏?答案是编译的magic让他符合直觉了。
  3. View()里面只声明了UI的结构,ui和逻辑分离了。这里面是DLight的dsl,看一遍文档就会了(别说不如jsx,说就是你taste不行)。

你想看的

没有显式声明state,setter或者.value;没有memo,store,副作用等

因为没有,所以就不写了。

深度更新和数组更新

@View
class Counter {
  obj = {
    data: {
      nested: {
        count: 10,
        arr: [1, 2, 3]
      }
    }
  }

  View() {
    div(this.obj.data.nested.count) // 点了就更新
    button("Increment")
      .onClick(() => {
        this.obj.data.nested.count++;
      })
    // -------------------
    div(this.obj.data.nested.arr.join(",")) // 点了就更新
    button("Add")
      .onClick(() => {
        this.obj.data.nested.arr.push(0);
      })
  }
}

这里没用proxy,也没有什么奇奇怪怪的store来包裹。你就定义值,赋值,用值。但是能支持所有的object更新,array,set,map的函数赋值调用,而且更新粒度是到最后一个member的property的。

DLight DSL的一些

真的写条件渲染写烦了:

// React
{light === "red" && <span>STOP</span>}
{light === "orange" && <span>SLOW DOWN</span>}
{light === "green" && <span>GO</span>}
// Svelte
{#if light === "red"}
  <span>STOP</span>
{:else if light === "orange"}
  <span>SLOW DOWN</span>
{:else if light === "green"}
  <span>GO</span>
{/if}
// Vue
<span v-if="light === 'red'">STOP</span>
<span v-else-if="light === 'orange'">SLOW DOWN</span>
<span v-else-if="light === 'green'">GO</span>
// Solid
<Switch>
  <Match when={light() === "red"}>
    <span>STOP</span>
  </Match>
  <Match when={light() === "orange"}>
    <span>SLOW DOWN</span>
  </Match>
  <Match when={light() === "green"}>
    <span>GO</span>
  </Match>
</Switch>
// Angular
@switch (light) {
  @case ("red") {
    <span>STOP</span>
  }
  @case ("orange") {
    <span>SLOW DOWN</span>
  }
  @case ("green") {
    <span>GO</span>
  }
}

能不能不要引入自己的语法?像DLight这样:

// DLight
if (light === "red") {
  span("STOP")
} else if (light === "orange") {
  span("SLOW DOWN")
} else if (light === "green") {
  span("GO")
}
// or
switch (light) {
  case "red":
    span("STOP")
    break;
  case "orange":
    span("SLOW DOWN")
    break;
  case "green":
    span("GO")
    break;
}

循环也是啊。。。

// React
{colors.map((color) => (
  <li key={color}>{color}</li>
))}
// Svelte
{#each colors as color (color)}
  <li>{color}</li>
{/each}
// Vue
<li
  v-for="color in colors"
  :key="color"
>
  {{ color }}
</li>
// Solid
<For each={colors}>
  {(color) => <li>{color}</li>}
</For>
// Angular
@for (let color of colors) {
  <li>{{ color }}</li>
}

DLight:

for (let color of colors) {
  li(color)
}

前面说你觉得jsx比较好你taste就不行可能是开玩笑的,但你看看这俩最常用的语法,家人们咱就是说平心而论一下是吧。

你已经会了这些,再加上:

  • 组件传prop
@View
class Child {
  @Content name
  @Prop age;
  View() {
    div(this.name)
    div(this.age)
  }
}
@View
class Parent {
  View() {
    Child("小明")
      .age(100)
  }
}
  • 一些上面没有提到的DSL:
// html
div("hello")
  .style({color: "red"})
  .class("red")
  .onClick(() => {
    console.log("它点我了!")
  })
// nested
div(); {
  div("1")
  div("2")
  table().style({border: "1px solid black"})
  {
    tr(); {
      td("3")
      td("4")
    }
  }
}
// text
"ok"
`cool`
'都是text'
  • 监听变化
@View
class Counter {
  count = 10
  @Watch
  watchCount() {
    console.log(this.count, "变啦""你不需要手动写监听")
  }

  View() {
    div(this.count)
  }
}
  • 生命周期
@View
class Counter {
  willMount() {
    console.log("马上有了")
  }
  didMount() {
    console.log("已经有了")
  }
  willUnmount() {
    console.log("马上没了")
  }
  didUnmount() {
    console.log("已经没了")
  }

  View() {
    div(this.count)
  }
}

你说,这不是也要记吗????:D

  • style
    • 这是js文件,你想用啥用啥。你用emotion也行,用tailwind也行,你用stylex也行,你手写css module我都不拦着。

好了,你现在已经会了90%的DLight了,可以写95%的网页了。还有些很爽的feature比如environment subview啥的就不在这里说了,感兴趣的可以去官网看看。

你说hooks,我说model

你说hooks好用,那确实,算是functional component糟糕的心智模型(react不糟糕,其他signal的糟糕)下为数不多的优点了。

但是你看看这个,比如我们要写一个简易的useFetch拉数据的hook:

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      })
      .catch((error) => {
        setError(error);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

使用:

function App() {
  const { data, loading, error } = useFetch("https://api.example.com/data");
  
  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

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

确实香,但你useEffect里面都不能用async,除非再包一层。

但在DLight的心智模型下面,这东西就是一个抽象的公共的model,你可以这么写:

@Model
class FetchModel {
  @Prop url
  data = null;
  loading = true;
  error = null;

  @Watch
  async fetch(url) {
    this.loading = true;
    try {
      const res = await fetch(url);
      const data = await res.json();
      this.data = data;
    } catch (error) {
      this.error = error;
    }
    this.loading = false;
  }
}

使用:

@View
class App {
  fetchModel = use(FetchModel, {url: "https://api.example.com/data"})

  View() {
    if (this.fetchModel.loading) {
      div("Loading...")
    } else if (this.fetchModel.error) {
      div(`Error: , ${this.fetchModel.error.message}`)
    } else {
      div(this.fetchModel.data)
    }
  }
}

在这里,写@Model和写@View的逻辑是一模一样的,一切declared property都有可能是state,你不需要显式定义,它只要被你用了,它就变成响应的了。

好玩的项目

有个discord老哥用DLight俩小时写了个小游戏:codesandbox.io/p/devbox/dl…

他刚听说DLight一天,看了遍文档就写了,不能说DLight好用了,确实是他牛逼。

总结

欢迎大家进wx群或者discord一起来讨论,整出一个解放画图的时代,好去做点技术力强的事情。

DLight的主页:dlight.dev/

Github:github.com/dlight-js/d…

Discord Link:discord.gg/SV6cMaQHHk

wx群:IMG_0345.JPG 或者加我(ID: iandxssxx)拉你进群