首页 > 解决方案 > 我应该把一些操作放在功能组件的哪里

问题描述

我从 grpahql 端点获取了一些数据。在渲染之前我必须做一些计算。但是这段代码在多次渲染这个组件后会导致浏览器冻结。我想我可以把这段代码变成useEffect。但我不确定为什么浏览器会冻结以及如何修改代码以清洁和运行良好。如您所见,这段代码有些错误。你能解释一下怎么做吗?

const StatusBar = () => {
  const { data: curProgram_data } = useQuery(GET_CUR_PROGRAM);
  const { data: programs_data, loading: programs_loading } = useQuery(GET_PROGRAMS);
  const { data: tests_data, loading: tests_loading } = useQuery(GET_TESTS);
  const { data: articles_data, loading: articles_loading } = useQuery(GET_ARTICLES);

  if (programs_loading || tests_loading || articles_loading)
    return <p>Loading ... </p>;

  const { curProgram } = curProgram_data;
  const { programs } = programs_data;
  const totalTests = tests_data.tests;
  const totalArticles = articles_data.articles;

  var startYear = moment(totalTests[0].date, 'YYYY/MM/DD').format('YYYY');
  var endYear = moment(totalTests[totalTests.length - 1].date, 'YYYY/MM/DD').format('YYYY');
  var years = [];
  for(var i = startYear; i <= endYear; i ++)
    years.push(i);

  for(var i = 0; i < programs.length; i ++) // Sort Programs by Date
    for(var j = i + 1; j < programs.length; j ++) {
      if(programs[i].tests[0].date > programs[j].tests[0].date) {
        var temp;
        temp = programs[i];
        programs[i] = programs[j];
        programs[j] = temp;
    }
  }
  console.log("I think we have to turn all variables to state with useEffect"); 

  var programsGroupedByYear = [];
  var eachProgramLength = [];

  var startYear = moment(programs[0].tests[0].date, 'YYYY/MM/DD').format('YYYY');
  var endYear = moment(programs[programs.length - 1].tests[0].date, 'YYYY/MM/DD').format('YYYY');

  for(i = 0; i < endYear - startYear + 1; i ++) {
    programsGroupedByYear[i] = [];
    eachProgramLength[i] = [];
  }

  var isNext = startYear;
  var yearIt = 0;
  for(i = 0; i < programs.length; i ++) { // Group Programs by Year
    var year = moment(programs[i].tests[0].date, 'YYYY/MM/DD').format('YYYY');

    if(year == isNext)
      programsGroupedByYear[yearIt].push(programs[i]);
    else {
      isNext = year;
      yearIt ++;
      programsGroupedByYear[yearIt].push(programs[i]);
    }
  }
  ...  ....

  return (
    <div>
      <YearBar 
        groupLengths = {groupLengths} 
        curProgramPos = {curProgramPos}
        years = {years}
        totalArticles = {totalArticles}
      />

      ....

    </div>
  )
}

标签: reactjsgraphqlreact-hooks

解决方案


React 强调所有动态数据都必须处于其状态这一事实。这甚至包括诸如表单输入值之类的 DOM 数据。你在这里做的是完全错误的。

你在这里犯的错误太多了

错误一:

不使用反应状态。这一点怎么强调都不过分。你所有的动态数据都必须进入 React 状态 - yeartotalTests- 一切都必须声明为 React 状态。这样 React 可以跟踪数据变化并优化性能。

错误2:

useEffect()用于执行副作用。不能在函数的根部执行副作用。你不应该。React 永远不会知道你的函数内部到底发生了什么。使用正确useEffect()的依赖数组

错误3:

这是 JavaScript 错误。不要var用于声明变量。有范围问题。用于let变量和const常量。

错误4:

不使用高效的内置 JavaScript 方法,而是编写自己的排序和其他算法。

此组件中的控制台日志调用不定式。为什么?

因为 console.log 是函数的根目录。每次有更新并且 React 需要重新渲染组件时,都会使用新的状态值再次调用该函数。由于您的代码使函数陷入无限循环,因此控制台记录了无限次。useState这就是为什么使用and更重要的原因useEffects,以便 React 知道要执行什么以及要更新什么,并防止自己陷入无限循环。

