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

我想将此钩子包装在一个新的钩子中,该钩子接受{ tablename, query }数组的初始数据,并返回一个对象,该对象具有数据库中每个表的数据,但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调用中给了我这个错误:


  React Hook“ 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];
};

最佳答案

通过使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])

关于javascript - React钩子(Hook):用单个钩子(Hook)包装钩子(Hook)的多个实例的最佳实践是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59460331/

10-12 06:03