状态管理如何运作?原生 JavaScript 中的简单状态管理
原生 JavaScript 中极其简单的状态管理。
你用 Redux、MobX 甚至 React Hooks 都好几年了,却对状态管理的工作原理以及它为什么这样工作一无所知?我将向你展示状态管理中极其简单的底层工作,无需任何优化或其他花哨的功能。
我们将构建一个包含脚本标签的极其简单的纯 HTML 页面。
<!DOCTYPE html>
<html>
<head>
<title>State Management in Vanilla JS</title>
</head>
<body>
<div id="app"></div>
<script>
//
</script>
</body>
</html>
现在让我们编写一些 JavaScript。
注意: TL;DR; 在下面⏬
const App = function _App() {
return `
<h1>Hello Vanilla JS</h1>
<div>Example of state management in Vanilla JS</div>
`;
}
document.getElementById("app").innerHTML = App();
我可以简单地声明为
const App = function() { // ...
// or
const App = () => { // ...
但我没这么做是有原因的,稍后我会解释。现在,让我们创建一些状态
App.state = {
count: 0,
increment: () => {
App.state.count++;
}
};
作为 App 函数的属性创建的简单状态。😉
等等!你能做到吗? 😲
是的,JavaScript 中的所有内容都是对象,从技术上讲,你甚至可以对字符串和数字进行对象化。这就是为什么像"hello world".toUppercase()
and这样的方法(12).toFixed(2)
能够正常工作。但是编译器不允许你在字符串或数字上定义自己的属性。
现在 App 已经具有状态,我们将整合状态并在文件末尾添加一个点击事件监听器。
`
<h1>${_App.state.count}</h1>
<button id="button">Increase</button>
`
// ...
document.getElementById("app").innerHTML = App();
// On Click Function
document
.getElementById("button")
.addEventListener("click", App.state.increment);
请注意,我访问 App 内部的方法既不是this
也不是 ,App
而是_App
。这被称为“命名函数表达式”。
命名函数表达式有两个特殊之处:
- 它允许函数在内部引用自身。
- 它在函数外部是不可见的。
即使我在下面做了类似的事情,代码也不会中断。
const Component = App;
App = null;
document.getElementById("app").innerHTML = Component();
即使 App 被重新赋值给 Component,然后又被置为 null,函数本身仍然保持不变,并在本地以 _App 的形式引用自身,因此不受影响。这和this
其他所有 OOP 编程语言中的“ ”一样(不过我们都知道this
JavaScript 是如何运作的)😅。
现在尝试运行它(只需双击 index.html 文件)。注意,点击函数不起作用!🙄 这是因为 UI 没有反映最新状态,让我们通过重新渲染元素来解决这个问题。这可以通过在状态更新后再次运行这段代码来实现。
document.getElementById("app").innerHTML = App();
// On Click Function
document
.getElementById("button")
.addEventListener("click", App.state.increment);
由于此代码将会重复,我们将其提取到函数中
const updateTree = () => {
document.getElementById("app").innerHTML = App();
// On Click Function
document
.getElementById("button")
.addEventListener("click", App.state.increment);
}
现在添加一个 setState 函数
const setState = (callback) => {
callback();
updateTree(); // extracted function
}
并将增量函数更新为
increment: () => {
// Call our set state function
setState(() => App.state.count++);
}
现在我们的应用已经按预期运行了。就这样!原生 JavaScript 中死气沉沉的状态管理就到此为止了。然而,如果只是照原样使用,会被认为是一个糟糕透顶的框架,原因并非在于它缺乏任何花哨的功能,而是因为它的优化很差,实际上它根本就没有优化,不过我在文章开头说“……没有任何优化或其他花哨的功能”的时候,你应该已经明白了。
要做的事情,
- 不应使整个应用程序反映一个简单的变化。
- 一旦我们更新以反映状态,附加到 DOM 的所有事件监听器都不应丢失,并且我们不应该在其位置添加新的事件监听器。
- 不受状态影响且未改变的 DOM 元素不应被强制更改。更改应尽可能小
因此,我们将对我们的应用程序进行一些优化,就像 React 和类似的库/框架在下一篇文章中所做的那样。
TL;DR;
这是我们迄今为止编码的完整 HTML 文件。
<!DOCTYPE html>
<html>
<head>
<title>State Management in Vanilla JS</title>
</head>
<body>
<div id="app"></div>
<script>
const App = function _App() {
return `
<h1>Hello Vanilla JS!</h1>
<div>
Example of state management in Vanilla JS
</div>
<br />
<h1>${_App.state.count}</h1>
<button id="button">Increase</button>
`;
};
App.state = {
count: 0,
increment: () => {
setState(() => App.state.count++);
}
};
const setState = (callback) => {
callback();
updateTree(); // extracted function
}
const updateTree = () => {
document.getElementById("app").innerHTML = App();
document
.getElementById("button")
.addEventListener("click", App.state.increment);
};
updateTree();
</script>
</body>
</html>
更新:
- (2021 年 3 月 13 日)添加了
setState
函数,修复了一些拼写错误,并添加了命名函数表达式的链接。