非常好用的导航条组件:nav-react

目录

一.简介
二.安装方式
三.基本使用方式
四.相关组件的功能说明
五.导航条的基本结构
六.简单的设置导航条的基本样式
七.设置导航条的默认配置
八.更改导航条的设置
九.隐藏导航条
十.嵌套导航条
十一.深入理解 NavConfiger 和 Navigator
十二.导航条的高级使用技巧:自定义导航条
十三.设置导航条的各种方式

内容

一.简介

nav-react 是React版本的导航条,后续还会开发 Vue、Angular 版本;
在项目中,导航条是很常用的,大多数页面都需要导航条,所以,在合理的项目结构中,应该把导航条抽离出来共用;于是,通过良好的设计,我便开发出了具备:超级可定制、可扩展、可以实现导航条根据页面内容而改变等强大能力的导航条 nav-react;

如果您在使用 nav-react 的过程中遇到了问题 或者 有更好的想法 和 建议,可以通过以下方式告诉我:

二.安装方式

目前,安装方式有以下2种:

方式1:直接下载原代码

您可直接从 nav-react的Git仓库 下载,此仓库里包含了 nav-react 和 下文的示例代码;nav-react 库在 nav-react的Git仓库 项目的 package/lib 目录下,您可以直接把 lib 目录拷贝到您的项目中去;然后使用如下代码在您的项目中引入 NavConfigerNavigator

import { NavConfiger,Navigator } from "path/to/lib/Navigator.jsx";

方式2:通过 npm 安装

npm install --save nav-react

三.基本使用方式

  1. 导入 NavConfigerNavigator:
    import { NavConfiger,Navigator } from "nav-react";
    
  2. NavConfiger 包在需要导航的内容的外层;
  3. Navigator 放在导航条的位置,并确保 Navigator 是被 NavConfiger 包住的;
    <NavConfiger>
       <div>
         <Navigator />
         <p>页面内容</p>
       </div>
    </NavConfiger>
    

效果如下:

默认

这是没有经过任何配置的 Navigator ,只有一个返回图标,点击可返回,导航条也没有背景色;我之所以没有给 Navigator 设置太多默认的样式,是因为:项目的导航条是因项目而异的,几乎没有太多共同的样式,我设置默认的样式越多,在您使用此组件时,需要修改的就越多;

四.相关组件的功能说明

  • NavConfiger : 设置 Navigator 的桥梁,为使用者提供了设置导航条的相关的方法;
  • Navigator : 导航条;

注意:

  • Navigator 必须被 包含于 NavConfiger 中;
  • NavConfiger 和 Navigator 可以不在同一组件中,即: NavConfiger 可以在 Navigator 所以在组件的祖先组件中,只要确保 Navigator 是被 NavConfiger 包着的就行;

五.导航条的基本结构

Navigator结构图

在上图所示的导航条结构中,area 表示的是导航条的布局结构,相当容器;item 是被布局的项目,所有的 area 和 item 的组合就构成了导航条;具体的解释如下:

  • navArea:就是导航条的容器,它是导航条最外层的元素;它里面有3个区域(area),分别用来放置导航条的 左、中、右 item;item 是导航条中 显示的、可操作的项目,可以是 字符串 或者 React元素;
  • leftArea:在导航条的左部,用来放置 leftItem ,如:返回按钮 等等;
  • centerArea:在导航条的中部,用来放置 centerItem ,如:导航条的标题 等等;
  • rightArea:在导航条的右部,用来放置 rightItem ;
  • leftItem:导航条左边的用来显示 或者 操作的 项目,可以是 字符串 或 React元素;
  • centerItem:导航条中间的用来显示 或者 操作的 项目,可以是 字符串 或 React元素,如:页面的标题 等等;
  • rightItem:导航条右部的用来显示 或者 操作的 项目,可以是 字符串 或 React元素;

六.简单的设置导航条的基本样式

您可以在 Navigator 上设置 classNamestyle 等普通 HTML 元素具有的 props ,Navigator 会把它设置到根元素上,即 navArea 上;如:

<Navigator className="navAreaClass" style={navAreaStyleObj} />

七.设置导航条的默认配置

