本文介绍了componentDidMount生命周期方法中的条件异步动作不断循环的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用连接到API的redux和sagas开发一个React应用.

I am working on a react app using redux and sagas connected to an API.

有一个表单组件,具有两个下拉字段:程序"和联系人"字段.表单设计的工作方式是,当用户选择程序时,表单使用programId来获取已为该程序注册的所有联系人.然后,将这些联系人填充为联系人"下拉字段的选项.这行得通,我已经使用componentWillReceiveProps实现了它,如下所示:-

There's a form component that has two dropdown fields: a Program and a Contact field. The way the form is designed to work is, when the user selects a program, the form uses the programId to fetch all the contacts that have registered for that program. These contacts are then populated as the options for the contact dropdown field. This works and I've implemented it using the componentWillReceiveProps, like this:-

componentWillReceiveProps(nextProps) {
    if (nextProps.programId !== this.props.programId) {
        this.props.fetchProgramContacts(nextProps.programId);
    }
}

现在,我正在尝试具有一项附加功能,当从程序的配置文件页面访问此表单时,该表单会使用programId自动填充该表单.在这种情况下,由于即使在组件安装之前,programId仍已预加载到formData中,所以不会触发componentWillReceiveProps,因为prop中没有任何更改.因此,我决定在componentDidMount生命周期方法中提取programContacts,如下所示:-

Now I'm trying to have an additional feature that autopopulates the form with the programId when this form is accessed from the program's profile page. In this case, since the programId is preloaded into the formData even before the component mounts, the componentWillReceiveProps is not triggered as there is no change in the prop. So I decided to have the programContacts fetching in the componentDidMount lifecycle method, like this:-

componentDidMount() {
    if (this.props.programId !== '' && !this.props.programContactData.length) {
        this.props.fetchProgramContacts(this.props.programId);
    }
}

逻辑是仅在programId不为空且programContacts为空时才必须进行获取请求.但这是一个无休止的获取循环.

The logic is that the fetch request must be made only when the programId is not empty and the programContacts are empty. But this goes on an endless loop of fetching.

我发现if语句被一遍又一遍地执行,因为if语句主体中的表达式甚至在之前的获取请求与结果一起返回之前由componentDidMount再次执行.而且,由于条件之一就是检查结果数组的长度是否为非空,因此if语句返回true,因此循环继续进行而没有让先前的请求完成.

I discovered that the if statement gets executed over and over because the expressions in the body of the if statement is executed again by the componentDidMount even before the previous fetch request gets returned with the results. And because one of the conditions is to check whether the length of the results array is nonempty, the if statement returns true and so the loop goes on without letting the previous requests reach completion.

我不明白的是为什么必须重复执行if语句.一旦执行了if语句,它不应该退出生命周期方法吗?

What I don't understand is why the if statement must be executed repeatedly. Shouldn't it exit the lifecycle method once the if statement is executed once?

我知道也许可以使用某种超时方法来使其正常工作,但这对我来说还不够强大.

I know that maybe it is possible to use some kind of a timeout method to get this to work, but that is not a robust enough technique for me to rely upon.

是否有最佳实践来实现这一目标?

Is there a best practice to accomplish this?

此外,如果componentDidMount方法中有条件,是否有不建议使用的建议?

Also, is there any recommendation to not use if conditionals within the componentDidMount method?

推荐答案

在React生命周期中,componentDidMount()仅被触发一次.

In the React lifecycle, componentDidMount() is only triggered once.

确保呼叫是通过componentDidMount而不是componentWillReceiveProps进行的.

Make sure the call is made from the componentDidMount and not componentWillReceiveProps.

如果呼叫确实来自componentDidMount,则意味着每次都将重新创建组件.可以通过在组件的constructor中添加console.log进行检查.

If the call trully comes from componentDidMount, it means you component is recreated every time.It can be checked by adding a console.log in the constructor of your component.

无论如何,您应该更喜欢使用redux的isFetchingdidInvalidate来处理数据获取/重新获取.

In any case, you should prefer using the isFetching and didInvalidate of redux to handle data fetching / refetching.

您可以在另一个问题中看到我的详细解答之一:

You can see one of my detailed answer of how it works in another question: React-Redux state in the component differs from the state in the store

如果我关注您的用例,则可以在下面看到isFetchingdidInvalidate概念的应用程序.

If I focus on your usecase, you can see below an application of the isFetching and didInvalidate concept.

1.组件

看看动作和减速器,但是redux的窍门是使用isFetchingdidInvalidate道具.

Take a look at the actions and reducers but the trick with redux is to play with the isFetching and didInvalidate props.

