您的位置:首页 > Web前端 > React

React Native NavigationExperimental

2016-06-30 14:36 351 查看

Overview

NavigationExperimental是react native的一个新的导航系统,重点是改进<Navigator/>组件. 本文章对应的react native 版本为0.23

单向数据流, 它使用reducers 来操作最顶层的state 对像,而在<Navigator/>中,当你在子导航页中,不可能操作到app最初打开页面时的state对像,除非,一级级的通过props传递过方法名或函数名,然后在子页面中调用这些方法或者函数,来修改某个顶层的数据。

为了允许存在本地和基于 js的导航视图,导航的逻辑和路由,必须从视图逻辑中独立出来。

改进了切换时的场景动画,手势和导航栏

如果你对react native 中的三个导航感到困惑,可以查看导航比较的文章

三个导航系统的比较

NavigatorNavigatorIOS 对于新人来说,不知道如何区别它们。一个是基于JS的,而NavigatorIOS则是第三方的开发的只针对ios的本地组件. 而Facebook正在将Navigator 过渡到NavigationExperimental. NavigationExperimental向前兼容navigation库。

NavigationExperimental 通常称为”新的导航”, 但其实它是以一种新的方法实现导航逻辑,这样允许作何的视图都可以作为导航的视图 。它包含了一个预编异的组件NavigationAnimatedView来管理场景间的动画。它内部的每一个视图都可以有自己的手势和动画。这些预编译的场景和overlay组件,看起来就会跟平台相一致(ios, android)

Navigator and NavigatorIOS两个都是有状态(即保存各个导航的序顺)的组件,允许你的APP在多个不同的场景(屏幕)之间管理你的导航。这两个导航管理了一个路由栈(route stack),这样就允许我们使用pop(), psh(), and replace()来管理状态。这类似于html5的history API. 这两者的主要区别在于NavigatorIOS是使用了iOS的 UINavigationController类,而Navigator都是基于Javascript。 Navigator适用于两个平台,而NavigatorIOS只能适用于iOS. 如果在一个APP中应用了多个导航组件(Navigator and NavigatorIOS一起使用). 那么在两者之间进行导航过渡,会变得非常困难.

NavigationExperimental

NavigationRootContainer允许导航的各个状态(屏幕)保存在app的最顶层.

使用reducer在导航状态中声明设置转换过渡

可以将state永久保存存到硬盘,这样刷新和app更新后,还能获得之前的导航状态

监听打开中的url链接,BackAndroid便于支持返回按纽

NavigationReducers 包含了预置的reducers, 用来管理导航状态之间的转换过渡。

Reducers可以彼此之前进行组合,设置更高级的导航逻辑

导航逻辑可以用于任何的视图

NavigationAnimatedView 是一个用来管理不同场景动画的组件,也可以用于Navigator和NavigatorIOS组件

每一个scene可以完全自定义,并且管理它自己的动画和手势

可以有一个Overlay/header, 用于跟场景的动画同步

NavigationCard 和NavigationHeader可以作为预编译的scenes和overlays. 然后跟NavigationAnimatedView一起使用

Navigator

Facebook会慢慢不支持Navigator, 重点会放在NavigationExperimental

它有自己的navigations state和API,这违返了React的单向数据流原则

Scene animations and gestures很难自定义

手势可以通过Navigator处理,但是不能基于每一个预编译scene进行自定义

Animation 的自定义是模糊的,因为它在Animated库出来之前就有了

可以用于iOS和Android

跟NavigatorIOS一样,只有一个简单的导航条:Navigator.NavigatorBar, 和一个breadcrumbs Navigator.BreadcrumbNavigatorBar. 可以看看React Native的官方UIExplorer demo 看看如何使用它们。

动画不如Apple的精致,你可以使用NavigatorIOS.

你可以通过navigationBar属性,提供你自己的navigation bar

NavigatorIOS

包含一 个专有的API, 不能很好的兼容其它的app

API很小,所以限制了它对Navigator or NavigationStackView的自定义

开发这个组件不是React Native团队,而是属于开源的社区

有很多积压的bug

如果社区将它重构为声明性的(declarative), 它将跟NavigationExperimental一起使用的很好

它在iOS UIKit的基础上包装的, 所以它跟其它的本地app是一样的。

仅支持iOS

包含一个默认的navigation bar. 这个navigation bar不是一个React Native view组件,它的样式只能轻微调整

NavigationExperimental Guide

我们通过一个简单的聊天APP开始,学习一个使用NavigationExperimental. 首先,我们需要确应用程序state的结构,因为我们的app有多个屏幕栈组成(类似于一个网站由多个网页组成). 所以我们需要在state中定义一个数组,用来保存场景列表。

class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
scenes: [
{key: 'home'}, // 表示应用程序主页
],
};
}


对于应用程序的render function, 我们想要显示scene stack中最顶层/当前(scenes数组中最后一项)的scene.

render() {
const scene = this.state.scenes[this.state.scenes.length - 1];
if (scene.key === 'home') {
return <HomeView />;
}
if (scene.type === 'chat') {
return <ChatView id={scene.key} />;
}
return null;
}


为了打开聊天页,我们要添加一个openChat的方法

render() {
const scene = this.state.scenes[this.state.scenes.length - 1];
if (scene.key === 'home') {
return <HomeView />;
}
if (scene.type === 'chat') {
return <ChatView id={scene.key} />;
}
return null;
}


如果我们想要返回,我们需要实现一个back方法

goBack() {
if (this.state.scenes.length > 1) {
this.setState({
scenes: this.state.scenes.slice(0, this.state.scenes.length - 1),
});
}
}


可是,这会变得难以维护,因为在你的应用中,每次导航都依赖于具体的方法。为此,我们需要将所有的导航逻辑都委托给一个reducer进行处理, 所以我们需要修改上面的代码。上面的代码修改如下。

constructor(props) {
super(props);
this.state = AppReducer(null, { type: 'init' });
}
dispatch(action) {
this.setState(AppReducer(this.state, action));
}


我们的reducer看起来如下所示,一个Reducer接受上一次的状态,以及一个action, 同时返回一个state. 可以在NavigationExperimental文档中,查看Reducer的定义