可以通过 Navigator 的prop navConfig 给 Navigator 设置 默认的配置,示例如下:

<Navigator  navConfig={defaultConfigObj} />

navConfig 接受一个配置对象,该配置对象可配置的字段如下:

  • hide ?: boolean, // 设置是否显示 导航条
  • left ?: string || element, //设置leftItem
  • center ?: string || element, //设置centerItem
  • right: ?: string || element, //设置rightItem
  • leftAction ?: function, //设置 leftItem 的 click 事件处理函数
  • rightAction ?: function, //设置 rightItem 的 click 事件处理函数
  • navClass ?: string, //设置 navArea 的 css 类
  • leftAreaClass ?: string, //设置 leftArea 的 css 类
  • centerAreaClass ?: string, //设置 centerClass 的 css 类
  • rightAreaClass ?: string, //设置 rightArea 的 csss 类

示例:

Index.jsx

//导入
import React, { Component } from 'react';

import './Index.css'
import { NavConfiger, Navigator } from 'nav-react';


class Index extends Component {

    rightClickHandle(){
        alert("您点击了导航条的rightItem!");
    }

    navConfig = {
            left: "返回",
            center: "默认的标题",
            right: <p onClick={this.rightClickHandle} >弹窗</p>,
            navClass: "nav_area_default"
    };

    render() {
        return (
            <NavConfiger>
                <fieldset className="page1" >
                    <legend>基本导航</legend>
                    <Navigator  navConfig={this.navConfig} />
                </fieldset>
            </NavConfiger>

        );
    }
}

export { Index };

Index.less

.nav_area_default {
    background-color: #00ff00;
}

效果:

默认navConfig

八.更改导航条的设置

在实际的项目中,通常会有很多页面都有导航条,为每个页面都加个导航条是编程思想不成熟的一种体现,最好的做法是:把导航条抽离成一个单独的组件,并且各个页面共享一个导航条;这就是本组件 Navigator 的任务,但是通常并非所有页面的导航条都一样,比如:有些页面需要给导航条换个样式,有些页面的导航条右侧有按钮,每一个页面的标题不一样...等等,这些都是常见的情况;为了实现页面能够个性化定制导航条,Navigator 提供了这样的机制,使用方法如下:

页面组件定制导航条:

  1. 导入 PropTypes

    import PropTypes from 'prop-types';
    
  2. 给组件类添加 contextTypes 属性;

    // ES6的设置方式
    static contextTypes = {
         pushNavConfig: PropTypes.func,
         popNavConfig: PropTypes.func,
     };
    
     // 普通的添加方式
     PageComponent.contextTypes = {
         pushNavConfig: PropTypes.func,
         popNavConfig: PropTypes.func,
     };
    
    
  3. 在需要设置导航条时,通过 pushNavConfig 方法将导航条的配置对象推入导航条的配置栈;

    let navConfig = {
      left: "默认",
      center: "页面1"
     };
    this.configID = this.context.pushNavConfig(navConfig);
    
  4. 在需要移除对导航条的设置时,通过 popNavConfig 方法将相应的导航条配置对象推出导航条配置栈;

    this.context.popNavConfig(this.configID);
    

完整的示例代码如下:
Page1.jsx

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Page1 extends Component {

    // 定义 contextTypes ,以使该组件能够接收到  context
    static contextTypes = {
        pushNavConfig: PropTypes.func,
        popNavConfig: PropTypes.func,
    };

    configID;

    navConfig = {
            left: "默认",
            center: "页面1",
            navClass: "page1_nav"
    };

    componentWillMount() {
        this.configID = this.context.pushNavConfig(this.navConfig);      //设置导航条
    }

    componentWillUnmount() {
        this.context.popNavConfig(this.configID);       //移除对导航条的设置
    }

    render() {
        return (
            <fieldset className="page1" >
                <legend>页面1</legend>
                我是页面1的内容
            </fieldset>
        );
    }
}


export {Page1};

效果:

页面1

用同样的方法,再添加个页面2,让 页面2 对 导航条的配置 与 页面1 不一样,可以试下来这2个页面做切换的效果,具体代码我就不再写了,但可以导示下示例效果,如下:


页面切换

