新手理解Navigator的教程




  • 注意!从RN 0.43版本开始,官方将停止维护Navigator,建议大家迁移到新的react-navigation库(文档地址需翻墙)。新的导航库无论从性能还是易用性上都要大大好于老的Navigator!

    下面是一个简单的例子,用Navigator来跳转页面,页面之间传递参数 (代码是ES6语法写的):

    		
    	import React from 'react';
        import {
    		View,
    		Navigator
    	} from 'react-native';
    	import FirstPageComponent from './FirstPageComponent';
    	
    	export default class SampleComponent extends React.Component {
    		render() {
    			let defaultName = 'FirstPageComponent';
    			let defaultComponent = FirstPageComponent;
    			return (
    	        <Navigator
    	          initialRoute={{ name: defaultName, component: defaultComponent }}
    	          configureScene={(route) => {
    	            return Navigator.SceneConfigs.VerticalDownSwipeJump;
    	          }}
    	          renderScene={(route, navigator) => {
    	            let Component = route.component;
    	            return <Component {...route.params} navigator={navigator} />
    	          }} />
    			);
    		}
    	} 
    

    这里来解释一下代码:

    第三行: 一个初始首页的component名字,比如我写了一个component叫HomeComponent,那么这个name就是这个组件的名字【HomeComponent】了。

    第四行: 这个组件的Class,用来一会儿实例化成 <Component />标签

    第七行: initialRoute={{ name: defaultName, component: defaultComponent }} 这个指定了默认的页面,也就是启动app之后会看到界面的第一屏。 需要填写两个参数: name 跟 component。(注意这里填什么参数纯粹是自定义的,因为这个参数也是你自己发自己收,自己在renderScene方法中处理。我们这里示例用了两个参数,但其实真正使用的参数只有component)

    第八,九,十行: configureScene={() => {
    return Navigator.SceneConfigs.VerticalDownSwipeJump;
    }} 这个是页面之间跳转时候的动画,具体有哪些?可以看这个目录下,有源代码的: node_modules/react-native/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js

    最后的几行: renderScene={(route, navigator) => {
    let Component = route.component;
    return <Component {...route.params} navigator={navigator} />
    }} />
    );

    这里是每个人最疑惑的,我们先看到回调里的两个参数:route, navigator。通过打印我们发现route里其实就是我们传递的name,component这两个货,navigator是一个Navigator的对象,为什么呢,因为它有push pop jump...等方法,这是我们等下用来跳转页面用的那个navigator对象。

    return <Component {...route.params} navigator={navigator} />
    

    这里有一个判断,也就是如果传递进来的component存在,那我们就是返回一个这个component,结合前面 initialRoute 的参数,我们就是知道,这是一个会被render出来给用户看到的component,然后navigator作为props传递给了这个component。

    所以下一步,在这个FirstPageComponent里面,我们可以直接拿到这个 props.navigator:

    import React from 'react';
    import {
    	View,
    	Navigator
    } from 'react-native';
    
    import SecondPageComponent from './SecondPageComponent';
    
    export default class FirstPageComponent extends React.Component {
    	constructor(props) {
    		super(props);
    		this.state = {};
    	}
    	_pressButton() {
    		const { navigator } = this.props;
    		//为什么这里可以取得 props.navigator?请看上文:
    		//<Component {...route.params} navigator={navigator} />
    		//这里传递了navigator作为props
    		if(navigator) {
    			navigator.push({
    				name: 'SecondPageComponent',
    				component: SecondPageComponent,
    			})
    		}
    	}
    	render() {
    		return (
    			<View>
    				<TouchableOpacity onPress={this._pressButton.bind(this)}>
    					<Text>点我跳转</Text>
    				</TouchableOpacity>
    			</View>
    		);
    	}
    }
    

    这个里面创建了一个可以点击的区域,让我们点击可以跳到SecondPageComponent这个页面,实现页面的跳转。
    现在来创建SecondPageComponent,并且让它可以再跳回FirstPageComponent:

    import React from 'react';
    import {
    	View,
    	Navigator
    } from 'react-native';
    
    import FirstPageComponent from './FirstPageComponent';
    
    export default class SecondPageComponent extends React.Component {
    
    	constructor(props) {
    		super(props);
    		this.state = {};
    	}
    
    	_pressButton() {
    		const { navigator } = this.props;
    		if(navigator) {
    			//很熟悉吧,入栈出栈~ 把当前的页面pop掉,这里就返回到了上一个页面:FirstPageComponent了
    			navigator.pop();
    		}
    	}
    
    	render() {
    	return (
    			<View>
    				<TouchableOpacity onPress={this._pressButton.bind(this)}>
    					<Text>点我跳回去</Text>
    				</TouchableOpacity>
    			</View>
    	);
    	}
    }
    

    大功告成,能进能出了。

    关于官方文档里有个东西,这里说一下:

    getCurrentRoutes() - 获取当前栈里的路由,也就是push进来,没有pop掉的那些
    jumpBack() - 跳回之前的路由,当然前提是保留现在的,还可以再跳回来,会给你保留原样。
    jumpForward() - 上一个方法不是调到之前的路由了么,用这个跳回来就好了
    jumpTo(route) - Transition to an existing scene without unmounting
    push(route) - Navigate forward to a new scene, squashing any scenes that you could jumpForward to
    pop() - Transition back and unmount the current scene
    replace(route) - Replace the current scene with a new route
    replaceAtIndex(route, index) - Replace a scene as specified by an index
    replacePrevious(route) - Replace the previous scene
    immediatelyResetRouteStack(routeStack) - Reset every scene with an array of routes
    popToRoute(route) - Pop to a particular scene, as specified by its route. All scenes after it will be unmounted
    popToTop() - Pop to the first scene in the stack, unmounting every other scene
    

    这些都是navigator可以用的public method,就是跳转用的,里面有些带参数的XXX(route),新手第一次看这个文档会疑惑,这个route参数是啥呢,这个route就是:

    renderScene={(route, navigator) => 
    

    这里的route,最基本的route就是:

    var route = {
    	component: LoginComponent
    }
    

    这种格式。这个地方有点模糊的,在这里先说清楚了。

    然后下面要讨论,怎么传递参数过去,或者从对方获取参数。
    传递参数,通过push就可以了。
    比如在一个 press的事件里:

    //FirstPageComponent.js
    import React from 'react';
    import {
    	View,
    	Navigator
    } from 'react-native';
    
    import SecondPageComponent from './SecondPageComponent';
    
    export default class FirstPageComponent extends React.Component {
    
        constructor(props) {
        	super(props);
        	this.state = {
        		id: 2
        	};
        }
    
    
        _pressButton() {
            const { navigator } = this.props;
            if(navigator) {
                navigator.push({
                    name: 'SecondPageComponent',
                    component: SecondPageComponent,
                    //这里多出了一个 params 其实来自于<Navigator 里的一个方法的参数...
                    params: {
                    	id: this.state.id
                    }
                });
            }
        }
    
        render() {
        	return (
    	    	<View>
    	            <TouchableOpacity onPress={this._pressButton.bind(this)}>
    	                <Text>点我跳转并传递id</Text>
    	            </TouchableOpacity>
    	       	</View>
        	);
        }
    }
    

    params的来历:

    // index.ios.js
    <Navigator
      initialRoute={{ name: defaultName, component: defaultComponent }}
      configureScene={() => {
        return Navigator.SceneConfigs.VerticalDownSwipeJump;
      }}
      renderScene={(route, navigator) => {
        let Component = route.component;
        if(route.component) {
        	//这里有个 { ...route.params }
        	return <Component {...route.params} navigator={navigator} />
        }
      }} />
    

    这个语法是把 routes.params 里的每个key 作为props的一个属性:

    navigator.push({
    	name: 'SecondPageComponent',
    	component: SecondPageComponent,
    	params: {
    		id: this.state.id
    	}
    });
    

    这里的 params.id 就变成了 <Navigator id={} 传递给了下一个页面。
    所以 SecondPageComponent就应该这样取得 id:

    //SecondPageComponent.js
    import React from 'react';
    import {
    	View,
    	Navigator
    } from 'react-native';
    
    import FirstPageComponent from './FirstPageComponent';
    
    export default class SecondPageComponent extends React.Component {
    
    	constructor(props) {
    		super(props);
    		this.state = {
    			id: null
    		}
    	}
    
    	componentDidMount() {
    		//这里获取从FirstPageComponent传递过来的参数: id
    		this.setState({
    			id: this.props.id
    		});
    	}
    
    	_pressButton() {
    		const { navigator } = this.props;
    		if(navigator) {
    			navigator.pop();
    		}
    	}
    
    	render() {
    		return (
    			<View>
    				<Text>获得的参数: id={ this.state.id }</Text>
    				<TouchableOpacity onPress={this._pressButton.bind(this)}>
    					<Text>点我跳回去</Text>
    				</TouchableOpacity>
    			</View>
    		);
    	}
    }
    

    这样在页面间传递的参数,就可以获取了。


    @sunnylqm 很多人不理解这里的params,我忍不住稍微补充一下。
    0_1453170797112_QQ图片20160119102616.png


    然后就是返回的时候,也需要传递参数回上一个页面:
    但是navigator.pop()并没有提供参数,因为pop()只是从 [路由1,路由2,路由3。。。]里把最后一个路由踢出去的操作,并不支持传递参数给倒数第二个路由,这里要用到一个概念,把上一个页面的实例或者回调方法,作为参数传递到当前页面来,在当前页面操作上一个页面的state:

    这是一个查询用户信息的例子,FirstPageComponent传递id到SecondPageComponent,然后SecondPageComponent返回user信息给FirstPageComponent

    //FirstPageComponent.js
    import React from 'react';
    import {
    	View,
    	Navigator
    } from 'react-native';
    
    import SecondPageComponent from './SecondPageComponent';
    
    export default class FirstPageComponent extends React.Component {
    
        constructor(props) {
        	super(props);
    
        	this.state = {
        		id: 2,
            user: null,
        	}
        }
    
    
        _pressButton() {
        	let _this = this;
            const { navigator } = this.props;
            if(navigator) {
                navigator.push({
                    name: 'SecondPageComponent',
                    component: SecondPageComponent,
                    params: {
                    	id: this.state.id,
                    	//从SecondPageComponent获取user
                    	getUser: function(user) {
                    		_this.setState({
                    			user: user
                    		})
                    	}
                    }
                });
            }
        }
    
        render() {
        	if( this.state.user ) {
    			return(
    				<View>
    					<Text>用户信息: { JSON.stringify(this.state.user) }</Text>
    				</View>
    	    	);
        	}else {
    			return(
    				<View>
    					<TouchableOpacity onPress={this._pressButton.bind(this)}>
    					    <Text>查询ID为{ this.state.id }的用户信息</Text>
    					</TouchableOpacity>
    				</View>
    	    	);
        	}
    
        }
    }
    

    然后再操作SecondPageComponent:

    //SecondPageComponent.js
    
    const USER_MODELS = {
    	1: { name: 'mot', age: 23 },
    	2: { name: '晴明大大', age: 25 }
    };
    
    import React from 'react';
    import {
    	View,
    	Navigator
    } from 'react-native';
    
    import FirstPageComponent from './FirstPageComponent';
    
    export default class SecondPageComponent extends React.Component {
    
        constructor(props) {
        	super(props);
        	this.state = {
        		id: null
        	}
        }
    
        componentDidMount() {
            //这里获取从FirstPageComponent传递过来的参数: id
            this.setState({
                id: this.props.id
            });
        }
    
        _pressButton() {
    			const { navigator } = this.props;
       			
    			if(this.props.getUser) {
    				let user = USER_MODELS[this.props.id];
    				this.props.getUser(user);
    			}
    			    
    			if(navigator) {
    			    navigator.pop();
    			}
        }
    
        render() {
        	return(
    	        <View>
    	            <Text>获得的参数: id={ this.state.id }</Text>
    	            <TouchableOpacity onPress={this._pressButton.bind(this)}>
    	                <Text>点我跳回去</Text>
    	            </TouchableOpacity>
    	        </View>
          	);
        }
    }
    

    看下效果如何吧。

    放个类似的例子代码: https://github.com/mozillo/navigation
    安装方法: npm install && react-native run-android



  • 能不能给代码加个行号啊。



  • @小木匠ang 这个要论坛支持codehighlight



  • @mozillo 哦哦,我以为你是建论坛的人里的呢。只是建议下,其实有没有也无所谓



  • return <Component {...route.params} navigator={navigator} />
    ...是省略的意思吗?省略了哪些属性?



  • @星星 这是展开的语法 route.params就是后面你 push时候

    push({
      name: 'Home',
      component: Home,
      params: {
        id: 100,
       ...
      }
    })
    

    这里的东西,类似url里的 ?params[id]=100



  • @mozillo 说:

    是RN提供的一个路由工具,不过缺陷是没有预设一个路由配置文件,

    @小木匠ang



  • @messi 这是我刚学rn时候的感觉,我也不是很了解,像我开发的时候,一般会创建一个router对象,因为有配置路由再开发MVC的习惯,会在router里写一些路由,最后像router.openHome(props)或者router.openProfile(props)这样去调用



  • @mozillo 说:

    r defaultComponen

    代码里面的export可以也写出来吗?新手表示看晕





  • @JYoung_Li 说:

    好,周末我再精简一下



  • Navigator里的手势只能用于跳转已经创建的Page, 不知道能不能有什么trick可以用向右的手势来创造(push或者replace)新的page



  • @Kiri 可以自己处理手势然后调用navigator.push或replace



  • 有什么办法能用Navigator外的控件来控制Navigate的转跳吗,比如

    <Navigator ... />
    <Button onPress={/*控制Navigator内的页面转跳*/} />
    


  • @LzxHahaha 将Navigator暴露出来,比如

    <Navigator ref={nav => global.nav = nav} ... />
    <Button onPress={/* nav.push/pop  */} />
    


  • @sunnylqm 嗯,问题解决了,谢谢



  • 此回复已被删除!


  • 此回复已被删除!


  • 可以给个demo吗 楼主



  • 绝赞~!学习了!