【问题标题】:Where should I put some operations in functional component我应该把一些操作放在功能组件的哪里
【发布时间】:2020-07-15 04:44:36
【问题描述】:

我从 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>
  )
}

【问题讨论】:

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

标签: reactjs graphql react-hooks


【解决方案1】:

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

你犯的错误太多了

错误一:

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

错误2:

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

错误3:

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

错误 4:

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

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

因为 console.log 位于函数的根目录。每次有更新并且 React 需要重新渲染组件时,都会使用新的状态值再次调用该函数。由于您的代码使函数陷入无限循环,因此控制台记录了无限次。这就是为什么使用useStateuseEffects 更为重要的原因,这样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 循环,我相信您正在尝试根据年份对程序进行分组。 (如果我错了,请纠正我)。

这几十行代码可以用我最喜欢的 JavaScript 的reduce 方法轻松替换。

您的分组算法可以替换为

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 中

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

将所有副作用移入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 />
  )
}

【讨论】:

  • var 没有范围问题,只是范围不同于 letconst
  • @Agney 也许取决于一个人的洞察力。对我来说,当声明一个变量时,我希望它留在我声明它的块内。所以这对我来说是一个范围问题。但在 OP 的情况下,他显然在利用它,在所有三个 for 循环中使用相同的 i。但是,IMO OP 的做法是错误的。
  • 我也想把所有的变量都变成状态,但是操作和变量太多了。我该如何处理它们?
  • 我尝试将变量放入 useState()。但是出现了一些错误。它表示 useState 有条件地运行。在其他组件中,我必须将 useState 放在 useQuery 之前。但是在这个组件中,我必须在 useQuery() 之后使用 useState()。我该如何解决?
  • 错误是什么?我会在一小时后回复你
猜你喜欢
  • 1970-01-01
  • 2015-11-04
  • 1970-01-01
  • 2020-09-30
  • 1970-01-01
  • 2016-08-25
  • 2013-08-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多