写点什么

ReactNative 进阶(三十五):应用脚手架 Yo 构建 RN 页面

  • 2022 年 1 月 27 日
  • 本文字数:5354 字

    阅读完需:约 18 分钟

ReactNative进阶(三十五):应用脚手架 Yo 构建 RN 页面

一、前言

前期将脚手架yo安装成功,本篇博文主要讲解如何利用yo提供的代码自动生成功能生成项目代码。

二、Bloc 数据流讲解

Bloc 数据流工具安装:


sudo npm install -g yosudo npm install -g generator-bloc
复制代码


安装完成后通过执行 npm ls generator-bloc -g 命令,查看bloc模板生成位置。执行结果如下:



进入node_modules目录,可看到生成的generator-bloc文件夹,进入该文件夹,查看README.md文件。


文件内容如下:



> # generator-bloc [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency> Status][daviddm-image]][daviddm-url] [![Coverage> percentage][coveralls-image]][coveralls-url]> > react-bloc cli> > ## Installation> > First, install [Yeoman](http://yeoman.io) and generator-bloc using> [npm](https://www.npmjs.com/) (we assume you have pre-installed> [node.js](https://nodejs.org/)).> > ```bash npm install -g yo npm install -g generator-bloc ```> > Then generate your new project:> > ```bash yo bloc ```> > ## Getting To Know Yeoman> > * Yeoman has a heart of gold. > * Yeoman is a person with feelings and opinions, but is very easy to work with. > * Yeoman can be too opinionated at times but is easily convinced not to be. > * Feel free to [learn more about Yeoman](http://yeoman.io/).> > ## License> > MIT © [MeePwn](https://github.com/maybewaityou)> > > [npm-image]: https://badge.fury.io/js/generator-bloc.svg [npm-url]:> https://npmjs.org/package/generator-bloc [travis-image]:> https://travis-ci.org/maybewaityou/generator-bloc.svg?branch=master> [travis-url]: https://travis-ci.org/maybewaityou/generator-bloc> [daviddm-image]:> https://david-dm.org/maybewaityou/generator-bloc.svg?theme=shields.io> [daviddm-url]: https://david-dm.org/maybewaityou/generator-bloc> [coveralls-image]:> https://coveralls.io/repos/maybewaityou/generator-bloc/badge.svg> [coveralls-url]: https://coveralls.io/r/maybewaityou/generator-bloc
复制代码

三、利用代码自动生成功能创建新页面

此处以新建 test 界面为例介绍使用 bloc 创建界面的方法步骤,页面新建在项目中 page-new 目录下。


  1. 进入项目根目录输入命令:yo bloc 回车;

  2. 弹出选择界面后使用上下移动键选择: create a new page. 回车;

  3. 输入页面名字:test 回车;

  4. 选择语言:js 回车;

  5. 输入模块路径:src/page-new 回车;

  6. 选择新建页面所在的模块:home 回车;

  7. 选择平台:react-native 回车;

  8. 待页面创建完成,未报错则说明页面创建成功;


通过以上步骤信息结合项目结构可知,


  • 脚手架 yo 为我们新生成了home/view/TestView.js、 home/bloc/TestBloc.js、home/bloc/interactor/TestUserCase.js、home/bloc/data/source/TestRepository.js、home/bloc/data/source/local/TestLocalDataSource.js、home/bloc/data/source/remote/TestRemoteDataSource.js等文件。

  • home/bloc/ioc/types.js文件中新增 bloc 标识:


export const DATA_SOURCE_TYPES = {  HomeRemoteDataSource: Symbol.for('HomeRemoteDataSource'),  HomeLocalDataSource: Symbol.for('HomeLocalDataSource'),
TestRemoteDataSource: Symbol.for('TestRemoteDataSource'), TestLocalDataSource: Symbol.for('TestLocalDataSource'),};
复制代码


export const USE_CASE_TYPES = {  HomeUseCase: Symbol.for('HomeUseCase'),  TestUseCase: Symbol.for('TestUseCase'),};
复制代码


export const REPOSITORY_TYPES = {  HomeRepository: Symbol.for('HomeRepository'),  TestRepository: Symbol.for('TestRepository'),};
复制代码


export const BLOC_TYPES = {  HomeBloc: Symbol.for('HomeBloc'),  TestBloc: Symbol.for('TestBloc'),};
复制代码


  • bloc/ioc/register.js文件中新增容器配置:


import TestBloc from '../TestBloc';import TestUseCase from '../interactor/TestUseCase';import TestRepository from '../data/source/TestRepository';import { TestRemoteDataSource } from '../data/source/remote/TestRemoteDataSource';import { TestLocalDataSource } from '../data/source/local/TestLocalDataSource';
container.bind(DATA_SOURCE_TYPES.TestRemoteDataSource).to(TestRemoteDataSource);container.bind(DATA_SOURCE_TYPES.TestLocalDataSource).to(TestLocalDataSource);container.bind(REPOSITORY_TYPES.TestRepository).to(TestRepository);container.bind(USE_CASE_TYPES.TestUseCase).to(TestUseCase);container.bind(BLOC_TYPES.TestBloc).to(TestBloc);
复制代码


  • home/route-config/HomeRouteConfig.js将新建页面添加到路由中;


将 TestView 添加到export中即可使用router.navigate(“TestView”)跳转到该页面。


注:本项目路由使用routerimport { router } from 'mario-navigation-extension';

四、Bloc 数据流使用说明


bloc数据流采用stream-builder的形式绑定数据,当stream绑定的数据有改变时,builder便会重新渲染界面,类似于RNsetState,下面以刚创建的 TestView 为例介绍使用步骤:


  1. 首先,该页面的入口文件为src/page-new/view/TestView.js, 该界面只写view渲染相关代码,export default ( ) ... 方法即为页面入口,该方法可接收路由传递的参数,可在此方法完成参数初始化,如果该页面需要在未被销毁的情形下多次加载,可以使用try catch 初始化,container.get(BLOC_TYPES.TestBloc)方法会完成bloc的唯一注册,只会在第一次初始化时渲染界面,重新渲染需要使用BlocProvider.of(BLOC_TYPES.TestBloc),因此多次加载同一界面可以使用如下代码:


export default (props) => {  let _bloc = null;  try {    _bloc = BlocProvider.of(BLOC_TYPES.TestBloc);  } catch (error) {    _bloc = container.get(BLOC_TYPES.TestBloc);  }  const params = props.navigation.state.params;  _bloc.init(params);  return <BlocProvider bloc={_bloc} child={_viewBuilder} />;};
复制代码


其中 init 方法在 TestBloc 中定义。


  1. 接下来便是_viewBuilder中的界面 UI:


function _viewBuilder() {  const _bloc = BlocProvider.of(BLOC_TYPES.TestBloc);  return (    <View style={style.container}>      <StreamBuilder stream={_bloc.viewState$} builder={_testBuilder} />      <StreamBuilder stream={_bloc.listData$} builder={_listBuilder} />    </View>  );}
复制代码


其中stream为 TestBloc 中定义的数据,builder为界面UIbuilder会获取绑定数据的快照snapshotsnapshot.data即为绑定的数据。


function _testBuilder(snapshot){  if(snapshot.hasData){    return (    <Text>{JSON.stringify(snapshot.data)}</Text>    )  } else   return (    <View></View>  )}
复制代码


stream可以绑定多个数据流:


<StreamBuilder stream={_bloc.listData$.pipe(combineLatest(_bloc.searchObj$))} builder={_userListBuilder} />
复制代码


  1. 页面数据定义在 src/page-new/home/bloc/TestBloc.js中,


listData = {    viewState: {      isRefresh: false,      isLoading: true,      hasMore: true,      hint: '加载中...',      pageNo: '1',      pageSize: '15',    },    list: [],    data: {},  };  listData$ = new EnhanceSubject(this.listData);
viewState = { open: false, pickerItems: [{ text: '待发货', handleFlag: '1' }, { text: '已发货', handleFlag: '2' }], pickedItem: { text: '待发货', handleFlag: '1', }, totalSize: 0, }; viewState$ = new EnhanceSubject(this.viewState);
复制代码


listData$即为 TestView 中绑定的数据,当listData$改变时界面就会重新渲染,例如:


