介绍
我已经编码很长时间了,我一直在使用React五年以上。
在这段时间里,我对如何改善反应有一些想法。
大约三年前,我开始努力实施这些想法。
首先,我测试了这些概念,然后决定将所有内容变成库。
在本文中,我想告诉你它发生了什么。
反应有什么好处
首先,这是在一个代码中组合JavaScript和HTML时的反应方法。其他框架也无法做到这一点。例如,某些框架发明了自己的模板编程语言,这些语言复制了JavaScript语言的构造,例如if
,else
,each
...
我认为,这是不必要的认知负担。幸运的是,某些框架从那以后获得了JSX的支持。
React的另一个重要但不是那么明显的特征是单向数据流和相应的心理模型,它有助于构建代码,同时保持灵活性。
反应怎么了
组件创建
在React中,建议使用函数组件而不是基于类的组件。这无疑使工作更容易。
让我们考虑以下示例:
function CounterButton() {
const [count, setCount] = useState(0);
const handleClick = () => setCount(count + 1);
return (
<button onClick={handleClick}>
You clicked {count} times
</button>
);
}
CounterButton
函数既是组件构造函数,又是更新数据的函数。这种责任的混合是一种不好的做法,它会造成许多问题,我们现在将讨论这些问题。
更新组件
React调用每次数据更新时CounterButton
函数。每次称为此功能中创建的所有对象。
在示例中,handleClick
函数就是这样的对象。如果在构造函数中创建此功能一次,则可以保存资源。但是它不存在于函数组件中。
另外,每次称为CounterButton
函数都会返回一个新的虚拟dom对象。
在heap上创建了新对象,并且garbage collector删除了旧对象。此过程会导致碎片和过度的内存使用。还需要定期defragmentation。所有这些过程都是资源密集的。
值得注意的是,在我们的具体示例中,React行为的这一方面无关紧要。但是对于具有数百或数千个组件的大型应用程序,它变得明显。
这就是为什么从版本17开始的React团队开始开发concurrent mode,以便在React流程运行时更新UI。
钩子
可以说,使用 hooks 将部分解决创建不必要对象的问题。
顺便说一句,在我工作的一家公司中,该政策是始终使用 hooks 。
所以这是一个示例:
const handleClick = useCallback(
() => setCount(count + 1),
[count]
);
一方面,钩子允许您忘记树上较低的组件的不必要更新。但另一方面,它们并不能解决创建“垃圾”对象的问题。例如,如果count
尚未更改,则创建函数() => setCount(count + 1)
仅由 hook 丢弃。此外,还创建了一个新的数组对象。
其他钩子也发生了同样的事情。例如,比较constructor(){code();}
和useEffect(() => {code();}, [])
。在第一种情况下,代码将仅在构造函数中运行一次。在第二种情况下,将在每个更新上创建两个额外的对象。
另外,钩子机制本身就是影响性能的其他逻辑。但是,我认为钩子的最大缺点是它们的冗长。为简单操作编写包装纸是非常无聊的。代码的可读性也遭受了。
综上所述
- 在 heap 上创建和删除了许多不必要的对象,导致碎片,内存过度消费和碎片整理,从而恶化了性能。
- 引入了更多逻辑,该并发模式也会使性能恶化。
- 使用挂钩的其他逻辑也会恶化性能。
- 钩子使代码更详细,更难理解。
如何提高
接下来,我想提出上述问题的解决方案。
所以,简而言之,我们需要:
- 提高性能。
- 降低冗长。
- 使代码更加明确,易于理解。
组件构造函数
让我们从一个假设的例子开始:
function CounterButton() {
let count = 0;
const handleClick = () => {
count++;
btn.update();
};
const btn = button(
{ click$e: handleClick }, // props
() => `You clicked ${count} times`, // child
);
return btn;
}
它看起来与一个反应示例非常相似。为简单起见,让我们暂时省略JSX。
这里唯一未知因素是button
函数,所有“魔术”发生。我们可以想象它可以根据我们的示例来工作。
button
功能应执行以下操作:
- 创建一个dom对象
HTMLButtonElement
。 - 为按钮设置单击事件处理程序
handleClick
。 - 使用lambda函数返回的值设置按钮的文本
() => `You clicked ${count} times`
。 - 允许使用lambda函数的结果更新按钮的文本。
button
函数应创建并返回具有两个属性的对象是有道理的:element
和update
。
此对象的类看起来像这样:
class Component {
get element() {}; // return DOM Element object
update() {}; // update dynamic data
}
单击按钮时,计数器将增加一个count++
。然后称为btn.update()
方法,该方法执行lambda函数并更新按钮文本。
使用组件
现在,让我们将此组件连接到DOM树:
document.body.append(
CounterButton().element,
);
首先,我们调用CounterButton
函数,该函数创建和返回组件,然后将其元素连接到DOM树。
现在,应显示带有计数器的按钮并正确计数点击数。
好吧,让我们假设:
- 除了
button
外,还有一套HTML功能:h1
,div
,span
等... - 这些功能创建的组件可以包含 child 组件,等等。
- 当更新 parent 组件时,它的 child 组件也将更新。
就是这样! ðρ
这种方法解决了上述所有问题,并保留了React的良好特征。
结果
我敢打赌您期望更大的东西。
不必担心要明确进行组件更新。通常,更新是由高阶组件触发的。但是,也可以选择性地更新任何部分。
顺便说一句,在React中,还需要调用明确的更新。
setState
函数和useState
钩可实现此目的。但是,它的灵活性较小,而且资源密集。例如,调用setCount(count + 1)
将通过状态机制设置变量,然后通过更新机制将更新的需求添加到队列中。如您所见,再次存在责任。
因此,上述概念有助于通过以下方式解决问题:
- 首先,组件创建函数用作其构造函数,仅称为一次。因此,该函数内部创建的对象也仅创建一次,并且不需要此类hacks hooks 。
- 没有钩子。没有其他逻辑。一切都会明确且可读。
- 并发模式不是必需的,因为首先,逻辑数量显着下降,其次,我们可以完全控制创建和更新过程,并且可以在需要的地方轻松插入接口渲染。 li>
Fusor
这是什么
Fusor是一个简单的库,可以声明地创建和更新DOM元素。
在Fusor中,没有其他机制:
- props
- 状态
- 上下文
- 生命周期
Fusor是一种简约且透明的方法,它使用JavaScript语言的构造和DOM函数“几乎没有库”。
尽管如此,Fusor可以完全替代React!怎么可能?让我们仔细看看
经济
Fusor是一个经济的图书馆。
Fusor不会在 heap 。
上创建一堆不必要的对象。例如,让我们考虑以下代码:
import { div, p } from '@fusorjs/dom/html';
const wrapper = div(
p('I am the static text')
);
变量wrapper
将包含一个HTMLDivElement
对象,而不是Component
,因为示例带有计数器按钮,因为这里没有 dynamic parts。
如果我们以按钮为例并稍作修改:
import { button } from '@fusorjs/dom/html';
function CounterButton() {
let count = 0;
const handleClick = () => {
count++;
btn.update();
};
const btn = button(
// props:
{ click$e: handleClick },
// child text nodes:
'You clicked ', // static
() => count, // dynamic
' times', // static
);
return btn;
}
因此,您可以看到现在只有 dynamic 的三个 child 元素之一。 btn
变量将是Component
类的对象。
更新时,只有一个文本 node 的值,lambda函数() => count
的绑定,如果值与已经存在的值不同,将会更改。
因此,仅在包含 dynamic data。
时创建附加组件对象。 动态数据也可以在属性中。例如,{class: () => selected ? 'selected' : 'unselected'}
。
生命周期
组件生命周期是Fusor缺乏替代React的唯一机制。
由于Fusor做了一件事并且做得很好,因此它没有组件生命周期逻辑。但是,这种逻辑存在于本地custom elements中。
Fusor完全支持所有Web标准,包括 Web组件。因此,它们可用于连接生命周期事件。
尽管如此,为方便起见,Fusor具有自定义元素称为fusor-life
及其包装器组件Life
:
import { Life } from '@fusorjs/dom/life';
const wrapper = Life(
{
connected$e: () => {},
disconnected$e: () => {},
// ... other props
},
// ... children
);
将此与组件生命周期机构和组件树的O(n)
遍历进行比较。
fusor | fusor-life | react | |
---|---|---|---|
安装 | 构造函数 | 连接 | 构造函数,getDerivedStateFromprops,渲染,componentDidmount |
更新 | 更新 | attributechanged | getDerivedStateFromprops,shoreComponentUpdate,render,getSnapShotBeforeUpdate,componentDidupDate |
卸载 | 断开连接 | componentwillunmount |
定制
不必使用位于html
,svg
或life
中的功能。它们的存在是为了避免必须手动创建它们并演示如何完成。
例如,如果您需要创建一组特定的HTML标签,则可以轻松地进行。
如果您需要为所有元素使用单个函数,则可以将h
函数用于HTML或SVG的s
函数。例如:h('div', props, children)
。或者您可以做其他变体。
还有一个更灵活的功能create(element, props, children)
。使用此功能,您可以配置与Fusor的JSX使用。
关于JSX
JSX支持也将提供。
功能符号也很好,因为:
- 这是纯粹的JavaScript,带有常规评论。
- 不需要转换,构建或汇编。
- 您可以按任何顺序使用任意数量的 props 和儿童。
链接
成熟的应用程序和其他资源:
- counter button-应用程序的交互式示例。
- tutorial with recipes(code) - 互动应用程序,具有Fusor的主要用法方案:生命周期,请求,路由等
- Implementation of TodoMVC(code) - 开发了Fusor的应用。因此,事实证明这不是最简单,最美丽的,而是意识形态上正确的。另外,此应用程序中不需要生命周期事件。
- Fusor repository and documentation.
结论
有应用。提供基本用法方案的示例。还提供测试覆盖范围。
API稳定了一段时间。 Fusor可以用于生产。
npm install @fusorjs/dom
ps:感谢所有终结的人! ðÖt
Fusor vs反应
fusor | react | |
---|---|---|
组件构造函数 | 显式,功能 | 与funtion组件中的更新机结合 |
组件中的对象 | 创建了一次 | 即使在记忆中都重新创建了每个更新 |
状态,效果,参考 | 变量和功能 | 复合物,钩子系统,详细的 |
更新组件 | 显式,灵活 | 隐式,复杂,扩散 |
dom | 真实 | 虚拟 |
事件 | 本机 | 合成 |
生命周期 | 本机,自定义元素 | 复合物,树步行 |