H5W3
当前位置:H5W3 > 其他技术问题 > 正文

【前端问题精选】【请教算法】请问,这个分页算法可有优化的地方?

interface INumberPagerItem {
    url: string;
    text: string;
    page: number;
    title: string;
    target: string;
    iscurrent: boolean;
    enabled: boolean;
}
interface INumberPager {
    total: number;
    current: number;
    showcount: number;
    items: INumberPagerItem[];
}

class NumberPagerItemModel implements INumberPagerItem {
    url: string;
    text: string;
    page: number;
    target: string;
    iscurrent: boolean;
    title: string;
    enabled: boolean;

    constructor(url: string, text: string, page: number, title: string, target: string, iscurrent: boolean, enabled: boolean) {
        this.url = url;
        this.text = text;
        this.page = page;
        this.title = title;
        this.target = target;
        this.iscurrent = iscurrent;
        this.enabled = enabled;
    }
}

class NumberPagerItem extends React.Component<INumberPagerItem, any>{
    render() {
        var a = null;
        if (this.props.enabled) {
            a = <a title={this.props.title} href={this.props.url + this.props.page} className={"ws-pager " + (this.props.iscurrent ? "ws-pager-current-page" : "ws-pager-page")} target={this.props.target} >{this.props.text}</a>
        }
        else {
            a = <span title={this.props.title} className={"ws-pager " + (this.props.iscurrent ? "ws-pager-current-page" : "ws-pager-page")} >{this.props.text}</span>
        }
        return (a);
    }
}

class NumberPager extends React.Component<INumberPager, any>{
    constructor(props: INumberPager) {
        super(props);
    }

    render() {
        var items = this.props.items.map((i) => {
            return (<NumberPagerItem key={i.text.toString() + i.page.toString()} title={i.title} enabled={i.enabled} text={i.text} url={i.url} page={i.page} iscurrent={i.iscurrent} target={i.target || '_self'} />);
        });

        return (
            <span>
                {items}
            </span>);
    }
}
interface IFastPager {
    current: number;
    totalpages: number;
    fastToPage(next: number): void;
}
class FastPager extends React.Component<IFastPager, any>{
    next: number;
    constructor() {
        super();

        this.state = { current: 1, godisabled: true }
    }
    componentWillMount() {
        this.state.current = this.props.current;
        this.next = this.props.current;
    }
    handleChange(e) {
        this.next = parseInt(e.target.value);
        this.state.godisabled = this.next == this.state.current;
        this.setState(this.state);
    }
    handleClick(e) {
        if (this.next != this.state.current) {
            this.next = this.next > this.props.totalpages ? this.props.totalpages : this.next;
            this.props.fastToPage(this.next);
        }
    }
    render() {
        return (
            <span className="ws-pager-fast">
                <label>
                    到第
                <input type="number" name="ws-pager-fast-number" className="ws-pager-fast-page-number" defaultValue={this.state.current} onChange={e => this.handleChange(e)} />
                    页
                </label>
                <input type="button" className="ws-pager-fast-confrim" value="确定" disabled={this.state.godisabled} onClick={e => this.handleClick(e)} />
            </span>
        );
    }
}

interface IWsPager {
    url: string;
    target?: string;
    total: number;
    size: number;
    current: number;
    showcount: number;
    needfast: boolean;
}

interface IWsPagerState {
    current: number;
    items: INumberPagerItem[];
}

