使用Redux,取消动作创建者中的先前操作是不好的做法?

时间:2017-06-13 05:58:35

标签: redux react-redux redux-thunk

我正在为字段创建验证器并在更新时触发验证。我只关心当前状态的验证结果。因此,如果在验证完成之前触发了另一个更新,我不希望旧数据的验证成功。

我已阅读并相信在https://stackoverflow.com/a/35674575/816584中访问动作创建者中的商店有点反模式。

我目前有以下内容:

export const USERNAME_VALIDATION_PENDING = 'USERNAME_VALIDATION_PENDING';
export const USERNAME_VALIDATION_SUCCESS = 'USERNAME_VALIDATION_SUCCESS';
export const USERNAME_VALIDATION_FAILURE = 'USERNAME_VALIDATION_FAILURE';
export const USERNAME_VALIDATION_ABORTED = 'USERNAME_VALIDATION_PENDING';

export interface Validator {
    validate(subject: string): Promise<void, Error>;
    cancel();
}

const cache = new WeakMap();

function memoize(obj: {}, subject: string, result: Error | true) {
    const map: {[subject: string]: Error | true} = cache.has(obj) ?
        cache.get(obj) : {};

    // TODO: Do consider caching just the message so the stack can get GC
    map[subject] = result;
    cache.set(obj, map);
}

function resultCacheP(obj: {}, subject: string) {
    const map: {[subject: string]: Error | true} = cache.has(obj) ?
        cache.get(obj) : {};
    return Object.keys(map).indexOf(subject) !== -1;
}

function resultCache(obj: {}, subject: string) {
    const map: {[subject: string]: Error | true} = cache.has(obj) ?
        cache.get(obj) : {};

    return map[subject];
}

export function requestUsernameValidation(username: string, validator: Validator) {
    return {
        type: USERNAME_VALIDATION_PENDING,
        isValidating: true,
        isValid: false,
        username,
        validator,
    }
}

export function receiveUsernameValidationSuccess() {
    return {
        type: USERNAME_VALIDATION_SUCCESS,
        isValidating: false,
        isValid: true,
    }
}

export function receiveUsernameValidationFailure(error: string) {
    return {
        type: USERNAME_VALIDATION_FAILURE,
        isValidating: false,
        isValid: false,
        error,
    }
}

export function validateUsername(username: string, validator: Validator) {
    return (dispatch) => {
        dispatch(requestUsernameValidation(username, validator));

        if (resultCacheP(validator, username)) {
            const result = resultCache(validator, username);
            if (result instanceof Error) {
                dispatch(receiveUsernameValidationSuccess());
            } else {
                dispatch(receiveUsernameValidationFailure(result.message));
            }
        } else {
            validator.validate(username)
                .then(() => {
                    memoize(validator, username, true);
                    dispatch(receiveUsernameValidationSuccess());
                })
                .catch((err: Error) => {
                    memoize(validator, username, err);
                    dispatch(receiveUsernameValidationFailure(err.message));
                });
        }
    };
}

如果我的状态当前正在验证以避免上一个验证器稍后完成并且错误地更新状态的竞争条件,我希望requestUsernameValidation从前一个操作调用验证器取消。

我想我需要将requestUsernameValidation更改为thunk才能获得当前状态。

这是我第一次尝试使用Redux。我读过的大多数教程都有精益动作创作者,但我所见过的很多真实世界的项目都有很多复杂的逻辑。这种方法是解决这个问题的正确方法吗?

1 个答案:

答案 0 :(得分:1)

通常使用输入字段我会使用debounce方法:

引用https://github.com/component/debounce

  

创建并返回传递函数的新debounced版本,该函数将推迟执行,直到自上次调用后经过等待毫秒为止。

例如,如果你这样做

let waitTime = 300;
const debouncedValidateUsername = debounce((username, validator) => dispatch => { ... })

验证将在最后一次更改后300毫秒内完成,您将拥有最新用户input

<强>更新

受此great answer的启发,您可以执行以下操作

let cancellationAmbassador = {
  cancel() {}
};

function validate(validator, username, cancellationAmbassador) {
  return new Promise((resolve, reject) => {
    validator
      .validate(username)
      .then(() => {
        resolve("valid");
      })
      .catch((err: Error) => {
        reject("invalid");
      });
    cancellationAmbassador.cancel = function() {
      reject("cancellation");
    };
  });
}

export function validateUsername(username: string, validator: Validator) {
  return dispatch => {
    cancellationAmbassador.cancel();
    dispatch(requestUsernameValidation(username, validator));
    if (resultCacheP(validator, username)) {
      const result = resultCache(validator, username);
      if (result instanceof Error) {
        dispatch(receiveUsernameValidationSuccess());
      } else {
        dispatch(receiveUsernameValidationFailure(result.message));
      }
    } else {
      validate(validator, username, cancellationAmbassador)
        .then(() => {
          memoize(validator, username, true);
          dispatch(receiveUsernameValidationSuccess());
        })
        .catch(reason => {
          if (reason !== "cancellation") {
            memoize(validator, username, err);
            dispatch(receiveUsernameValidationFailure(err.message));
          }
        });
    }
  };
}