SkillAgentSearch skills...

RNProjectPlayground

🍨React Native 相关,涉及 MobX、MST使用,原生简易导航模块、列表组件封装,一些动画尝试,以及 HOC 应用。

Install / Use

/learn @ljunb/RNProjectPlayground
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

目录

概览

这是一个自己随意玩耍的仓库,主要涉及的东西有以下几部分:

  • 基于 MobXMST 重新实现了 React Native 版食物派的个别页面
  • 通过 UINavigationController 和 Activity ,实现导航功能:push、pop、popTo、popToRoot。每个页面,React Native 都只作为 View 的角色存在
  • 收集一些自己的练习 Demo、组件,或是项目实践中的想法

导航功能

口袋蜜蜂(AppStore | 小米应用商店)是混编 App,在项目启动的前期,跟同事一起尝试了原生与 React Native 页面之间的各种导航场景,在此过程中也尝试了不同的几个 React Native 导航组件,略去其中细节,一番尝试后,回过头来想:既然是原生为主导,为何不就地取材,直接用原生的导航功能?React Native 本来就应该只承担 View 层的角色,数据的流转,实际仍是在原生层面。

因此,每次跳转的起始或最终界面,不管是原生,还是 React Native 页面,实际上都是原生到原生的导航。React Native 可通过注册多个 Component 的形式来加载多个页面,而口袋在几个版本的迭代下来之后,我们总结了较为推荐的方式是:

  • 共同:只注册一个 Component,不同页面在初始参数中添加标识位区分
  • iOS:采用单例 RCTBridge,并通过 - initWithBridge: moduleName: initialProperties: 的方式来创建 RCTRootView,然后在 initialProperties 这个初始化参数字典中,传入页面标识位和其他必要数据。
  • Android:通过一个 ReactActivityManager 来模拟 Activity 栈的管理,可以实现与 iOS 一样的 popTo 功能。在传递 Bundle 数据的时候,需注意的是 MapBundle 的转换处理。因为在 React Native 端调用 push(pageName, params) 时,带参情况传入的 params 为字典,映射到原生端的 MapBundle 对象存入数据时需按对应类型来进行获取。

在这个模式中,不同 React Native 页面之间的通知事件可正常使用,也可以按需在项目中集成 Redux 或是 MobX。口袋中集成了 MobX,类似代码在 App.js 文件中:

// App.js
import { Provider } from 'mobx-react';
import Router from './src/routers';
import stores from './src/stores';

export default (props) => {
  const { pageName: routerKey } = props;
  const Page = Router[routerKey].default;
  return (
    <Provider {...stores}>
      <Page {...props} />
    </Provider>
  );
};

store 的注入与普通的纯 React Native 项目一致,在相关页面通过 inject 按需检出子树即可。Router 是路由配置,页面标识位和页面文件路径是字典中 keyvalue 的关系:

// routers.js

export default {
  'main_tab': require('./pages'),
  'home': require('./pages/home'),
  'search': require('./pages/home/Search'),
  ...
}

所以只要在 routers 中配置好关系,通过 propspageName,即可匹配到不同的 React Native 页面。

↑ 返回顶部

与JavaScript的事件交互

既然是混编的 App ,那就免不了原生与 JavaScript 之间的事件交互。为了更方便地进行两端的发布&订阅,封装一个 CJNotification 的工具类。从 JavaScript 到原生端这一块的交互,当前工具类是不提供相关方法的,只是处理原生到 JavaScript 和 不同 React Native 页面之间的事件发布。工具类概览:

import {
  NativeEventEmitter,
  NativeModules,
  Platform,
  DeviceEventEmitter,
} from 'react-native';

const { CJNotificationCenter } = NativeModules;
const emitter = Platform.OS === 'android' ? new NativeEventEmitter() : new NativeEventEmitter(CJNotificationCenter);
const NativeEventName = 'NATIVE_TO_RN';

class Emitter {
  /**
   * 监听从 Native 发来的事件
   * @param event 事件名称
   * @param callback 监听回调
   * @function dispose 销毁监听对象
   */
  static addNativeListener = (event, callback) => {
    const subscription = emitter.addListener(
      NativeEventName,
      reminder => {
        const { eventName, body } = reminder;
        if (eventName !== event) return;
        callback && callback(body);
      },
    );
    subscription.dispose = () => subscription && subscription.remove();
    return subscription;
  };

  /**
   * 监听不同 RN 页面的通知事件
   * @param event 事件名称
   * @param callback 监听回调
   * @function dispose 销毁监听对象
   */
  static addRNListener = (event, callback) => {
    const subscription = DeviceEventEmitter.addListener(
      event,
      reminder => callback && callback(reminder),
    );
    subscription.dispose = () => subscription && subscription.remove();
    return subscription;
  };

  /**
   * 发送 RN 页面之间的通知
   * @param event 事件名称
   * @param body 发送内容
   */
  static sendRNEvent = (event, body) => DeviceEventEmitter.emit(event, body);
}

export default Emitter;

这里是原生端 iOSAndroid 的对应实现。

使用方式示例:

import CJNotification from '../utils/CJNotification';

export default class TestPage extends Component {
  componentDidMount() {
    this.addNativeListener();
    this.addRNListener();
  }