希望它能回答你的问题,下一步转向解决方案

解决方案

我必须明确表示我不能也不会重写你的整个代码并确保一切正常,这是你的工作,但我非常乐意帮助你并解释它们。

在转向 React 之前,让我先清理一下 JavaScript 空间

避免使用var

var在 JavaScript 中没有正确限定范围,这会在调试时导致很多困惑和头痛。所以使用新的关键字来声明变量

  • let对于变量
  • const为常数

它们的范围和行为与您期望它们的行为方式相同。

如果存在某些内容,请不要重写它 - 使用内置方法

不要编写自己的排序算法,而是使用 JavaScript 内置sort方法。拥抱有趣的函数式编程

您的排序算法可以替换为

const sortedPrograms = [ ...programs ].sort((a, b) => 
  a.tests[0].date > b].tests[0].date ? -1 : 1
)

我使用[ ...programs ].sort而不是的原因programs.sort是因为sort方法会改变原始数组,这在 React 中是不可取的,这就是我浅克隆数组的原因。

对于接下来的两个 for 循环,我相信您正在尝试根据其年份对程序进行分组。(如我错了请纠正我)。

那几十行代码可以很容易地用我最喜欢的 JavaScriptreduce方法代替。

您的分组算法可以替换为

const programsGroupedByYear = programs.reduce(
  // acc - Accumulated, cur - current item
  (acc, cur) => {
    const year = moment(cur.tests[0].date, 'YYYY/MM/DD').format('YYYY');
    return {
      ...acc,
      // Check if the year index exist in the accumulated object
      // if yes, append it to the array
      // if no, create a new array
      // and store in the year index
      [year]: acc[year] ? [ ...acc[year], cur ] : [cur]
    }
  }
, {})

就是这样,你的 20+ 行两个 for 循环可以替换为 6+ 行Array.reduce方法。

将所有数据存储在 React State 中

ApollouseQuery是一个自定义钩子,这意味着输出data是一个 React 状态。所以需要在这里声明任何新的状态,除了变量来存储你的排序数据和分组数据。

将所有副作用移到里面useEffects

函数的根本不应该有任何副作用。他们都必须进去useEffects

useEffect(() => {
  // All your side effects goes in here
}, [deps])
// React will track the items in dep array, 
// if there's any change in them, React will call this useEffect

如果函数的根不允许有副作用,那么根应该允许什么?这让我们

始终在函数的根部使用钩子

不要在循环、条件或嵌套函数中调用 Hook。

最终建议

使用 JavaScript 作为函数式编程方式,特别是如果您使用 React 而不是考虑 for 循环和数据变异

我强烈建议您使用Rule of Hooks linter 插件。

所以总结,

您的代码的 GIST

const StatusBar = () => {
  const { data: curProgram_data } = useQuery(GET_CUR_PROGRAM);
  const { data: programs_data, loading: programs_loading } =
    useQuery(GET_PROGRAMS);
  const { data: tests_data, loading: tests_loading } = useQuery(GET_TESTS);
  const { data: articles_data, loading: articles_loading } =
    useQuery(GET_ARTICLES);

  const [sortedProgram, setSortedProgram] = useState([]);
  const [programsGroupedByYear, setProgramsGroupByYear] =
    useState([]);

  useEffect(() => {
    if (!programs_loading) {
      const _sortedPrograms = [ ...programs_data.programs ].sort((a, b) => 
        a.tests[0].date > b].tests[0].date ? -1 : 1
       )
      setSortedProgram(_sortedPrograms)

      const _programsGroupedByYear = programs.reduce(
        (acc, cur) => {
          const year = moment(cur.tests[0].date, 'YYYY/MM/DD').format('YYYY');
          return {
            ...acc,
            [year]: acc[year] ? [ ...acc[year], cur ] : [cur]
          }
        }
        , {})
      }
      setProgramsGroupedByYear(__programsGroupedByYear)
  }, [programs_data, programs_loading])


  // Then place your if condition

  if (programs_loading || tests_loading || articles_loading)
    return <p>Loading ... </p>;

  return (
    <YourJSXCode />
  )
}


推荐阅读