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 组件上的 selectinput 字段时,此代码为 运行:

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 来掩盖这个问题,但实际上你是在直接改变你的组件状态,这将导致各种奇怪的错误(比如这个)。

出现这个是因为你有一个全局数组在你的组件状态中发生了变化,所以所有这些变化都反映在不同的地方。