要获取数据时,仅有的两个问题是:

The only two questions when you want to fetch your data will be:

  1. 我的数据仍然有效吗?
  2. 我当前正在获取数据吗?

您可以在下面看到,无论何时选择程序,都将使无效,以便使用新的programId作为过滤器再次进行获取.

You can see below that whenever you select a program you will invalidate the fetched data in order to fetch again with the new programId as filter.

注意:当然,您应该使用redux中的connect来将动作和减速器传递给您的组件!

Note: You should use connect of redux to pass the actions and reducers to your components of course !

MainView.js

MainView.js

class MainView extends React.Component {
  return (
    <div>
      <ProgramDropdown />
      <ContactDropdown />
    </div>
  );
}

ProgramDropdown.js

ProgramDropdown.js

class ProgramDropdown extends React.Component {
  componentDidMount() {
    if (this.props.programs.didInvalidate && !this.props.programs.isFetching) {
      this.props.actions.readPrograms();
    }
  }

  render() {
    const {
      isFetching,
      didInvalidate,
      data,
    } = this.props;

    if (isFetching || (didInvalidate && !isFetching)) {
      return <select />
    }

    return (
      <select>
        {data.map(entry => (
          <option onClick={() => this.props.actions.setProgram(entry.id)}>
            {entry.value}
          </option>
        ))}
      </select>
    );
  }
}

ContactDropdown.js

ContactDropdown.js

class ContactDropdown extends React.Component {
  componentDidMount() {
    if (this.props.programs.selectedProgram &&
      this.props.contacts.didInvalidate && !this.props.contacts.isFetching) {
      this.props.actions.readContacts(this.props.programs.selectedProgram);
    }
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.programs.selectedProgram &&
      nextProps.contacts.didInvalidate && !nextProps.contacts.isFetching) {
      nextProps.actions.readContacts(nextProps.programs.selectedProgram);
    }
  }

  render() {
    const {
      isFetching,
      didInvalidate,
      data,
    } = this.props;

    if (isFetching || (didInvalidate && !isFetching)) {
      return <select />
    }

    return (
      <select>
        {data.map(entry => (
          <option onClick={() => this.props.actions.setContact(entry.id)}>
            {entry.value}
          </option>
        ))}
      </select>
    );
  }
}

2.联络人动作

我将只关注接触动作,因为程序之一几乎是相同的.

I'm going to focus only on the contact actions as the program one is nearly the same.

export function readContacts(programId) {
  return (dispatch, state) => {
    dispatch({ type: 'READ_CONTACTS' });

    fetch({ }) // Insert programId in your parameter
      .then((response) => dispatch(setContacts(response.data)))
      .catch((error) => dispatch(addContactError(error)));
  };
}

export function selectContact(id) {
  return {
    type: 'SELECT_CONTACT',
    id,
  };
}

export function setContacts(data) {
  return {
    type: 'SET_CONTACTS',
    data,
  };
}

export function addContactError(error) {
  return {
    type: 'ADD_CONTACT_ERROR',
    error,
  };
}

3.联系Reducers

import { combineReducers } from 'redux';

export default combineReducers({
  didInvalidate,
  isFetching,
  data,
  selectedItem,
  errors,
});

function didInvalidate(state = true, action) {
  switch (action.type) {
    case 'SET_PROGRAM': // !!! THIS IS THE TRICK WHEN YOU SELECT ANOTHER PROGRAM, YOU INVALIDATE THE FETCHED DATA !!!
    case 'INVALIDATE_CONTACT':
        return true;
    case 'SET_CONTACTS':
      return false;
    default:
      return state;
  }
}

function isFetching(state = false, action) {
  switch (action.type) {
    case 'READ_CONTACTS':
      return true;
    case 'SET_CONTACTS':
      return false;
    default:
      return state;
  }
}

function data(state = {}, action) {
  switch (action.type) {
    case 'SET_CONTACTS':
      return action.data;
    default:
      return state;
  }
}

function selectedItem(state = null, action) {
  switch (action.type) {
    case 'SELECT_CONTACT':
      return action.id;
    case 'READ_CONTACTS':
    case 'SET_CONTACTS':
      return null;
    default:
      return state;
  }
}

function errors(state = [], action) {
  switch (action.type) {
    case 'ADD_CONTACT_ERROR':
      return [
        ...state,
        action.error,
      ];
    case 'SET_CONTACTS':
      return state.length > 0 ? [] : state;
    default:
      return state;
  }
}

希望有帮助.

这篇关于componentDidMount生命周期方法中的条件异步动作不断循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-03 17:36