我有一个应用。使用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.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
。页面网址已更改,但不应再次触发getInitialProps
,并且以后的查询更改将通过componentWillUpdate
等进行处理。我确认getInitialProps
不会因缺少各自的{{1 }}射击。
但是,在检查/取消选中console.log
组件上的选择字段时,我惊讶地发现Search
的状态仍在更新,尽管没有证据证明SearchPage
被调用。
this.setState()
没有被调用,constructor
也没有被调用,所以我不知道是什么导致状态改变。
初始SSR之后,调试块如下所示:
getInitialProps
然后惊奇地检查了// PROPS
{
"parsedQuery": {
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
中的选择字段,它更新为:
Search
我找不到这种行为的解释,控制台没有任何输出,我也找不到如何通过dev跟踪状态变化的起源。工具。
当然只有在我通过// PROPS
{
"parsedQuery": {
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
进行更新时,状态才应该更新吗?难道componentDidUpdate
道具不应该只由parsedQuery
更新吗?那就是创建和注入它的原因吗?
为了进一步混淆,如果我更改了getInitialProps
上的数字input
字段(例如Search
),则URL会按预期更新,但是调试中的prop或页面状态都不会更改块。无法理解这种不一致的行为。
这是怎么回事?
编辑
我添加了一个仓库。可以在GitHub上以MWE的形式重现此问题,您可以在此处将其克隆:problem MWE repo.
答案 0 :(得分:1)
哇,有趣的问题。这是一个有趣的小难题。
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,然后将其整体传递给Search组件-您的Search组件然后“制作对象的副本”以避免对其进行变异。很好,对吧?
您错过了一些东西。
在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来掩盖该问题,但实际上您是在直接更改组件状态,这将导致各种奇怪的错误(如此错误)。
之所以出现这种情况,是因为您有一个全局数组在组件状态内部发生了变异,所以所有这些变异都反映在各个地方。