all files / src/ index.ts

100% Statements 26/26
100% Branches 6/6
100% Functions 3/3
100% Lines 25/25
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                                                                                                            14×   14×                                                                                              
export type onChangeCallback<actionType> = ((lastAction: actionType) => void);
 
/**
 * gets called when a dispatch with an action is triggered
 */
export interface reducer<stateType, actionType> {
  (previousState: stateType, action: actionType): stateType;
}
 
/**
 * thats how a complete store is organized
 */
export interface redchain {
  <stateType, actionType>(initValue: stateType, reducer: reducer<stateType, actionType>): store<stateType, actionType>;
}
 
export interface store<stateType, actionType> {
  /**
   * this value gets replaced, each time the reducer gets called
   */
  state: stateType;
 
  /**
   *  when the state property should change, thats the way to call it
   */
  dispatch(action: actionType): boolean;
 
  /**
   *  eventlisteners when a dispatch caused a change in state
   */
  addOnChange(onChange: onChangeCallback<actionType>): store<stateType, actionType>;
 
  /**
   * when a eventlistener is not needed, this function should get called
   */
  removeOnChange(removeOnChange: onChangeCallback<actionType>): boolean;
 
  /**
   * flushes all existing eventlisteners
   */
  flush(): void;
}
 
const store: redchain = <stateType, actionType>(initValue: stateType, reducer: reducer<stateType, actionType>): store<stateType, actionType> => {
 
  let onChanges: onChangeCallback<actionType>[] = [];
 
  const result: store<stateType, actionType> = {
    /**
     * holds the actual value of the current store
     */
    state: initValue,
 
    /**
     * takes listeners, when the reducer returnvalue is triggered they
     */
    addOnChange(onChange: onChangeCallback<actionType>): store<stateType, actionType> {
      onChanges.push(onChange);
 
      return this;
    },
 
    /**
     * takes listeners, when the reducer returnvalue is triggered they
     * and returns true, when something changed
     */
    removeOnChange(removeOnChange: onChangeCallback<actionType>) {
      const previousLength = onChanges.length;
      onChanges = onChanges.filter((currentOnChange: onChangeCallback<actionType>) => currentOnChange !== removeOnChange);
 
      return previousLength !== onChanges.length;
    },
 
    /**
     * flushes all existing eventlisteners
     */
    flush() {
      onChanges = [];
    },
 
    /**
     * this function triggers the reducer
     * when the returnvalue is unequal to the previous state it will trigger the listeners from addOnChange
     */
    dispatch: null as any,
  };
 
  /**
   * dispatch got added after creating the scope for the result object, to bind the function to this scope
   */
  result.dispatch = function (this: store<stateType, actionType>, action: actionType) {
    const currentState = reducer(this.state, action);
 
    if (this.state !== currentState) {
      this.state = currentState;
      
      const calledOnChanges: onChangeCallback<actionType>[] = [];
      for (let i = 0; i < onChanges.length; i += 1) {
        const onChange = onChanges[i];
 
        if (calledOnChanges.indexOf(onChange) === -1) {
          // currently no other listeners will get notified, when the following line will fuck up
          // try-catch should be avoided, to improve debuggability
          // setTimeout would break the call-stack
          // If you have an opinion on this matter, please make a github issue and tell me
          onChange(action);
          calledOnChanges.push(onChange);
 
          if (onChange !== onChanges[i]) {
            // Reset if onchange removed itself
            i = -1;
          }
        }
      }
 
      return true;
    }
 
    return false;
  }.bind(result);
 
  return result;
};
 
export default store;