class WsPager extends React.Component<IWsPager, IWsPagerState>{
    constructor(props: IWsPager) {
        super(props);

        this.state = { current: this.props.current, items: [] };
    }
    componentWillMount() {
        let numbers: number[] = [];

        var total = parseInt((document.getElementById('ws_pager_total') as HTMLInputElement).value);
        var current = parseInt((document.getElementById('ws_pager_current') as HTMLInputElement).value);
        this.state.current = current;

        let totalpage = Math.ceil(total / this.props.size);
        let prepage = this.state.current - 1;
        let nextpage = this.state.current + 1;

        numbers.push(-10);

        if (totalpage - this.props.showcount <= 0) {
            for (var i = 1; i <= totalpage; i++) {
                numbers.push(i);
            }
        }
        else {
            let half = (this.props.showcount - 1) / 2;
            let pre = this.state.current - half;
            let next = this.state.current + half;

            numbers.push(1);

            if (pre > half) {
                numbers.push(-9);
            }
            var start = pre;
            if (pre < 2) {
                start = 2;
                var end = start + this.props.showcount > totalpage ? totalpage : start + this.props.showcount
                for (var i = start; i < end; i++) {
                    numbers.push(i);
                }
            }
            else {
                start = pre;
                if (next >= totalpage) {
                    next = totalpage - 1;
                    start = next - this.props.showcount;

                    for (var i = start; i <= next; i++) {
                        numbers.push(i);
                    }
                } else {
                    for (var i = pre; i < this.state.current; i++) {
                        numbers.push(i);
                    }

                    if (this.state.current != 1) {
                        numbers.push(this.state.current);
                    }

                    for (var i = this.state.current + 1; i <= next; i++) {
                        numbers.push(i);
                    }
                }
            }

            if (next + 1 < totalpage) {
                numbers.push(-2);
            }

            numbers.push(totalpage);
        }

        numbers.push(-1);

        var items: INumberPagerItem[] = [];

        for (var i = 0; i < numbers.length; i++) {
            var num = numbers[i];
            if (num == -10) {
                items.push(new NumberPagerItemModel(this.props.url, '上一页', prepage, '上一页', null, false, prepage > 0));
            } else if (num == -1) {
                items.push(new NumberPagerItemModel(this.props.url, '下一页', nextpage, '下一页', null, false, nextpage <= totalpage));
            } else if (num == -9) {
                items.push(new NumberPagerItemModel(null, '...', num, '向前' + this.props.showcount.toString() + "页", null, false, false));
            } else if (num == -2) {
                items.push(new NumberPagerItemModel(null, '...', num, '向后' + this.props.showcount.toString() + "页", null, false, false));
            } else {
                items.push(new NumberPagerItemModel(this.props.url, num.toString(), num, num.toString(), null, this.state.current == num, this.state.current != num));
            }
        }

        this.setState({ current: this.state.current, items: items });
    }
    handleFastToPage(e) {
        window.location.href = this.props.url + e;
    }
    render() {
        var div = null;
        if (this.props.needfast) {
            div =
                (
                    <div>
                        <NumberPager items={this.state.items} total={this.props.total} current={this.state.current} showcount={this.props.showcount} />
                        <FastPager current={this.state.current} totalpages={Math.ceil(this.props.total / this.props.size)} fastToPage={e => this.handleFastToPage(e)} />
                    </div>
                );
        } else {
            div =
                (
                    <div>
                        <NumberPager items={this.state.items} total={this.props.total} current={this.state.current} showcount={this.props.showcount} />
                    </div>
                )
        }
        return (div);
    }
}

ReactDOM.render(
    <WsPager url="/pager/index/" needfast={true} current={1} showcount={9} size={10} total={124} />,
    document.getElementById('pager')
)

目前我就是最笨的判断,if else 嵌套,可有算法优化一下?
就是计算numbers数组的值的算法。

回答:

这个问题相当的不容易回答,我只能以目前的所知的范围提供一些思路而已。

不容易回答的原因:

  • 代码中使用了TypeScript在撰写React组件,两者都要会的人不多

  • 代码中完全乎没有注解,组件/方法/运行代码并未达到能自我文档化的程度,也没有具体的说明各方法或各组件的用途

  • 这个代码的用途与对照的视觉介面、操作目的、事件触发规则,都没有额外的文档可以参考

代码的重构有几个方针可以参考:

撰写风格的统一

注释是很重要的,尤其是针对重要的某些运行代码行或方法。代码中混用了var/let/const。另外在变量的识别名的命名也有一些小地方,例如godisabled应为goDisablediscurrent应为isCurrent。比较严重的风格是在React的JSX语法,一行的长度许多超过80字元,有些行已经超过屏幕的宽度,这建议要调整,不然真的不容易维护。

分隔组件的清楚工作