function AppReducer(lastState, action) {
let state = lastState;
if (!state) {
state = {
scenes: [
{key: 'home'}
],
};
}
if (action.type === 'back' && state.scenes.length > 1) {
return {
scenes: state.scenes.slice(0, this.state.scenes.length - 1),
};
}
if (action.type === 'openChat') {
return {
scenes: [
...state.scenes,
{
type: 'chat',
key: action.id
}
],
};
}
return state;
}


现在,我们可以非常容易的实现我们的导航的方法,如下所示

openChat(id) {
this.dispatch({ type: 'openChat', id });
}
goBack() {
this.dispatch({ type: 'back' });
}


我们现在实现了this.dispatch方法,所以通过props,将dispatch方法传定给子组件(子页面). 那么就可以在子页面中访问到dispatch action.

render() {
const scene = this.state.scenes[this.state.scenes.length - 1];
if (scene.key === 'home') {
return (
<HomeView
dispatch={this.dispatch.bind(this)}
/>
);
}
if (scene.type === 'chat') {
return (
<ChatView
id={scene.key}
dispatch={this.dispatch.bind(this)}
/>
);
}
return null;
}


function HomeView(props) {
return
<Text
onPress={() => {
props.dispatch({ type: 'openChat', id: 'A' });
}}>
This is the home screen. Tap to open Chat A.
</Text>;
}
function ChatView(props) {
return
<Text
onPress={() => {
props.dispatch({ type: 'back' });
}}>
This is chat {props.id}. Tap to go back home.
</Text>;
}


现在我们的应用就可以打开一个chat view 和一个主页,完成的代码如下

function MyChatAppReducer(lastState, action) {
let state = lastState;
if (!state) {
state = {
scenes: [
{key: 'home'}
],
};
}
if (action.type === 'back' && state.scenes.length > 1) {
return {
scenes: state.scenes.slice(0, state.scenes.length - 1),
};
}
if (action.type === 'openChat') {
return {
scenes: [
...state.scenes,
{
type: 'chat',
key: action.id
}
],
};
}
return state;
}

function HomeView(props) {
return (
<Text
onPress={() => {
props.dispatch({ type: 'openChat', id: 'A' });
}}>
This is the home screen. Tap to open Chat A.
</Text>
);
}

function ChatView(props) {
return (
<Text
onPress={() => {
props.dispatch({ type: 'back' });
}}>
This is chat {props.id}. Tap to go back home.
</Text>
);
}

class MyChatApp extends React.Component {
constructor(props) {
super(props);
this.state = MyChatAppReducer(null, { type: 'init' });
}
dispatch(action) {
this.setState(MyChatAppReducer(this.state, action));
}
render() {
return (
<View style={styles.container}>
{this.renderCurrentScene()}
</View>
);
}
renderCurrentScene() {
const scene = this.state.scenes[this.state.scenes.length - 1];
if (scene.key === 'home') {
return (
<HomeView
dispatch={this.dispatch.bind(this)}
/>
);
}
if (scene.type === 'chat') {
return (
<ChatView
id={scene.key}
dispatch={this.dispatch.bind(this)}
/>
);
}
return null;
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
padding: 10,
paddingTop: 30,
},
});


名词解释

本章内容太理论化,一开始会看不懂的,可以先记信概念,在结合后面的例子,进行详细的理解

Navigation State

state是一个Javascript对像,在React Native中可以能过this.setState()进行设置,它的几个字段,由NavigationStates中定义。你可以在库文件/node_modules/react-native/Libraries/NavigationExperimental/NavigationTypeDefinition.js中看到它对NavigationStates的定义.

const myState = {
key: 'myPage0',
}


一个NavigationParentState 包含一组路由(routes), 并且有一个index字段,表示当前的路由.

const myState = {
key: 'myAppTabs',
children: [
{key: 'home'},
{key: 'notifs'},
{key: 'settings'},
],
index: 1, // points to the 'notifs' tab
}


navigation state types在NavigationStateUtils中保存,同时在NavigationStateUtils还有一些函数,通过这些函数可以改变NavigationParentState。

Containers

在NavigationExperimental中提供了一个最顶级的组件,用于维护导航的状态以及处理永久性(将导航保存到硬盘或者从硬盘中读取导航的状态数据)。

如果你使用redux 或者flux, 你可以不需要NavigationContainer. 你可以使用现有的stores and providers.

NavigationRootContainer

开发者可以为根容器设置一个reducer, reducer会包含整个app的导航逻辑。我们的navigation reducers将会接受最后的导航状态,一个我们需要处理的action. 然后它为我们的app输出一个新的导航装态。为了获得初始化的state, reducers可以在调用时,不需要上一个状态或者action.

<NavigationRootContainer
reducer={MyReducer}
renderNavigation={(navigationState, onNavigate) => (
<Text>Currently at {navigationState.routes[navigationState.index]}</Text>


它也提供了一个针对navigation action的处理器,并且允许reducer被自定义.

NavigationContainer.create

在整个应用中,都要传递onNavigate会非常繁锁,因此我们可以提供一个更高阶的”container”组件, 就不需要每次都传递onNavigate属性。

<NavigationRootContainer
reducer={MyReducer}
renderNavigation={(navigationState) => <ExampleComponent />}
...

class ExampleComponent {
render() {
<Text onPress={() => { this.props.onNavigate(new ExampleAction()) }}>
This action will work, even though `onNavigate` was not directly passed in
</Text>
}
}
ExampleComponent = NavigationContainer.create(ExampleComponent);


如果onNavigation作为一个属性被传递给container, 它会覆盖处理程序中包含的组件和所有的子容器.

Reducers

一个导航的reducer是一个action 处理器,它返回当前的navigation state.当调用navigation reducers, 你要提供一个可选的previous state和一个字符串类型的 navigation action.

let state = MyReducer(null, { type: 'InitialAction' });
//output
> {
key: 'Root',
index: 0,
routes: [
{key: 'Home'},
]
}
state = MyReducer(state, { type: 'PushPerson', name: 'Christopher' });
//output
> {
key: 'Root',
index: 1,
routes: [
{key: 'Home'},
{key: 'Person0', name: 'Christopher'},
]
}


Stack Reducer

常见的导航逻辑是一个’stack’(栈), 这可以通过stack reducer来处理

const MyReducer = NavigationStackReducer({
// First, define the initial parent state that will be used if there was no previous state.
initialState: {
key: 'Root',
index: 0,
children: [ //routes
{key: 'Home'},
]
},
getPushedReducerForAction: (action) => {
if (action.type === 'PushPerson') {
// We need to push some additional state, that will be defined by this reducer:
return () => ({
key: 'Person'+(i++),
name: action.name,
});
}
// In this case we do not need to push, so our reducer for this action is nothing
return null;
},
});

let state = MyReducer(null, { type: 'InitAction' });
> {
key: 'Root',
index: 0,
routes: [
{key: 'Home'},
]
}

state = MyReducer(state, { type: 'PushPerson', name: 'Christopher' });
> {
key: 'Root',
index: 1,
children: [
{key: 'Home'},
{key: 'Person0', name: 'Christopher'},
]
}

// The back action can be used to pop:
state = MyReducer(state, NavigationRootContainer.getBackAction());
> {
key: 'Root',
index: 0,
children: [
{key: 'Home'},
]
}


stack reducer中也可以包含sub-reducers, 它需要你实现getReducerForState. 它会为sub-state 返回一个sub-reducer. 当前的sub-state的sub-reducer将会被使用.

源文件分析

node_modules/react-native/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js

NavigationStackReducer是一个高阶函数,它返回一个StackReducer的函数

/*

Example:
let ExampleStackReducer = NavigationReducer.StackReducer({
initialState: {
key: "key1",
index:0,
children: [
{key: "first route"}
]
},
getPushedReducerForAction: (action) => {
if (action.type === 'push') {
return (state) => state || {key: action.key};
}
return null;
},
//返回一个用于处理子路由的sub-reducer. 它返回的函数将在处理子sub-state(在children中保存存的对像)时调用.
//默认情况为,直接返回state或者initalState,
//但这个函数也可以定义为(initialState) => (state, action) => {//根据action, 返回相应的state}
getReducerForState: (initialState) => (state) => state || initialState,

})
ExampleStackReducer(); //返回整个应用的初始路由
ExampleStackReducer(this.state, {type:'push', key: 'push key #1'});// 将当前路由添加到栈,即state中的children字段中

*/
//defaultGetReducerForState是一个函数,它接受一个initialState的参数
//它返回一个一个函数,返回的这个函数,接受一个state参数,如果有传递这个state, 则返回state, 否则,这个函数,返回initialState;
const defaultGetReducerForState = (initialState) => (state) => state || initialState;

function NavigationStackReducer(
{initialState, getReducerForState, getPushedReducerForAction}: StackReducerConfig): NavigationReducer {
/*
* initialState,指定的初始路由状态, 它的类型为NavigationParentState
* source file: /node_modules/react-native/Libraries/NavigationExperimental/NavigationTypeDefinition.js
* export type NavigationParentState = {
*  index: number,
*  key: string,
*  children: Array<NavigationState>,
* };
* export type NavigationState = {
*    key: string,
* };
* 实际对像
* {
*   children: [
*     {key: 'subState0'},
*     {key: 'subState1'},
*   ],
*   index: 0,
*   key: 'navStackKey'
* }
* getReducerForState 返回一个用于处理子路由的sub-reducer. 它返回的函数将在处理子sub-state(在children中保存存的对像)时调用.
* 如果没有返回reducer, 则action不会被接受
*
* getPushedReducerForAction 返回一个sub-reducer, 这个sub-reducer将会用于添加一个新的路由
* 如果返回一个reducer, 这个reducer会被调用,并且获得添加了路由后的新state.
*/

//如果有传递getReducerForState, 则使用传入的这个函数,否则调用上面定义的defaultGetReducerForState函数
const getReducerForStateWithDefault = getReducerForState || defaultGetReducerForState;

//返回一个reducer,这个函数接受一个state和一个必须的action.
return function (lastState: ?NavigationState, action: any): NavigationState {

//如果没有指定state, 则返回调用NavigationReducer.StackReducer指定的initialState.
if (!lastState) {
return initialState;
}
/*
/node_modules/react-native/Libraries/NavigationExperimental/NavigationStateUtils.js
getParent 判断传入的state是否符合NavigationParentState的定义,
即
let lastState = {
key: "root",
index: 0, //必须存在,且为数值
children: [{key: "first route"}]  //必须为数组,index指定的元素也同时存在
}
符合以上的要求时,返回这个state, 不符合,则返回null;
*/
const lastParentState = NavigationStateUtils.getParent(lastState);

//如果上次没有state, 则返回传入的state,或者初始的initialState;
if (!lastParentState) {
return lastState;
}

//返回或者栈的类型,必须为"back" or "BackAction"
//如果lastParentState的index为0,或者路由children只有一个元素时,则直接返回
switch (action.type) {
case 'back':
case 'BackAction':
if (lastParentState.index === 0 || lastParentState.children.length === 1) {
return lastParentState;
}
return NavigationStateUtils.pop(lastParentState);
}
//从children子路由中,获取到state, 保存为activeSubState
//默认为 activeSubState = {key: "first route"}
const activeSubState = lastParentState.children[lastParentState.index];

// StackReducer初始化时,传入的getReducerForState,
// 即调用NavigationReducer.StackReducer指定的getReducerForState.
// 通常这个函数的格式为
// getReducerForState: (initialState) => (state) => state || initialState
// 它返回一个reducer(即有initialState, 所有的后续操作都是基于这个initialState上下文中),
// 默认情况下,它返回(state) => state || initialState函数,
// initialState对于没有sub-state的情况下为 {key: "first route"},
// 而不是{key:"first route", index:0, children: [{key: "sub first route"}]}
// 所以activeSubReduer = (state) => state || initialState //initalState = {key: "first route"}
const activeSubReducer = getReducerForStateWithDefault(activeSubState);

//调用上面返回的方法, 获取新的state
//假设 getReducerForState: (initialState) => (state) => state || initialState
//则返回activeSubState, 即{key: "first route"}
const nextActiveState = activeSubReducer(activeSubState, action);

//如果接下来的state,跟当前的sub-state不同,即getReducerForState中会根据不同的action返回不同的state.
//则将接下来的state, 合并到lastParentState的children[index] 为新的state
//以下通过代码来说明(**还没有实际中发现会使用到这种情况**)
/*
lastParentState为
{
key: "root",
index: "1",
children: [
{
key: "home page"
},
{
key: "company",
index: 0,
children: [
{key: "profile"},
{key: "culture"},
{key: "history"},
]
},

]
}
那么
activeSubState = {
key: "company",
index: 0,
children: [
{key: "profile"},
{key: "culture"},
{key: "history"},
]
}
如果在调用getReducerForState, 根据某一个action条件,改变当前ActiveState,
nextActiveState = {
key: "product"
}
则,整个reducer返回
{
key: "root",
index: "1",
children: [
{
key: "home page"
},
{
key: "product"
}
]
}
*/
if (nextActiveState !== activeSubState) {
const nextChildren = [...lastParentState.children];
nextChildren[lastParentState.index] = nextActiveState;
return {
...lastParentState,
children: nextChildren,
};
}
//调用传入的getPushedReducerForAction方法
//这个方法返回一个reducer
const subReducerToPush = getPushedReducerForAction(action, lastParentState);

if (subReducerToPush) {
//NavigationStateUtils将subReducerToPush返回的state, 添加到lastParentState
//然后返回整个state, subReducerToPush会修改lastParentState中的index.
return NavigationStateUtils.push(
lastParentState,
subReducerToPush(null, action)
);
}
return lastParentState;
};
}


Tabs Reducer

Tabs reducer允许你有多个子sub-reducers, 但有一个是激活状态。对于每一个action, 都会被发送给tabs reducer, 它会首先使用active状态的sub-reducer. 如果reducers没有返回一个新的sub-state, 则另外的reducers将会获得机会,并进行处理。如果一个不同的tab reducer处理了它,tabs reducer将返回一个新的new sub-state, 并且交换active tab. 它可处处理两种类型的action, 一个是切换tab, 即action的类型为{type: ActionTypes.JUMP_TO, index: 1}, 则直接修改 tab state中的index, 返回它的一个state. 另一种是点击子页面的按钮,则需要调用sub-state所对应的Reducer进行处理,比如,一个App有4个tab, 每个tab对应的页面中,有一个Add按钮,当点击这个按纽时,则在当前tab中添加一个子页面(有向左回到第一个页面的按钮). 那么就需要调用这个页面state, 所对应的StackReducer.

node_modules/react-native/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js

//传入给NavigationReducer.TabsReducer的初始函数
type TabsReducerConfig = {
key: string;
initialIndex: number;
tabReducers: Array<NavigationReducer>;
};

//实际例子
const ExampleTabsReducer = NavigationReducer.TabsReducer({
tabReducers: [
(lastRoute) => lastRoute || {key: 'one'},
(lastRoute) => lastRoute || {key: 'two'},
(lastRoute) => lastRoute || {key: 'three'},
],
});


主要功能实现

/*
const ExampleTabsReducer = NavigationReducer.TabsReducer({
key: "tabs",
index: 0,
tabReducers: [
(lastRoute) => lastRoute || {key: 'one'}, //reducer函数
(lastRoute) => lastRoute || {key: 'two'},
(lastRoute) => lastRoute || {key: 'three'},
],
});

*/
function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConfig): NavigationReducer {
return function(lastNavState: ?NavigationState, action: ?any): NavigationState {
//调用tab reducer时,没有传入lastNavState
//则lastNavState为
/*
key: "tabs",
index: 0
children: [
{key: "one"},
{key: "two"},
{key: "three"},
]
*/
if (!lastNavState) {
lastNavState = {
children: tabReducers.map(reducer => reducer(null, null)), //调用tabReducer数组中的每个函数, 生成所有的sub-state
index: initialIndex || 0,
key,
};
}
//判断lastNavState是否符合NavigationParentState标准,可以看上一节的StackReducer.
const lastParentNavState = NavigationStateUtils.getParent(lastNavState);

//如果没有指定action或者lastParentNavState, 则直接返回,这主要针对reducer的空调用
if (!action || !lastParentNavState) {
return lastNavState;
}
//如果action的类型为定义的常量JUMP_TO(源文件头部定义的常量)
//JUMP_TO = "react-native/NavigationExperimental/tabs-jumpTo"
//相对于stackreducer,action还多了一个index
//只有当action中的index跟reducer中保存的index不同时,才进行跳转
if (
action.type === ActionTypes.JUMP_TO &&
action.index !== lastParentNavState.index
) {
//jumpToIndex接受一个要跳转的state和要跳转到state中index的位置
//它返回如下的参数
/*
{
...lastParentNavState,
index //action.index
}
在react中,... spread the object(扩展一个对像)
this.state = ExampleTabsReducer();
this.state = {children: [{key:"one"}, {key:"two"}], index: 0, key: undefined}
NavigationReducer.TabsReducer.JumpToAction(1) 则返回{index:1}
const newState = ExampleTabsReducer(this.state, NavigationReducer.TabsReducer.JumpToAction(1))
NavigationStateUtils.jumpToIndex({children: [{key:"one"}, {key:"two"}], index: 0, key: undefined}, 1)
返回{children: [{key:"one"}, {key:"two"}], index: 1, key: undefined}
有一个问题:为什么传入的是1,而不是{index:1}, 即可以在修改this.state中对应的index?
这是因为在NavigationStateUtils.jumpToIndex中,第二个参数名为index, 所以它会根据参数名,修改第一个参数中对应的字段,
如果参数命名为i, 则返回的对像为
{children: [{key:"one"}, {key:"two"}], index: 0, key: undefined, i: 1}
*/
return NavigationStateUtils.jumpToIndex(
lastParentNavState,
action.index,
);
}
/*
NavigationReducer.TabsReducer.JumpToAction(1),传入action, 即能过index对
tab切换
*/
//如果传入的action类型为非{type: ActionTypes.JUMP_TO, index: 1},
//则调用sub-reducer进行处理, 常见的应用就tabsReducer为四个NavigationReducer.StackReducer, 然后在子页面中,点击某一个为当前tab添加子页面的按纽。
/*
返回各sub-reducer
const ExampleAppReducer = NavigationReducer.TabsReducer({
key: 'AppNavigationState',
initialIndex: 0,
tabReducers: [
NavigationReducer.StackReducer({
getPushedReducerForAction: (action) => {
if (PageAction.match(action)
&& !ExampleProfilePageAction.match(action)) {
return (state) => (state || pageStateActionMap(action));
}
return null;
},
initialState: {
key: 'notifs',
index: 0,
children: [
{key: 'base', type: 'NotifsPage'},
],
},
}),
....
]
});
*/

const subReducers = tabReducers.map((tabReducer, tabIndex) => {
return function(navState: ?NavigationState, tabAction: any): NavigationState {
//在处理子页面的action时,如果没有传入它当前的子页面的state, 则直接返回整个app的状态.
if (!navState) {
return lastParentNavState;
}
const parentState = NavigationStateUtils.getParent(navState);
//subReducers所对应的tabState
const tabState = parentState && parentState.children[tabIndex];
//调用sub-reducer,即,在TabsReducer中的StackReducer. 返回一个当前页面的
//的导航状态.
const nextTabState = tabReducer(tabState, tabAction);
//如果调用StackReducer返回的状态跟当前的tab状态不同,
//
if (nextTabState && tabState !== nextTabState) {
//获取整个tab的状态,并且将它的index,修改为当前的页面所对应的index.
const tabs = parentState && parentState.children || [];
tabs[tabIndex] = nextTabState;

//lastParentNavState 为上一次tab的state, 即第一个参数传入的state
//tabs为当前调用了sub-reducer,产生的新的state,
//tab的index个改为tabIndex.
return {
...lastParentNavState,
tabs,
index: tabIndex,
};
}
return lastParentNavState;
};
});

//根据当前tabs的index,在subReducers中,找到当前Tab所对应的reducer
//并且将它从subReducers中删除
let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0];
//sbuReducers开头添加一个reducer. 这个reducer的用途是,将当前的tabs的状态lastParentNavState保存到这个reducer, 之后在调用subReducers的第一个子reducers时,其action如果为
//构建一个BackAction,
//如果当前的selectedTabReducer没有处理BackAction的方法,则返回传入的第一个参数state, 并且将它的index设置为0. 而如果tab中的tabsReducer中的sub reducer为StackRecuder, 它是可以处理BackAction的,所以会先调用sub reducer的action, 返回到上一个页面,而不是返回到第一个Tab。

subReducers.unshift(function(navState: ?NavigationState, action: any): NavigationState {
if (navState && action.type === 'BackAction') {
return NavigationStateUtils.jumpToIndex(
lastParentNavState,
initialIndex || 0
);
}
return lastParentNavState;
});
//接着刚刚删除的sub reducer,在重新添加到最前面, 这样可以从当前的tab所对应的reducer进行查询action.
subReducers.unshift(selectedTabReducer);
//NavigationFindReducer接受一个reducers数组,然后返回一个reducer,
//返回的reducer可以接受当前的tabs state,和action, 然后遍历所有的subReducers数组中的元素,
//并且将tabs state和action 传递给subReducers中的每个sub reducer.
//当其中每个sub reducer返回的状态,跟传入的state不同时(即在所有的subReducers中找到了相应的action), 返回更新后的状态
const findReducer = NavigationFindReducer(subReducers, lastParentNavState);
return findReducer(lastParentNavState, action);

};
}


