您好,登錄后才能下訂單哦!
這篇文章主要介紹了React中Native項目框架搭建的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
React Native 是Facebook于2015年4月開源的跨平臺移動應用開發框架, 短短的一兩年的發展就已經有很多家公司支持并采用此框架來搭建公司的移動端的應用,
React Native使你能夠在Javascript和React的基礎上獲得完全一致的開發體驗,構建世界一流的原生APP。
react native、react hook、typescript、immer、tslint、jest等.
都是比較常見的,就不多做介紹了
思想與redux是一致的,用起來相對比較簡單,適合不太復雜的業務場景.
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> ); };
const HomeContainer = () => { const { dispatch, state } = useContext(HomeContext); ...
|-page1 |-handler // 處理邏輯的純函數,需進行UT覆蓋 |-container // 整合數據、行為與組件 |-component // 純UI組件,展示內容與用戶交互,不處理業務邏輯 |-store // 數據結構不能超過3層,可使用外部引用、冗余字段的方式降低層級 |-reducer // 使用immer返回新的數據(immutable data) |-... |-page2 |-...
整個項目做為一個多頁應用,最基本的拆分單元是page.
每一個page有相應的store,并非整個項目使用一個store,這樣做的原因如下:
各個頁面的邏輯相對獨立
各個頁面都可作為項目入口
結合RN頁面生命周期進行數據處理(避免數據初始化、緩存等一系列問題)
各個頁面中與外部相關的操作,都在Page組件中定義
頁面跳轉邏輯
回退之后要處理的事件
需要操作哪些storage中的數據
需要請求哪些服務等等
以其自身業務模塊為基礎,把可以抽象出來的外部依賴、外部交互都集中到此組件的代碼中.
方便開發人員在進行各個頁面間邏輯編寫、問題排查時,可根據具體頁面+數據源,準確定位到具體的代碼.
在以往的項目中,reducer中可能會涉及部分數據處理、用戶行為、日志埋點、頁面跳轉等等代碼邏輯.
因為在開發人員寫代碼的過程中,發現reducer作為某個處理邏輯的終點(更新了state之后,此次事件即為結束),很適合去做這些事情.
隨著項目的維護,需求的迭代,reducer的體積不斷的增大.
因為缺乏條理,代碼量又龐大,再想去對代碼進行調整,只會困難重重.
讓你去維護這樣的一個項目,可想而知,將會是多么的痛苦.
為此,對reducer中的代碼進行了一些減法:
reducer中只對state的數據進行修改
使用immer的produce產生immutable data
冗余單獨字段的修改,進行整合,枚舉出頁面行為對應的action
以可枚舉的形式,匯總出頁面中所有操作數據的場景.
在其本身適用于react框架的特性之外,賦予一定的業務邏輯閱讀屬性,在不依賴UI組件的情況下,可大致閱讀出頁面中的所有數據處理邏輯.
// 避免dispatch時進行兩次,且定義過多單字段的更新case // 整合此邏輯后,與頁面上的行為相關聯,利于理解、閱讀 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; });
這里先引入一個純函數的概念:
一個函數的返回結果只依賴于它的參數,并且在執行過程里面沒有副作用,我們就把這個函數叫做純函數.
把盡可能多的邏輯抽象為純函數,然后放入handler中:
涵蓋較多的業務邏輯
只能是純函數
必須進行UT覆蓋
負責數據源到store、container到component、dispatch到reducer等等場景下的邏輯處理.
作為各類場景下,邏輯處理函數的存放地,整個文件不涉及頁面流程上的關聯關系,每個函數只要滿足其輸入與輸出的使用場景,即可復用,多用于container文件中.
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; }
describe(getFilterAndSortResult.name, () => { test('getFilterAndSortResult', () => { expect(getFilterAndSortResult(flightList, filterList, false, EFlightSortType.PriceAsc)).toEqual(filterSortResult); }); });
由上面的項目結構圖可以看出,每個Page都有base Container,作為數據處理的中心.
在此base Container之下,會根據不同模塊,定義出各個子Container:
生命周期處理(初始化時要進行的一些異步操作)
為渲染組件Components提供數據源
定義頁面中的行為函數
整個項目中,各種數據、UI、用戶行為的匯合點,要盡可能的把相關的模塊抽離出來,避免造成代碼量過大,難以維護的情況.
Container的定義應以頁面展示的模塊進行抽象.如Head Contianer、Content Container、Footer Container等較為常見的劃分方式.
一些頁面中相對獨立的模塊,也應該產出其對應的Container,來內聚相關邏輯,如贈送優惠券模塊、用戶反饋模塊等.
多個Container中公用的行為,可直接放入base Container中
在上文架構圖中的action事例(setAction)為另外一種行為復用,根據具體的場景進行應用
利于代碼閱讀,A模塊的浮層展示邏輯,B模塊使用時
模塊產生的先后順序,先有A模塊再有B模塊需要使用A的方法
定義數據埋點、用戶行為埋點
頁面跳轉方法的調用(Page-->base Container-->子Container)
其他副作用的行為
const OWFlightListContainer = () => { // 通過Context獲取數據 const { state, dispatch } = useContext(OWFlightListContext); ... // 初次加載時進行超時的倒計時 useOnce(overTimeCountDown); ... // 用戶點擊排序 const onPressSort = (lastSortType: EFlightSortType, isTimeSort: boolean) => { // 引用了handler中的getNextSortType函數 const sortType = getNextSortType(lastSortType, isTimeSort); dispatch({ type: EOWFlightListAction.updateSortType, payload: sortType }); // 埋點操作 logSort(state, sortType); }; // 渲染展示組件 return <.../>; }
由easy to code到easy to read
在整個項目中,定義了很多規范,是想在功能的實現之上,更利于項目人員的維護.
Page組件中包含頁面相關的外部依賴
reducer枚舉出所有對頁面數據操作的事件
handler中集合了業務邏輯的處理,以純函數的實現及UT的覆蓋,確保項目質量
Container中的行為函數,定義出所有與用戶操作相關的事件,并記錄埋點數據
Componet中避免出現業務邏輯的處理,只進行UI展示,減少UI自動化case,增加UT的case
規范的定義是比較容易的,想要維護好一個項目,更多的是依靠團隊的成員,在達成共識的前提下,持之以恒的堅持了
根據對象路徑取值
/** * 根據對象路徑取值 * @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);
讀取根據配置信息
// 在與外部對接時,經常會定義一些固定結構,可擴展性的數據列表 // 為了適應此類契約,便于更好的閱讀與維護,總結出了以下函數 export const GLOBAL_NOTE_CONFIG = { 2: 'refund', 3: 'sortType', 4: 'featureSwitch' }; /** * 根據配置,獲取attrList中的值,返回json對象類型的數據 * @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; } // DEMO // 適用于外部定義的一些可擴展note節點列表的取值邏輯 const { sortType, featureSwitch } = getNoteValue(list, GLOBAL_NOTE_CONFIG, 'ntype');
多條件數組排序
/** * 獲取用于排序的sort函數 * @param fn 同類型元素比較函數,true為排序優先 */ 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; }; } /** * 多重排序 */ 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));
感謝你能夠認真閱讀完這篇文章,希望小編分享的“React中Native項目框架搭建的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。