是否可以创建React Hook工厂功能?

时间:2019-06-21 16:16:38

标签: reactjs react-redux react-hooks

我有一个React应用程序,该应用程序高度依赖reduxreact-reduxredux-saga。我开始尝试使用React Hooks,以及useSelector中的useDispatchreact-redux钩子,并且在如何实质上编写工厂函数以生成钩子方面遇到了问题我的每个redux节点。

通常,我的应用程序使用的每个API端点都有一个Redux节点。此应用程序中大约有100个唯一端点,因此,大约有100个Redux节点。然后,这些节点中的每个节点都对应一个state-[node].js文件,例如state-users.js等。这些状态文件每个封装它们应调用的端点,触发sagas来处理HTTP生命周期(开始,失败,成功等)。 ),等等。

随着时间的流逝,我编写的代码将很多样板抽象为实用程序功能,包括自动生成动作创建者,选择器,缩减器和connect函数的函数。这是一堆代码,有些令人费解,但是要旨看起来像这样。首先,我建立了一个对象数组,描述了该Redux节点可能执行的操作。简化版本如下:

const messages = [
  { action: 'GET', creator: 'get', connect: true },
  { action: 'POST', creator: 'post', connect: true },
  { action: 'CLEAR', creator: 'clear', connect: true },
];

这说明将有三个动作getpostclear,并且应该将它们暴露在连接器中。我有一组通用的化简器(例如,大多数get化简器在节点之间都是相同的),因此这里根据名称假定它们。

然后,我建立了一个选择器列表,如下所示: const selectorKeys = ['data','pending','errors'];

...然后我有一个工厂函数,可以将这些数组输入其中,如下所示:

const { connector } = stateGenerators({
   keyword: 'users', //redux node keyword
   messages: messages,
   selectorKeys: selectorKeys

}) 

这是一切工作原理的简化版本,但这是关键。同样,上述所有代码都被抽象到状态文件中,例如state-users.js

然后,在我的班级组件中,只需从connector导入state-users.js,如下所示:

import { connector } from 'state-users';

class Users extends Component {
    componentDidMount() {
       this.props.get();
    }
    componentWillUnmount() {
      this.props.clear();
    }
    render () {
      const { data } = this.props;
      return (
        <div>
        {data.map()}
        </div>

      )
    }
}

export connector()(Users)

该模型有时确实很笨拙,但令人高兴的是,几乎所有的redux样板都抽象为公共文件,因此,我的单个状态文件在很大程度上非常简单。

现在,问题来了:是否可以用Hooks做类似“工厂功能”的方法?到目前为止,在我的实验中,我还没有弄清楚如何做到这一点。有两个基本问题:

  • 首先,您不能将钩子放入循环中,所以我不能这样做:
const selectors = {}
const reduxNodeKeyword = 'users';
['data','pending','errors'].map((selectorKey) => {
   selectors[selectorKey] = useSelector((state) => state[keyword].selectorKey);
})

该代码导致此错误: React hook "useSelector" cannot be called inside of a callback.

实际上,这意味着我不能只传递想要的选择键数组,然后将其吐回我的选择器。

  • 第二,您不能在条件语句中添加钩子。因此,由于第一个想法失败了,因此我在工厂函数中尝试了另一种方法,如下所示:
 if (_.includes(stateSelectors, 'data')) {
    result['data'] = useSelector((state) => state[keyword].data);
 }

导致此错误: React hook "useSelector" is called conditionally. React Hooks must be called in the exact same order in every component render

那也真是令人讨厌。但是我认为剩下的是,对于我的100个Redux节点中的每一个,我将不得不编写一个自定义且冗长的钩子,以或多或少地复制connect

我知道,钩子应该鼓励我改变思维方式,但是我仍然需要从服务器获取数据并将其提供给组件,并且我需要遵循相同的基本模式100次。即使钩子使我的组件更加优雅(正如我期望的那样),还是想到要手工写出100个左右的钩子,每个钩子都有大量的重复数据,而不是通过某种工厂方法以某种方式自动创建它们,给了我荨麻疹。

帮助?

谢谢!

1 个答案:

答案 0 :(得分:0)

不确定这是否对其他人有用,但是我找到了一种对我有用的方法。我不能将钩子放在迭代器中,也不能放在if语句中,但是我确实到处都有通用模式。因此,答案是将那些常见的模式抽象为通用的钩子。例如,我有一个名为useCommonReduxListSelectors的钩子,它看起来像这样:

export const useReduxListCommonSelectors = (keyword: string) => {
  return {
    data: useSelector((state: any) => state[keyword].data),
    errorMessage: useSelector((state: any) => state[keyword].errorMessage),
    filters: useSelector((state: any) => state[keyword].filters),
    pending: useSelector((state: any) => state[keyword].pending),
    totalCount: useSelector((state: any) => state[keyword].totalCount)
  };
};

我有很多Redux状态节点,它们负责处理从API返回的列表,并且大多数这些列表端点的数据形状就是您在上面看到的。因此,组件将调用的钩子使用useReduxListCommonSelectors,如下所示:

export const useReduxState = ({ id, defaultValues }) => {
  const selectors = useReduxListCommonSelectors({
    keyword:'users'
  });

  return {
    ...selectors,

  };
};

然后显然useReduxState可以在那里有该节点所需的任何其他数据(actionCreators,自定义选择器等)。因此,它使我可以将最常见的模式抽象为可重用的钩子,还可以根据需要灵活地向每个useReduxState实例添加自定义代码。