码迷,mamicode.com
首页 > 其他好文 > 详细

React Native NavigationExperimental

时间:2016-07-03 19:59:55      阅读:222      评论:0      收藏:0      [点我收藏+]

标签:

Overview

NavigationExperimental是react native的一个新的导航系统,重点是改进<Navigator/>组件.

  • 单向数据流, 它使用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一起使用). 那么在两者之间进行导航过渡,会变得非常困难.

  • NavigationRootContainer允许导航的各个状态(屏幕)保存在app的最顶层.
    • 使用reducer在导航状态中声明设置转换过渡
    • 可以将state永久保存存到硬盘,这样刷新和app更新后,还能获得之前的导航状态
    • 监听打开中的url链接,BackAndroid便于支持返回按纽
  • NavigationReducers 包含了预置的reducers, 用来管理导航状态之间的转换过渡。
    • Reducers可以彼此之前进行组合,设置更高级的导航逻辑
  • 导航逻辑可以用于任何的视图
  • NavigationAnimatedView 是一个用来管理不同场景动画的组件,也可以用于Navigator和NavigatorIOS组件
    • 每一个scene可以完全自定义,并且管理它自己的动画和手势
    • 可以有一个Overlay/header, 用于跟场景的动画同步
  • NavigationCard 和NavigationHeader可以作为预编译的scenes和overlays. 然后跟NavigationAnimatedView一起使用
  • 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
  • 包含一 个专有的API, 不能很好的兼容其它的app
  • API很小,所以限制了它对Navigator or NavigationStackView的自定义
  • 开发这个组件不是React Native团队,而是属于开源的社区
    • 有很多积压的bug
    • 如果社区将它重构为声明性的(declarative), 它将跟NavigationExperimental一起使用的很好
  • 它在iOS UIKit的基础上包装的, 所以它跟其它的本地app是一样的。
  • 仅支持iOS
  • 包含一个默认的navigation bar. 这个navigation bar不是一个React Native view组件,它的样式只能轻微调整

我们通过一个简单的聊天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,
  },
});

你整个app的导航状态(state)可以被NavigationStates模式化, 一个NavigationState是一个对像。

const myState = {
  key: ‘myPage0‘,
  myType: ‘ExamplePage‘,
  myParams: {foo:‘bar‘},
}

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

const myState = {
  key: ‘myAppTabs‘,
  routes: [
    {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.

开发者可以为根容器设置一个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被自定义.

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

<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,
    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,
    routes: [
      {key: ‘Home‘},
      {key: ‘Person0‘, name: ‘Christopher‘},
    ]
  }

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

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

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.

Find Reducer

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

Views

最简单的视图是render当前sub-state的场景(scene). 常用于tabs, 因为它不需要转换。

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

NavigationCard和NavigationHeader就是场景和叠加的NavigationAnimateView。这是为了看起来跟iOS或android一样。

<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>
  )}
/>
<NavigationAnimatedView
  navigationState={navigationState}
  renderOverlay={(props) => (
    <NavigationHeader
      navigationState={props.navigationParentState}
      position={props.position}
      getTitle={state => state.key}
    />
  )}
  renderScene={this._renderScene}
/>

包装了NavigationAnimateView,可以为每一个Scene,render一个NavigationCard. 类似于过时的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‘},
        ]
      }}
    />
  );
}

UIExplorer Example中的入口文件为UIExplorerApp.android.js,在这个文件中,主要的也是App的一些初始化操作,比如state, reducer等导航相关 设置。

React Native NavigationExperimental

标签:

原文地址:http://blog.csdn.net/cexo425/article/details/51788671

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!