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 解析读取XML文档 实例代码
Jul 07 Javascript
javascript 的Document属性和方法集合
Jan 25 Javascript
在jquery中combobox多选的不兼容问题总结
Dec 24 Javascript
完美兼容IE,chrome,ff的设为首页、加入收藏及保存到桌面js代码
Dec 17 Javascript
jQuery实现复选框批量选择与反选的方法
Jun 17 Javascript
基于canvas实现的绚丽圆圈效果完整实例
Jan 26 Javascript
关于javascript原型的修改与重写(覆盖)差别详解
Aug 31 Javascript
Node.js学习入门
Jan 03 Javascript
React快速入门教程
Jan 17 Javascript
微信小程序 弹幕功能简单实例
Feb 14 Javascript
js实现日历与定时器
Feb 22 Javascript
JavaScript闭包与作用域链实例分析
Jan 21 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
最简单的jQuery程序 入门者学习
2009/07/09 Javascript
js保存当前路径(cookies记录)
2010/12/14 Javascript
jQuery大于号(&gt;)选择器的作用解释
2015/01/13 Javascript
jQuery选择器源码解读(七):elementMatcher函数
2015/03/31 Javascript
原生javascript实现匀速运动动画效果
2016/02/26 Javascript
JavaScript实现同一个页面打开多张图片
2016/12/29 Javascript
Vue.js学习示例分享
2017/02/05 Javascript
ES6新特性之Symbol类型用法分析
2017/03/31 Javascript
JavaScript实现移动端轮播效果
2017/06/06 Javascript
JavaScript禁用右键单击优缺点分析
2019/01/20 Javascript
浅谈JavaScript_DOM学习篇_图片切换小案例
2019/03/19 Javascript
vue路由对不同界面进行传参及跳转的总结
2019/04/20 Javascript
js定义类的方法示例【ES5与ES6】
2019/07/30 Javascript
详解element-ui级联菜单(城市三级联动菜单)和回显问题
2019/10/02 Javascript
Vue 禁用浏览器的前进后退操作
2020/09/04 Javascript
python实现的解析crontab配置文件代码
2014/06/30 Python
python使用urlparse分析网址中域名的方法
2015/04/15 Python
解决Python中字符串和数字拼接报错的方法
2016/10/23 Python
windows环境下tensorflow安装过程详解
2018/03/30 Python
Pandas 对Dataframe结构排序的实现方法
2018/04/10 Python
python 简单照相机调用系统摄像头实现方法 pygame
2018/08/03 Python
python钉钉机器人运维脚本监控实例
2019/02/20 Python
Python数据处理篇之Sympy系列(五)---解方程
2019/10/12 Python
Python超越函数积分运算以及绘图实现代码
2019/11/20 Python
Tensorflow进行多维矩阵的拆分与拼接实例
2020/02/07 Python
python简单的三元一次方程求解实例
2020/04/02 Python
Python+Dlib+Opencv实现人脸采集并表情判别功能的代码
2020/07/01 Python
Pretty You London官网:英国拖鞋和睡衣品牌
2019/05/08 全球购物
校长竞聘演讲稿
2014/05/16 职场文书
司机工作自我鉴定
2014/09/19 职场文书
专家推荐信怎么写
2015/03/25 职场文书
十月围城观后感
2015/06/08 职场文书
关于社会实践的心得体会(2016最新版)
2016/01/25 职场文书
解决Python字典查找报Keyerror的问题
2021/05/26 Python
Java循环队列与非循环队列的区别总结
2021/06/22 Java/Android
Java设计模式之享元模式示例详解
2022/03/03 Java/Android