init = (params) => {    this.listData= processModify(this.listData, {      viewState: {        isRefresh: params.isRefresh,        isLoading: params.isLoading,        hint: '加载中...'      },      });    this.listData$.add(this.listData); // 相当于RN的setState  };
复制代码


!注:定义在 TestBloc 中的方法建议使用箭头函数,防止this指向跑偏。


  1. 网络请求使用封装好的 WebService: src/main/service/WebService.js,WebService 的 BaseUrl 为:src/main/constant/Constant.jsBASE_URL,请求参数有个meta数据,该meta数据会在真正请求前删除,使用meta: { silence: true }可以在请求时不展示Loading框,默认会加载Loading框。


bloc中定义异步接口方法,


queryList = async (refresh, handleFlag) => {    let index = refresh ? '1' : this.listData.viewState.pageNo;    this.listData = processModify(this.listData, {      viewState: {        ...this.listData.viewState,        isRefresh: refresh,        isLoading: !refresh,        hint: '加载中...'      },    });    this.listData$.add(this.listData); // 发起请求时刷新界面状态  // 使用userCase调用接口,返回的是两个对象,第一个为接口报错(非业务逻辑错)时的对象,第二个为接口正常返回的对象    const [error, data] = await this.useCase.execute({      bizline: '4',      tasktyp: '25',      handleFlag: handleFlag || this.viewState.pickedItem.handleFlag,      pageNo: index,      pageSize: this.listData.viewState.pageSize,      meta: { silence: true },    });    if (error) {      ...      this.listData$.add(this.listData); // 接口报错时更新界面UI      return;    }    let hasList = this.listData.list || [];    if (refresh) {      hasList = [];    }    let list = data.resultList;    let isMore = list.length >= parseInt(this.listData.viewState.pageSize);    let hint = '';    if (hasList.length < 1 && list.length < 1) {      hint = '抱歉~没有相关信息';    }    this.listData = processModify(this.listData, {      list: hasList.concat(list),      data,      viewState: {        ...this.listData.viewState,        isRefresh: false,        isLoading: false,        hasMore: isMore,        pageNo: `${parseInt(index) + 1}`,        hint: hint || (isMore ? '加载更多' : '没有更多了'),      }    });    this.listData$.add(this.listData); // 请求得到后台数据后刷新界面数据    this.viewState = processModify(this.viewState, {      totalSize: data.totalSize,    });    this.viewState$.add(this.viewState); // 刷新界面状态  };
复制代码


添加:


buildUseCasePromise(params) {    return this.repository.queryList(params);}
复制代码


添加接口参数,也就是this.useCase.execute传过来的参数,userCase 的excute方法执行的是buildUseCasePromise方法,如果是其它方法需要自定义:


  handleCheck(params) {    return to(this.repository.handleCheck(params));  }
复制代码


此时需要将结果用to包裹,以规范返回数据的格式:


import { to } from '../../../../main/utilities';
复制代码


调用自定义方法时,需要将 bloc 里的调用方法由this.useCase.execute({})改为this.useCase.handleCheck({});


然后定义数据流策略,使用远程服务器数据还是本地数据库数据(定义在src/page-new/home/bloc/data/source/TestRepository.js:):


queryList(params) {    return this.remoteDataSource.queryList(params);}
复制代码


在该处可以进行数据的并行或者串行请求或者数据的其它一些处理:


async queryList(params) {    const data = await this.remoteDataSource.queryList({ meta: { silence: true } });    if (data.list.length > 0) {      let customeridStr = '';      data.list.forEach((item) => {        customeridStr += item.customerid + ',';      });    // 串行接口      const signalData = await this.remoteDataSource.queryNewsTheme(        { customeridStr, flag: 'index', ...params, meta: { silence: true } });      let listIndex = []      for (let i = 0; i < signalData.listIndex.length; i++) {    ...      }      return { totalSize: signalData.totalSize, listIndex };    } else {      return { 'listIndex': [] }    }  }
复制代码


最后就是调用 webService 请求后台接口:


queryList(params) {    return this.webService.request(queryTaskPageList, params); }
复制代码


其中 params 是传过来的,queryTaskPageList 为接口名称,需要自定义:


const queryTaskPageList = 'queryTaskPageList'; 
复制代码


数据流如下图所示:


五、拓展阅读

发布于: 36 分钟前阅读数: 6
用户头像

No Silver Bullet 2021.07.09 加入

岂曰无衣 与子同袍

评论

发布
暂无评论
ReactNative进阶(三十五):应用脚手架 Yo 构建 RN 页面