简体   繁体   English

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

[英]Using Location State in Functional Component

I'm working on a React project that requires the app URL to be fully-declared, static and immutable, so just:我正在开发一个 React 项目,该项目要求应用 URL 是完全声明的、静态的和不可变的,所以只需:

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

..but none of the following: ..但以下都不是:

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

So, the project uses state and withRouter to provide basic app navigation.因此,该项目使用statewithRouter来提供基本的应用导航。 The responsible component is as follows:负责的组件如下:

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);

The location.state object is set by the <Link /> component: location.state对象由<Link />组件设置:

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

The result is normal browser navigation throughout the app while the URL remains unchanged.结果是整个应用程序的浏览器导航正常,而 URL 保持不变。

My question is: how can the class-based RenderDisplay component above be converted to a functional component (using hooks)?我的问题是:如何将上面基于类的RenderDisplay组件转换为功能组件(使用钩子)?

I've tried several permutations of useEffect , useState , and useLocation , but I clearly don't understand hooks well enough to reproduce the behavior of the class-based component above.我已经尝试了useEffectuseStateuseLocation几种排列,但我显然对钩子的理解不够好,无法重现上面基于类的组件的行为。

So,some steps that we always need to do:所以,我们总是需要做的一些步骤:

  1. No need of HOCs, specifically withRouter .不需要withRouter ,特别是withRouter We can use the useHistory hook instead.我们可以改用useHistory钩子。
  2. All initialization either from the constructor or from componentDidMount can be done either directly in state, or in an useEffect that only runs once (empty dependency array).所有来自构造函数或componentDidMount初始化都可以直接在 state 中完成,也可以在只运行一次的useEffect中完成(空依赖数组)。
  3. All updates to state, specifically from componentDidUpdate can be done in an useEffect that depends on your state variable.状态的所有更新,特别是来自componentDidUpdate更新都可以在依赖于您的状态变量的useEffect中完成。 In your specific case, you need the previous prop as well.在您的特定情况下,您还需要之前的prop In these situations, we can create a custom hook and use it.在这些情况下,我们可以创建一个自定义钩子并使用它。 In my case, it will be usePrev .就我而言,它将是usePrev
  4. All cleanup tasks, specifically from componentWillUnmount can be covered in an useEffect that returns the cleanup action.所有清理任务,特别是来自componentWillUnmount清理任务都可以包含在返回清理操作的useEffect中。

Keeping these things in mind, these are probably the changes that need to be done:记住这些事情,这些可能是需要做的改变:

Note: During this, I realized you are trying to render an array.注意:在此期间,我意识到您正在尝试渲染一个数组。 I modified it to render a single element, you can update it otherwise.我修改它以呈现单个元素,否则您可以更新它。

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;

Can you try this?你能试试这个吗? You can convert your class component to function this way.您可以将类组件转换为这种方式。 Let me know if this helps.如果这有帮助,请告诉我。

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;

Also for your this block of code,同样对于您的这段代码,

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

We need to handle in another useEffect hook.我们需要在另一个useEffect钩子中处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM