javascript - NextJS - 使用浅路由器推送时无法识别页面组件上发生状态变化的位置
问题描述
我有一个应用程序。使用 NextJS。我有一个如下所示的页面:
import React from 'react'
import { parseQuery } from '../lib/searchQuery'
import Search from '../components/search'
class SearchPage extends React.Component {
static getInitialProps ({ query, ...rest }) {
console.log('GET INITIAL PROPS')
const parsedQuery = parseQuery(query)
return { parsedQuery }
}
constructor (props) {
console.log('CONSTRUCTOR OF PAGE CALLED')
super(props)
this.state = props.parsedQuery
}
render () {
return (
<div>
<div>
<h1>Search Results</h1>
</div>
<div>
<h1>DEBUG</h1>
<h2>PROPS</h2>
{JSON.stringify(this.props)}
<h2>STATE</h2>
{JSON.stringify(this.state)}
</div>
<div>
<Search query={this.state} />
</div>
</div>
)
}
}
export default SearchPage
getInitialProps
为 SSR 运行 - 它接收查询字符串作为对象(通过后端的 Express)通过一个简单的“清洁”功能运行它parseQuery
- 我制作了它,并通过 props 将其注入页面,props.parsedQuery
如您在上面看到的. 这一切都按预期工作。
该Search
组件是一个包含许多字段的表单,其中大部分select
基于预定义字段和一些基于数字的input
字段,为简洁起见,我省略了整个组件的标记。Search
获取道具并通过函数query
将它们分配给其内部状态。constructor
在更改组件上的select
和input
字段时,将Search
运行此代码:
this.setState(
{
[label]: labelValue
},
() => {
if (!this.props.homePage) {
const redirectObj = {
pathname: `/search`,
query: queryStringWithoutEmpty({
...this.state,
page: 1
})
}
// Router.push(href, as, { shallow: true }) // from docs.
this.props.router.push(redirectObj, redirectObj, { shallow: true })
}
}
)
这里的意图是 CSR 接管 - 因此是肤浅的router.push
。页面 URL 更改但getInitialProps
不应再次触发,后续查询更改通过componentWillUpdate
etc. 处理。我确认getInitialProps
由于缺少相应触发而不会再次console.log
触发。
问题
但是,在检查/取消选中Search
组件上的选择字段时,我惊讶地发现状态SearchPage
仍在更新,尽管没有this.setState()
被调用的证据。
constructor
没有被调用,也没有getInitialProps
,所以我不知道是什么导致状态改变。
在初始 SSR 之后,调试块如下所示:
// PROPS
{
"parsedQuery": {
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
然后在检查一个选择字段后Search
令人惊讶地更新为:
// PROPS
{
"parsedQuery": {
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
我找不到对这种行为的解释,没有任何东西输出到控制台,我也找不到如何通过 dev 跟踪状态更改的来源。工具。
当然,只有当我通过componentDidUpdate
? 而且真的不应该parsedQuery
只更新道具getInitialProps
吗?因为这就是创建和注入它的原因?
为了进一步增加混淆,如果我更改(例如)input
上的数字字段,URL 会按预期更新,但调试块中的道具和页面状态会发生变化。无法理解这种不一致的行为。Search
lowPrice
这里发生了什么?
编辑
我已经添加了一个回购。它在 GitHub 上将这个问题作为 MWE 重现,您可以在此处克隆它:problem MWE repo。
解决方案
哇,有趣的问题。这是一个有趣的小谜题。
TL;DR:这是你的错,但你是如何做到的却很微妙。首先,问题出在这一行: https ://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L17
在这个例子中,它是这样的:
this.state = props.parsedQuery
让我们考虑一下那里实际发生的事情。
在 IndexPage.getInitialProps 中,您正在执行以下操作:`
const initialQuery = parseQuery({ ...query })
return { initialQuery }
通过 Next 的机制,这些数据通过 App.getInitialProps 以作为 pageProps.initialQuery 返回,然后在 IndexPage 中成为 props.initialQuery,然后被批发到您的搜索组件 - 然后您的搜索组件“制作副本" 来避免改变它。都很好,对吧?
你错过了什么。
在 lib/searchQuery.js 中有这一行:
searchQuery[field] = []
同一个数组被传递到 Search 中——除非你没有复制它。您正在复制 props.query - 其中包含对该数组的引用。
然后,在您的搜索组件中,当您更改复选框时执行此操作:
const labelValue = this.state[label]
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L57
你正在改变你在构造函数中“复制”的数组。你正在直接改变你的状态!这就是 initialQuery 似乎在主页上更新的原因——您改变了 initialQuery 引用的制造商数组——它从未被复制。您拥有在 getInitialProps 中创建的原始参考!
您应该知道的一件事是,即使在浅推送时未调用 getInitialProps,App 组件仍会重新渲染。它必须为了反映路由更改到消费组件。当您在内存中更改该数组时,您的重新渲染会反映更改。添加价格时,您不会改变 initialQuery 对象。
这一切的解决方案很简单。在 Search 组件构造函数中,您需要查询的深层副本:
this.state = { ...cloneDeep(props.query) }
做出改变,问题就消失了,您在打印输出中不再看到 initialQuery 发生变化 - 正如您所期望的那样。
您还想更改它,即直接访问您所在州的数组:
const labelValue = this.state[label]
对此:
const labelValue = [...this.state[label]]
为了在更改数组之前复制数组。你通过立即调用 setState 来掩盖这个问题,但实际上你是在直接改变你的组件状态,这将导致各种奇怪的错误(比如这个)。
之所以出现这种情况,是因为您的组件状态中有一个全局数组正在发生突变,因此所有这些突变都反映在不同的地方。
推荐阅读
- amazon-web-services - 如何使 terraform 的版本与亚特兰蒂斯不可变?
- hibernate - 带有列表字段的 pojo 的条件查询
- c++ - 有没有办法强制 linux 帧缓冲区立即显示帧?
- google-sheets - 谷歌表格 | 查询不会带来所有数据
- java - 从 xml 的“单位分隔符”(0x1f)字符中清除字符串
- laravel - 使用 www 访问站点时,Laravel 路由停止工作
- qt - 下载完成后带结果的 QT 回调
- c# - 如何在 C# 中修复 protobuf 消息模型属性名称
- c# - 在文化之间转换日名称
- javascript - @react-google-maps/api 的自动完成功能显示“此页面无法正确加载 Google 地图”