1

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

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

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

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

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

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

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

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

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

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

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

4

2 回答 2

3

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

确保呼叫是从componentDidMountand not发出的componentWillReceiveProps

如果调用真正来自componentDidMount,则意味着您的组件每次都重新创建。可以通过console.logconstructor组件中添加一个来检查它。

在任何情况下,您都应该更喜欢使用redux的isFetchingand来处理数据获取/重新获取。didInvalidate

您可以在另一个问题中看到我对其工作原理的详细回答:React-Redux state in the component is different from the state in the store


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

1. 组件

看看 action 和 reducer,但 redux 的诀窍是使用isFetchinganddidInvalidate道具。

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

  1. 我的数据还有效吗?
  2. 我目前正在获取数据吗?

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

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

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

希望能帮助到你。

于 2017-10-27T13:45:27.543 回答
0

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

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

componentWillReceiveProps(nextProps){
  if(nextProps.reqFinished){
    this.props.fetchProgramContacts(nextProps.programId);
  }
}
于 2017-10-27T13:27:03.893 回答