Find Reducer

Reducers的一个常见模式是组合了多个reducers, 当其中一个reducer返回一个新的state时停止。Find Reducer会接受一个reducers数组,然后遍历数据中的每一个元素,直到state改变时,返回这个reducer. 如果这些reducers没有返回一个新的state, find reducer将返回默认的state.

Views

Scenes

在App的导航中,每个state都会对应一个页面,比如<HomeView> <SettingsView>它们保存在state的Scenes中,当导航处于不同的action,render不同的scene.

NavigationTypeDefinition对 scene的定义

export type NavigationScene = {
index: number,
isStale: boolean,
key: string,
navigationState: NavigationState, //当前场景对应的路由值,即navigationState children中的元素
};


而/react-native/Libraries/NavigationExperimental/Reducer/NavigationScenesReducer.js可以基于上一个scene以及导航的state, 返回一个scene

//code from NavigationAnimatedView
/*
NavigationScenesReducer首先接收的第一个参数为scenes类型的数组,
*/
//实始化, 案例1
/*
navigationState = {
key: "root",
index: 0,
children: [{key:"first route"}]
}
返回
scenes1 = [{key: "first route", index: 0, isStale: false, navigationState: {"key":"frist route"}}]
*/
scenes: NavigationScenesReducer([], this.props.navigationState),

