在功能组件中使用位置状态

时间:2021-07-02 15:47:14

标签: javascript reactjs react-hooks

我正在开发一个 React 项目,该项目要求应用 URL 完全声明、静态且不可变,所以只需:

https://exampleurl.com/path/to/app/index.html

..但以下都不是:

https://exampleurl.com/path/to/app/

https://exampleurl.com/path/to/app/index.html?query=value

https://exampleurl.com/path/to/app/subdir/index.html

因此,该项目使用 statewithRouter 来提供基本的应用导航。负责的组件如下:

import React from 'react';
import { withRouter} from "react-router-dom";
import RenderNav from './RenderNav';
import Cohort from '../view/Cohort';
import Holdings from '../view/Holdings';
import Policy from '../view/Policy';
import Search from '../view/Search';
import Start from '../view/Start';
import Training from '../view/Training';
import WatchList from '../view/WatchList';

class RenderDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = this.props.location.state || { activeDisplay: 'start' };
    this.props.history.replace(this.props.location.pathname, this.state);
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.location !== prevProps.location) {
      this.setState(this.props.location.state);
    }
  }

  setDisplay() {
    let displayMode = [];
    let d = this.state.activeDisplay;

    switch(d) {
      case 'start': displayMode = [
        <Start key={`display-${d}`} />
      ]; break;

      // ...and so on - activeDisplay determines displayMode (and so screen contents)

      default: displayMode = [<div>Error</div>]; break;
    }; return displayMode;
  }

  render() {
    return (
      <div className="container">
        {this.setDisplay()}
      </div>
    );
  }
}

export default withRouter(RenderDisplay);

location.state 对象由 <Link /> 组件设置:

<Link to={{ state: {activeDisplay: 'start'} }}>
  <button type="button">Home</button>
</Link>

结果是整个应用程序中的正常浏览器导航,而 URL 保持不变。

我的问题是:如何将上面基于类的 RenderDisplay 组件转换为功能组件(使用钩子)?

我已经尝试了 useEffectuseStateuseLocation 的几种排列,但我显然不太了解钩子,无法重现上面基于类的组件的行为.

2 个答案:

答案 0 :(得分:2)

所以,一些我们总是需要做的步骤:

  1. 不需要 HOC,特别是 withRouter。我们可以改用 useHistory 钩子。
  2. 从构造函数或从 componentDidMount 的所有初始化都可以直接在 state 中完成,也可以在只运行一次的 useEffect 中完成(空依赖数组)。
  3. 状态的所有更新,特别是来自 componentDidUpdate 的更新都可以在取决于您的状态变量的 useEffect 中完成。在您的特定情况下,您还需要前面的 prop。在这些情况下,我们可以创建一个自定义钩子并使用它。就我而言,它将是 usePrev
  4. 所有清理任务,特别是来自 componentWillUnmount 的清理任务都可以包含在返回清理操作的 useEffect 中。

记住这些事情,这些可能是需要做的改变:

注意:在此期间,我意识到您正在尝试渲染一个数组。我修改它以呈现单个元素,否则您可以更新它。

import {useRef} from "react";

export const usePrev = (value) => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

import React, {useState, useEffect} from 'react';
import {useHistory, useLocation} from 'react-router-dom';
import RenderNav from './RenderNav';
import Cohort from '../view/Cohort';
import Holdings from '../view/Holdings';
import Policy from '../view/Policy';
import Search from '../view/Search';
import Start from '../view/Start';
import Training from '../view/Training';
import WatchList from '../view/WatchList';
import usePrev from './usePrev';

const RenderDisplay = (props) => {

  const history = useHistory();
  const location = useLocation();
  const prevLocation = usePrev(location);  

  const [componentState, setComponentState] = useState(location.state || { activeDisplay: 'start' });

  useEffect(() => {
    history.replace(location.pathname, componentState);
  }, [location.pathname, history]);

  useEffect(() => {
    if (location.state.activeDisplay !== prevLocation.state.activeDisplay) {
        setComponentState(location.state);
    }
  }, [location]);

  setDisplay() {
    let displayMode;
    let d = componentState.activeDisplay;

    switch(d) {
      case 'start': displayMode = <Start key={`display-${d}`} />; 
      break;

      // ...and so on - activeDisplay determines displayMode (and so screen contents)

      default: displayMode = <div>Error</div>; break;
    }; 
    return displayMode;
  }

  
  return (
    <div className="container">
      {setDisplay()}
    </div>
  );
  
}

export default RenderDisplay;

答案 1 :(得分:0)

你能试试这个吗?您可以将类组件转换为这种方式。 如果这有帮助,请告诉我。

import { useEffect, useState } from "react";
import { useHistory, useLocation } from "react-router-dom";

function RenderDisplay() {
    const location = useLocation();
    const history = useHistory();
    const [state, setState] = useState(
        location.state || { activeDisplay: "start" }
    );

    useEffect(() => {
        history.replace(location.pathname, state);
    }, [history, location, state]);

    const setDisplay = () => {
        let displayMode = [];
        const d = state.activeDisplay;

        switch (d) {
            case "start":
                displayMode = [<Start key={`display-${d}`} />];
                break;

            // ...and so on - activeDisplay determines displayMode (and so screen contents)

            default:
                displayMode = [<div>Error</div>];
                break;
        }
        return displayMode;
    };

    return <div className="container">{setDisplay()}</div>;
}

export default RenderDisplay;

同样对于你的这段代码,

componentDidUpdate(prevProps, prevState) {
    if (this.props.location !== prevProps.location) {
      this.setState(this.props.location.state);
    }
  }

我们需要处理另一个 useEffect 钩子。