React Native項(xiàng)目框架搭建的一些心得體會(huì)
React Native 是Facebook于2015年4月開源的跨平臺(tái)移動(dòng)應(yīng)用開發(fā)框架, 短短的一兩年的發(fā)展就已經(jīng)有很多家公司支持并采用此框架來(lái)搭建公司的移動(dòng)端的應(yīng)用,
React Native使你能夠在Javascript和React的基礎(chǔ)上獲得完全一致的開發(fā)體驗(yàn),構(gòu)建世界一流的原生APP。
項(xiàng)目框架與項(xiàng)目結(jié)構(gòu)
1. 項(xiàng)目中使用的技術(shù)棧
react native、react hook、typescript、immer、tslint、jest等.
都是比較常見(jiàn)的,就不多做介紹了
2. 數(shù)據(jù)處理使用的是react hook中的useContext+useReducer
思想與redux是一致的,用起來(lái)相對(duì)比較簡(jiǎn)單,適合不太復(fù)雜的業(yè)務(wù)場(chǎng)景.
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); ...
3. 項(xiàng)目的結(jié)構(gòu)如下
|-page1 |-handler // 處理邏輯的純函數(shù),需進(jìn)行UT覆蓋 |-container // 整合數(shù)據(jù)、行為與組件 |-component // 純UI組件,展示內(nèi)容與用戶交互,不處理業(yè)務(wù)邏輯 |-store // 數(shù)據(jù)結(jié)構(gòu)不能超過(guò)3層,可使用外部引用、冗余字段的方式降低層級(jí) |-reducer // 使用immer返回新的數(shù)據(jù)(immutable data) |-... |-page2 |-...
項(xiàng)目中的規(guī)范
1. Page
整個(gè)項(xiàng)目做為一個(gè)多頁(yè)應(yīng)用,最基本的拆分單元是page.
每一個(gè)page有相應(yīng)的store,并非整個(gè)項(xiàng)目使用一個(gè)store,這樣做的原因如下:
- 各個(gè)頁(yè)面的邏輯相對(duì)獨(dú)立
- 各個(gè)頁(yè)面都可作為項(xiàng)目入口
- 結(jié)合RN頁(yè)面生命周期進(jìn)行數(shù)據(jù)處理(避免數(shù)據(jù)初始化、緩存等一系列問(wèn)題)
各個(gè)頁(yè)面中與外部相關(guān)的操作,都在Page組件中定義
- 頁(yè)面跳轉(zhuǎn)邏輯
- 回退之后要處理的事件
- 需要操作哪些storage中的數(shù)據(jù)
- 需要請(qǐng)求哪些服務(wù)等等
Page組件的主要作用
以其自身業(yè)務(wù)模塊為基礎(chǔ),把可以抽象出來(lái)的外部依賴、外部交互都集中到此組件的代碼中.
方便開發(fā)人員在進(jìn)行各個(gè)頁(yè)面間邏輯編寫、問(wèn)題排查時(shí),可根據(jù)具體頁(yè)面+數(shù)據(jù)源,準(zhǔn)確定位到具體的代碼.
2. reducer
在以往的項(xiàng)目中,reducer中可能會(huì)涉及部分?jǐn)?shù)據(jù)處理、用戶行為、日志埋點(diǎn)、頁(yè)面跳轉(zhuǎn)等等代碼邏輯.
因?yàn)樵陂_發(fā)人員寫代碼的過(guò)程中,發(fā)現(xiàn)reducer作為某個(gè)處理邏輯的終點(diǎn)(更新了state之后,此次事件即為結(jié)束),很適合去做這些事情.
隨著項(xiàng)目的維護(hù),需求的迭代,reducer的體積不斷的增大.
因?yàn)槿狈l理,代碼量又龐大,再想去對(duì)代碼進(jìn)行調(diào)整,只會(huì)困難重重.
讓你去維護(hù)這樣的一個(gè)項(xiàng)目,可想而知,將會(huì)是多么的痛苦.
為此,對(duì)reducer中的代碼進(jìn)行了一些減法:
- reducer中只對(duì)state的數(shù)據(jù)進(jìn)行修改
- 使用immer的produce產(chǎn)生immutable data
- 冗余單獨(dú)字段的修改,進(jìn)行整合,枚舉出頁(yè)面行為對(duì)應(yīng)的action
reducer的主要作用
以可枚舉的形式,匯總出頁(yè)面中所有操作數(shù)據(jù)的場(chǎng)景.
在其本身適用于react框架的特性之外,賦予一定的業(yè)務(wù)邏輯閱讀屬性,在不依賴UI組件的情況下,可大致閱讀出頁(yè)面中的所有數(shù)據(jù)處理邏輯.
// 避免dispatch時(shí)進(jìn)行兩次,且定義過(guò)多單字段的更新case // 整合此邏輯后,與頁(yè)面上的行為相關(guān)聯(lián),利于理解、閱讀 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; });
3. handler
這里先引入一個(gè)純函數(shù)的概念:
一個(gè)函數(shù)的返回結(jié)果只依賴于它的參數(shù),并且在執(zhí)行過(guò)程里面沒(méi)有副作用,我們就把這個(gè)函數(shù)叫做純函數(shù).
把盡可能多的邏輯抽象為純函數(shù),然后放入handler中:
- 涵蓋較多的業(yè)務(wù)邏輯
- 只能是純函數(shù)
- 必須進(jìn)行UT覆蓋
handler的主要作用
負(fù)責(zé)數(shù)據(jù)源到store、container到component、dispatch到reducer等等場(chǎng)景下的邏輯處理.
作為各類場(chǎng)景下,邏輯處理函數(shù)的存放地,整個(gè)文件不涉及頁(yè)面流程上的關(guān)聯(lián)關(guān)系,每個(gè)函數(shù)只要滿足其輸入與輸出的使用場(chǎng)景,即可復(fù)用,多用于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); }); });
4. Container
由上面的項(xiàng)目結(jié)構(gòu)圖可以看出,每個(gè)Page都有base Container,作為數(shù)據(jù)處理的中心.
在此base Container之下,會(huì)根據(jù)不同模塊,定義出各個(gè)子Container:
- 生命周期處理(初始化時(shí)要進(jìn)行的一些異步操作)
- 為渲染組件Components提供數(shù)據(jù)源
- 定義頁(yè)面中的行為函數(shù)
Container的主要作用
整個(gè)項(xiàng)目中,各種數(shù)據(jù)、UI、用戶行為的匯合點(diǎn),要盡可能的把相關(guān)的模塊抽離出來(lái),避免造成代碼量過(guò)大,難以維護(hù)的情況.
Container的定義應(yīng)以頁(yè)面展示的模塊進(jìn)行抽象.如Head Contianer、Content Container、Footer Container等較為常見(jiàn)的劃分方式.
一些頁(yè)面中相對(duì)獨(dú)立的模塊,也應(yīng)該產(chǎn)出其對(duì)應(yīng)的Container,來(lái)內(nèi)聚相關(guān)邏輯,如贈(zèng)送優(yōu)惠券模塊、用戶反饋模塊等.
特別注意的是行為函數(shù)
- 多個(gè)Container中公用的行為,可直接放入base Container中
- 在上文架構(gòu)圖中的action事例(setAction)為另外一種行為復(fù)用,根據(jù)具體的場(chǎng)景進(jìn)行應(yīng)用
利于代碼閱讀,A模塊的浮層展示邏輯,B模塊使用時(shí)
模塊產(chǎn)生的先后順序,先有A模塊再有B模塊需要使用A的方法
- 定義數(shù)據(jù)埋點(diǎn)、用戶行為埋點(diǎn)
- 頁(yè)面跳轉(zhuǎn)方法的調(diào)用(Page-->base Container-->子Container)
- 其他副作用的行為
const OWFlightListContainer = () => { // 通過(guò)Context獲取數(shù)據(jù) const { state, dispatch } = useContext(OWFlightListContext); ... // 初次加載時(shí)進(jìn)行超時(shí)的倒計(jì)時(shí) useOnce(overTimeCountDown); ... // 用戶點(diǎn)擊排序 const onPressSort = (lastSortType: EFlightSortType, isTimeSort: boolean) => { // 引用了handler中的getNextSortType函數(shù) const sortType = getNextSortType(lastSortType, isTimeSort); dispatch({ type: EOWFlightListAction.updateSortType, payload: sortType }); // 埋點(diǎn)操作 logSort(state, sortType); }; // 渲染展示組件 return <.../>; }
小結(jié)
由easy to code到easy to read
在整個(gè)項(xiàng)目中,定義了很多規(guī)范,是想在功能的實(shí)現(xiàn)之上,更利于項(xiàng)目人員的維護(hù).
- Page組件中包含頁(yè)面相關(guān)的外部依賴
- reducer枚舉出所有對(duì)頁(yè)面數(shù)據(jù)操作的事件
- handler中集合了業(yè)務(wù)邏輯的處理,以純函數(shù)的實(shí)現(xiàn)及UT的覆蓋,確保項(xiàng)目質(zhì)量
- Container中的行為函數(shù),定義出所有與用戶操作相關(guān)的事件,并記錄埋點(diǎn)數(shù)據(jù)
- Componet中避免出現(xiàn)業(yè)務(wù)邏輯的處理,只進(jìn)行UI展示,減少UI自動(dòng)化case,增加UT的case
規(guī)范的定義是比較容易的,想要維護(hù)好一個(gè)項(xiàng)目,更多的是依靠團(tuán)隊(duì)的成員,在達(dá)成共識(shí)的前提下,持之以恒的堅(jiān)持了
分享幾個(gè)實(shí)用的函數(shù)
根據(jù)對(duì)象路徑取值
/** * 根據(jù)對(duì)象路徑取值 * @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);
讀取根據(jù)配置信息
// 在與外部對(duì)接時(shí),經(jīng)常會(huì)定義一些固定結(jié)構(gòu),可擴(kuò)展性的數(shù)據(jù)列表 // 為了適應(yīng)此類契約,便于更好的閱讀與維護(hù),總結(jié)出了以下函數(shù) export const GLOBAL_NOTE_CONFIG = { 2: 'refund', 3: 'sortType', 4: 'featureSwitch' }; /** * 根據(jù)配置,獲取attrList中的值,返回json對(duì)象類型的數(shù)據(jù) * @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]; // 有多個(gè)值時(shí),改為數(shù)組類型 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 // 適用于外部定義的一些可擴(kuò)展note節(jié)點(diǎn)列表的取值邏輯 const { sortType, featureSwitch } = getNoteValue(list, GLOBAL_NOTE_CONFIG, 'ntype');
多條件數(shù)組排序
/** * 獲取用于排序的sort函數(shù) * @param fn 同類型元素比較函數(shù),true為排序優(yōu)先 */ 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; }); //判斷條件先后順序可調(diào)整 const arr = [nameSort, ageSort, sexSort]; const ret = data.sort(getMultipleSort(arr));
以上就是React Native項(xiàng)目框架搭建的一些心得體會(huì)的詳細(xì)內(nèi)容,更多關(guān)于React Native項(xiàng)目框架搭建的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解React開發(fā)中使用require.ensure()按需加載ES6組件
本篇文章主要介紹了詳解React開發(fā)中使用require.ensure()按需加載ES6組件,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05React項(xiàng)目配置prettier和eslint的方法
這篇文章主要介紹了React項(xiàng)目配置prettier和eslint的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06React?Hook?Form?優(yōu)雅處理表單使用指南
這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03如何利用React實(shí)現(xiàn)圖片識(shí)別App
圖片識(shí)別這個(gè)功能在很多app中都有,下面這篇文章主要給大家介紹了關(guān)于如何利用React實(shí)現(xiàn)圖片識(shí)別App的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01React為什么需要Scheduler調(diào)度器原理詳解
這篇文章主要為大家介紹了React為什么需要Scheduler調(diào)度器原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10