既然要使用React,想必应该能理解React库是用来开发一个个的组件用的,组件是一个有上下(内外)层级的树状构造,每个组件中的state都是独立的,父子组件用props来传递数据,细节不多说网上教程很多。但首要的是需要先清楚各组件/模组该作的事。回到原来的代码,这里面的这些组件都有没有分割清楚的问题,像FastPager应该是个带有个文字框,然后按下按钮跳到第几页的功能,它就单纯作这事就行。以React的组件设计观点来说,FastPager必然只是个子组件,它这中只会用来呈现这操作介面,然后准备让用户触发click事件,向父层说现在要跳转到第几页了,其它的事与它无关。现在的FastPager里除了有state之外,还会传入totalpages(总页数),FastPager并不需要知道总共有多少页,甚至连现在是第几页(current)都不需要,检查这事是它的父组件的触发相关方法中就行了。另外关于NumberPagerNumberPagerItem,它们的工作与FastPager也很像,是用来跳转页面用的,但只是操作介面呈现上的差异,但现在父组件WsPager中出现相当复杂的算法,也是因为工作不明所造成的,要优化这段代码,就是要从这两者的工作分隔著手。

掌控数据流&生命周期

数据是从什么地方来的?事件是如何被触发的,触发时要调用哪个方法?数据流是整个应用的核心,数据该集中在何处,事件该在何处撰写它的业务逻辑,这些都是很重要的。

在React中,state与props的角色完全不同,state是一个状态机,记录了这个组件的目前与可能变动的状态。更动state代表著会触发重新渲染。以一个小型的React应用来说,大概只会在最上层的父组件有state,子组件中会避免使用state,state并不是都需要的,子组件中的state更动不见得会触发整个应用的重新渲染,这机制应该要理解。

componentWillMount是一个React中的生命周期方法,在componentWillMount中调用setState方法并不会触发重新渲染,所以是用在与呈现无关的数据上使用。在componentDidMount方法里中调用setState方法会触发重新渲染。但它们都只在组件挂载到网页上调用一次而已,之后的更新期间并不会再调用,所以大概的用途是组件初始时加载数据用的,尤其是向外部服务器加载数据等等的工作。

由简单开始改起

一开始的应用代码必定是很粗糙简单,也没什么太大的配置弹性。对于所有的应用来说,一次就要到位是不太可能作得到的,这个题目的代码已经作了相当程度的配置弹性,也就是说它已经在撰写时决定了要这些配置,当然这也让它增加了许多复杂的部份。为了要让整体的代码作重构,应该先把部份重要性不高的配置先移除,例如风格之类的,或是要能某些弹性配置的值。

简化复杂的运算

复杂的运算结构,或许是能达成目前的需求,例如内嵌(巢状)的for逥圈、递归、大量的if判断等等,但它们都有一定的复杂程度。在维护与未来扩展上,都会造成一定的困难程度,简化是必要的,如果因为过多的功能造成代码的复杂,那么是应退一步思考,看是先把某几个部份的功能分拆,或是某些功能上的调整,避免产生复杂的运算结构。

以上都是概念性的说词,只是一些感想而已。


上面废话说了太多,实际上直接看这应用在React里怎么实作才有用,因为我对题目中的代码也是看了许久一知半解,所以只能以所想到的几个功能来写,下面只是简单的演示,提供一些思路供参考,我在代码里面都有加注释了,看不懂再问吧。

演示的样子(图中的文字框输入触发事件后,应该要强制转为数字,代码已经改过了,图就不改了):

演示

App.js

import React from 'react'
import FastPager from './FastPager'
import ItemList from './ItemList'
import NumberPager from './NumberPager'

class App extends React.Component {

  constructor() {
    super()

    // items 范例数据
    // displayItems 转化过的数组,每个成员代表每页呈现的资料
    // currentPage 目前页数 (currentPage - 1)
    //  即为displayItems中的数组索引值
    // size 每页数量
    // showCount 向前或后的跳页数量
    this.state = {
      items: [],
      displayItems: [],
      currentPage: 1,
      size: 10,
      showCount: 9,
      warningMessage: '',
    }
  }

