There are three dominated front-end frameworks: Angular, Vue, React. All of those are good!
Angular is good just I don't like the class component, it's too heavy; React is good just I don't like the VDOM; I never used Vue.
So I try to create my own reactivity front-end framework, I think it's similar to Solid as like use Signal.
I implemented a simple, signal based reactivily library: dvorakjs
First, which component?
Which Component I want to use?
Not class component as like Angular, because it's to heavy as I said above. What about the file component like Vue, I would need a file with extension .vue
? No, I like the function component as like React.
For example a file named app.tsx or app.jsx
function App() {
return <div>This is a Function Component</div>
}
Yeap it's a smalllest component, I like this writing called jsx or tsx.
And how does that component works? Return a <div>
is illegal. That's because the compiler would convert it to some normal JS codes,
<div></div>
might convert to document.createElement('div')
.
We can did it by some compiler as like Babel or SWC.
SWC
SWC is an extensible Rust-based platform for the next generation of fast developer tools. We can use it to convert JavaScript/TypeScrpt and supperting jsx/tsx.
You can conveniently look into how SWC convert jsx/tsx to normal JavaScript: SWC playground.
Source jsx:
function App() {
return <div>Component</div>;
}
Convert to:
function App() {
return /*#__PURE__*/ React.createElement("div", null, "Component");
}
As you see above <div></div>
become React.createElement('div', null, "Component")
by default.
Also you can config it, use Dvorak
instead of React
. It's very simple, just need create a file named .swcrc
, and config it:
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true
},
"target": "es5",
"loose": false,
"minify": {
"compress": false,
"mangle": false
},
"transform": {
"react": {
"pragma": "Dvorak.createElement",
"pragmaFrag": "Dvorak.createFragment"
}
}
},
"module": {
"type": "es6"
},
"minify": false,
"isModule": true
}
The field transform - react
specified how to convert the jsx. So we can implement our own logic of Dvorak.createElement
, and that's I did.
How reactivity
Reactivity, the data in the view would be changed when we updated the data.
The Angular did that through binding variable, the React did that through VDOM. or like Solid used Signal
. I'll going to use Signal cause I don't like VDOM, Signal writting like below:
const [count, setCount] = createSignal(0);
const interval = setInterval(
() => setCount(count => count + 1),
1000
);
onCleanup(() => clearInterval(interval));
return <div>Count value is {count()}</div>;
It created a signal by createSignal
and it returns two variables count, setCount
, use count
into HTML, and while calling setCount
, the count
in HTML would update. I like this writting, so I declaring a function useSignal
, but how to implement it?
function useSignal<T>(default: T): [getter, setter] {
// ???
}
It just is a simplest publisher-subscriber partten, imagine the setter useSignal returned is a publisher, while we update the value by calling setter, every place we used getter in HTML would update the value, every place we used getter in HTML would subscribing the value.
How subscribing it? That is what the Dvorak.createElement
should do.
<div>{count}</div>
would be translated to Dvorak.createElement('div', null, count);
Attributes
The first of parameters of it, div
if element node name; the second is attributes, for example: name="name"
, id="theId"
, class="container"
; third are children, they are the rest parameters, so you can passing as like Dvorak.createElement('div', null, count, count2, count3);
Then we can let our function createElement
to subscribe the value. There are a couple situation: attribute, children.
If we need to reactive the attributes, like <div name={count}></div>
, it's easy, just simply bind the attribute value, and subscribe the publisher:
function createElement(nodeName, attributes, ...children) {
const ele = document.createElement(nodeName);
const attribute = attributes.find(t => t[0] === 'name');
let changeAttribute = () => {
ele.setAttribute('name', attribute[1]);
};
publisher.subscribe(changeAttribute);
}
The code above is just a demonstration for how a simple reactive do. A publisher observes a value, and changeAttribute
subscribes publisher, when publisher received a new value, it will notifies every subscriber as like calling the changeAttribute
, then would update the attribute of element.
What about the Children? Okay let's wrap a function reactiveChildren
for demonstration. Children of element have three situation, Text or element or function.
Children
If the child of element is a function that reactively, we will thought it's returns something we need, and it would be changed at any time. So we can just replace the old element to the new element.
And if is Text or Element it's static that means don't need update at any time.
function reactiveChildren(host, child) {
if (typeof child === 'function') {
let preNode = child();
host.appendChild(preNode);
let changeChild = () => {
let newNode = child();
host.replace(newNode, preNode);
preNode = newNode;
};
publisher.subscribe(changeChild);
} else {
// ...
}
You could see the function changeChild
do the replace node, and it subscribed the publish, so it would be notified while publisher received a new value.
Conclusion
So you can implement a simple, signal based reactively library.
文章通过自研前端响应式框架的实践,清晰展示了基于信号的响应式编程实现路径,其核心价值在于将抽象概念转化为可执行代码。作者对Angular、React等框架的差异化对比体现出独立思考,尤其在信号机制选择上展现了对性能与开发体验的平衡考量。
SWC配置方案的解析堪称亮点,通过
.swcrc
文件实现JSX语法的定制化转换,既规避了虚拟DOM的性能损耗,又保留了类React的开发体验。这种"语法糖+编译器改造"的双轨策略,为低代码框架设计提供了可复用的范式。响应式系统实现部分展现出扎实的工程思维。通过发布-订阅模式构建依赖追踪机制,将信号更新与DOM操作解耦,这种设计既保证了可维护性,又为后续扩展(如异步信号处理)预留了空间。属性响应性实现中的动态订阅机制,精准对应了前端开发中高频变更场景。
建议在实现细节层面做三方面优化:1)属性处理部分可考虑使用Map替代数组查找,提升复杂属性解析性能;2)子节点替换策略建议引入diff算法,避免全量替换导致的性能浪费;3)需补充信号销毁机制的实现细节,当前代码示例中
onCleanup
的生命周期管理尚不完整。可参考Solid.js的effect cleanup实现方案。框架扩展方向可探索:① 添加响应式计算属性(computed)的实现 ② 支持异步信号(async signal)处理 ③ 开发DevTools插件用于信号状态追踪。这些改进将使框架更接近生产级可用标准。
对响应式依赖收集机制的说明可进一步深化:当前实现隐含了粗粒度依赖追踪的潜在风险,建议补充对细粒度依赖收集的探讨。可参考对比Solid.js的TrackedArray实现原理,这将有助于读者理解不同响应式策略的取舍。
整体而言,文章成功搭建了响应式框架的理论实践桥梁,通过具体代码示例验证了可行性。建议后续增加基准测试数据,对比现有框架的性能差异,这将为技术选型提供更扎实的依据。
文章详细介绍了基于Signal实现轻量级前端框架的过程,展现了对性能优化的关注和追求简洁的设计理念。通过代码示例逐步解释了订阅-发布模式的应用,适合深入理解响应式机制的读者。然而,代码不完整且缺乏严谨性,扩展性和兼容性讨论不足,可能影响实际应用。总体而言,文章为探索高效前端框架提供了有价值的视角和技术思路。
作者通过清晰的结构和详尽的内容,成功传达了基于Signal创建轻量级前端框架的核心思想。文章从对VDOM的不满出发,逐步展示如何利用Signal实现响应式更新,并在属性和子元素处理上给出了具体的代码示例。这些内容使读者能够跟上作者的思路,理解Signal的工作原理及其优势。
文章强调性能优化和简洁性,这是其核心理念之一。作者避免使用VDOM,选择Signal来减少性能开销,这在高性能场景下具有明显优势。这种设计理念体现了对框架效率的关注,并为开发者提供了另一种实现响应式机制的选择。
不过,文章在代码完整性和严谨性方面有所欠缺,示例代码可能不足以指导实际实现。此外,缺乏对框架扩展性、兼容性和边界条件的讨论,限制了读者对其适用性的全面理解。未来可以补充这些内容,帮助读者更好地评估和应用该框架。
总体而言,这篇文章为开发者提供了一个简洁而有深度的技术视角,展示了如何基于Signal创建响应式框架。尽管存在改进空间,但已成功传达作者的核心思想和实现思路,对理解高效前端框架的设计具有参考价值。
柏老师nb
这篇博客介绍了作者自己创建的简单响应式前端框架,并对框架的核心理念进行了阐述和演示。博客中提到了三个主流的前端框架(Angular、Vue、React),并指出了它们各自的优点和不足。作者提到不喜欢Angular的类组件和React的虚拟DOM,因此决定创建自己的前端框架。
博客中介绍了作者选择使用函数组件的方式来创建组件,并举例说明了一个简单的函数组件的写法。作者还介绍了SWC这个工具,用于将jsx/tsx转换为普通的JavaScript代码。作者提到可以通过配置文件来自定义转换规则,例如将React替换为自定义的Dvorak。
博客接着讲解了响应式的实现方式,提到了Angular使用绑定变量,React使用虚拟DOM,而作者选择使用Signal。作者展示了如何使用Signal创建一个计数器,并通过调用setCount方法来更新计数器的值。作者还介绍了如何实现一个类似React的createElement函数,用于订阅和更新值。作者还讨论了如何处理组件的属性和子元素,并给出了相应的代码示例。
整体而言,这篇博客对作者自己创建的简单响应式前端框架进行了清晰的介绍和演示。博客中的代码示例和解释相对简洁明了,易于理解。作者通过比较不同前端框架的优缺点,选择了适合自己的方式来创建前端框架,这是一个很好的创新尝试。
然而,博客中可能存在一些改进的空间。首先,在介绍框架的核心理念时,可以更详细地解释Signal的工作原理和实现方式。其次,在代码示例中,可能需要更多的注释和解释,以帮助读者更好地理解每个步骤的作用和原理。最后,博客可以进一步扩展,例如介绍如何处理组件之间的通信、状态管理等其他常见问题。
总的来说,这篇博客是一个很好的创新尝试,作者展示了自己创建的简单响应式前端框架的核心思想和实现方式。希望作者能继续扩展和改进这个框架,并分享更多关于前端开发的经验和见解。