体验了一把华为的 OpenInula,谈谈使用感受

开发 前端
openInula​ 的使用体验与 React 几乎一样。与 React 不同的是,他增加了一个响应式 API。因此能够增加一些不同的开发体验。也正是由于这个响应式 API 的存在,让 openInula 在 API 设计上有了自己的独创性。

华为在今年开源了一款类似于 React 的前端框架, openInula。他的宣传语上面,把 openInula 与大语言模型、前端 AI 赋能结合在一起,主打一个高性能、全场景、智能化。

果然遥遥领先在宣传语的设计上还是有点水平的。然后我就去了解了一下这个框架。

一、无缝迁移

我想先试一下能不能真的做到无缝切换。如果真的能做到的话,我们就可以非常方便的使用 React 的生态直接搞 openinula 项目了。

然后我在 vite 上随便搞了一个项目,把 openinula 跑了起来。能运行官方文档首页的 demo。

然后我在项目中引入了一个 react 生态中最常用的 react-router。

yarn add react-router-dom

然后写了一个很小的 demo 想看看能不能跑起来。

function ReactiveApp() {
  return (
    <Routes>
      <Route path='/' element={Index} />
      <Route path='child' element={Child} />
    </Routes>
  );
}

结果不出所料。跑不起来。

然后尝试修改了一下,发现要改的地方太多了,算了,就算最后改成功,也不是我想要的那种无缝切换的效果,还是比较麻烦。所以想要顺利把 React 生态的东西直接用到 openinula 上也并不简单,需要调整和修改内容。

react 的底层模块区分了 react 和 react-dom ,就导致了区别还是比较大。

无缝切换:GG

但是他确实在兼容 React API 上做得比较好,几乎所有常用的 api 都有支持。所以如果只是基于这些 api 写出来的东西应该切换起来难度还是不高的。

二、响应式 API

openInula 还支持了一个响应式 API:useReactive

响应式 API 其实就是当监听的数据发生变化时,组件函数不需要重新执行。通过这样的方式减少函数执行范围,可以比 diff 少一些逻辑执行。

function ReactiveApp() {
  const renderCount = ++useRef(0).current;

  const data = useReactive({ count: 0 });
  const countText = useComputed(() => {
    return `计时: ${data.count.get()}`;
  });

  setInterval(() => {
    data.count.set((c) => c + 1);
  }, 1000);

  return (
    <div>
      <div>{countText}</div>
      <div>组件渲染次数:{renderCount}</div>
    </div>
  );
}

export default ReactiveApp;

这个 api 比较有意思的他的 getter 和 setter 的设计。

data.count.get()
data.count.set(() => c + 1)

项目经验丰富,对可维护性很重视的同学应该能想得通为什么要设计成这样。因为看上去使用比较麻烦,没有直接像 Vue 那样,通过 Proxy 劫持来省略掉显示的调用 get/set ,所以肯定会给人带来一些疑惑和不解。

data.count
data.count += 1

这样又简洁又舒适,有什么不好。

与 React 非常相似的 Solid.js 也没有这样做。而是选择了另外一种方式

const CountingComponent = () => {
  const [count, setCount] = createSignal(0);
  const interval = setInterval(
    () => setCount(count => count + 1),
    1000
  );
  onCleanup(() => clearInterval(interval));
  return <div>Count value is {count()}</div>;
};

一个最主要的原因是,当项目变得庞大和久远,我们在重新阅读项目或者修改 bug 时,或者阅读别人的项目时,无法在代码逻辑中快速区分普通数据和响应式数据,从而增加了维护成本。

如下例所示,我们只有追溯到数据最初声明的地方,才能分清他到底是响应式数据还是普通数据。

data.count
data.count += 1
result.count
result.count++

综合来看,从语法上我更喜欢 openinula 的 api 设计。

// openInula
data.count.get()
data.count.set((v) => v + 1)

// solid
count()
setCount(count => count + 1)

openInula 还有一个比较重要的问题,就是 React API 和 响应式 API 共存的问题。也就是说,响应式 API 使用的一个很重要的前提,就是函数组件不会重新执行。也就意味着,他们的混用,特别是当 useState 存在于父级中时,会出现严重的混乱。

function ReactiveApp() {
  const [index, setIndex] = useState(0)

  return (
    <div>
      <div notallow={() => setIndex(index + 1)}>index: {index}</div>
      <Child />
    </div>
  );
}
function Child() {
  const counter = useReactive({ count: 1 })
  const p = ++useRef(0).current

  const timer = useRef

  useEffect(() => {
    setInterval(() => {
      counter.count.set((c) => c + 1)
    }, 1000)
  }, [])

  return (
    <>
      <div>Child 执行次数:{p}</div>
      <div>记时:{counter.count.get()}</div>
    </>   
  )
}

也就意味着,他们的共存在使用时一定要非常小心。在这种情况下,useReactive 的存在与 useState 有点犯冲,显得格格不入。或者可以在项目中,尽量避免使用 useState,具体效果如何,还要深度使用之后才能体会到。

三、迁移我的 React 组件库

