在(无框架)基于 Javascript 的单页应用程序中,URL queryString 更新但脚本无法解析更新后的 URL
In a (frameworkless) Javascript-based Single Page App, the URL queryString updates but the script fails to parse the updated URL
我正在学习 状态管理 在基于 javascript 的无框架应用程序的上下文中,我想我会尝试在URL queryString
.
我开发了一个设置,其中 URL queryString
包含少量参数,一些对应于 boolean variables
,其他对应于 string variables
.
与 UI 的任何用户交互都会启动一个 两步 过程:
- 用户交互更新
queryString
中的相应参数
- 在
queryString
更新之后,另一个脚本 立即解析 queryString
以将应用程序状态的新表示形式转化为整个应用程序的实际变化
本质上,queryString
作为应用程序核心的 single source of truth (SSOT) 运行。
解析 步骤 2 中的 queryString
提供设置以使用值填充各种自定义数据-* 属性并在整个应用程序中启动各种功能。这可能更容易用一个例子来证明,所以...
简化示例:
const section = document.getElementsByTagName('section')[0];
const div1 = document.getElementsByClassName('div1')[0];
const div2 = document.getElementsByClassName('div2')[0];
let queryString = '?';
const appState = {
'div1-color': 'red',
'div1-shape': 'circle',
'div2-color': 'blue',
'div2-shape': 'square'
};
const updateQueryString = (appState) => {
const queryStringArray = [];
for (let appStateKey in appState) {
queryStringArray.push(appStateKey + '=' + appState[appStateKey]);
}
queryString = '?' + queryStringArray.join('&');
console.log('QueryString: ' + queryString);
// ^^^
// THIS FINAL LINE IS FOR THE SAKE OF THIS EXAMPLE
// IN THE REAL APP, THE FINAL LINE IS:
// window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
}
const updateApp = (queryString) => {
let urlParams = new URLSearchParams(queryString);
for (let entry of urlParams.entries()) {
let key = 'data-' + entry[0];
let value = entry[1];
section.setAttribute(key, value);
}
}
const updateAppState = (e) => {
const target = e.target.className;
switch (e.type) {
case ('mouseover') :
appState[target + '-color'] = (appState[target + '-color'] === 'red') ? 'blue' : 'red';
break;
case ('click') :
appState[target + '-shape'] = (appState[target + '-shape'] === 'circle') ? 'square' : 'circle';
break;
}
updateQueryString(appState);
updateApp(queryString);
}
div1.addEventListener('mouseover', updateAppState, false);
div1.addEventListener('click', updateAppState, false);
div2.addEventListener('mouseover', updateAppState, false);
div2.addEventListener('click', updateAppState, false);
h2 {
font-family: sans-serif;
font-size: 16px;
}
[class^="div"] {
display: inline-block;
margin-right: 12px;
width: 80px;
height: 80px;
cursor: pointer;
transition: all 0.6s linear;
}
[data-div1-color="red"] .div1 {
background-color: rgb(255, 0, 0);
}
[data-div1-color="blue"] .div1 {
background-color: rgb(0, 0, 191);
}
[data-div2-color="red"] .div2 {
background-color: rgb(255, 0, 0);
}
[data-div2-color="blue"] .div2 {
background-color: rgb(0, 0, 191);
}
[data-div1-shape="circle"] .div1 {
border-radius: 50%;
}
[data-div1-shape="square"] .div1 {
border-radius: 0;
}
[data-div2-shape="circle"] .div2 {
border-radius: 50%;
}
[data-div2-shape="square"] .div2 {
border-radius: 0;
}
<h2>Mouseover to change colour, click to change shape:</h2>
<section
data-div1-color="red"
data-div1-shape="circle"
data-div2-color="blue"
data-div2-shape="square"
>
<div class="div1"></div>
<div class="div2"></div>
</section>
上面的示例按预期工作,因为为了演示 Stack Overflow 上的设置,我不想在浏览器指向 [=25 时开始更新 URLs =].
我在真实应用程序中遇到的问题是即使queryString
更新,它从不被解析正确。
从 queryString
解析的数据表明 queryString
根本没有更新。
然而,当我查看 queryString
时,它肯定已经更新了。
如果 queryString
正在更新,为什么它没有被正确解析?
我花了更多时间思考这个问题。
我得出的结论是,这段代码中最有可能发生的事情是:
updateQueryString(appState);
updateApp(window.location.search);
是在第一个函数中
updateQueryString(appState)
最后一行
window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
尚未完成更新当前URL,在第二个函数
之前
updateApp(window.location.search)
伸手抓住window.location.search
。
所以,总而言之,我认为我正在处理一个 同步进程 ,但我实际上正在查看一个 异步进程 .
解决方案尝试 #1 - 更改单一事实来源 (SSOT)
我突然想到,虽然 URL 几乎没有立即更新,但 appState object
.
这增加了我可能使用 appState object
作为 SSOT 而不是 queryString
.
的可能性
然后 appState
会通知 both queryString
和 提供应用程序的新表示可以转化为整个应用程序的实际变化的状态。
我拒绝了这个可能的解决方案,原因有二:
我不希望出现这样的情况,即在沟通不畅的情况下,应用程序的状态 与 不完全一致queryString
中应用程序状态的表示。如果 queryString
扮演 SSOT 的角色,这是不可能的,但如果 queryString
只是一个翻译(或 appState object
.
的误译)
在上面的示例中,appState object
扮演着 助手 的角色,确保正确表示任何用户交互产生的状态在 queryString
。因为这个例子很简单,所以 appState object
代表了整个应用程序的状态(有效地复制了 queryString
中的表示)。但我绝对希望助手可以仅与 queryString
状态的更新部分进行通信。如果 appState object
担任 SSOT 的角色,那么它将始终需要代表应用程序的整个状态。
解决方案尝试 #2 - 使用 popstate
事件
因为我只想调用
updateApp(window.location.search)
queryString
更新后,我可以直接使用 window.onpopstate
。最简单的方法是添加 Event Listener
作为 updateQueryString(appState)
的最后一行,如下所示:
window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
window.addEventListener('popstate', updateApp);
这样,updateApp()
仅在 window.history.pushState
完成后才开始执行。
最终解决方案
解决方案 #2 非常好,但令我震惊的是我可以通过将 window.history.pushState
视为 [=118= 来更快地调用 updateApp
updateQueryString(appState)
的]side-effect(而不是主要结果)并重新考虑函数,以便函数的最后两行包含此 return
语句,而不是:
window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
return queryString;
这意味着 updateApp()
甚至不必等待 window.history.pushState
完成。
相反,updateApp()
可以直接处理 updateQueryString(appState)
返回的数据,即使该函数仍在更新 queryString
。
解法:
将updateApp()
的第一行改为:
let urlParams = new URLSearchParams(window.location.search);
至:
let urlParams = new URLSearchParams(updateQueryString(appState));
替换每个实例:
updateQueryString(appState);
updateApp();
与:
updateApp(appState);
我正在学习 状态管理 在基于 javascript 的无框架应用程序的上下文中,我想我会尝试在URL queryString
.
我开发了一个设置,其中 URL queryString
包含少量参数,一些对应于 boolean variables
,其他对应于 string variables
.
与 UI 的任何用户交互都会启动一个 两步 过程:
- 用户交互更新
queryString
中的相应参数
- 在
queryString
更新之后,另一个脚本 立即解析queryString
以将应用程序状态的新表示形式转化为整个应用程序的实际变化
本质上,queryString
作为应用程序核心的 single source of truth (SSOT) 运行。
解析 步骤 2 中的 queryString
提供设置以使用值填充各种自定义数据-* 属性并在整个应用程序中启动各种功能。这可能更容易用一个例子来证明,所以...
简化示例:
const section = document.getElementsByTagName('section')[0];
const div1 = document.getElementsByClassName('div1')[0];
const div2 = document.getElementsByClassName('div2')[0];
let queryString = '?';
const appState = {
'div1-color': 'red',
'div1-shape': 'circle',
'div2-color': 'blue',
'div2-shape': 'square'
};
const updateQueryString = (appState) => {
const queryStringArray = [];
for (let appStateKey in appState) {
queryStringArray.push(appStateKey + '=' + appState[appStateKey]);
}
queryString = '?' + queryStringArray.join('&');
console.log('QueryString: ' + queryString);
// ^^^
// THIS FINAL LINE IS FOR THE SAKE OF THIS EXAMPLE
// IN THE REAL APP, THE FINAL LINE IS:
// window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
}
const updateApp = (queryString) => {
let urlParams = new URLSearchParams(queryString);
for (let entry of urlParams.entries()) {
let key = 'data-' + entry[0];
let value = entry[1];
section.setAttribute(key, value);
}
}
const updateAppState = (e) => {
const target = e.target.className;
switch (e.type) {
case ('mouseover') :
appState[target + '-color'] = (appState[target + '-color'] === 'red') ? 'blue' : 'red';
break;
case ('click') :
appState[target + '-shape'] = (appState[target + '-shape'] === 'circle') ? 'square' : 'circle';
break;
}
updateQueryString(appState);
updateApp(queryString);
}
div1.addEventListener('mouseover', updateAppState, false);
div1.addEventListener('click', updateAppState, false);
div2.addEventListener('mouseover', updateAppState, false);
div2.addEventListener('click', updateAppState, false);
h2 {
font-family: sans-serif;
font-size: 16px;
}
[class^="div"] {
display: inline-block;
margin-right: 12px;
width: 80px;
height: 80px;
cursor: pointer;
transition: all 0.6s linear;
}
[data-div1-color="red"] .div1 {
background-color: rgb(255, 0, 0);
}
[data-div1-color="blue"] .div1 {
background-color: rgb(0, 0, 191);
}
[data-div2-color="red"] .div2 {
background-color: rgb(255, 0, 0);
}
[data-div2-color="blue"] .div2 {
background-color: rgb(0, 0, 191);
}
[data-div1-shape="circle"] .div1 {
border-radius: 50%;
}
[data-div1-shape="square"] .div1 {
border-radius: 0;
}
[data-div2-shape="circle"] .div2 {
border-radius: 50%;
}
[data-div2-shape="square"] .div2 {
border-radius: 0;
}
<h2>Mouseover to change colour, click to change shape:</h2>
<section
data-div1-color="red"
data-div1-shape="circle"
data-div2-color="blue"
data-div2-shape="square"
>
<div class="div1"></div>
<div class="div2"></div>
</section>
上面的示例按预期工作,因为为了演示 Stack Overflow 上的设置,我不想在浏览器指向 [=25 时开始更新 URLs =].
我在真实应用程序中遇到的问题是即使queryString
更新,它从不被解析正确。
从 queryString
解析的数据表明 queryString
根本没有更新。
然而,当我查看 queryString
时,它肯定已经更新了。
如果 queryString
正在更新,为什么它没有被正确解析?
我花了更多时间思考这个问题。
我得出的结论是,这段代码中最有可能发生的事情是:
updateQueryString(appState);
updateApp(window.location.search);
是在第一个函数中
updateQueryString(appState)
最后一行
window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
尚未完成更新当前URL,在第二个函数
之前updateApp(window.location.search)
伸手抓住window.location.search
。
所以,总而言之,我认为我正在处理一个 同步进程 ,但我实际上正在查看一个 异步进程 .
解决方案尝试 #1 - 更改单一事实来源 (SSOT)
我突然想到,虽然 URL 几乎没有立即更新,但 appState object
.
这增加了我可能使用 appState object
作为 SSOT 而不是 queryString
.
然后 appState
会通知 both queryString
和 提供应用程序的新表示可以转化为整个应用程序的实际变化的状态。
我拒绝了这个可能的解决方案,原因有二:
我不希望出现这样的情况,即在沟通不畅的情况下,应用程序的状态 与 不完全一致
的误译)queryString
中应用程序状态的表示。如果queryString
扮演 SSOT 的角色,这是不可能的,但如果queryString
只是一个翻译(或appState object
.在上面的示例中,
appState object
扮演着 助手 的角色,确保正确表示任何用户交互产生的状态在queryString
。因为这个例子很简单,所以appState object
代表了整个应用程序的状态(有效地复制了queryString
中的表示)。但我绝对希望助手可以仅与queryString
状态的更新部分进行通信。如果appState object
担任 SSOT 的角色,那么它将始终需要代表应用程序的整个状态。
解决方案尝试 #2 - 使用 popstate
事件
因为我只想调用
updateApp(window.location.search)
queryString
更新后,我可以直接使用 window.onpopstate
。最简单的方法是添加 Event Listener
作为 updateQueryString(appState)
的最后一行,如下所示:
window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
window.addEventListener('popstate', updateApp);
这样,updateApp()
仅在 window.history.pushState
完成后才开始执行。
最终解决方案
解决方案 #2 非常好,但令我震惊的是我可以通过将 window.history.pushState
视为 [=118= 来更快地调用 updateApp
updateQueryString(appState)
的]side-effect(而不是主要结果)并重新考虑函数,以便函数的最后两行包含此 return
语句,而不是:
window.history.pushState({}, document.title, 'https://example.com/?' + queryString);
return queryString;
这意味着 updateApp()
甚至不必等待 window.history.pushState
完成。
相反,updateApp()
可以直接处理 updateQueryString(appState)
返回的数据,即使该函数仍在更新 queryString
。
解法:
将updateApp()
的第一行改为:
let urlParams = new URLSearchParams(window.location.search);
至:
let urlParams = new URLSearchParams(updateQueryString(appState));
替换每个实例:
updateQueryString(appState);
updateApp();
与:
updateApp(appState);