//改变了navigationState时, 案例2
/*
nextNavigationState = {
key: "root",
index: 1,
children: [{key:"first route"}, {key: "second route"}]
}
prevNavigationState = {
key: "root",
index: 0,
children: [{key:"first route"}]
}
scenes2的值为[{key: "first route", index: 0, isStale: false, navigationState: {"key":"frist route"}}, {key: "second route", index: 1, isStale: false, navigationState: {"key":"second route"}}]
scenes2 = NavigationScenesReducer(scene1, nextNavigationState, prevNavigationState),

//如果从案例2 返回到案例1

scenes3的值为
[{key: "first route", index: 0, isStale: false, navigationState: {"key":"frist route"}}, {key: "second route", index: 1, isStale: true, navigationState: {"key":"second route"}}]
*/
//源代码
function NavigationScenesReducer(
scenes: Array<NavigationScene>,
nextState: NavigationParentState,
prevState: ?NavigationParentState,
): Array<NavigationScene> {

const prevScenes = new Map();   //将传入的第一个scenes参数,保存到上一个场景及中
const freshScenes = new Map();  //保存新鲜场景,即根据state.children路由,新创建的scene
const staleScenes = new Map();  //保存过期场景, 比如从第三个导航,返回到第1个时,那么第三个scene就会标记为过期,可以从scenes中删除

// 从scenes中提取出过期的场景,同时将scenes中的场景,都保存到prevScenes
scenes.forEach(scene => {
const {key} = scene;
if (scene.isStale) {
staleScenes.set(key, scene);
}
//对于案例2来说,prevScenes为{"scene_first route": {key: "first route", index: 0, isStale: false, navigationState: {"key":"frist route"}}}
prevScenes.set(key, scene);
});

const nextKeys = new Set();
//读取导航的state中的children(路由),根据路由的顺序,构建一个对应的数组类型的scenes

nextState.children.forEach((navigationState, index) => {
const key = SCENE_KEY_PREFIX + navigationState.key; //scene_ + key
//构建一个scene对像
const scene = {
index,
isStale: false,
key,
navigationState,  //当前场景对应的路由值,即navigationState children中的元素
};

invariant(
!nextKeys.has(key),
`navigationState.children[${index}].key "${key}" conflicts with` +
'another child!'
);
nextKeys.add(key);

if (staleScenes.has(key)) {
//如果传入的scenes参数中,有这个key值的scene,并且它已经过期了
//而这时又新建了一个scene, 则将它从staleScenes中删除
// A previously `stale` scene is now part of the nextState, so we
// revive it by removing it from the stale scene map.
staleScenes.delete(key);
}
//将新创建的scence保存到freshScens
freshScenes.set(key, scene);
});
//如果有传递prevState, 则遍历上一个状态的所有children中的路由
//如果当前nextState中children包含了上一次prevState的路由,则直接跳过
//如果上次的路由,没有包含在新的路由中,则把它置为isStale为true.
//比如从案例2回到案例1的情况
if (prevState) {
// Look at the previous children and classify any removed scenes as `stale`.
prevState.children.forEach((navigationState, index) => {
const key = SCENE_KEY_PREFIX + navigationState.key;
if (freshScenes.has(key)) {
return;
}
staleScenes.set(key, {
index,
isStale: true,
key,
navigationState,
});
});
}

const nextScenes = [];

/*
用于合并staleScenes,freshScenes中与prevScenes中上相同的值,这样就可以重用prevScenes中的场景(没有理解,可以先不管prevScenes, 即nextScenes为staleScenes和freshScenes的合集)
*/
const mergeScene = (nextScene => {
const {key} = nextScene;
//prevScenes为传入的第一个参数
const prevScene = prevScenes.has(key) ? prevScenes.get(key) : null;
if (prevScene && areScenesShallowEqual(prevScene, nextScene)) {
// 重用`prevScene`,这样view可以避免re-render
nextScenes.push(prevScene);
} else {
nextScenes.push(nextScene);
}
});
staleScenes.forEach(mergeScene);
freshScenes.forEach(mergeScene);
//按照scene中的index升序排列
return nextScenes.sort(compareScenes);
}