  // 初始化演示数据使用
  componentWillMount() {
    const items = []

    for (let i = 1, obj = {}; i < 1000; i ++) {
      obj = { id: i, text: `范例文章标题 ${i}` }
      items.push(obj)
    }

    const displayItems = []

    // 下面这是把所有项目分组,1维数组转2维数组
    for (let i = 0; i < items.length; i += this.state.size) {
      displayItems.push(items.slice(i, i + this.state.size))
    }

    this.setState({ items, displayItems })
  }

  // 处理FastPager的按钮点按,只有一个传参,代表要跳到某个页面
  handleButtonClick = (goToPage) => {
    // 检查要跳转的页面数值,小于1或大于总页数 不允许
    if (goToPage < 1 || goToPage > this.state.displayItems.length) {
      this.setState({ warningMessage: '小于页数或超出最大页数' })
      return
    }

    // 更动目前页面数值
    this.setState({ currentPage: +goToPage, warningMessage: '' })
  }

  // 处理NumberPager的每个连结点按,两个传参
  // direction: 'previous' | 'next' 代表向前或向后
  // goToPageNumber: number 代表要跳几页
  handleLinkClick = (direction, goToPageNumber) => {
    // 检查是不是超过页面数值
    if (direction === 'next'
      && (this.state.currentPage + goToPageNumber) > this.state.displayItems.length) {
      this.setState({ warningMessage: '超出最大页数' })
      return
    }

    // 检查是不是少过页面数值
    if (direction === 'previous'
      && (this.state.currentPage - goToPageNumber) < 1) {
      this.setState({ warningMessage: '小于页数' })
      return
    }

    const currentPage = (direction === 'previous')
      ? this.state.currentPage - goToPageNumber
      : this.state.currentPage + goToPageNumber

    this.setState({ currentPage, warningMessage: '' })
  }

  render() {
    return (
        <div>
          <ItemList items={this.state.displayItems[this.state.currentPage - 1]} />
          <NumberPager onLinkClick={this.handleLinkClick} count={this.state.showCount} />
          <FastPager onButtonClick={this.handleButtonClick} currentPage={this.state.currentPage} />
          <div>目前在第 {this.state.currentPage} 页,共有 {this.state.displayItems.length} 页</div>
          {
            (this.state.warningMessage) && <div style={{ color: 'red' }}>警告: {this.state.warningMessage}</div>
          }
        </div>
    )
  }
}

export default App

ItemList.js

import React from 'react'

const ItemList = (props) => (
    <ul>
      {
        props.items.map((item) => (
          <li key={item.id}>{item.text}</li>
        ))
      }
    </ul>
)

export default ItemList

FastPager.js

import React from 'react'

const FastPager = (props) => {
  let titleField = null

  return (
    <span className="ws-pager-fast">
        <label>
          到第
          <input
            type="number"
            name="ws-pager-fast-number"
            defaultValue={props.currentPage}
            ref={el => { titleField = el }}
          />
          页
        </label>
        <input
          type="button"
          value="确定"
          onClick={() => {
            if (titleField.value.trim()) {
              // 触发跳转页面
              props.onButtonClick(titleField.value)
            }
          }
          }
        />
    </span>
  )
}

export default FastPager

NumberPager.js

import React from 'react'

const NumberPager = (props) => (
    <ul className="navlist">
      <li>
        <a href='#' onClick={() => { props.onLinkClick('previous', 1) }}>
          上一页
        </a>
      </li>
      <li>
        <a href='#' onClick={() => { props.onLinkClick('previous', props.count) }}>
          向前 {props.count} 页
        </a>
      </li>
      <li>
        <a href='#' onClick={() => { props.onLinkClick('next', props.count) }}>
          向后 {props.count} 页
        </a>
      </li>
      <li>
        <a href='#' onClick={() => { props.onLinkClick('next', 1) }}>
          下一页
        </a>
      </li>
    </ul>
)

export default NumberPager

回答:

几个问题

  1. document.getElementById(‘ws_pager_total’)不应该出现

  2. 分页你的需求如果够了这样也可以,不然还要再拆分原子化

  3. 最好支持 smart 和 dumped

本文地址:H5W3 » 【前端问题精选】【请教算法】请问,这个分页算法可有优化的地方?

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址