说明:

  • pushNavConfig : (navConfig)-> configId //往导航条配置栈中推入一个导航条的配置对象 navConfig,并返回配置id,该 id 可用于通过 popNavConfig 方法推出该 id 对应的配置对象;
  • popNavConfig : (configId)->void //从导航条配置栈中推出 configId 对应的配置对象;configId 是 执行 pushNavConfig 方法的返回值;
  • 在您需要改变导航条时 通过 pushNavConfig 方法设置导航条,在需要还原改变时,通过 popNavConfig 方法还原导航条;
  • 对于一个 React 组件,您可以在 constructorcomponentWillMountcomponentDidMount 这些方法中执行 pushNavConfig 推入导航条的配置对象 ,在 componentWillUnmount 中执行 pushNavConfig 推出导航条的配置对象;
  • 我个人建议:最好在 componentWillMount 或者 componentDidMount 中 执行 pushNavConfig,不建议在 constructor 中执行;

九.隐藏导航条

可以通过导航条的配置对象 navConfig 的 hide 字段来设置导航条是否需要隐藏;

十.嵌套导航条

有时在组件的层级树中可能需要多个导航条,比如:在有导航的页面中打开一个带有导航的子页面...等等; Navigator 支持这种使用场景,在 Navigator 中,这种机制叫做 嵌套导航;

嵌套导航的使用方式和普通导航的使用方式没什么区别,您只需要在原来的 NavConfiger 所包的 内容 或者子组件中 再添加一对 NavConfigerNavigator 即可;

示例代码:

Index.jsx

class Index extends Component {

    navConfig = {
        left: "上一页",
        center: "外层导航",
        right: <NavLink to={"/nested/page1"} >下一页</NavLink>,
        navClass: "nav_area_default"
    };

    render() {

        return (
            <NavConfiger>
                <fieldset className="page1" >
                    <legend>嵌套导航</legend>
                    <Navigator navConfig={this.navConfig} />
                    <div className="content_box" >
                        <p>我是外层页面的内容</p>
                        <Route path={`${this.props.match.path}/page1`} component={Page1} />
                    </div>
                </fieldset>
            </NavConfiger>

        );
    }
}

Index.css

.nav_area_default {
    background-color: #00ff00;
}

Page1.jsx

class Page1 extends Component {

    // 定义 contextTypes ,以使该组件能够接收到  context
    static contextTypes = {
        pushNavConfig: PropTypes.func,
        popNavConfig: PropTypes.func,
    };

    configID;

    navConfig = {
        left: "默认",
        center: "页面1",
        right: <NavLink to={`${this.props.match.url}/page2`} >子导航</NavLink>,
        navClass: "page1_nav"
    };

    componentWillMount() {
        this.configID = this.context.pushNavConfig(this.navConfig);      //设置导航条
    }

    componentWillUnmount() {
        this.context.popNavConfig(this.configID);       //移除对导航条的设置
    }

    render() {
        return (
            <fieldset className="page1" >
                <legend>页面1</legend>
                <p>我是页面1</p>
                <Route path={`${this.props.match.path}/page2`} component={Page2} />
            </fieldset>
        );
    }
}

Page1.css

.page1_nav {
    background-color: red;
}

Page2.jsx

class Page2 extends Component {


    static contextTypes = {
        pushNavConfig: PropTypes.func,
        popNavConfig: PropTypes.func,
    };

    configID;

    myNavConfig = {
        left: "页面1",
        center: "页面2",
        navClass: "page2_nav"
    };

    defaultNavConfig = {
        left: "上一页",
        center: "内层导航",
        right: <NavLink to={`${this.props.match.url}/page3`} >下一页</NavLink>,
        navClass: "nav_area_default"
    };

    componentWillMount() {
        this.configID = this.context.pushNavConfig(this.myNavConfig);
    }

    componentWillUnmount() {
        this.context.popNavConfig(this.configID);
    }

    render() {
        return (
            <NavConfiger>
                <fieldset className="page2" >
                    <legend>页面2</legend>
                    <Navigator navConfig={this.defaultNavConfig} />
                    <div className="content_box" >
                        <p>我是内层导航的内容</p>
                        <Route path={`${this.props.match.path}/page3`} component={Page3} />
                    </div>
                </fieldset>
            </NavConfiger>
        );
    }
}

