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 相关文章推荐
js 事件处理函数间的Event物件是否全等
Apr 08 Javascript
Jquery 绑定时间实现代码
May 03 Javascript
js表格排序实例分析(支持int,float,date,string四种数据类型)
May 06 Javascript
jquery实现漫天雪花飞舞的圣诞祝福雪花效果代码分享
Aug 20 Javascript
Query常用DIV操作获取和设置长度宽度的实现方法
Sep 19 Javascript
axios基本入门用法教程
Mar 25 Javascript
Swiper实现轮播图效果
Jul 03 Javascript
关于vue.extend和vue.component的区别浅析
Aug 16 Javascript
vue 项目如何引入微信sdk接口的方法
Dec 18 Javascript
浅谈JS中几种轻松处理'this'指向方式
Sep 16 Javascript
浅谈Vue为什么不能检测数组变动
Oct 14 Javascript
JS PHP字符串截取函数实现原理解析
Aug 29 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
Mysql和网页显示乱码解决方法集锦
2008/03/27 PHP
php格式化金额函数分享
2015/02/02 PHP
thinkPHP框架动态配置用法实例分析
2018/06/14 PHP
“不能执行已释放的Script代码”错误的原因及解决办法
2007/09/09 Javascript
jQuery 过滤方法filter()选择具有特殊属性的元素
2014/06/15 Javascript
用jquery的方法制作一个简单的导航栏
2014/06/23 Javascript
jQuery通过扩展实现抖动效果的方法
2015/03/11 Javascript
BootStrap点击下拉菜单项后显示一个新的输入框实现代码
2016/05/16 Javascript
基于BootStrap实现局部刷新分页实例代码
2016/08/08 Javascript
HTML中setCapture、releaseCapture 使用方法浅析
2016/09/25 Javascript
jQuery插件HighCharts绘制2D柱状图、折线图的组合双轴图效果示例【附demo源码下载】
2017/03/09 Javascript
JS检测window.open打开的窗口是否关闭
2017/06/25 Javascript
vue 实现 tomato timer(蕃茄钟)实例讲解
2017/07/24 Javascript
浅析从vue源码看观察者模式
2018/01/29 Javascript
Angular封装搜索框组件操作示例
2019/04/25 Javascript
JavaScript实现单图片上传并预览功能
2019/09/30 Javascript
vue实现多个echarts根据屏幕大小变化而变化实例
2020/07/19 Javascript
vue实现路由懒加载的3种方法示例
2020/09/01 Javascript
在vue中实现某一些路由页面隐藏导航栏的功能操作
2020/09/21 Javascript
零基础写python爬虫之爬虫编写全记录
2014/11/06 Python
用Python实现通过哈希算法检测图片重复的教程
2015/04/02 Python
python实现整数的二进制循环移位
2019/03/08 Python
Ubuntu+python将nii图像保存成png格式
2019/07/18 Python
Flask框架学习笔记之使用Flask实现表单开发详解
2019/08/12 Python
pycharm双击无响应(打不开问题解决办法)
2020/01/10 Python
详解Python中openpyxl模块基本用法
2021/02/23 Python
CSS3 flex布局之快速实现BorderLayout布局
2015/12/03 HTML / CSS
CSS3中的Transition过度与Animation动画属性使用要点
2016/05/20 HTML / CSS
德国亚洲食品网上商店:asiafoodland.de
2019/12/28 全球购物
英国网上电器商店:Electricshop
2020/03/15 全球购物
土建施工员岗位职责
2014/07/16 职场文书
2014年科研工作总结
2014/12/03 职场文书
明确岗位职责
2015/02/14 职场文书
创业计划书之餐饮
2019/09/02 职场文书
七年级写作指导之游记作文
2019/10/07 职场文书
redis 限制内存使用大小的实现
2021/05/08 Redis