  addNativeListener = () => {
    this.nativeListener = CJNotification.addNativeListener('updateUserInfo', userInfo => {
      const { name } = userInfo;
      // todo sth
    });
  };

  addRNListener = () => {
    this.rnListener = CJNotification.addRNListener('updateFeedList', () => {
      // todo sth
    });
  };

  componentWillUnmount() {
    this.nativeListener.dispose();
    this.rnListener.dispose();
  }

  render() {
    ...
  }
}

iOS 端原生发送事件示例:

#import "CJNotificationCenter.h"

// 发送事件到 JavaScript
[[CJNotificationCenter center] sendRNEventWithName:@"updateUserInfo" body:@{@"name": @"cookiej"}];

Android 端:

// 发送事件到 JavaScript
WritableMap body = Arguments.createMap();
body.putString("name", "cookiej");
CJNotification.sendRNEvent("updateUserInfo", body);

不同 React Naitve 页面之间:

import CJNotification from '../utils/CJNotification';

export default class OtherPage extends Component {

  handleUpdateTestPageFeedList = () => CJNotification.sendRNEvent("updateFeedList");

  render() {
    ...
  }
}

↑ 返回顶部

Demo目录

这里主要是一些平时在有意无意中看到一些效果时,而做的 Demo 实践。没有一一罗列,更多的 Demo 可 clone 项目到本地查看。

类朋友圈查看图片

该效果 是类朋友圈查看图片效果的尝试,不过页码切换有所不一样,支持设置形变动画。运行示例: demo

新手引导装饰器

DemoDecorator 的简易应用,主要是实现一个快速为 React Native App 添加新手引导遮盖的需求,方便快捷易使用,相应组件地址

浮动文本动画输入框

效果 其实是属于 Google 的 Material 系列中的交互效果,上周有简单玩了下 Flutter ,发现里面的输入框组件,就是默认这种交互效果。而 React Native 相关的,其实网上也有类似组件,这里是自己看到效果后,做个简易版实现。运行示例:

demo

类Path菜单动画

Demo 是仿 Path 的菜单动画效果:

demo

常见支付密码输入框

Demo 是与支付宝类似的密码输入框:

demo

类WhatsApp转场动画

Demo 是自己在偶然之中,发现一位国外开发者的 仓库,里面是参考 UI Movement 上的动画而做的 React Native 实现,自己看完也是跃跃欲试,所以写了这个动画 Demo。运行示例:

demo

↑ 返回顶部

带索引SectionList

口袋项目中有一个选择汽车的分组列表,在指压并滑动索引时会有动画,项目启动时评估过 React Native 实现的性能问题,最终还是选择了原生实现。恰巧早上写完了家居的业务功能,想着用纯 React Native 来实现这个列表:

demo

iOS 在模拟器上的效果如上所示,JavaScript 线程掉帧还是挺严重的,UI FPS 看起来倒是正常,实际滑动起来表现并不卡。Android 端在模拟器上表现一般般,没有在真机中测试,并且还需要处理 overflow 的问题,所以到时布局还需根据平台做适配处理。

其实之前用官方自带的 SectionList 实现过这个模块,但是效果挺差的,分组跨度较大时,点击索引滚动时会出现白屏(只在 iOS 模拟器下调试,Android 没做进一步尝试)。当前 Demo 基于 react-native-largelist 实现(自己只在该示例中使用了该组件,并未集成到商业项目中)。

↑ 返回顶部

粘性TabBar

可能存在于某些商城类 App 中,在页面滚动至顶部时,分段菜单停留在导航栏底部,表现为粘性效果,并可点击菜单项滚动到对应的分组。在 iPhone 效果如下:

demo

不过比较意外的是,iPhone 上运行时,滑动过程中设置了粘性的子组件老是会跳动,Android 反而表现良好……当前 Demo 没有集成下拉刷新,可能仍需基于某些第三方来做定制。

↑ 返回顶部

轮播图动画指示器

暂时做了流动样式,后面考虑再做个 scale 渐变样式: demo

↑ 返回顶部

组件

PullRefreshListView

PullRefreshListView 是对 react-native-smart-pull-to-refresh-listview 的二次封装,可自定义下拉刷新、上拖加载更多的样式,也添加了空列表、数据加载出错时(分有数据和无数据)的样式定制,更适用于商业项目使用。简单使用示例:


import PullRefreshListView from './PullRefreshListView';

export default class MsgList extends Component {
  pageNo = 1;
  msgList = [];

  componentDidMount() {
    this.listView && this.listView.beginRefresh();
  }
  
  fetchMsgList = async() => {
    try {
      const responseData = await fetch(url).then(res => res.json());
      
      const result = this.pageNo === 1 ? [...responseData.list] : [...this.msgList, ...responseData.list];
      this.msgList = result;
      const isLoadAll = this.msgList.length >= responseData.total;
      this.listView && this.listView.setData(result, this.pageNo, isLoadAll);
    } catch (e) { 
      this.listView 
View on GitHub
GitHub Stars95
CategoryDevelopment
Updated20d ago
Forks23

Languages

JavaScript

Security Score

85/100

Audited on Mar 17, 2026

No findings