componentDidMount 生命周期方法中的条件异步操作不断循环

conditional async actions in componentDidMount lifecycle method keeps going on a loop

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

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

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

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

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

逻辑是只有当programId不为空且programContacts为空时,才必须进行fetch请求。但这会无限循环获取。

我发现 if 语句被反复执行,因为 if 语句主体中的表达式甚至在前一个获取请求返回结果之前就被 componentDidMount 再次执行。并且因为条件之一是检查结果数组的长度是否为非空,所以 if 语句 returns 为真,因此循环继续进行而不让先前的请求完成。

我不明白的是为什么if语句必须重复执行。难道if语句执行一次就退出生命周期方法吗?

我知道也许可以使用某种超时方法来让它工作,但这不是我可以依赖的足够强大的技术。

是否有实现此目的的最佳实践?

此外,是否有任何建议不要在 componentDidMount 方法中使用 if 条件?

实际问题出在componentWillReceiveProps方法本身,死循环就是在这里创建的。 您正在检查当前和下一个 programId 是否不匹配,然后触发一个操作,使当前和下一个 programId 不再匹配。使用给定的操作 fetchProgramContacts,您会以某种方式改变 programId。检查你的减速器。

解决这个问题的方法之一是在你的 reducer 中设置 reqFinished (true/false),然后你应该这样做:

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

在React生命周期中,componentDidMount()只触发一次

确保调用来自 componentDidMount 而不是 componentWillReceiveProps

如果调用真正来自componentDidMount,这意味着你的组件每次都被重新创建。 可以通过在组件的 constructor 中添加 console.log 来检查它。

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

您可以在另一个问题中看到我对它如何工作的详细回答之一:


如果我专注于您的用例,您可以在下面看到 isFetchingdidInvalidate 概念的应用。

1.组件

看看 actions 和 reducers,但 redux 的技巧是使用 isFetchingdidInvalidate 道具。

您想要获取数据时唯一的两个问题是:

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

您可以在下面看到,每当您 select 一个程序时,您都会 使获取的数据无效 以便使用新的 programId 作为过滤器再次获取。

注意:您当然应该使用 connectredux 来将动作和缩减器传递给您的组件!

MainView.js

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

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

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。联系行动

我将只关注接触操作,因为程序几乎相同。

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。联系减速机

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;
  }
}

希望对您有所帮助。