Bläddra i källkod

docs: renamed file and added redux framework file

Hugo Häggmark 6 år sedan
förälder
incheckning
ed1b001904
2 ändrade filer med 168 tillägg och 7 borttagningar
  1. 10 7
      style_guides/pull-request-review-checklist.md
  2. 158 0
      style_guides/redux.md

+ 10 - 7
style_guides/frontend-review-checklist.md → style_guides/pull-request-review-checklist.md

@@ -1,4 +1,4 @@
-# Frontend Review Checklist
+# Pull Request Review Checklist
 
 ## High level checks
 
@@ -15,19 +15,22 @@
 - [ ] The pull request contains necessary link(s) to issue(s).
 - [ ] The pull request contains commits with commit messages that are small and understandable.
 - [ ] The pull request does not contain magic strings or numbers that could be replaced with an `Enum` or `const` instead.
-- [ ] The pull request does not increase the number of `implicit any` errors.
-- [ ] The pull request does not contain uses of `any` or `{}` that are unexplainable.
-- [ ] The pull request does not contain large React component that could easily be split into several smaller components.
-- [ ] The pull request does not contain back end calls directly from components, use actions and Redux instead.
 
 ### Bug specific checks
 
 - [ ] The pull request contains only one commit if possible.
 - [ ] The pull request contains `closes: #Issue` or `fixes: #Issue` in pull request description.
 
+## Frontend specific checks
+
+- [ ] The pull request does not increase the number of `implicit any` errors.
+- [ ] The pull request does not contain uses of `any` or `{}` without comments describing why.
+- [ ] The pull request does not contain large React component that could easily be split into several smaller components.
+- [ ] The pull request does not contain back end calls directly from components, use actions and Redux instead.
+
 ### Redux specific checks (skip if pull request does not contain Redux changes)
 
 - [ ] The pull request does not contain code that mutate state in reducers or thunks.
-- [ ] The pull request uses helpers `actionCreatorFactory` and `reducerFactory` instead of traditional `switch statement` reducers in Redux.
-- [ ] The pull request uses `reducerTester` to test reducers.
+- [ ] The pull request uses helpers `actionCreatorFactory` and `reducerFactory` instead of traditional `switch statement` reducers in Redux. ([Redux framework](https://github.com/grafana/grafana/blob/master/style_guides/redux.md))
+- [ ] The pull request uses `reducerTester` to test reducers.([Redux framework](https://github.com/grafana/grafana/blob/master/style_guides/redux.md))
 - [ ] The pull request does not contain code that access reducers state slice directly, instead the code uses state selectors to access state.

+ 158 - 0
style_guides/redux.md

@@ -0,0 +1,158 @@
+# Redux framework
+
+To reduce the amount of boilerplate code used to create a strongly typed redux solution with actions, action creators, reducers and tests we've introduced a small framework around Redux.
+
+`+` Much less boilerplate code
+`-` Non Redux standard api
+
+## New core functionality
+
+### actionCreatorFactory
+
+Used to create an action creator with the following signature
+
+```typescript
+{ type: string , (payload: T): {type: string; payload: T;} }
+```
+
+where the `type` string will be ensured to be unique and `T` is the type supplied to the factory.
+
+#### Example
+
+```typescript
+export const someAction = actionCreatorFactory<string>('SOME_ACTION').create();
+
+// later when dispatched
+someAction('this rocks!');
+```
+
+```typescript
+// best practices, always use an interface as type
+interface SomeAction {
+  data: string;
+}
+export const someAction = actionCreatorFactory<SomeAction>('SOME_ACTION').create();
+
+// later when dispatched
+someAction({ data: 'best practices' });
+```
+
+```typescript
+// declaring an action creator with a type string that has already been defined will throw
+export const someAction = actionCreatorFactory<string>('SOME_ACTION').create();
+export const theAction = actionCreatorFactory<string>('SOME_ACTION').create(); // will throw
+```
+
+### noPayloadActionCreatorFactory
+
+Used when you don't need to supply a payload for your action. Will create an action creator with the following signature
+
+```typescript
+{ type: string , (): {type: string; payload: undefined;} }
+```
+
+where the `type` string will be ensured to be unique.
+
+#### Example
+
+```typescript
+export const noPayloadAction = noPayloadActionCreatorFactory('NO_PAYLOAD').create();
+
+// later when dispatched
+noPayloadAction();
+```
+
+```typescript
+// declaring an action creator with a type string that has already been defined will throw
+export const noPayloadAction = noPayloadActionCreatorFactory('NO_PAYLOAD').create();
+export const noAction = noPayloadActionCreatorFactory('NO_PAYLOAD').create(); // will throw
+```
+
+### reducerFactory
+
+Fluent API used to create a reducer. (same as implementing the standard switch statement in Redux)
+
+#### Example
+
+```typescript
+interface ExampleReducerState {
+  data: string[];
+}
+
+const intialState: ExampleReducerState = { data: [] };
+
+export const someAction = actionCreatorFactory<string>('SOME_ACTION').create();
+export const otherAction = actionCreatorFactory<string[]>('Other_ACTION').create();
+
+export const exampleReducer = reducerFactory<ExampleReducerState>(intialState)
+  // addMapper is the function that ties an action creator to a state change
+  .addMapper({
+    // action creator to filter out which mapper to use
+    filter: someAction,
+    // mapper function where the state change occurs
+    mapper: (state, action) => ({ ...state, data: state.data.concat(action.payload) }),
+  })
+  // a developer can just chain addMapper functions until reducer is done
+  .addMapper({
+    filter: otherAction,
+    mapper: (state, action) => ({ ...state, data: action.payload }),
+  })
+  .create(); // this will return the reducer
+```
+
+#### Typing limitations
+
+There is a challenge left with the mapper function that I can not solve with TypeScript. The signature of a mapper is
+
+```typescript
+<State, Payload>(state: State, action: ActionOf<Payload>) => State;
+```
+
+If you would to return an object that is not of the state type like the following mapper
+
+```typescript
+mapper: (state, action) => ({ nonExistingProperty: ''}),
+```
+
+Then you would receive the following compile error
+
+```shell
+[ts] Property 'data' is missing in type '{ nonExistingProperty: string; }' but required in type 'ExampleReducerState'. [2741]
+```
+
+But if you return an object that is spreading state and add a non existing property type like the following mapper
+
+```typescript
+mapper: (state, action) => ({ ...state, nonExistingProperty: ''}),
+```
+
+Then you would not receive any compile error.
+
+If you want to make sure that never happens you can just supply the State type to the mapper callback like the following mapper:
+
+```typescript
+mapper: (state, action): ExampleReducerState => ({ ...state, nonExistingProperty: 'kalle' }),
+```
+
+Then you would receive the following compile error
+
+```shell
+[ts]
+Type '{ nonExistingProperty: string; data: string[]; }' is not assignable to type 'ExampleReducerState'.
+  Object literal may only specify known properties, and 'nonExistingProperty' does not exist in type 'ExampleReducerState'. [2322]
+```
+
+## New test functionality
+
+### reducerTester
+
+Fluent API that simplifies the testing of reducers
+
+#### Example
+
+```typescript
+reducerTester()
+  .givenReducer(someReducer, initialState)
+  .whenActionIsDispatched(someAction('reducer tests'))
+  .thenStateShouldEqual({ ...initialState, data: 'reducer tests' });
+```