如何在我的情况下仅使用一次React Hook useEffect?

4

我觉得我的问题似乎有点重复,但我认为并不是,因为我查看了许多标题相同但大小写不同的问题。

问题是我被迫在我的代码中两次使用react hook,甚至考虑让它们变成三个,但我感觉这是一种不好的做法,我相信有一个解决方法或解决方案。

我有一些数据,我想要最初获取,并通过第一个useEffect调用实现了这一点。稍后可以对数据进行过滤或排序,我有过滤复选框和一个用于排序查询的选项卡,选择一个复选框将对数据进行过滤,选择一个选项将对数据进行排序,我使用redux和react-redux connect方法来创建状态树,其中包括filterssortBy

  //the first call
  useEffect(() => {
    loadProducts();
  }, []);


//the second call
  useEffect(() => {
    loadProducts(filters, sortBy)
  }, [filters, sortBy])

const mapStateToProps = (state) => ({
  filters: state.filters,
  sortBy: state.sortBy
});


const mapDispatchToProps = (dispatch) => ({
  loadProducts: (filters, sortBy) => {
    fetch('certain API').then(res => res.json()).then(json => {
      dispatch(fetchProducts(json.products, filters, sortBy));
    }).catch(err => 'error fetching data');
  }
});

现在的第一次调用是从API中最初地检索数据,loadProducts函数不需要任何参数,第二次则在进行筛选或排序时,此时函数需要参数,并且当任何一个筛选器或排序方式发生变化时,该函数会起作用。我甚至考虑将它们分成三个调用,将第二个调用分成两个调用,如下所示:
 useEffect(() => {
    loadProducts(filters)
  }, [filters])

useEffect(() => {
    loadProducts(undefined, sortBy)
  }, [sortBy])

这是因为我不希望每次仅需要一个条件进行筛选或排序时都进行筛选和排序,因为我认为当用户只筛选数据时,排序也会起作用,反之亦然。

这是我的fetchProducts操作:

import { filterProducts, sortProducts } from './../../util/util';
export const fetchProducts = (products, filters, sortBy) => {
    if (filters && filters.length) {
        products = filterProducts(products, filters);
    }

    if (sortBy) {
        sortProducts(products, sortBy);
    }

    return {
        type: FETCH_PRODUCTS,
        payload: products
    }
}


1
似乎过滤/排序发生在UI端。那么,如果将其作为mapStateToProps的一部分而不是动作创建器的一部分,会怎样呢? - skyboyer
1
顺便提一下,如果您现在的 useEffect 没有清理功能,可能会遇到竞争条件,并且如果您能够连续发送多个请求,则可能会导致 UI 闪烁。 - skyboyer
1
@skyboyer,您能否详细解释一下如何改进我的代码! - Saher Elgendy
2个回答

3

如果您想在单个useEffect调用中处理所有逻辑,可以从useEffect中取出所有属性依赖[filters, sortBy]

useEffect(() => {
  //This runs all the time a component (re)renders
});

现在在useEffect中,您必须有条件地调用fetch操作,具体取决于prop更改。您可以使用useRef来跟踪props的先前值。下面是一个草稿。

function App(props){
  const {products = [], filter, sortBy} = props;
  const filterRef = useRef();
  const sortRef = useRef();
  const loadedRef = useRef(false); 


  // The effect runs all the time
  useEffect(() => {
    if(!loadedRef.current){
      console.log("Dispatch the loadProducts action");
      //Update ref
      loadedRef.current =  true;
    }else{
      if(sortBy !== sortRef.current){
        console.log("Dispatch the filter action");
        // Update ref
        sortRef.current = sortBy;
        // return after this line if you want to avoid next if condition;
      }
      if(filter !== filterRef.current){
        console.log("Dispatch the sort action");
        //Update ref
        filterRef.current = filter;
      }
    }
  });


  return <div>{products.map(product => <h1>{product}</h1>)}</div>
}

3
在 fetchProducts 中,您正在改变 products,这不是一个好主意,请尝试使用 products = sortProducts([...products], sortBy); 代替。
如果 state.filters 的初始值为 [],sortBy 的初始值为 false 或有效的默认排序,则只需要一个 effect。
正如 skyboyer 发表评论的那样;如果用户更改过滤器或排序时进行异步处理,则应避免竞争条件
这是您可以编写 effect 的方式:
useEffect(() => {
  const cancel = {current:false};
  //add cancel, only dispatch fetchProducts when it's
  //  the latest result from user interaction
  loadProducts(filters, sortBy, cancel);
  return ()=>cancel.current=true;
  //added loadProducts as an effect dependency
  //  the linter should have warned you about it
}, [filters, loadProducts, sortBy])

const mapDispatchToProps = (dispatch) => ({
  loadProducts: (filters, sortBy, cancel) => {
    fetch('certain API').then(res => res.json()).then(json => {
      //only dispatch if this is the latest list of products
      //  if user changed something during fetching then don't
      //  set products
      cancel.current || dispatch(fetchProducts(json.products, filters, sortBy));
    }).catch(err => 'error fetching data');
  }
});

如果 filters 最初是 [],而 sortBy 最初具有有效值或为 false,则应该可以正常工作。

fetchProducts 是我的 action creator,但我所有的异步工作都在 loadProducts 函数中完成,你能否请检查一下我问题中的最终代码片段! - Saher Elgendy
1
@SaherElgendy 我更新了我的答案,并提供了代码以避免竞态条件 - HMR
谢谢,它完美地运行了。顺便提一下,在 useEffect 中调用 loadProducts 函数时你忘记传递 cancel 参数了! - Saher Elgendy
抱歉,您最后一句话是否仍然有效,因为我认为loadProducts是异步的,因为它使用了ajax函数调用“fetch”。 - Saher Elgendy
1
@SaherElgendy 我忘了删除最后一句话,现在变得有点复杂,因为你现在必须传递一个取消参数,以确保你不会只是按照响应的顺序呈现它们,而是仅在响应是针对最后一个用户输入时才呈现它们。我会更新答案。 - HMR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接