React Router 在重定向到具有不同 slug 的同一页面时触发重新加载/重新安装组件

React Router trigger re-load / re-mount component on redirecting to same page with different slug

我有一个使用 React (v.16.8.6)HashRouter 的演示应用程序(使用散列路由器)。我的应用程序只是获取一些天气数据并显示某些城市天气的相关组件。

问题是索引页面有搜索功能,搜索页面也有类似功能。当给出搜索词时,应用会重定向到 #/search/{term} 路由。一切都按预期工作,但是当我已经在搜索页面上并触发新搜索时,我重定向到具有不同 {term} slug 的同一搜索页面(需要更新浏览器当前 url 包括哈希)。

因此页面不会重新加载,组件不会重新安装,因此不会触发生命周期挂钩(尤其是 componentDidMount 进行设置的地方)。

我做了一个模拟组件重新加载的解决方法,但它有点像 hack(例如直接操作状态和设置超时,请参见下面的相关代码)。

在组件 reloaded/remounted 正确时,是否有更好的方法实现重定向到同一页面(并更改浏览器 url 包括哈希)?

App.js

import React, { Component } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';

import HomePage from './home';
import SearchPage from './search';
import WeatherPage from './weather';

import './App.css';

class App extends Component {
  render() {
    return (
    <HashRouter basename="/" hashType="slash">
    <Switch>
        <Route exact path={'/'} component={HomePage}></Route>
        <Route path={'/weather/:woeid'} component={WeatherPage}></Route>
        <Route path={'/search/:term'} component={SearchPage}></Route>
    </Switch>
    </HashRouter>
    );
  }
}

export default App;

SearchPage.js分量

import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import Weather from '../components/Weather';

function fetchData( term ) {
    return fetch('{api_url_here}?keyword='+term)
    .then(response => response.json());
}

class SearchPage extends Component {
  constructor(props){
      super(props);
      this.state = {
          loading: true,
          text: this.props.match.params.term,
          redirect: false,
          data: null
      };
  }

  componentDidMount() {
    fetchData(this.props.match.params.term).then(jsonResponse => {
        this.setState({
            loading: false,
            data: jsonResponse
        });
    });
  }

  handleOnClick = () => {
    this.setState({redirect: true});
  }

  handleInput = (evt) => {
    this.setState({text: evt.target.value});
  }

  render() {
    // any way to avoid this workaround while redirecting to same page?
    // [from here]
     if (this.state.redirect) {
        this.state.redirect = false;
        this.state.text = this.state.text.toLowerCase();
        this.state.data = null;
        this.state.loading = true;
        // simulate componentDidMount, since it os not re-called on same page
        setTimeout(()=>(fetchData(this.state.text).then(jsonResponse => {
            this.setState({
                loading: false,
                data: jsonResponse
            });
        })), 40);
        // redirect and update url
        return <Redirect to={"/search/"+this.state.text} />;
       // [to here]
  }
    return (
      <div>
      <h1>Search Page</h1>
      <div>
      <input type="text" placeholder="search.." value={this.state.text} onChange={this.handleInput} />
      <button type="button" onClick={this.handleOnClick}>Search!</button>
      </div>
      {this.state.loading ? 'Loading..' : (this.state.data && this.state.data.length ? this.state.data.map(res => (<Weather woeid={res.woeid} city={res.title} />)) : 'No results found!')}
      </div>
    );
  }
}

export default SearchPage;

基于Redirect的解决方案(将重新加载/重新挂载组件)()

App 更改为以下内容:

class App extends Component {
  render() {
    return (
    <HashRouter basename="/" hashType="slash">
    <Switch>
        <Route exact path={'/'} component={HomePage}></Route>
        <Route path={'/weather/:woeid'} component={WeatherPage}></Route>
        <Route path={'/search/:term'} render={props => <SearchPage key={props.match.params.term.toLowerCase()} {...props} />}></Route>
    </Switch>
    </HashRouter>
    );
  }
}

SearchPage 更改为以下内容:

class SearchPage extends Component {
  constructor(props){
      super(props);
      this.state = {
          loading: true,
          text: this.props.match.params.term,
          redirect: false,
          data: null
      };
  }

  componentDidMount() {
    fetchData(this.props.match.params.term).then(jsonResponse => {
        this.setState({
            loading: false,
            data: jsonResponse
        });
    });
  }

  handleOnClick = () => {
    this.setState({redirect: true});
  }

  handleInput = (evt) => {
    this.setState({text: evt.target.value});
  }

  render() {
    if (this.state.redirect) {
        // redirect and update url
        return <Redirect to={"/search/"+this.state.text.toLowerCase()} />;
  }
    return (
      <div>
      <h1>Search Page</h1>
      <div>
      <input type="text" placeholder="search.." value={this.state.text} onChange={this.handleInput} />
      <button type="button" onClick={this.handleOnClick}>Search!</button>
      </div>
      {this.state.loading ? 'Loading..' : (this.state.data && this.state.data.length ? this.state.data.map(res => (<Weather woeid={res.woeid} city={res.title} />)) : 'No results found!')}
      </div>
    );
  }
}

一种解决方法是向 SearchPage 组件添加一个 componentDidUpdate 生命周期挂钩,并检查 term 参数是否更改并再次进行搜索。

如果你想在 term 参数改变时重新挂载组件,你可以使用 key 属性卸载以前的组件并在 key 时挂载一个新组件变化。

<Route
  path={"/search/:term"}
  render={props => <SearchPage key={props.match.params.term} {...props} />}
/>

使用 react-router-dom 中的 Link 而不是 <button> 来更改路由,而不仅仅是重定向。然后在 <Link> 组件的 onClick 中调用 fetchData 并相应地设置状态。

<Link className="btn" to={"/search/"+this.state.text} onClick={this.handleOnClick}>Search!</Link>

然后在handleOnClick函数中

handleOnClick() {
   this.setState({
      loading: true
   });
   fetchData(this.state.text).then(jsonResponse => {
     this.setState({
        loading: false,
        data: jsonResponse
     });
   });
}