Page2.css

.page2_nav {
    background-color: blue;
}

Page3.jsx

class Page3 extends Component {

    // 定义 contextTypes ,以使该组件能够接收到  context
    static contextTypes = {
        pushNavConfig: PropTypes.func,
        popNavConfig: PropTypes.func,
    };

    configID;

    navConfig = {
        left: "上一页",
        center: "页面3",
        right: <NavLink to={"/"} >列表</NavLink>,
        navClass: "page3_nav"
    };

    componentWillMount() {
        this.configID = this.context.pushNavConfig(this.navConfig);      //设置导航条
    }

    componentWillUnmount() {
        this.context.popNavConfig(this.configID);       //移除对导航条的设置
    }

    render() {
        return (
            <fieldset className="Page3" >
                <legend>页面3</legend>
                <p>我是页面3</p>
            </fieldset>
        );
    }
}

Page3.css

.page3_nav {
    background-color: yellow;
}

示例效果:

嵌套导航

NavConfiger 和 Navigator 的嵌套使用规则

为了使用更加简单,我把 NavConfiger 设计成可以管理多个 Navigator ,但是由于 React 在切换组件时,会先创建和挂载新组件,然后再卸载被替换掉的组件,导致这个特性只能在以下情况中使用:

  • 如果:多个 Navigator 总是同时销毁,那么:这几个 Navigator 可以共同一个 NavConfiger,也可以每个 Navigator 单独用一个 NavConfiger 管理 ;
  • 如果:多个 Navigator 并不是同时销毁,那么:应该用多个 NavConfiger 分别管理不同时销毁的 Navigator; 否则,将可能产生与您预期不符的现象;

关于造成这种使用限制的原因,可参考: 下面的《NavConfiger 和 Navigator 的工作原理》并结合 “《React特性精华》/5. 组件/组件的渲染”;

十一.深入理解 NavConfiger 和 Navigator

NavConfiger 有以下几个功能:

  • 为子组件在上下文中提供 pushNavConfigpopNavConfig 方法来 推入 和 弹出 导航条的配置对象;
  • 提供一个 导航条栈 ,管理着子组件中的导航条;

Navigator 有以下几个功能:

  • 提供一个导航条配置对象栈,管理着多个导航条的配置对象;
  • 把配置栈中栈顶的配置对象渲染成导航条;

导航条栈 和 导航条配置对象栈 的工作机制:

  • 导航条栈 由 NavConfiger 实例管理;导航条配置对象栈 由 Navigator 实例管理;
  • 每当 Navigator 实例被将要被挂载时,会把自己推入到祖先组件中最近的 NavConfiger 实例的 导航条栈 的 栈顶;
  • 每当 Navigator 实例在将根被卸载时,会把自己从祖先组件中最近的 NavConfiger 实例的 导航条栈 导航条栈中 弹出;
  • 每当在组件中调用 pushNavConfig 方法时,会把传入的配置对象推入到该组件的祖先组件中最近的 NavConfiger 实例的 导航条栈 中 位于栈顶的 Navigator 实例的 导航条配置对象栈 的栈顶;
  • 每当在组件中调用 popNavConfig 方法时,会从该组件的祖先组件中最近的 NavConfiger 实例的 导航条栈 中 位于栈顶的 Navigator 实例的 导航条配置对象栈 中弹出与传入的 configID 对应的导航条配置对象;

十二.导航条的高级使用技巧:自定义导航条

通过 navConfig 配置对象,您可以充分配置 Navigator 实例,通常,这已经能够满足绝大部分场景;尽管如此,我仍然给 Navigator 设计了允许您完全定制导航条的接口,您可以通过 Navigator 的 children 实现自定义的导航条,具体规则如下:

通过 Navigator 实例的 children 来定制 导航条的规则:

  • 当 Navigator 实例有1个 children 时,该 children 会作为整个导航条;
  • 当 Navigator 实例有2个 children 时,第1个 children 会作为导航条的 leftItem,第2个 children 会作为导航条的 rightItem;
  • 当 Navigator 实例有3个 children 时,第1个 children 会作为导航条的 leftItem,第2个 children 会作为导航条的 centerItem,第3个 children 会作为导航条的 rightItem;
  • 所有的 children 都会接收一个表示当前生效的配置对象的prop navConfig,所以,可以在自定义的 children 组件中 通过 props.navConfig 访问当前生效的导航条配置对象;