NavigationView

最简单的视图. 常用于tabs, 因为在不同tab之前不需要动画转换。

NavigationAnimateView

NavigationAnimateView 采用声明API, 它使用Animate library向scenes委派动画和手势

NavigationCard和NavigationHeader分别实现了NavigationAnimateView的scene(实现的页面内容<HomeView>) 和 overlays。通过这两个,使用得页面动画在iOS和android上看起来更统一.

//实际使用的例子
/*
传递给NavigationAnimatedView的属性
const propTypes = {
applyAnimation: PropTypes.func,
navigationState: NavigationPropTypes.navigationState.isRequired,
onNavigate: PropTypes.func.isRequired,
renderOverlay: PropTypes.func,
renderScene: PropTypes.func.isRequired,
};
*/

render() {
return (
<NavigationAnimatedView
navigationState={this.state} //App state is generated by StackReducer
style={styles.animatedView}  //自定义页面样式
onNavigate={this._handleAction} //点击按钮时的动作
renderOverlay={this._renderHeader} //读取导航的头部,leftButton, Title
applyAnimation={(pos, navState) => { //指定动画
Animated.timing(pos, {toValue: navState.index, duration: 500}).start();
}}
renderScene={this._renderCard}//render scene.
/>
);
}


NavigationCard

NavigationCard的源文件在node_modules/react-native/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js

//renderScence是在新建NavigationCard是传入给它的属性, 用于读取页面的内容, 并且将接受到的属性,继续传递给renderScene()
render(): ReactElement {
let {
style,
panHandlers,
renderScene,
...props,
} = this.props;

if (style === undefined) {
// fall back to default style.
//NavigationCardStackSylteInterpolator会根据从NavigationAnimatedView
//中传递过的的动画值position,以及scene和layout来决定动画的方式
//react-native/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackStyleInterpolator.js
/*
const index = scene.index;
//position的变化范围
const inputRange = [index - 1, index, index + 1];
const width = layout.initWidth;
//width的输出范围,对应于position的值
/*
第一张card时,它的position为0, index为0, width: 375
当push 第二张卡片时
position from 0 到 1时
第一张卡片的scene的index为0, inputRange [-1, 0, 1]
第二张卡片的scene的index为1, inputRange [0, 1, 2]
第一张卡片的opacity输入的值[0,1] [-1, 0, 1] [1,1,0.3] = 后半段范围
第一张卡片translateX的值为 375到-10
第二张卡片的opacity输入的值[0,1] [0,1,2] [1,1,0.3] = 对应前半段
第三张卡片push时,opacity [1,2] [1,2,3][1,1,0.3] =同样对应前半段
这时,第二张卡的opacity输入的值为[1,2] [0,1,2][1,1,0.3],它的值变为0.3
当从第三张回到第二张时position的值为[2,1]
所以第三张的opacity[2,1][1,2,3][1,1,0.3] opaicty不变
对于x的位置
card1 to card2
card1 0-1 [-1,0,1] [375, 0, -10] = 0 to -10
card2 0-1 [0,1,2] [375, 0, -10]  = 375 to 0
card2 to card1
card2 1-0 [0,1,2] [375, 0 -10] = 0 to 375
card1 1-0 [-1,0,1] [375, 0, -10] = -10 to 0
*/
const opacity = position.interpolate({
inputRange,
outputRange: [1, 1, 0.3],
});
const translateX = position.interpolate({
inputRange,
outputRange: [width, 0, -10],
});
return {
opacity,
transform: [
{ scale },
{ translateX },
{ translateY },
],
};
*/
style = NavigationCardStackStyleInterpolator.forHorizontal(props);
}

if (panHandlers === undefined) {
// fall back to default pan handlers.
// 用于响应手势动作    //reactnative/Libraries/NavigationExperimental/NavigationLinearPanResponder.js
panHandlers = NavigationLinearPanResponder.forHorizontal(props);
}

return (
<Animated.View {...panHandlers} style={[styles.main, style]}>
{renderScene(props)}
</Animated.View>
);
}


<NavigationAnimatedView
navigationState={navigationState}
renderScene={(props) => (
<NavigationCard
key={props.navigationState.key}
index={props.index}
navigationState={props.navigationParentState}
position={props.position}
layout={props.layout}>
<MyInnerView info={props.navigationState} />
</NavigationCard>
)}
/>


NavigationHeader

NavigationHeader在没有指定左边的按钮时,会在scene.index不为0时,render一个返回按纽. 在NavigationHeaderBackButton组件中,而整个NavigationHeader是一个NavigationContainer.create创建,所以

