Project framework and project structure

1. Technology stack used in the project

React Native, React Hook, typescript, Immer, TSLint, Jest, etc. All are relatively common, I won’t do more introduction

2. UseContext +useReducer in React Hook is used for data processing

The idea is the same as Redux, which is relatively simple to use and suitable for less complex business scenarios.

const HomeContext = createContext<IContext>({
  state: defaultState,
  dispatch: () => {}
});
const ContextProvider = ({ urlQuery, pageCode }: IProps) => {
  const initState = getInitState(urlQuery, pageCode);
  const [state, dispatch]: [IHomeState, IDispatch] = useReducer(homeReducer, initState);

  return (
    <HomeContext.Provider value={{ state, dispatch }}>
      <HomeContainer />
    </HomeContext.Provider>
  );
};
Copy the code
const HomeContainer = () => { const { dispatch, state } = useContext(HomeContext); .Copy the code

3. The structure of the project is as follows

| - page1 | - handler / / processing logic pure functions, need to UT cover | - container / / integration of data, behavior, and component | - component / / pure UI components, display content and user interaction, does not handle the business logic | - store / / Data structure is no more than 3 layer, can use external references, the way to reduce levels of redundancy field | - reducer / / use immer returns the new data (immutable data) | -... |-page2 |-...Copy the code

Specifications in the project

1. Page

The entire project as a multi-page application, the most basic split unit is page.

There is a store for each page, not a store for the entire project, for the following reasons:

  • The logic of each page is relatively independent
  • Each page can be used as a project entry point
  • Data processing in conjunction with the RN page life cycle (avoiding data initialization, caching, etc.)

External operations within each Page are defined in the Page component

  • Page jump logic
  • Events to be processed after the rollback
  • Which storage data needs to be operated
  • What services need to be requested, etc

The main role of the Page component

Based on its own business module, the external dependencies, external interactions can be abstracted into the code of this component.

It is convenient for developers to accurately locate the specific code according to the specific page + data source when writing logic between pages and troubleshooting problems.

2. reducer

In previous projects,reducer may involve some data processing, user behavior, log burying point, page jump and other code logic.

As developers write code, they find reducer to be the endpoint of some processing logic (the event ends after updating the state) and are well suited to do these things.

With the maintenance of the project and the iteration of requirements, the reducer volume has been increasing. Because of the lack of organization and the volume of code, it is difficult to adjust the code.

You can imagine how painful it would be for you to maintain such a project.

To this end, some subtractions were made to the reducer code:

  • Only state data is modified in the Reducer
  • Use immer produce to produce immutable data
  • Redundant individual field modification, integration, enumeration of page behavior corresponding to the action

Main roles of reducer

A summary of all operational data scenarios on a page in enumerable form.

In addition to its own features applicable to the React framework, it grants certain business logic reading properties, which allow you to roughly read all the data processing logic in the page without relying on UI components.

/ / avoid dispatch twice, and define too many single field case/update/integration of this logic, is associated with the behavior of the page, to understand, read case EFHListAction. UpdateSpecifyQueryMessage: return produce(state, (draft: IFHListState) => { draft.specifyQueryMessage = payload as string; draft.showSpecifyQueryMessage = true; }); case EFHListAction.updateShowSpecifyQueryMessage: return produce(state, (draft: IFHListState) => { draft.showSpecifyQueryMessage = payload as boolean; });Copy the code

3. handler

Here we introduce the concept of a pure function:

A function whose returns depend only on its arguments and have no side effects during execution is called pure.

Abstract as much logic as possible into pure functions and put it in handler:

  • Covers more business logic
  • It can only be a pure function
  • UT overrides must be performed

The main functions of handler

Responsible for logical processing from data source to Store, container to Component, dispatch to Reducer, etc.

The container file is used to store logical processing functions in various scenarios. The entire file does not involve the association relationship on the page flow. Each function can be reused as long as it meets the application scenarios of its input and output.

export function getFilterAndSortResult( flightList: IFlightInfo[], filterList: IFilterItem[], filterShare: boolean, filterOnlyDirect: boolean, sortType: EFlightSortType ) { if (! isValidArray(flightList)) { return []; } const sortFn = getSortFn(sortType); const result = flightList.filter(v => doFilter(v, filterList, filterShare, 1, filterOnlyDirect)).sort(sortFn); return result; }Copy the code
describe(getFilterAndSortResult.name, () => {
  test('getFilterAndSortResult', () => {
    expect(getFilterAndSortResult(flightList, filterList, false, EFlightSortType.PriceAsc)).toEqual(filterSortResult);
  });
});
Copy the code

4. Container

As you can see from the project structure diagram above, each Page has a Base Container as the data processing center.

Under the base Container, each child Container is defined according to the module:

  • Lifecycle processing (some asynchronous operations to do at initialization)
  • Provide data sources for render component Components
  • Define the behavior functions in the page

Main functions of Containers

In the whole project, the convergence point of various data, UI and user behavior should be removed from relevant modules as far as possible to avoid excessive code quantity and difficult maintenance.

The definition of a Container should be abstracted as a module displayed on the page. Common division methods include Head Contianer, Content Container, and Footer Container.

Some relatively independent modules on the page should also produce corresponding containers to consolidate related logic, such as the coupon giving module and user feedback module.

Special attention is paid to the behavior function

  • Behaviors common to multiple Containers can be placed directly into a Base Container
  • The action example (setAction) in the architecture diagram above is another kind of behavior reuse, which is applied according to the specific scenario
    • Easy to read code, module A floating layer to display logic, module B when used
    • In the order of module generation, module A comes first and then module B needs to use the method of A
  • Define data burying points and user behavior burying points
  • Call to Page jump method (Page–> Base Container–> child Container)
  • Other side effects of behavior
Const OWFlightListContainer = () => {// Get data from Context const {state, dispatch} = useContext(OWFlightListContext); . UseOnce (overTimeCountDown); . // user click sort const onPressSort = (lastSortType: EFlightSortType, isTimeSort: Boolean) => {// refer to getNextSortType const sortType = getNextSortType(lastSortType, isTimeSort); dispatch({ type: EOWFlightListAction.updateSortType, payload: sortType }); LogSort (state, sortType); }; // Render display component return <... / >; }Copy the code

summary

Easy to Code to Easy to read

In the whole project, a lot of specifications were defined to facilitate the maintenance of project personnel on the realization of functions.

  • The Page component contains page-specific external dependencies
  • Reducer enumerates all events that operate on page data
  • Handler sets the processing of business logic, and ensures the quality of the project by the implementation of pure functions and UT coverage
  • Behavior functions in containers that define all events related to user actions and record buried data
  • Avoid business logic processing in Componet, only UI display, reduce UI automation case, increase UT case

The definition of norms is relatively easy, want to maintain a project, more rely on team members, under the premise of consensus, persistent persistence

Share a few useful functions

Value based on object path

@param target {a: {b: {c: [1]}}} * @param path 'A.B.C. 0' */ export function getVal(target: any, path: string, defaultValue: any = undefined) { let ret = target; let key: string | undefined = ''; const pathList = path.split('.'); do { key = pathList.shift(); if (ret && key ! == undefined && typeof ret === 'object' && key in ret) { ret = ret[key]; } else { ret = undefined; } } while (pathList.length && ret ! == undefined); return ret === undefined || ret === null ? defaultValue : ret; } // DEMO const errorCode = getVal(result, 'rstlist.0.type', 0);Copy the code

Read according to the configuration information

Export const GLOBAL_NOTE_CONFIG = {2: 'refund', 3: * * * * * * * * * * * * * * * * * * * * * 'sortType', 4: 'featureSwitch' }; /** * Get attrList based on the configuration. Return json object type data * @private * @memberof DetailService */ export function getNoteValue<T>(noteList: Array<T> | undefined | null, config: { [_: string]: string }, keyName: string = 'type' ) { const ret: { [_: string]: T | Array<T> } = {}; if (! isValidArray(noteList!) ) { return ret; } //@ts-ignore noteList.forEach((note: any) => { const typeStr: string = (('' + note[keyName]) as unknown) as string; if (! (typeStr in config)) { return; } if (note === undefined || note === null) { return; } const key = config[typeStr]; If (ret[key] === undefined) {ret[key] = note; } else if (Array.isArray(ret[key])) { (ret[key] as T[]).push(note); } else { const first = ret[key]; ret[key] = [first, note]; }}); return ret; } const {sortType, featureSwitch} = getNoteValue(list, GLOBAL_NOTE_CONFIG, featureSwitch); 'ntype');Copy the code

Multiconditional array sort

*/ export function getSort<T>(fn: (a: T, b: T) => Boolean): (a: T, b: T) => 1 | -1 | 0 { return (a: T, b: T): 1 | -1 | 0 => { let ret = 0; if (fn.call(null, a, b)) { ret = -1; } else if (fn.call(null, b, a)) { ret = 1; } return ret as 0; }; } / multiple sort of * * * * / export function getMultipleSort < T > (arr: Array < = (a: T, b: T) > 1 | 1 | 0 >) {return (a: T, b: T) => { let tmp; let i = 0; do { tmp = arr[i++](a, b); } while (tmp === 0 && i < arr.length); return tmp; }; } // DEMO const ageSort = getSort(function(a, b) { return a.age < b.age; }); const nameSort = getSort(function(a, b) { return a.name < b.name; }); const sexSort = getSort(function(a, b) { return a.sex && ! b.sex; }); Const arr = [nameSort, ageSort, sexSort]; const ret = data.sort(getMultipleSort(arr));Copy the code