React Native NavigationExperimental - Go语言中文社区

React Native NavigationExperimental


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

  • 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,
  },
});

名词解释

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

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.

开发者可以为根容器设置一个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”组件, 就不需要每次都传递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的index0,或者路由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和要跳转到stateindex的位置
    //它返回如下的参数
    /*
      {
        ...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中的路由
  //如果当前nextStatechildren包含了上一次prevState的路由,则直接跳过
  //如果上次的路由,没有包含在新的路由中,则把它置为isStaletrue.
  //比如从案例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 = [];

  /* 
用于合并staleScenesfreshScenes中与prevScenes中上相同的值,这样就可以重用prevScenes中的场景(没有理解,可以先不管prevScenes, 即nextScenesstaleScenesfreshScenes的合集)
  */
  const mergeScene = (nextScene => {
    const {key} = nextScene;
    //prevScenes为传入的第一个参数
    
                            
                            版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/cexo425/article/details/51788671
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 20:14:03
  • 阅读 ( 1621 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