我在 React 中有一些积累的组件库,然后我把一些常用的迁移到 openInula 中来,经过简单的修改,迁移成功。使用语法没有任何变化。

<Icon type='search' color='red' />
<Button type='primary'>hello world</Button>

这样来看的话,确实能够快速将 React 的生态迁移到 openInula 上面来。但是由于我大多数组件都是基于 useState 来编写的,因此,想要使用 useReactive 的话,只能全部替换掉。

- const [display, setDisplay] = useState(false)
+ const display = useReactive({ show: false })

替换掉之后功能基本上没什么毛病。但是在最佳实践的摸索上还存在一些疑问。比如当我想要将一个响应式数据传递给子组件时,下面哪种方式更好一些呢?我还没有一个定论,还需要进一步的体会和摸索。

<Dialog show={data.open.get()}}>hello</Dialog>
<Dialog show={data.open}>hello</Dialog>

第一种方式会更加契合解耦方面的思考,但书写稍微繁琐了一点,第二种方式呢,会对子组件逻辑造成更大的干扰。想到这里,突然之间明白了在 arkUI 里的状态设计,如果从父组件里传递一个响应式数据给子组件时,子组件必须使用 @Prop 装饰来接收这个状态。

这样在子组件中,我们就能够清晰的知道这个数据类型的特性到底是怎么回事了。从而降低了维护成本。这样一想的话,arkUI 在组件状态的设计上,确实有点东西。

@Component
struct ChildComponent {
  @Prop
  private count: number

  build() {
    Text(`Child Count: ${this.count}}`)
  }
}

四、意外之喜

当我试图使用解构的方式来拆解 useReactive 时,居然不会失去响应性。

const {count, open} = useReactive({ 
  count: 0, 
  open: false 
});

const countText = useComputed(() => {
  return `计时: ${count.get()}`;
});

setInterval(() => {
  count.set((c) => c + 1);
}, 1000);

这可就解决了大问题了!当数据变得庞大,它的繁琐的程度将会大大的降低。所以在使用上会比 solid.js 方便许多。

我了解到的 Vue3 和 Solid 实际上在这一点上都做得不是很好,解构之后,Vue3 的状态会失去响应性。

// 直接使用 count 无法具备响应性
const {count} = reactive({ count: 0 })

Solid 的 API 设计,又无法做到把颗粒度细分到每个子属性

const [count, setCount] = createSignal({n: 1});

function clickHandler() {
  setCount({ n: count().n + 1 })
}

所以,当需要更细的属性时,Vue3 可能会更多的使用 ref 来做,而 solid 则与 useState 一样,单独声明这个属性。

这么横向一对比,openInula 的响应式 API 就有点厉害了。在设计上充分体现了自己的独创性和先进性,如果其他方面不出什么问题的话,应该会受到一大批程序员的喜爱。

不愧是遥遥领先。 

五、总结

openInula 的使用体验与 React 几乎一样。与 React 不同的是,他增加了一个响应式 API。因此能够增加一些不同的开发体验。也正是由于这个响应式 API 的存在,让 openInula 在 API 设计上有了自己的独创性。

与其他响应式框架相比,我更喜欢 openInula 的 API 设计,在开发体验与维护体验的综合考虑上目前是做得最好的,虽然为了考虑维护体验牺牲了一些开发体验,不过我完全能接受。由于接触了几款华为的框架,可以感受到,他们在设计 API 时,会把可维护性的重要性看得比开发体验更高。

当然,svelte 我还没有怎么了解过,不过有听到坊间传言说是模仿 Vue3 的,那估计设计模式跟 Vue3 差别不算大。

var { count, a, b, c } = useReactive({
  count: 1,
  a: 1,
  b: 1,
  c: 1
})

count.set((v) => v + 1)
count.get()

a.set((v) => v + 1)
a.get()

b.set((v) => v + 1)
b.get()

c.set((v) => v + 1)
c.get()
责任编辑:姜华 来源: 这波能反杀
相关推荐

2012-08-27 16:20:10

Windows 7操作系统

2021-03-17 11:29:20

Linux操作系统

2022-10-25 15:25:22

网关并行Flowable

2024-01-04 14:16:05

腾讯红黑树Socket

2020-08-05 07:27:54

SQL优化分类

2020-03-02 17:04:47

戴尔

2021-03-26 06:14:26

Hashcode项目排查

2023-11-13 08:03:53

Next.js命令变量

2022-01-27 08:44:58

调度系统开源

2018-03-19 08:30:02

编程开发技能

2021-08-16 15:40:04

分布式架构系统

2023-07-06 22:42:55

AIGC工具变革者

2019-06-12 15:20:25

Redis高性能线程

2010-05-21 13:25:40

统一通信系统服务

2011-05-04 17:11:12

打印机

2011-11-08 08:14:40

WLANWi-Fi

2018-03-23 08:26:44

Hadoop集群SQL

2022-07-20 08:55:02

区块链技术数据记录

2019-10-30 05:51:07

物联网设备物联网安全物联网

2017-06-14 17:39:40

微服务架构服务器
点赞
收藏

51CTO技术栈公众号