NavigationHeaderBackButton会自动获取到传递给NavigationHeader的props.onNavigation处理函数,当点击Back时,调用回调函数, 一般为为handleAction, 并且传递{type:”BackAction”}参数

renderLeftComponent: (props, scene) => scene.index !== 0 ? <NavigationHeaderBackButton /> : null


<NavigationAnimatedView
navigationState={navigationState}
renderOverlay={(props) => (
<NavigationHeader
navigationState={props.navigationParentState}
position={props.position}
getTitle={state => state.key}
/>
)}
renderScene={this._renderScene}
/>


NavigationCardStack

包装了NavigationAnimateView, 类似于过时的Navigator. 这是因为它内置了animations和gestures

使用:

render() {
return (
<NavigationCardStack
style={styles.main}
renderScene={props =>
<MyPetView
name={props.navigationState.key}
species={props.navigationState.species}
/>
}
renderOverlay={props => <NavigationHeader {...props} />}
navigationState={{
key: 'MyPetStack',
index: 2,
routes: [
{key: 'Pluto', species: 'dog'},
{key: 'Snoopy', species: 'dog'},
{key: 'Garfield', species: 'cat'},
]
}}
/>
);
}


NavigationExperimental 实际例子

NavigationBasicExample



点击Push后



首先我们定一个通用组件,即上面中每一行的外观组件, NavigationExampleRow.js

import React, {
View,
Component,
Text,
TextInput,
StyleSheet,
TouchableHighlight,
PixelRatio,
} from 'react-native';

export default class NavigationExampleRow extends Component {
render() {
//如果这一行有链接,则返回TouchableHighlight, 否则返回纯文本的一行
if (this.props.onPress) {
return (<TouchableHighlight
style={styles.row}
underlayColor="#D0D0D0"
onPress={this.props.onPress}>
<Text style={styles.buttonText}>
{this.props.text}
</Text>
</TouchableHighlight>);
}
return (
<View style={styles.row}>
<Text style={styles.rowText}>
{this.props.text}
</Text>
</View>
);
}
}

const styles = StyleSheet.create({
row: {
padding: 15,
backgroundColor: 'white',
borderBottomWidth: 1 / PixelRatio.get(),
borderBottomColor: '#CDCDCD',
},
rowText: {
fontSize: 17,
},
buttonText: {
fontSize: 17,
fontWeight: '500',
},
});

/*
//app.js
import NavigationExampleRow from "./NavigationExampleRow.js";
class App extends Component {
constructor(props) {
super(props)

}
componentDidMount() {

}

render() {
return <NavigationExampleRow text="Hello"></NavigationExampleRow>
}
}
*/




NavigationBasicExample 完整代码

'use strict'

import React, {
AppRegistry,
Component,
StyleSheet,
Text,
Linking,
View,
ScrollView,
NavigationExperimental,
} from 'react-native';

const {
Reducer: NavigationReducer,
} = NavigationExperimental

import NavigationExampleRow from "./NavigationExampleRow.js";

//ExampleReducer是一个函数,接受一个App的state和一个action,返回一个state
//
const ExampleReducer = NavigationReducer.StackReducer({
initialState: {
key: "BaseExampleStackKey",
index: 0,
children: [
{key: "First Route"}
]
},
//获取sub-state的reducer, 通常不用指定这个方法
//getReducerForState: (initialState) => (state) => state || initialState,
getPushedReducerForAction: (action) => {
if (action.type === 'push'){
return (state) => state || {key: action.key}; //返回一个新的reducer (函数), 返回的这个函数会在接下来被ExampleReducer调用,请参考上一节中StackReducer中的源代码解释
}
return null;
}

})

class App extends Component {
constructor(props) {
super(props)
this.state = ExampleReducer();
/*
//console.log(this.state.toString())
{
key: "BaseExampleStackKey",
index: 0,
routes: [
{key: "First Route"}
]
}
*/
}
/*
* Exit Base Nav example一行的行为可以不用理解
* 它主要调用创建App时,传递给它回调属性
* */
render() {
return (<ScrollView>
<NavigationExampleRow
text={`Current page: ${this.state.children[this.state.index].key}`}
/>
<NavigationExampleRow
text={`Push page #${this.state.children.length}`}
onPress={()=> {
this.handleAction({type: 'push', key: 'page #'+(this.state.children.length + 1)})
}
}
/>
<NavigationExampleRow
text="Pop"
onPress={()=>{
this.handleBackAction();
}}
/>
<NavigationExampleRow
text="Exit Basic Nav example"
onPress={this.props.onExameleExit}
/>
</ScrollView>);
}
//action arguments: {type:'push', key: 'page #2'}
handleAction(action){
if (!action) {
return false;
}
//每一次导航,都是通过ExampleReducer函数,传递当前的state, 和action类型,
//然后由Reducer决定,返回新的state, 可以查看上一节中的state和reducer名词解释
let newState = ExampleReducer(this.state, action);

if (newState === this.state){
return false;
}
this.setState(newState)
return true;
}
handleBackAction(){
return this.handleAction({type: 'BackAction'})
}
}

AppRegistry.registerComponent('App', () => App);


NavigationExampleTabBar



'use strict'

import React, {
AppRegistry,
Component,
StyleSheet,
Text,
Linking,
View,
ScrollView,
NavigationExperimental,
} from 'react-native';

const {
Reducer: NavigationReducer,
} = NavigationExperimental

import NavigationExampleRow from "./NavigationExampleRow";
import NavigationExampleTabBar from "./NavigationExampleTabBar" //底部tabs的View
//首先创建每个tab页面,显示tab按纽,以及当前在哪个tab页面中。
class ExampleTabPage extends Component {
render() {
const currentTabRoute = this.props.tabs[this.props.index];
return (
<ScrollView style={styles.tabPage}>
<NavigationExampleRow
text={`Current Tab is: ${currentTabRoute.key}`}
/>
{this.props.tabs.map((tab, index) => (
<NavigationExampleRow
key={tab.key}
text={`Go to ${tab.key}`}
onPress={
()=> {
//调用一个static函数JumpToAction,返回一个action, 如{index:1}
this.props.onNavigate(NavigationReducer.TabsReducer.JumpToAction(index));
}
}
/>
))}
<NavigationExampleRow
text="Exit Tabs Example"/>
</ScrollView>
);

}
}

