React:动态路由未正确重新渲染

React: Dynamic route not re-rendering correctly

Codesandbox Repo

通过 react router 4 了解动态路由,同时决定将功能组件重构为 class 组件。这背后的原因,如果我错了请纠正我,是 TopNavSub 组件从 react router 4this.props.match.params.<var> 获取信息并选择要呈现的内容。

对我来说,这听起来像是状态的改变。唯一让我怀疑的是状态永远不会在同一条路线上改变并且每条路线总是不同所以我不确定这是否与处理状态相同。例如,/mac 将仅映射 mac 数组中的项目等

我被告知直接在 this.state 中使用 this.props 是一个糟糕的选择,而是使用 componentWillReceiveProps() 但 React 团队似乎对此感觉不佳,UNSAFE_componentWillReceiveProps(),但它什么也没解决。

TLDR;
所以这里的问题是,重构后,我的数据只显示第一个初始路由,之后无论选择哪条路由,除非刷新,否则只会显示初始选择的路由数据(f5)。

TopNavSub.js

import React, { Component } from 'react';

// data
import { subNavLinks } from './navigationData';

class TopNavSub extends Component {
  constructor(props) {
    super(props);
    this.state = {
      navLinks        : { ...subNavLinks },
      product         : this.props.match.params.product,
      currentProduct  : Object.keys(subNavLinks).filter(subNavLinksKey => subNavLinksKey === this.props.match.params.product),
    }
  }

  render() {
    return (
      (!this.state.navLinks)
      ?
        null
      :
        <ul>
          {
            this.state.navLinks[this.state.product].map(product => {
              return (
                <li key={ product }>{ product }</li>
              )
            })
          }
        </ul>
    )
  }
};

export default TopNavSub;

navigationData.js

export const subNavLinks = {
  mac: ['MacBook', 'MacBook Air', 'MacBook Pro', 'iMac', 'iMac Pro', 'Mac Pro', 'Mac Mini', 'Accessories', 'High Sierra', 'Compare'],
  ipad: ['iPad Pro', 'iPad', 'iPad Mini 4', 'iOS 11', 'Accessories', 'Compare'],
  iphone: ['iPhone X', 'iPhone 8', 'iPhone 7', 'iPhone 6s', 'iPhone SE', 'iOS 11', 'Accessories', 'Compare'],
  watch: ['Apple Watch 3', 'Apple Watch Nike+', 'Apple Watch Hermes', 'Apple Watch Edition', 'Apple Watch 1', 'Watch OS', 'Bands', 'Accessories', 'Compare'],
  tv: ['Apple TV 4k', 'Apple TV', 'TV App', 'Accessories', 'Compare'],
  music: ['Apple Music', 'iTunes', 'HomePod', 'iPod Touch', 'Music Accessories', 'Gift Cards'],
  support: ['Apple doesn\'t support anything older than iOS11']
};

您可以使用 componentWillReceiveProps 解决您的问题,或者,如果您想要面向未来,使用新的静态 getDerivedStateFromProps.

class TopNavSub extends Component {
  constructor(props) {
    super(props);
    //This could be extracted to avoid repetition, I'm copy-pasting it
    const currentProduct = Object.keys(subNavLinks)
      .filter(subNavLinksKey => subNavLinksKey === this.props.match.params.product);
    this.state = {
      navLinks: subNavLinks[currentProduct[0]],
      product: this.props.match.params.product,
      currentProduct
    }
  }

  //IMPORTANT: USE EITHER ONE OR THE OTHER
  //I wrote both options for demonstration purposes

  componentWillReceiveProps(nextProps, prevState) {
    const currentProduct = Object.keys(subNavLinks)
      .filter(subNavLinksKey => subNavLinksKey === nextProps.match.params.product);
    this.setState({
      product: nextProps.match.params.product,
      navLinks: subNavLinks[currentProduct[0]],
      currentProduct
    });
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    const currentProduct = Object.keys(subNavLinks)
      .filter(subNavLinksKey => subNavLinksKey === nextProps.match.params.product);
    return {
      product: nextProps.match.params.product,
      navLinks: subNavLinks[currentProduct[0]],
      currentProduct
    };
  }

  render() {
    return (
      (!this.state.navLinks)
        ?
        null
        :
        <ul>
          {
            this.state.navLinks.map(product => {
              return (
                <li key={product}>{product}</li>
              )
            })
          }
        </ul>
    )
  }
};

更新

关于你的第二个问题,一个好的通用规则是,如果你不需要处理用户输入事件或保存外部数据(比如来自后端的数据 API),你 ("""PROBABLY""") 不需要组件状态。在这种情况下,您是从 props 派生整个状态而不进行任何事件处理,因此您可以将此组件简单地编写为一个函数,如下所示:

const TopNavSub = props => {

    //Since you're using ES6 you can change filter for find to get a single element instead of an array
    const currentProduct = Object.keys(subNavLinks)
        .find(subNavLinksKey => subNavLinksKey === props.match.params.product);
    const navLinks = subNavLinks[currentProduct];

    return (
        (!navLinks)
        ?
            null
        :
            <ul>
            {
                navLinks.map(product => (li key={product}>{product}</li>))
            }
            </ul>
    );
};