React钩子:用单个钩子包装钩子的多个实例的最佳实践是什么?

时间:2019-12-23 19:21:25

标签: javascript reactjs react-hooks

我有一个反应钩子useDbReadTable,用于从接受tablenamequery的初始数据的数据库中读取数据。它返回一个对象,该对象除包含来自数据库的数据外,还包含isLoading状态。

我想将此钩子包装在一个新的钩子中,该钩子接受{ tablename, query } array 的初始数据,并为每个表返回一个带有数据库数据的对象,但是isLoading状态根据我的新挂钩中的逻辑合并为一个布尔值。

这个想法是,新钩子的调用者可以从多个表中请求数据,但只需要检查一个状态值即可。

我的想法是使新钩子看起来像

编辑:代码已更新(我粘贴了错误的版本)

export const useDbRead = tableReads => {
  let myState = {};

  for (let i = 0; i < tableReads.length; ++i) {
    const { tablename, query = {} } = tableReads[i];
    const [{ isLoading, isDbError, dbError, data }] = useDbReadTable(tablename, query);
    myState = { ...myState, [tablename]: { isLoading, isDbError, dbError, data }};
  }

  const finalState = {
    ...myState,
    isLoading: Object.values(myState).reduce((acc, t) => acc || t.isLoading, false),
  };

  return [finalState];
};

但是,eslint在我的useDbReadTable通话中给了我这个错误:

  

反应钩子“ useDbReadTable”可以执行多次。可能是因为它是在循环中调用的。在每个组件渲染中,必须以完全相同的顺序调用React Hook。反应钩/钩规则

Rules for Hooks说,

  

仅顶层呼叫挂钩

     

请勿在循环,条件或嵌套函数中调用Hook。相反,请始终在您的React函数的顶层使用挂钩。通过遵循此规则,可以确保每次渲染组件时都以相同的顺序调用Hook。这就是让React在多个useState和useEffect调用之间正确保留Hook的状态的原因。 (如果您感到好奇,我们将在下面对此进行深入说明。)

阅读规则和解释后,似乎唯一的问题是确保在所有重新渲染时都以相同的顺序调用挂钩。只要我确保传入新钩子的表的列表永不改变,我的新钩子是否可以正常工作(如我的初始测试所示)?还是我错过了什么?

更重要的是,有没有更好的主意如何实现这一点,而又没有违反《钩子规则》?

Edit2 :如果有帮助,请访问useDbReadTable。请注意,它包含的功能比我在问题中提到的功能还要多,因为我想使问题尽可能简单。我的问题是我的useDbRead是一个很好的解决方案,还是在不违反《钩子规则》的情况下有一个好的方法?

export const useDbReadTable = (initialTableName, initialQuery = {}, initialData = []) => {
  const dbChangeFlag = useSelector(({appState}) => appState.dbChangeFlag);
  const [tableName, setTableName] = useState(initialTableName);
  const [query, setQuery] = useState(initialQuery);
  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isDbError: false,
    dbError: {},
    data: initialData,
  });
  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: dataFetch.FETCH_INIT });

      try {

        const result = Array.isArray(query) ?
          await db[tableName].batchGet(query)  // query is an array of Ids
          :
          await db[tableName].find(query);

        if (!didCancel) {
          dispatch({ type: dataFetch.FETCH_SUCCESS, payload: result });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: dataFetch.FETCH_FAILURE, payload: error });
        }
      }
    };

    fetchData().then(); // .then() gets rid of eslint warning

    return () => {
      didCancel = true;
    };
  }, [query, tableName, dbChangeFlag]);

  return [state, setQuery, setTableName];
};

1 个答案:

答案 0 :(得分:1)

您可能可以通过使useDbReadSingle本身知道数组来避免使用useDbRead。像这样:

export const useDbRead = tableReads => {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const doIt = async () => {
      // you would also need to handle the error case, but you get the idea
      const data = await Promise.all(
        tableReads.map(tr => {
          return mydbfn(tr);
        })
      );

      setLoading(false);
    };

    doIt();
  }, [tableReads]);

  return { loading, data };
};

当需要将其用于单个表读取时,只需使用具有单个元素的数组来调用它。

const {loading, data: [d]} = useDbRead([mytableread])