React Native项目框架搭建的一些心得体会


Posted in Javascript onMay 28, 2021

React Native 是Facebook于2015年4月开源的跨平台移动应用开发框架, 短短的一两年的发展就已经有很多家公司支持并采用此框架来搭建公司的移动端的应用,
React Native使你能够在Javascript和React的基础上获得完全一致的开发体验,构建世界一流的原生APP。

项目框架与项目结构

1. 项目中使用的技术栈

react native、react hook、typescript、immer、tslint、jest等.

都是比较常见的,就不多做介绍了

2. 数据处理使用的是react hook中的useContext+useReducer

思想与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);
...

3. 项目的结构如下

|-page1
    |-handler   // 处理逻辑的纯函数,需进行UT覆盖
    |-container // 整合数据、行为与组件
    |-component // 纯UI组件,展示内容与用户交互,不处理业务逻辑
    |-store     // 数据结构不能超过3层,可使用外部引用、冗余字段的方式降低层级
    |-reducer   // 使用immer返回新的数据(immutable data)
    |-...
|-page2
|-...

项目中的规范

1. Page

整个项目做为一个多页应用,最基本的拆分单元是page.

每一个page有相应的store,并非整个项目使用一个store,这样做的原因如下:

  • 各个页面的逻辑相对独立
  • 各个页面都可作为项目入口
  • 结合RN页面生命周期进行数据处理(避免数据初始化、缓存等一系列问题)

各个页面中与外部相关的操作,都在Page组件中定义

  • 页面跳转逻辑
  • 回退之后要处理的事件
  • 需要操作哪些storage中的数据
  • 需要请求哪些服务等等

Page组件的主要作用

以其自身业务模块为基础,把可以抽象出来的外部依赖、外部交互都集中到此组件的代码中.

方便开发人员在进行各个页面间逻辑编写、问题排查时,可根据具体页面+数据源,准确定位到具体的代码.

2. reducer

在以往的项目中,reducer中可能会涉及部分数据处理、用户行为、日志埋点、页面跳转等等代码逻辑.

因为在开发人员写代码的过程中,发现reducer作为某个处理逻辑的终点(更新了state之后,此次事件即为结束),很适合去做这些事情.

随着项目的维护,需求的迭代,reducer的体积不断的增大.

因为缺乏条理,代码量又庞大,再想去对代码进行调整,只会困难重重.

让你去维护这样的一个项目,可想而知,将会是多么的痛苦.

为此,对reducer中的代码进行了一些减法:

  • reducer中只对state的数据进行修改
  • 使用immer的produce产生immutable data
  • 冗余单独字段的修改,进行整合,枚举出页面行为对应的action

reducer的主要作用

以可枚举的形式,汇总出页面中所有操作数据的场景.

在其本身适用于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;
    });

3. handler

这里先引入一个纯函数的概念:

一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数.

把尽可能多的逻辑抽象为纯函数,然后放入handler中:

  • 涵盖较多的业务逻辑
  • 只能是纯函数
  • 必须进行UT覆盖

handler的主要作用

负责数据源到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);
  });
});

4. Container

由上面的项目结构图可以看出,每个Page都有base Container,作为数据处理的中心.

在此base Container之下,会根据不同模块,定义出各个子Container:

  • 生命周期处理(初始化时要进行的一些异步操作)
  • 为渲染组件Components提供数据源
  • 定义页面中的行为函数
  •  

Container的主要作用

整个项目中,各种数据、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项目框架搭建的一些心得体会的详细内容,更多关于React Native项目框架搭建的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
IE8 引入跨站数据获取功能说明
Jul 22 Javascript
Ext JS Grid在IE6 下宽度的问题解决方法
Feb 15 Javascript
将光标定位于输入框最右侧实现代码
Dec 04 Javascript
深入理解JavaScript高级之词法作用域和作用域链
Dec 10 Javascript
JS对象转换为Jquery对象实现代码
Dec 29 Javascript
js库Modernizr的介绍和使用
May 07 Javascript
三分钟带你玩转jQuery.noConflict()
Feb 15 Javascript
JS实现全屏的四种写法
Dec 30 Javascript
vue+iview+less+echarts实战项目总结
Feb 22 Javascript
wx-charts 微信小程序图表插件的具体使用
Aug 18 Javascript
详解Vue中CSS样式穿透问题
Sep 12 Javascript
Openlayers实现地图的基本操作
Sep 28 Javascript
使用react-virtualized实现图片动态高度长列表的问题
May 28 #Javascript
element多个表单校验的实现
May 27 #Javascript
springboot+VUE实现登录注册
May 27 #Vue.js
vue+springboot实现登录验证码
vue+spring boot实现校验码功能
May 27 #Vue.js
vue-cropper组件实现图片切割上传
May 27 #Vue.js
vue-cropper插件实现图片截取上传组件封装
May 27 #Vue.js
You might like
怎样才能成为PHP高手?学会“懒惰”的编程
2006/12/05 PHP
php 上传功能实例代码
2010/04/13 PHP
PHP获取用户访问IP地址的5种方法
2016/05/16 PHP
php实现批量修改文件名称的方法
2016/07/23 PHP
laravel使用Faker数据填充的实现方法
2019/04/12 PHP
JavaScript中json使用自己总结
2013/08/13 Javascript
javascript中的document.open()方法使用介绍
2013/10/09 Javascript
使用jQuery异步加载 JavaScript脚本解决方案
2014/04/20 Javascript
jQuery产品间断向下滚动效果核心代码
2014/05/08 Javascript
js返回前一页刷新本页重载页面
2014/07/29 Javascript
ES6记录异步函数的执行时间详解
2016/08/31 Javascript
Bootstrap Table使用心得总结
2016/11/29 Javascript
ES6下React组件的写法示例代码
2017/05/04 Javascript
JavaScript中错误正确处理方式小结你用对了吗
2017/10/10 Javascript
打字效果动画的4种实现方法(超简单)
2017/10/18 Javascript
Vue实现表格批量审核功能实例代码
2019/05/28 Javascript
vue学习笔记之作用域插槽实例分析
2020/02/01 Javascript
Vue+Element ui 根据后台返回数据设置动态表头操作
2020/09/21 Javascript
[02:48]DOTA2英雄基础教程 拉席克
2013/12/12 DOTA
[00:43]FTP典藏礼包 DOTA2三大英雄霸气新套装
2014/03/21 DOTA
基于Python实现的扫雷游戏实例代码
2014/08/01 Python
python操作mongodb根据_id查询数据的实现方法
2015/05/20 Python
在Python中操作字典之setdefault()方法的使用
2015/05/21 Python
Django管理员账号和密码忘记的完美解决方法
2018/12/06 Python
Pytorch在dataloader类中设置shuffle的随机数种子方式
2020/01/14 Python
基于python实现把json数据转换成Excel表格
2020/05/07 Python
python 字符串格式化的示例
2020/09/21 Python
带你认识HTML5中的WebSocket
2015/05/22 HTML / CSS
Html5 video标签视频的最佳实践
2020/02/26 HTML / CSS
迪卡侬印尼体育用品商店:Decathlon印尼
2020/03/11 全球购物
广州一家公司的.NET面试题
2016/06/11 面试题
医学院毕业生自荐信范文
2014/03/06 职场文书
聚美优品励志广告词
2014/03/14 职场文书
教师考核评语
2014/04/28 职场文书
党员自我评议个人对照检查材料
2014/09/16 职场文书
5种方法告诉你如何使JavaScript 代码库更干净
2021/09/15 Javascript