//创建tabs reducer函数
//调用这个函数时,返回一个navigationState对像
/*
{children: [{key:"one"}, {key:"two"}, key: "three"], index: 0, key: undefined}
*/
const ExampleTabsReducer = NavigationReducer.TabsReducer({
tabReducers: [
(lastRoute) => lastRoute || {key: 'one'},
(lastRoute) => lastRoute || {key: 'two'},
(lastRoute) => lastRoute || {key: 'three'},
],
});
class App extends Component {
constructor(props) {
super(props)

this.state = ExampleTabsReducer();
}

render() {
return (<View style={styles.topView}>
<ExampleTabPage
tabs={this.state.children}
index={this.state.index}
onNavigate={this.handleAction.bind(this)}
></ExampleTabPage>
<NavigationExampleTabBar
tabs={this.state.children}
index={this.state.index}
onNavigate={this.handleAction.bind(this)}
/>
</View>);
}

//action arguments: {index:1}
//此方法会在ExampleTabPage中的点击tab后调用
handleAction(action) {
if (!action) {
return false;
}
const newState = ExampleTabsReducer(this.state, action)
if (newState === this.state) {
return false
}
this.setState(newState)
return true;
}
}
const styles = StyleSheet.create({
topView: {
flex: 1,

paddingTop: 30,
},
tabPage: {
backgroundColor: '#E9E9EF',
},
});
AppRegistry.registerComponent('App', () => App);


Tab导航的定义



/*自定义一个底部tab导航*/
import React, {
View,
Component,
Text,
StyleSheet,
TouchableOpacity,
PixelRatio,
NavigationExperimental,
} from 'react-native';
const {
Reducer: NavigationReducer,
} = NavigationExperimental
const {
JumpToAction
} = NavigationReducer.TabsReducer; //从TabsReducer提取出JumpToAction函数

export default class NavigationExampleTabBar extends Component {
render() {
return (
<View style={styles.tabBar}>
{this.props.tabs.map(this._renderTab.bind(this))}
</View>
)
}
_renderTab(tab, index) {
const textStyle = [styles.tabButtonText];
if (this.props.index === index) {
textStyle.push(styles.selectedTabButtonText)
}
return (
<TouchableOpacity
style={styles.tabButton}
key={tab.key}
onPress={()=>{
this.props.onNavigate(JumpToAction(index));
}}
>
<Text style={textStyle}>
{tab.key}
</Text>
</TouchableOpacity>
)
}
}

const styles = StyleSheet.create({
tabBar: {
height: 50,
flexDirection: 'row',
},
tabButton: {
flex: 1,

},
tabButtonText: {
paddingTop: 20,
textAlign: 'center',
fontSize: 17,
fontWeight: '500',
},
selectedTabButtonText: {
color: 'blue',
},
});


NavigationAnimateExample



'use strict'

import React, {
AppRegistry,
Animated,
Component,
StyleSheet,
Text,
Linking,
View,
ScrollView,
NavigationExperimental,
} from 'react-native';

const {
Reducer: NavigationReducer,
AnimatedView: NavigationAnimatedView,
Card: NavigationCard,
Header: NavigationHeader,
} = NavigationExperimental

import NavigationExampleRow from "./NavigationExampleRow";

const ExampleReducer = NavigationReducer.StackReducer({
getPushedReducerForAction: (action) => {
if (action.type === 'push') {
return (state) => state || {key: action.key};
}
return null;
},
getReducerForState: (initialState) => (state) => state || initialState,
initialState: {
key: 'AnimatedExampleStackKey',
index: 0,
children: [
{key: 'First Route'},
],
},
});

class App extends Component {
constructor(props) {
super(props);
this.state = ExampleReducer();
}

componentWillMount() {
//将定义的方法绑定到当前上下文中,如果直接调用,比如在_renderCard中,需要调用_renderScene
//因为调用_renderCard是在NavigationAnimatedView中,而它是没有_renderScene
//所以会出现_rednerScene没有定义的错误
this._renderCard = this._renderCard.bind(this);
this._renderHeader = this._renderHeader.bind(this);
this._renderScene = this._renderScene.bind(this);
this._renderTitleComponent = this._renderTitleComponent.bind(this);
this._handleAction = this.handleAction.bind(this);
}

render() {
return (<View>
<NavigationAnimatedView
navigationState={this.state}
style={styles.animatedView}
onNavigate={this._handleAction}
renderOverlay={this._renderHeader}
applyAnimation={(pos, navState) => {
Animated.timing(pos, {toValue: navState.index, duration: 500}).start();
}}
renderScene={this._renderCard}
>
</NavigationAnimatedView>
</View>);
}

_renderCard(props) {
return (
<NavigationCard
{...props}
key={'card_' + props.scene.navigationState.key}
renderScene={this._renderScene}
>
</NavigationCard>
)
}
_renderHeader(/*NavigationSceneRendererProps*/ props) {
return (
<NavigationHeader
navigationProps={props}
renderTitleComponent={this._renderTitleComponent}
/>
);
}

_renderTitleComponent(/*NavigationSceneRendererProps*/ props) {
return (
<NavigationHeader.Title>
{props.scene.navigationState.key}
</NavigationHeader.Title>
);
}

_renderScene(props) {

return (
<ScrollView style={styles.scrollView}>
<NavigationExampleRow
text={props.scene.navigationState.key}
/>
<NavigationExampleRow
text="Push!"
onPress={() => {
props.onNavigate({
type: 'push',
key: 'Route #' + props.scenes.length,
});
}}
/>
<NavigationExampleRow
text="Exit Animated Nav Example"
onPress={this.props.onExampleExit}
/>
</ScrollView>
)
}

//action arguments: {index:1}
//此方法会在ExampleTabPage中的点击tab后调用
handleAction(action) {
if (!action) {
return false;
}
const newState = ExampleReducer(this.state, action);
if (newState === this.state) {
return false;
}
this.setState(newState);
return true;
}
/*可以不需要,需要在push之后,ReactNative会自动为我们添加BackAction.
handleBackAction(): boolean {
return this._handleAction({ type: 'BackAction', });
}
}
const styles = StyleSheet.create({
animatedView: {
flex: 1,
},
scrollView: {
marginTop: NavigationHeader.HEIGHT,
},
});

AppRegistry.registerComponent('App', () => App);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  react native