NextJS - 使用浅路由器推送时无法识别页面组件上发生状态更改的位置
NextJS - can't identify where state change is occurring on page component when using shallow Router push
我有一个应用程序。使用 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)通过一个简单的 'cleaner' 函数运行它 - parseQuery
- 这是我制作的,并通过 props.parsedQuery
道具将其注入页面,如上所示。这一切都按预期工作。
Search
组件是一个有很多字段的表单,其中大部分是select
基于预定义字段和一些基于数字的input
字段,为了为简洁起见,我省略了整个组件的标记。 Search
获取 query
道具并通过 constructor
函数将它们分配给其内部状态。
在更改 Search
组件上的 select
和 input
字段时,此代码为 运行:
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
等处理。我确认 getInitialProps
不会再次触发各自 console.log
射击。
问题
但是,在 checking/unchecking Search
组件的 select 字段上,我惊讶地发现 SearchPage
的状态仍在更新,尽管没有 this.setState()
被调用。
constructor
未被调用,getInitialProps
也未被调用,所以我不知道是什么导致状态发生变化。
初始 SSR 后,调试块如下所示:
// PROPS
{
"parsedQuery": {
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
然后在检查 Search
中的 select 字段后,令人惊讶的是它更新为:
// PROPS
{
"parsedQuery": {
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
我找不到对这种行为的解释,没有任何内容输出到控制台,我也找不到如何通过开发人员跟踪状态更改的来源。工具。
只有在我通过 componentDidUpdate
进行更新时,状态才应该更新吗? parsedQuery
属性真的不应该只由 getInitialProps
更新吗?因为这就是创建和注入它的原因?
更令人困惑的是,如果我更改 Search
上的数字 input
字段(例如 lowPrice
),URL 会按预期更新,但 props 也不调试块中的页面状态更改。无法理解这种不一致的行为。
这是怎么回事?
编辑
我添加了一个回购协议。它在 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,然后被批发到您的搜索component - 你的 Search 组件然后 "makes a copy" 对象以避免改变它。还好吧?
你错过了什么。
在lib/searchQuery.js中是这一行:
searchQuery[field] = []
同一个数组正在传递到搜索中 - 但您没有复制它。您正在复制 props.query - 其中包含对该数组的引用。
然后,在您的搜索组件中,当您更改复选框时执行此操作:
const labelValue = this.state[label]
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L57
您正在改变构造函数中 "copied" 的数组。你正在直接改变你的状态!这就是 initialQuery 似乎在主页上更新的原因——你改变了 initialQuery 引用的制造商数组——它从未被复制过。您拥有在 getInitialProps 中创建的原始引用!
您应该知道的一件事是,即使在浅推送时未调用 getInitialProps,App 组件仍会重新呈现。它必须将路由更改反映到消费组件。当您在内存中更改该数组时,您的重新渲染会反映更改。添加价格时,您并没有改变 initialQuery 对象。
解决这一切的方法很简单。在您的搜索组件构造函数中,您需要查询的深层副本:
this.state = { ...cloneDeep(props.query) }
进行更改后,问题就消失了,并且您不会再在打印输出中看到 initialQuery 发生变化 - 正如您所期望的那样。
您还需要更改此设置,即直接访问您所在州的数组:
const labelValue = this.state[label]
对此:
const labelValue = [...this.state[label]]
为了在更改之前复制数组。你通过立即调用 setState 来掩盖这个问题,但实际上你是在直接改变你的组件状态,这将导致各种奇怪的错误(比如这个)。
出现这个是因为你有一个全局数组在你的组件状态中发生了变化,所以所有这些变化都反映在不同的地方。
我有一个应用程序。使用 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)通过一个简单的 'cleaner' 函数运行它 - parseQuery
- 这是我制作的,并通过 props.parsedQuery
道具将其注入页面,如上所示。这一切都按预期工作。
Search
组件是一个有很多字段的表单,其中大部分是select
基于预定义字段和一些基于数字的input
字段,为了为简洁起见,我省略了整个组件的标记。 Search
获取 query
道具并通过 constructor
函数将它们分配给其内部状态。
在更改 Search
组件上的 select
和 input
字段时,此代码为 运行:
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
等处理。我确认 getInitialProps
不会再次触发各自 console.log
射击。
问题
但是,在 checking/unchecking Search
组件的 select 字段上,我惊讶地发现 SearchPage
的状态仍在更新,尽管没有 this.setState()
被调用。
constructor
未被调用,getInitialProps
也未被调用,所以我不知道是什么导致状态发生变化。
初始 SSR 后,调试块如下所示:
// PROPS
{
"parsedQuery": {
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
然后在检查 Search
中的 select 字段后,令人惊讶的是它更新为:
// PROPS
{
"parsedQuery": {
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
我找不到对这种行为的解释,没有任何内容输出到控制台,我也找不到如何通过开发人员跟踪状态更改的来源。工具。
只有在我通过 componentDidUpdate
进行更新时,状态才应该更新吗? parsedQuery
属性真的不应该只由 getInitialProps
更新吗?因为这就是创建和注入它的原因?
更令人困惑的是,如果我更改 Search
上的数字 input
字段(例如 lowPrice
),URL 会按预期更新,但 props 也不调试块中的页面状态更改。无法理解这种不一致的行为。
这是怎么回事?
编辑
我添加了一个回购协议。它在 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,然后被批发到您的搜索component - 你的 Search 组件然后 "makes a copy" 对象以避免改变它。还好吧?
你错过了什么。
在lib/searchQuery.js中是这一行:
searchQuery[field] = []
同一个数组正在传递到搜索中 - 但您没有复制它。您正在复制 props.query - 其中包含对该数组的引用。
然后,在您的搜索组件中,当您更改复选框时执行此操作:
const labelValue = this.state[label]
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L57
您正在改变构造函数中 "copied" 的数组。你正在直接改变你的状态!这就是 initialQuery 似乎在主页上更新的原因——你改变了 initialQuery 引用的制造商数组——它从未被复制过。您拥有在 getInitialProps 中创建的原始引用!
您应该知道的一件事是,即使在浅推送时未调用 getInitialProps,App 组件仍会重新呈现。它必须将路由更改反映到消费组件。当您在内存中更改该数组时,您的重新渲染会反映更改。添加价格时,您并没有改变 initialQuery 对象。
解决这一切的方法很简单。在您的搜索组件构造函数中,您需要查询的深层副本:
this.state = { ...cloneDeep(props.query) }
进行更改后,问题就消失了,并且您不会再在打印输出中看到 initialQuery 发生变化 - 正如您所期望的那样。
您还需要更改此设置,即直接访问您所在州的数组:
const labelValue = this.state[label]
对此:
const labelValue = [...this.state[label]]
为了在更改之前复制数组。你通过立即调用 setState 来掩盖这个问题,但实际上你是在直接改变你的组件状态,这将导致各种奇怪的错误(比如这个)。
出现这个是因为你有一个全局数组在你的组件状态中发生了变化,所以所有这些变化都反映在不同的地方。