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 相关文章推荐
用JavaScript隐藏控件的方法
Sep 21 Javascript
jquery Mobile入门—多页面切换示例学习
Jan 08 Javascript
Javascript中常见的校验如域名、手机、邮箱等等
Jan 02 Javascript
js控制浏览器全屏示例代码
Feb 20 Javascript
Extjs表单常见验证小结
Mar 07 Javascript
一个css与js结合的下拉菜单支持主流浏览器
Oct 08 Javascript
JS代码实现根据时间变换页面背景效果
Jun 16 Javascript
浅谈html转义及防止javascript注入攻击的方法
Dec 04 Javascript
使用微信小程序开发前端【快速入门】
Dec 05 Javascript
DropDownList控件绑定数据源的三种方法
Dec 24 Javascript
在Vue组件中获取全局的点击事件方法
Sep 06 Javascript
react-native滑动吸顶效果的实现过程
Jun 03 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
咖啡知识 咖啡养豆要养多久 排气又是什么
2021/03/06 新手入门
joomla内置的表单验证功能使用方法
2010/06/11 PHP
js身份证验证超强脚本
2008/10/26 Javascript
初窥JQuery(二) 事件机制(1)
2010/11/25 Javascript
javascript parseInt() 函数的进制转换注意细节
2013/01/08 Javascript
九种js弹出对话框的方法总结
2013/03/12 Javascript
JavaScript实现获取dom中class的方法
2015/02/09 Javascript
javascript转换静态图片,增加粒子动画效果
2015/05/28 Javascript
AngularJS使用ng-repeat和ng-if实现数据的删选显示效果示例【适用于表单数据的显示】
2016/12/13 Javascript
JavaScript实现大图轮播效果
2017/01/11 Javascript
JS中的Replace()传入函数时的用法详解
2017/09/11 Javascript
微信小程序数据存储与取值详解
2018/01/30 Javascript
浅谈Vue内置component组件的应用场景
2018/03/27 Javascript
详解vue-router传参的两种方式
2018/09/10 Javascript
详解Node.js读写中文内容文件操作
2018/10/10 Javascript
解决layer.confirm选择完之后消息框不消失的问题
2019/09/16 Javascript
webpack+vue.js构建前端工程化的详细教程
2020/05/10 Javascript
python实现搜索指定目录下文件及文件内搜索指定关键词的方法
2015/06/28 Python
python模拟事件触发机制详解
2018/01/19 Python
Opencv+Python 色彩通道拆分及合并的示例
2018/12/08 Python
Python中函数参数匹配模型详解
2019/06/09 Python
python实现串口自动触发工作的示例
2019/07/02 Python
python基于paramiko将文件上传到服务器代码实现
2019/07/08 Python
从0到1使用python开发一个半自动答题小程序的实现
2020/05/12 Python
PyTorch的torch.cat用法
2020/06/28 Python
Python 如何定义匿名或内联函数
2020/08/01 Python
pycharm配置安装autopep8自动规范代码的实现
2021/03/02 Python
Tory Burch美国官方网站:美国时尚生活品牌
2016/08/01 全球购物
老师推荐信
2013/10/28 职场文书
关于学习的演讲稿
2014/05/10 职场文书
自愿离婚协议书范本
2015/01/26 职场文书
2015个人年度工作总结范文
2015/05/28 职场文书
趣味运动会通讯稿
2015/07/18 职场文书
爱心捐赠活动简讯
2015/07/20 职场文书
《灰雀》教学反思
2016/02/19 职场文书
Python OpenCV实现图形检测示例详解
2022/04/08 Python