我将如何使用 React Hooks 替换我的 withAuth() HOC?

How would I use React Hooks to replace my withAuth() HOC?

我花了很多时间阅读 React Hooks,虽然它的功能看起来比 类 与本地状态和生命周期方法一起使用更直观、可读和简洁,但我一直在阅读参考资料Hooks 是 HOCs 的替代品。

我在 React 应用程序中使用的主要 HOC 是 withAuth —— 基本上是一个检查 currentUser(存储在 Redux 状态中)是否经过身份验证的函数,如果是,则呈现包装的组件。

这是一个实现:

import React, { Component } from "react";
import { connect } from "react-redux";

export default function withAuth(ComponentToBeRendered) {
  class Authenticate extends Component {
    componentWillMount() {
      if (this.props.isAuthenticated === false) {
        this.props.history.push("/signin");
      }
    }
    componentWillUpdate(nextProps) {
      if (nextProps.isAuthenticated === false) {
        this.props.history.push("/signin");
      }
    }
    render() {
      return <ComponentToBeRendered {...this.props} />;
    }
  }

  function mapStateToProps(state) {
    return { isAuthenticated: state.currentUser.isAuthenticated };
  }

  return connect(mapStateToProps)(Authenticate);
}

我看不到的是如何用挂钩替换这个 HOC,特别是因为挂钩 运行 直到调用 render 方法之后。这意味着我将无法在以前被 ProtectedComponent(用 withAuth 包裹)的东西上使用钩子来确定是否渲染它,因为它已经被渲染了。

处理这种情况的新花式钩子方法是什么?

渲染()

我们可以稍微重构 'to render or not to render' 的问题。 render 方法将 总是 在 hook-based 回调或生命周期方法之前被调用。这适用于一些 soon-to-be deprecated lifecycle methods.

因此,您的渲染方法(或功能组件)必须处理所有可能的状态,包括不需要渲染任何内容的状态。要么,要么什么都不渲染的工作可以提升到父组件。两者的区别是:

const Child = (props) => props.yes && <div>Hi</div>;
// ...
<Parent>
  <Child yes={props.childYes} />
</Parent>

const Child = (props) => <div>Hi</div>;
// ...
<Parent>
  {props.childYes && <Child />}
</Parent>

根据具体情况决定使用其中的哪一个。

挂钩

有多种方法可以使用钩子来解决 HOC 所遇到的相同问题。我将从 HOC 提供的内容开始;一种访问有关应用程序状态的用户数据并在数据表示无效会话时重定向到 /signin 的方法。我们可以为这两样东西提供钩子。

import { useSelector } from "react-redux";

const mapState = state => ({
  isAuthenticated: state.currentUser.isAuthenticated
});

const MySecurePage = props => {
  const { isAuthenticated } = useSelector(mapState);

  useEffect(
    () => {
      if (!isAuthenticated) {
        history.push("/signin");
      }
    },
    [isAuthenticated]
  );
  return isAuthenticated && <MyPage {...props} />;
};

上面的例子中发生了一些事情。我们正在使用 react-redux 中的 useSelector 挂钩来访问状态,就像我们之前使用 connect 所做的那样,只是代码少得多。

我们还使用从 useSelector 获得的值来有条件地触发 useEffect 挂钩的副作用。默认情况下,我们传递给 useEffect 的回调在每次渲染后调用。但在这里我们还传递了一个依赖项数组,它告诉 React 我们只希望在依赖项更改时触发效果(除了第一个渲染,它总是触发效果)。因此,当 isAuthenticated 开始为假或变为假时,我们将被重定向。

虽然此示例使用了组件定义,但它也可以用作自定义挂钩:

const mapState = state => ({
  isAuthenticated: state.currentUser.isAuthenticated
});

const useAuth = () => {
  const { isAuthenticated } = useSelector(mapState);

  useEffect(
    () => {
      if (!isAuthenticated) {
        history.push("/signin");
      }
    },
    [isAuthenticated]
  );
  return isAuthenticated;
};

const MySecurePage = (props) => {
  return useAuth() && <MyPage {...props} />;
};

最后一件事 - 你可能想知道做这样的事情:

const AuthWrapper = (props) => useAuth() && props.children;

为了能够做这样的事情:

<AuthWrapper>
  <Sensitive />
  <View />
  <Elements />
</AuthWrapper>

您可能会认为最后一个示例是适合您的方法,但在决定之前我会 read this

基于 backtick 提供的答案,这段代码应该可以满足您的需求:

import React, { useEffect } from "react";
import { useSelector } from "react-redux";

const withAuth = (ComponentToBeRendered) => {
    const mapState = (state) => ({
        isAuthenticated: state.currentUser.isAuthenticated,
    });

    const Authenticate = (props) => {
        const { isAuthenticated } = useSelector(mapState);

    useEffect(() => {
        if (!isAuthenticated) {
            props.history.push("/signin");
        }
    }, [isAuthenticated]);
        return isAuthenticated && <ComponentToBeRendered {...props} />;
    };
    return Authenticate;
};

export default withAuth;

您可以使用 React-Router-DOM 在容器中渲染它:

import withAuth from "../hocs/withAuth"
import Component from "../components/Component"

// ...

<Route path='...' component={withAuth(Component)} />