完全自定义导航条

<Navigator>
    <CustomNav />
</Navigator>

CustomNav 是您自定义的导航条,CustomNav 会接收一个表示当前生效的配置对象的prop navConfig, 所以,在 CustomNav 中,可以通过 this.props.navConfig 访问当前生效的配置对象;

示例代码:

function CustomNav(props) {
    return <p className="custom_nav" >自定义:{props.navConfig.center}</p>
}

class Index extends Component {

    rightClickHandle() {
        alert("您点击了导航条的rightItem!");
    }

    navConfig = {
        left: "返回",
        center: "默认的标题",
        right: <p onClick={this.rightClickHandle} >弹窗</p>,
        navClass: "nav_area_default"
    };


    render() {

        return (
            <NavConfiger>
                <fieldset className="page1" >
                    <legend>自定义导航</legend>
                    <Navigator navConfig={this.navConfig} >
                        <CustomNav />
                    </Navigator>
                    <div className="content_box" >
                        <Route path={`${this.props.match.path}/page1`} component={Page1} />
                        <Route path={`${this.props.match.path}/page2`} component={Page2} />
                        
                        <br />
                        <NavLink to={`${this.props.match.url}/page1`} >跳转到:页面1</NavLink>
                    </div>
                </fieldset>
            </NavConfiger>

        );
    }
}

示例效果:

自定义导航条

自定义导航条的 左部 和 右部

<Navigator>
    <Left />
    <Right />
</Navigator>

自定义导航条的 左部、中部、右部

<Navigator>
    <Left />
    <Center />
    <Right />
</Navigator>

示例代码:
Index.jsx


function Left(props) {
    return <p className="custom_nav" >左:{props.navConfig.left}</p>
}

function Center(props) {
    return <p className="custom_nav" >中:{props.navConfig.center}</p>
}

function Right(props) {
    return <p className="custom_nav" >右:{props.navConfig.right}</p>
}


class Index extends Component {

    rightClickHandle() {
        alert("您点击了导航条的rightItem!");
    }

    navConfig = {
        left: "LeftItem",
        center: "Title",
        right: "RightItem",
        navClass: "nav_area_default"
    };


    render() {

        return (
            <NavConfiger>
                <fieldset className="page1" >
                    <legend>自定义导航</legend>
                    <Navigator navConfig={this.navConfig} >
                        <Left />
                        <Center />
                        <Right />
                    </Navigator>
                    <div className="content_box" >
                        <Route path={`${this.props.match.path}/page1`} component={Page1} />
                        <Route path={`${this.props.match.path}/page2`} component={Page2} />
                        
                        <br />
                        <NavLink to={`${this.props.match.url}/page1`} >跳转到:页面1</NavLink>
                    </div>
                </fieldset>
            </NavConfiger>

        );
    }
}

Page1的 navConfig 对象:

navConfig = {
        left: "1左",
        center: "页面1",
        right:"1右",
        navClass: "page1_nav"
    };

Page2的 navConfig 对象:

navConfig = {
        left: "2左",
        center: "页面2",
        right:"2右",
        navClass: "page2_nav"
    };

示例效果:

自定义导航条Item

十三.设置导航条的各种方式

看到这里,您应该发现:有多种配置导航条的方式;现总结如下:

  • 给 Navigator 标签设置 prop <Navigator prop={propValue} />,如: classNamestyle 等等;
    这种方式只能设置导航条的样式;
  • 给 Navigator 设置默认配置对象 ,如:<Navigator navConfig={this.navConfig} >
    这种方式只是提供导航条的默认配置对象;
  • 通过context的方法 pushNavConfigpopNavConfig,来设置 导航条的配置对象;
    这种方式用于每个页面根据自己的情况配置单独配置导航条;这种方式提供的配置对象 会 覆盖 Navigator 的默认配置对象;
  • 通过 Navigator 的 children 来自定义导航条;
    这种方式可以自定义导航条,可能完全改变导航条的结构;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容