NextJS-使用浅层路由器推送

时间:2019-03-19 15:38:59

标签: javascript reactjs next.js

我有一个应用。使用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组件上的inputSearch字段时,将运行此代码:

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.

1 个答案:

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

之所以出现这种情况,是因为您有一个全局数组在组件状态内部发生了变异,所以所有这些变异都反映在各个地方。