DevUI is a team with both design and engineering perspectives, serving huawei Cloud DevCloud platform and several huawei internal middle and background systems, as well as designers and front-end engineers. Design Ng Component library: Ng-devUI
The introduction
“He felt it, he recorded it at noon, at dusk, at many times of the day, and as a result we saw so many differences. The Angle from which he paints it remains the same, but its face has changed dramatically.”
Monet, the famous impressionist painter of the 19th century, liked to paint different colors of the same scene at different times and under different lights.
Like three aspens in different seasons:
Like the Cathedral of Fuweng at different times of the day:
What difference does it make if the same component is implemented by a different framework?
With this in mind, I used three of the most popular Vue/React/Angular frameworks to implement a simple Pagination component.
1 Component Requirements
The paging component we want to implement looks like this:
The main functions are as follows:
- Click the left and right paging button to jump to the previous/next page;
- Clicking the page number button in the middle may jump to the corresponding page number;
- The back page of the home page should always be displayed (if only 1 page is displayed, the back page is not displayed);
- In addition to the first and last pages, a maximum of 2 pages (a total of 5 pages) can be displayed about the current page number.
- If there are too many pages, the more page number button will be displayed. Click the more page number button to jump to 5 pages.
2 Module Design
As can be seen from the design draft, Pagination component is mainly composed of two modules:
- Button – Left and right paging Button
- Pager – The middle Pager
3 Empty Pagination component
We create the component ina top-down fashion by creating an empty Pagination component.
Note ⚠ ️
The version of the framework I used is as follows:
3.1 the Vue
Create a basic Vue project using the Vue CLI and start it up with the NPM run serve command.
Then create a pagination folder in the Components folder and create 3 components files:
- Button component – button.vue
- Pager Component – Pager. Vue
- Pagination component – pagination.vue
Add the following code to the pagination. vue file:
<template>
<div class="x-pagination"</div> </template> <script>export default {
name: 'Pagination'}; </script>Copy the code
The characteristic of the Vue component is that the HTML/CSS/JavaScript are all unified in a file with the.vue suffix.
It’s a natural fit for front-end developers who are used to writing HTML/CSS/JavaScript separately, and with its concise syntax and low barriers to entry, Vue has taken the world by storm since its launch in 2014.
Use Pagination in views/ home.vue:
<template>
<div class="home">
<img alt="Vue logo" src=".. /assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App by kagol"/>
<Pagination />
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue';
import Pagination from '@/components/pagination/Pagination.vue';
export default {
name: 'home',
components: {
HelloWorld,
Pagination,
},
};
</script>Copy the code
Components are also used much like normal HTML elements:
<Pagination />Copy the code
Note that you need to declare a Vue local component in Components before you can use it.
This is an empty component that only shows the “Pagination component” text, so it doesn’t mean much, but don’t worry, we will refine this component to achieve the functionality we want, and it will continue to expand and evolve. Before moving on to the Vue version of the Pagination component, let’s look at how other frameworks implement and use a component.
Here’s what it looks like:
3.2 the React
Looking at the React framework first, we also Create a basic React project using the Create React App and start it with the NPM start command.
As with the Vue project, create the following three component files:
- Button component – button.js
- Pager component – Pager. Js
- Pagination component – pagination.js
Add the following code to the pagination. js file:
import React from 'react';
function Pagination() {
return (
<div className="x-pagination"Word-break: break-word! Important; "> < div style =" text-align: center; }export default Pagination;Copy the code
React promotes Functional Programming (FP). Each React component is a function. HTML/CSS/JavaScript is inside the function, and the template content is returned inside the function.
Note ⚠️ that the class of HTML elements in React needs to be written as className. The reason is that class is a reserved keyword in JavaScript, and JSX used by React is an extension of JavaScript. Using class will cause naming conflicts.
React is a very special way of writing it, and you might not get used to it as a beginner, but once you get used to it, it’s pretty cool, and it makes sense, and that’s how the components should be written.
Use Pagination component in app.js:
import React from 'react';
import Pagination from './components/pagination/Pagination';
import './App.scss';
function App() {
return (
<div className="App">
<Pagination />
</div>
);
}
export default App;Copy the code
Using the React component is also simple, similar to using regular HTML elements:
<Pagination />Copy the code
The display is the same as the Vue version.
Version 3.3 presents
Unlike Vue/React, which is a lightweight framework that focuses on the View layer, Angular is a heavy framework that is fully equipped. Everything you need for Web development is provided by the Angular framework, and you can just grab it and use it.
Let’s see how to develop an Angular component.
Create a basic Angular project using the Angular CLI and start it with the NPM start command.
Unlike React/Vue components, Angular components can’t be used separately and need to package a layer of modules, so we need to create 1 Module file and 3 component files:
- Pagination module – pagination.module.ts
- Button components – button.component.ts
- Paginator component – pager.component.ts
- Pagination Component – pagination.component.ts
HTML/CSS can be in A TS file or in a separate file.
In general, when HTML/CSS content is small, it is put into TS files.
To create the Pagination module, add the following code to the pagination.module.ts file:
import { NgModule } from "@angular/core";
@NgModule()
export class PaginationModule { }Copy the code
Next, create the Pagination component and add the following code to the pagination.component.ts file:
import { Component } from "@angular/core";
@Component({
selector: 'x-pagination',
template: `
<div class="x-pagination"> Pagination </div> ',})export class PaginationComponent { }Copy the code
The obvious difference between Angular and Vue/React has been shown:
First, a component needs to exist based on a Module.
Then we need to use decorators whether we’re defining a Module or a Component.
For example, defining an Angular module requires the @NgModule decorator, and defining an Angular Component requires the @Component decorator.
Almost everything in Angular is classes and objects. In addition to modules and components, services, pipes, and so on are classes.
To use the Pagination component, we need to import the Pagination module first, declare the Pagination component, and add the following code to the app.module.ts file:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { PaginationModule } from './components/pagination/pagination.module';
import { PaginationComponent } from './components/pagination/pagination.component'; @NgModule({declarations: [AppComponent, PaginationComponent, // declare Pagination), imports: [BrowserModule, PaginationModule, // Import Pagination module], providers: [], bootstrap: [AppComponent]})export class AppModuleCopy the code
To use the Pagination component, add the following code to the app.component.ts file:
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml; base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwM zEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwM DJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iT TEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwL jl6IiAvPgogIDwvc3ZnPg==">
</div>
<x-pagination></x-pagination>Copy the code
Angular components are used in a similar way to normal HTML elements:
<x-pagination></x-pagination>Copy the code
Displays the same effect as Vue/React.
4 List components and fake data
Before adding the actual paging functionality we need to make a List component that simulates the display of paging data.
Based on the way we introduced the three frameworks to implement components, and with a little extra knowledge, we can quickly make a List of data rendering components.
Let’s look at the Vue framework first.
4.1 the Vue
Create a new list. vue component file and enter the following code:
<template>
<ul>
<li v-for="list in lists" :key="list.id">
{{ list.name }}
</li>
</ul>
</template>
<script>
export default {
name: 'List',
props: {
dataSource: Array
},
data() {
return{lists: this.dataSource}}, watch: {lists: this.dataSource}}, watch: {lists: this.dataSource}}, watch: {lists: this.dataSource}}, watch: {lists: this.dataSource}}, watch: {lists: this.dataSource}} { handler(newValue, oldValue) { this.lists = newValue; }}}}; </script>Copy the code
In the template section, we use Vue’s V-for directive to loop through the lists array in the Li element and display the name value. Where :key is the short form of V-bind :key, which binds a unique key value to an element and is used for performance optimization during DOM comparison.
1) Pass data through props
Originally I was going to put the lists value directly into props and pass it in externally, as follows:
<template>
<ul>
<li v-for="list in lists" :key="list.id">
{{ list.name }}
</li>
</ul>
</template>
<script>
export default {
name: 'List',
props: {
lists: Array
}
};
</script>Copy the code
The problem is that if the lists passed in from the outside change, the lists bound in the template will not change accordingly.
2) Maintain internal state
To listen for changes in the values of props, I put lists in the component’s internal state (data), and called the dataSource as follows:
<script>
export default {
name: 'List',
props: {
dataSource: Array
},
data() {
return {
lists: this.dataSource
}
},
};
</script>Copy the code
3) Listen for changes in the external props
Then listen for changes to the dataSource, and when the dataSource changes, assign the new value to lists:
{handler(newValue, oldValue) {this.lists = newValue;} }}}Copy the code
The lists array passed to the List component is as follows:
export const lists = [
{ id: 1, name: 'Curtis' },
{ id: 2, name: 'Cutler' },
{ id: 3, name: 'Cynthia' },
{ id: 4, name: 'Cyril' },
{ id: 5, name: 'Cyrus' },
{ id: 6, name: 'Dagmar' },
{ id: 7, name: 'Dahl' },
{ id: 8, name: 'Dahlia' },
{ id: 9, name: 'Dailey' },
{ id: 10, name: 'Daine'},];Copy the code
Display data using the List component:
<List :data-source="lists" />Copy the code
Note ⚠️ that all bound data needs to be named with a dotted line, such as the data-source above, which corresponds to the data dataSource with the hump name.
The effect is shown as follows:
4.2 the React
React writes function components, props changes are reflected directly in the template and don’t need to be listened to separately, so it’s very simple to write:
import React from 'react';
function List({ dataSource }) {
return (
<ul className="m-list">
{
dataSource.map(list => {
return <li key={ list.id }>{ list.name }</li>;
})
}
</ul>
);
}
export default ListCopy the code
The external data is passed through the function’s props parameter. Here, we destruct the props directly to the dataSource field.
React is also different from Vue. React is a functional programming method. It does not require v-for or other instructions to render list data, but directly returns the corresponding Li element through the map method of array. The key value bound to the li element is similar to the key value in Vue.
Use similar to Vue:
<List dataSource={dataSource} />Copy the code
Version 4.3 presents
Angular is a bit more cumbersome, requiring both modules and Components to be defined:
- List module – list.module.ts
- List component: list.component.ts
Write list.module.ts first:
import { NgModule } from "@angular/core";
@NgModule()
export class ListModule { }Copy the code
Then write the List component list.component.ts:
import { Component, Input } from "@angular/core";
@Component({
selector: 'x-list',
template: `
<ul>
<li *ngFor="let list of dataSource; trackBy: trackByIndex">
{{ list.name }}
</li>
</ul>
`,
})
export class ListComponent {
@Input() dataSource;
trackByIndex(index, list){
returnlist.id; }}Copy the code
Angular is different from Vue/React:
- One is that external parameters are passed differently. Angular uses the @Input decorator to represent external parameters.
- Second, Angular renders list data using the ngFor directive.
- Third, Angular optimizes DOM comparison by using trackBy.
Angular components are used in much the same way as other frameworks:
<x-list [dataSource]="dataSource"></x-list>Copy the code
5 Basic paging function
Now let’s start adding the actual Pagination functionality to the Pagination component.
Before adding Pagination, let’s design the Pagination API:
- Total data – total
- Number of data per page – defaultPageSize
- Current page number – defaultCurrent
- Page number change event – onChange
The total and defaultPageSize parameters can be combined into a single parameter totalPage, but split it for subsequent extensibility (such as needing to change pageSize).
Paging buttons are implemented in the following steps:
- Implement a generic button component
- Use the button component in the paging component
- Use Pagination component to paginate the List
5.1 the Vue
5.1.1 Implementing universal button components
You are already familiar with the Vue component by writing the empty Pagination and List components.
Create a new button.vue component file and write the following code:
<template>
<button type="button" @click="$emit('click')"><slot></slot></button>
</template>
<script>
export default {
name: 'Button'}; </script>Copy the code
Special note here:
- How Vue components expose events: use the $emit method;
- Also, Vue defines slots using the
tag.
In fact, this is a short form, but it should look like this:
<template>
<button type="button" @click="click()"><slot></slot></button>
</template>
<script>
export default {
name: 'Button',
methods: {
click() {
this.$emit('click'); }}}; </script>Copy the code
$EMIT is a method used by a Vue component instance to expose events and pass data. You’ll see an example of passing parameters later.
5.1.2 Using the Button component in the Pagination component
After all that prep work, we can finally do some real functionality.
Remember earlier when we wrote an empty Pagination component? Now we can write some functionality into it.
<template>
<div class="x-pagination">
<Button class="btn-prev" @click="setPage(current - 1)">< </Button> {{ current }} <Button class="btn-next" @click="setPage(current + 1)">></Button>
</div>
</template>
<script>
import Button from './Button.vue';
export default {
name: 'Pagination'Props: {defaultCurrent: Number, defaultPageSize: Number, total: Number,}, // component internal state datadata() {
return{current: this.defaultCurrent,}}, computed: {totalPage:function () {
returnMath.ceil(this.total / this.defaultPageSize); },}, // Methods: {setPage(page) {
if (page < 1) return;
if (page > this.totalPage) return;
this.current = page;
this.$emit('change', this.current); }}}; </script>Copy the code
Delete the previous text “Pagination component”, add the previous page (<)/next page (>) two page turn buttons, and we also display the current page number between the two page turn buttons, so that we can know more about the current page.
Because the open Angle bracket conflicts with the open Angle bracket of the HTML tag, it cannot be used directly. Instead.
All the API parameters for the Pagination component are props:
// interface definition props: {defaultCurrent: Number, // default page Number defaultPageSize: Number, // Default Number of data per page total: Number, // total Number of data}Copy the code
We define an internal component property, current, to hold dynamic page numbers:
// Component internal state datadata() {
return {
current: this.defaultCurrent,
}
}Copy the code
Note that the data attribute of ⚠️ is in the form of a function that returns an object inside the function. This ensures that each instance maintains a separate copy of the object being returned. See the official website for details.
In addition, we define a computation property that is used to get the totalPage (needed to limit the page boundary) :
// Computed attributes: {totalPage:function () {
returnMath.ceil(this.total / this.defaultPageSize); }},Copy the code
Finally, we defined an internal method, setPage, to change the page number:
// Methods: {setPage(page) {
if (page < 1) return; // Limit the boundary of the previous page turn buttonif (page > this.totalPage) return; // Limit the boundaries of the next page turn button this. Current = page; this.$emit('change', this.current); }},Copy the code
This method is called when the previous/next page turn button is clicked, passing in the changed page number value.
If previous, pass current-1:
<Button class=”btn-prev” @click=”setPage(current – 1)”>< </Button>
The next page is current + 1:
<Button class=”btn-next” @click=”setPage(current + 1)”>></Button>
In addition to setting the current page number in the setPage, the page number change event is emitted and the current page number is passed outside the component.
this.$emit('change', this.current);Copy the code
In addition, some logic has been added to limit the page-turning boundary to avoid unnecessary bugs caused by exceeding the page-number boundary when turning the page:
if (page < 1) return; // Limit the boundary of the previous page turn buttonif (page > this.totalPage) return; // Limit the boundaries of the next page turn buttonCopy the code
5.1.3 Using the Pagination component to paginate the List
With the Pagination component and the List component, you can use Pagination to paginate the List.
Use the Pagination component in the home.vue component.
<template>
<div class="home">
<img alt="Vue logo" src=".. /assets/logo.png">
<List :data-source="dataList" />
<Pagination :default-current="defaultCurrent" :default-page-size="defaultPageSize" :total="total" @change="onChange" />
</div>
</template>
<script>
import Pagination from '@/components/pagination/Pagination.vue';
import List from './List.vue';
import { lists } from '@/db';
import { chunk } from '@/util';
export default {
name: 'home',
components: {
Pagination,
List,
},
data() {
return {
defaultCurrent: 1,
defaultPageSize: 3,
total: lists.length,
dataList: [],
}
},
created() {
this.setList(this.defaultCurrent, this.defaultPageSize);
},
methods: {
onChange(current) {
this.setList(current, this.defaultPageSize);
},
setList: function(current, pageSize) { this.dataList = chunk(lists, pageSize)[current - 1]; }}}; </script>Copy the code
Besides defaultCurrent defaultPageSize/total outside the parameters of the three Pagination components, we are in a state of internal data also defines a dataList fields, used for dynamic introduced to give the List component, to achieve the effect of paging.
The lists are partitioned in the setList method, and the paging data is retrieved based on the current page number and assigned to the dataList field, so that the corresponding paging data is displayed in the List component.
setList: function(current, pageSize) {
this.dataList = chunk(lists, pageSize)[current - 1];
}Copy the code
The setList method is called in two places: the created lifecycle method and the onChange page change event.
The created life cycle event is executed after the Vue instance is initialized but before it is mounted to the DOM. In the created event, we assign the data on page 1 to the dataList:
created() {
this.setList(this.defaultCurrent, this.defaultPageSize);
}Copy the code
So the List component will display the data on page 1:
The onChange event is the Pagination component’s page number change event, which is executed when the previous/next page turn button is clicked. In this event, the current page number is obtained.
In this event, we assign the data for the current page number to the dataList, so that the List component will display the data for the current page number, thus achieving paging effect.
onChange(current) {
this.setList(current, this.defaultPageSize);
}Copy the code
The setList method calls the chunk method (similar to the chunk method in Lodash), which is used to split an array into smaller arrays of a specified size. The source code for this method is as follows:
// Block the array by the specified sizeexport function chunk(arr = [], size = 1) {
if (arr.length === 0) return [];
return arr.reduce((total, currentValue) => {
if (total[total.length - 1].length === size) {
total.push([currentValue]);
} else {
total[total.length - 1].push(currentValue);
}
return total;
}, [[]]);
}Copy the code
For example, if the previous lists array is divided into chunks (lists, 3) according to 3 pieces of data on each page, the result is as follows:
[[{"id": 1, "name": "Curtis" },
{ "id": 2."name": "Cutler" },
{ "id": 3."name": "Cynthia"}], [{"id": 4."name": "Cyril" },
{ "id": 5, "name": "Cyrus" },
{ "id": 6, "name": "Dagmar"}], [{"id": 7, "name": "Dahl" },
{ "id": 8, "name": "Dahlia" },
{ "id": 9, "name": "Dailey"}], [{"id": 10, "name": "Daine"}]]Copy the code
The final paging effect is as follows:
Now for a quick summary, to implement paging, we:
- Firstly, a general button component is implemented.
- Then use this general component, add two page turning buttons in Pagination component, click to change the current page number;
- Next, use the Pagination component to paginate the List components.
Let’s take a look at how React does this.
5.2 the React
5.1.1 Implementing universal button components
Again, define a generic Button component button.js:
import React from 'react';
function Button({ onClick, children }) {
return (
<button type="button" onClick={ onClick }>{ children }</button>
);
}
export default ButtonCopy the code
With the Pagination/List component developed earlier, we believe that you are not unfamiliar with the React function component.
Unlike Vue, React doesn’t need to launch events or anything like that.
Another difference is the way slots are defined. React uses props. Children to represent the content passed in the middle of the component label.
5.1.2 Using the Button component in the Pagination component
Then use the generic button component to add two page turn buttons in the Pagination component:
import React, { useState } from 'react';
import Button from './Button';
functionPagination(props) { const { total, defaultCurrent, defaultPageSize, onChange } = props; // Declare a state variable called "current" to hold the current page number; //setThe Page method is used to change current. const [current,setPage] = useState(defaultCurrent);
const totalPage = Math.ceil(total / defaultPageSize);
return (
<div className="m-pagination">
<Button className="btn-prev" onClick={() => {
if (current < 2) return;
setPage(current - 1); onChange(current - 1); }}>< </Button> {{ current }} <Button className="btn-next" onClick={() => {
if (current >= totalPage) return;
setPage(current + 1);
onChange(current + 1);
}}>></Button>
</div>
);
}
export default Pagination;Copy the code
Here is an important concept after React 16.8: React Hooks
To define component internal state in function components, we introduced the method useState from the React library:
import React, { useState } from 'react';Copy the code
UseState is a Hook that adds some internal state to the component by calling it inside the function component. React will retain this state during repeated renderings.
UseState returns a pair of values: the current state and a function that lets you update it.
The only parameter in useState is the initial state, in this case the defaultCurrent page number (defaultCurrent). This initial state parameter is only used for the first rendering.
const [current, setPage] = useState(defaultCurrent);Copy the code
When the previous/next page turn button is clicked, we call the setPage method, passing in the new page number, and thus changing the current page number.
Also as in the Vue version, the page number change event is emitted by calling the onChange method and passing the current page number out of the component.
If previous page:
<Button className="btn-prev" onClick={() => {
if (current < 2) return;
setPage(current - 1); onChange(current - 1); }}>< </Button>Copy the code
If it is the next page:
<Button className="btn-next" onClick={() => {
if (current >= totalPage) return;
setPage(current + 1);
onChange(current + 1);
}}>></Button>Copy the code
5.1.3 Using the Pagination component to paginate the List
Now that the Pagination component is ready, we can use it to paginate the List components.
Add a List and Pagination component to app.js:
import React, { useState } from 'react';
import Pagination from './components/pagination/Pagination';
import List from './components/List';
import { lists } from './db';
import { chunk } from './util';
import './App.scss';
function App() { const defaultCurrent = 1; const defaultPageSize = 3; // Set the List to default page data: chunk(lists, defaultPageSize)[defaultcurrent-1] const [dataSource,setLists] = useState(chunk(lists, defaultPageSize)[defaultCurrent - 1]);
return (
<div className="App"> <List dataSource={dataSource} /> <Pagination total={lists.length} defaultCurrent={defaultCurrent} DefaultPageSize ={defaultPageSize} onChange={current => {// Resets the current pagination data when the page number changessetLists(chunk(lists, defaultPageSize)[current - 1]);
}} />
</div>
);
}
export default App;Copy the code
DataSource (useState) : dataSource (useState) : dataSource (useState) : dataSource
// Set the List to default page data: chunk(lists, defaultPageSize)[defaultcurrent-1] const [dataSource,setLists] = useState(chunk(lists, defaultPageSize)[defaultCurrent - 1]);Copy the code
When the page number changes, the Pagination onChange event is captured and executed. This event is used to retrieve the current page number. In this case, we can change the dataSource by calling useState’s second return value — setLists.
<Pagination ... OnChange ={current => {// Resets the current page data when the page number changessetLists(chunk(lists, defaultPageSize)[current - 1]); }} / >Copy the code
React and Vue differ significantly in the way they maintain state within components. Here’s a simple comparison:
Location for storing the internal state of a component |
How to change the internal state of a component |
|
React |
UseState The first return value. const [state, setState] = useState(initialState]; |
UseState The second return value (a method). const [state, setState] = useState(initialState]; |
Vue |
Data method. data() { return { state: [], } } |
Methods object. methods: { setState: function() { // Execute the specific code } } |
One more note ⚠️ :
In Vue, in order to initialize the List of data sources, there is no way to write directly in data, such as:
data() {
return {
dataList: chunk(lists, this.defaultPageSize)[this.defaultCurrent - 1],
}
}Copy the code
Instead, you have to write in the created initialization method:
created() {
this.dataList = chunk(lists, this.defaultPageSize)[this.defaultCurrent - 1];
}Copy the code
React is much simpler and more natural:
// set List to default paged data: first page of data const [dataSource,setLists] = useState(chunk(lists, defaultPageSize)[defaultCurrent - 1];Copy the code
But React is not very friendly to beginners, so you get used to it and feel comfortable.
Version 5.3 presents
5.1.1 Implementing universal button components
Finally, we’ll look at how Angular implements paging. We’ll define a generic button component, button.component.ts:
import { Component, Output, EventEmitter } from "@angular/core";
@Component({
selector: 'x-button'.template: ` `,})export class ButtonComponent {
@Output() btnClick = new EventEmitter();
onClick() {
this.btnClick.emit(); }}Copy the code
The difference between Angular and React/Vue is obvious:
- First, the binding event syntax is different;
- Second, the way to define the slot is different;
- Third, the way to expose external events is different from the way to emit external events.
Here’s a quick comparison:
The binding event |
Define the slot |
External events |
|
Vue |
V-on directive (short form: @) |
The < slot > tag |
$emit() |
React |
Props to pass props.onClick |
props.children |
Props. No need to launch |
Angular |
Operator brackets () (click)=”btnClick()” |
< ng – content > tag |
@Output()+emit() |
5.1.2 Using the Button component in the Pagination component
Now templates use generic button component pagination.com ponent. HTML:
<div class="x-pagination">
<x-button
class="btn-prev"
(btnClick)="setPage(current - 1)">< </x-button> {{ current }} <x-button class="btn-next"
(btnClick)="setPage(current + 1)"
>></x-button>
</div>Copy the code
Then define the concrete logic in pagination.component.ts:
import { Component, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: 'x-pagination',
templateUrl: './pagination.component.html',
styleUrls: ['./pagination.component.scss']})exportClass PaginationComponent {@input () total: number; @Input() defaultCurrent = 1; @Input() defaultPageSize: number; @Output() onChange = new EventEmitter(); // Calculate the @input () get attributetotalPage() {
returnMath.ceil(this.total / this.defaultPageSize); } // Component internal state current = this.defaultCurrent; // Component methodssetPage(page) {
if (this.current < 2) return;
if (this.current > this.totalPage - 1) return; this.current = page; this.onChange.emit(this.current); }}Copy the code
Vue/React: define component interface/compute attribute/internal state/component method, but the specific syntax is different.
Here’s how to use the Pagination component to paginate the List.
5.1.3 Using the Pagination component to paginate the List
Add Pagination/List to app.component.html:
<x-list [dataSource]="dataSource"></x-list>
<x-pagination
[total]="total"
[defaultCurrent]="defaultCurrent"
[defaultPageSize]="pageSize"
(onChange)="onChange($event)"
></x-pagination>Copy the code
Define the concrete logic in app.component.ts:
import { Component, OnInit } from '@angular/core';
import { lists } from './db';
import { chunk } from './util';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']})export class AppComponent implements OnInit {
defaultCurrent = 1;
defaultPageSize = 3;
total = lists.length;
dataSource = [];
ngOnInit() { this.setLists(this.defaultCurrent, this.defaultPageSize); } onChange(current) {this.setlists (current, this.defaultPagesize); }setLists(page, pageSize) { this.dataSource = chunk(lists, pageSize)[page - 1]; }}Copy the code
When the component initializes, it sets the initial page of the dataSource (the first page of the data), and then resets the value of the dataSource when the page number changes.
There are just a few differences to note ⚠️ :
- Angular’s initialization method is ngOnInit, Vue is created;
- Angular binds properties using brackets []. Vue uses the V-bind directive (or key for short).
Now that you’ve covered how the three frameworks implement basic paging and how they differ, the next section will cover the core of this article: the implementation of the pager.
6 Pager Component Pager
Let’s review the module diagram for the paging component again:
The pager, which displays the page number in the middle, has the logic of page number display and page number omission at its heart.
6.1 Page number Display Policy
In order to jump to any page number conveniently, but not to display too many pages in the page, the page number is not always displayed, but in the page number is small, only part of the page number is displayed. This is where the display policy comes in.
Let’s start with the current page number, such as page 5 in the module diagram:
Then take the page number as the center, display a certain page number on both sides, such as two pages on each side;
In addition, the home page and the last page need to be always displayed, convenient to go back to the home page and jump to the last page;
The page numbers from the front page to the middle of the third page and from the seventh page to the end of the page are hidden, and the left/right more button can be clicked to quickly jump to multiple pages (such as five pages).
The other thing to consider is that the number of pages is small. What if you only have 8 pages?
It’s easy, just get rid of more buttons on the right:
What if the current page number is page 4? Remove more buttons on the left and show more buttons on the right:
That’s the entire page number display strategy.
It is summarized as follows:
- The back page of the home page should always be displayed (if only 1 page is displayed, the back page is not displayed);
- In addition to the first and last pages, a maximum of 2 pages (a total of 5 pages) can be displayed about the current page number.
- Other page numbers are folded and replaced with more buttons.
Let’s look at how to implement this logic using three frameworks.
6.2 the Vue
6.2.1 Component Interface Design
Before writing a Pager component, design the API for the component:
- Total pages – totalPage
- DefaultCurrent page number – defaultCurrent
- Page number change event – onChange
6.2.2 Basic Template Framework
Then write the template and write the following code in <template> of pager.vue:
<template>
<ul class="x-pager">
<li class="number">1</li>
<li class="more left"></li>
<li class="number"></li>
<li class="more right"></li>
<li class="number">{{ totalPage }}</li>
</ul>
</template>Copy the code
Then write the basic logic in <script> :
<script>
import Vue from 'vue';
export default {
name: 'Pager', // component interface definition props: {totalPage: Number, // totalPage Number defaultCurrent: Number, // defaultCurrent page Number},}; </script>Copy the code
With the basic framework in place, we took the idea of a Minimum Viable Product (MVP) :
The pager function is realized in 3 steps:
- The first step is to start and end page turning
- Step 2 implements quick pagination
- Step 3 implements the paging button group
6.2.3 Step 1: First/last page turning logic
Step 1: Display and page skipping logic at the end of the home page:
Home page
<li
class="number"
:class="{ active: this.current == 1 }"
@click="setPage(1)"
>1</li>Copy the code
back
<li
class="number"
:class="{ active: this.current == totalPage }"
v-if="totalPage ! = = 1"
@click="setPage(totalPage)"
>{{ totalPage }}</li>Copy the code
Since the current page number may change from outside the Pager component (previous/Next button), we need to listen for changes in defaultCurrent, so we need to add the component internal state current instead of defaultCurrent:
data() {
return{current: this.defaultcurrent, // current page number}}Copy the code
Then listen for defaultCurrent and assign the new value to current when the external defaultCurrent changed:
watch: { defaultCurrent: { handler(newValue, oldValue) { this.current = newValue; }}}Copy the code
Then define the page-turning method:
methods: {
setPage(Page) {// Limit the number of pages to [1, totalPage]let newPage = page;
if (page < 1) newPage = 1;
if(page > this.totalPage) newPage = this.totalPage; this.current = newPage; // Set the current page number this.$emit('change', this.current); // Send out the page number change event}}Copy the code
The effect is shown as follows:
6.2.4 Using the Pager Component in the Pagination Component
We can try the first version of Pager in the Pagination component.
In Pagination. Vue, remove the previous line of code that the page number shows and replace it with the Pager component:
<template>
<div class="m-pagination">
<Button class="btn-prev" @click="setPage(current - 1)">< </Button> // Remove the line {{current}} and replace it with the following Pager component <Pager :total-page="totalPage" :default-current="current" @change="onChange"></Pager>
<Button class="btn-next" @click="setPage(current + 1)">></Button>
</div>
</template>Copy the code
Then add Pager’s onChange callback event to the page number change:
methods: { onChange(current) { this.current = current; // Set the current page number this.$emit('change', this.current); // send page number change event to PaginationCopy the code
Try the first/last page turn effect:
6.2.5 Step 2: Add more buttons on the left and right to turn the page
It’s not enough to have the first and last page turns; you need to keep improving the quick page turns of more buttons.
Let’s comb through the display logic for more buttons:
- There are 5 pages of the middle button, plus 2 pages of the first and last buttons, making a total of 7 pages. In other words, more buttons can be displayed only if there are more than 7 pages.
- Left and right more buttons will be displayed or hidden according to the current page number, with the fourth page and the last page as the boundary;
- When the page number is larger than page 4, more buttons on the left should be displayed;
- When the page number is less than the fourth from the bottom, more buttons on the right should be displayed.
The specific implementation is as follows:
<! --> <li class="more left"
v-if="totalPage > 7 && current >= 5"></li> <! --> <li class="more right"
v-if="totalPage > 7 && current <= totalPage - 4"
></li>Copy the code
But we don’t want to overwrite these numbers, so let’s assume the middle page number is centerSize (here it’s 5), and recompose it:
<li
class="more left"
v-if="totalPage > centerSize + 2 && current >= centerSize"
></li>
<li
class="more right"
v-if="totalPage > centerSize + 2 && current <= totalPage - centerSize + 1"
></li>Copy the code
Next, add quick page-turning events:
<li
class="more left"
v-if="totalPage > centerSize + 2 && current >= centerSize"
@click="setPage(current - jumpSize)"
></li>
<li
class="more right"
v-if="totalPage > centerSize + 2 && current <= totalPage - centerSize + 1"
@click="setPage(current - jumpSize)"
></li>Copy the code
Note ⚠️ : In order not to kill the page number of each quick jump, we save this value with jumpSize.
Next we can see the effect of quick page turning. To make it clear which page we are on, we temporarily display the unimplemented page number group in the middle as the current page number:
<! < span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important;"number">{{ current }}</li>Copy the code
Initial on page 1:
After clicking more buttons on the right (jump to page 6) :
Click the more button on the right (jump to page 11) :
Clicking more buttons on the left brings you back to page 6, perfect as expected.
6.2.6 Step 3: Implement the middle page number button group
CenterPages is an array of lengths between [0, centerSize]. Its value is determined by totalPage and current page. It is calculated as follows:
- If the total number of pages is less than or equal to 7, centerPages is all pages except the first and last pages;
- If the total number of pages is greater than 7, centerPages is an array of pages with two left and two left pages, centered on current.
CenterPages is defined as a computed property. The implementation is as follows:
computed: {
centerPages: function() {// count the middle page numberlet centerPage = this.current;
if (this.current > this.totalPage - 3) {
centerPage = this.totalPage - 3;
}
if (this.current < 4) {
centerPage = 4;
}
if(this.totalpage <= this.centersize + 2) {const centerArr = []; (this.totalpage <= this.centersize + 2);for (let i = 2; i < this.totalPage; i++) {
centerArr.push(i);
}
return centerArr;
} elseConst centerArr = [];for (let i = centerPage - 2; i <= centerPage + 2; i++) {
centerArr.push(i);
}
returncenterArr; }}Copy the code
With an array of middle page numbers, we can render the middle page group:
<! < span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important;"number"
v-for="(page, index) in centerPages"
:key="index"
>{{ page }}</li>Copy the code
Next, add the active class (for highlighting) and bind the click event (for jumping to the appropriate page number) :
<! < span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important;"number"
:class="{ active: current === page }"
v-for="(page, index) in centerPages"
:key="index"
@click="setPage(page)"
>{{ page }}</li>Copy the code
The final result is as follows:
Only 1 page of the case:
<= Page 7:
>7 pages and current page number <=4 pages:
> page 7 and current page number > page 4:
At this point, the Vue version of the paginator component is fully implemented, and the entire Pagination component is fully implemented.
Let’s take a look at how React/Angular implements pagers.
6.3 the React
Using the same MVP approach, we developed the Pager Pager component in the following steps:
- Build the basic template framework
- To achieve the first page page
- More buttons for quick page turning
- Page number button group page turning
6.3.1 Basic Template Framework
Let’s set up the basic template framework and write the following code in pager.js:
import React from 'react';
function Pager({ totalPage, defaultCurrent, onChange }) {
return (
<ul className="x-pager">
<li className="number">1</li>
<li className="more left"></li>
<li className="number"></li>
<li className="more right"></li>
<li className="number">{ totalPage }</li>
</ul>
);
}
export default Pager;Copy the code
It’s just an empty shell that doesn’t do anything, so let’s add some real functionality.
6.3.2 Step 1: First/last page turning logic
Added first and last page display condition, highlight condition and page turning function.
import React, { useState } from 'react';
functionPager({totalPage, defaultCurrent, onChange}) {// use useState to define internal state:setPage] = useState(defaultCurrent);
return (
<ul className="x-pager">
<li
className={'number' + (current == 1 ? ' active' : ' ')}
onClick={() => {
setPage(1);
onChange(1);
}}
>1</li>
<li className="more left"></li>
<li className="number"></li>
<li className="more right"></li> { totalPage ! == 1 && <li className={'number' + (current == totalPage ? ' active' : ' ')}
onClick={() => {
setPage(totalPage);
onChange(totalPage);
}}
>{ totalPage }</li> }
</ul>
);
}
export default Pager;Copy the code
Note that React and Vue have different conditional renderings:
- React is wrapped in curly braces ({}) and then writes the branch judgment as if it were JS
- Vue uses the V-if directive in HTML elements to branch
The other thing is that Vue has the ability to bind the tag class, but React doesn’t have the same ability. You need to write the triad operator inside the {} braces to determine the highlighting.
So far, Pager can be directly used in Pagination, but only the home page and the last page page, then continue to enhance the function of Pager.
Step 2: Add more buttons on the left and right to the page turning function
More buttons display the same logic as the Vue version:
- More buttons are only possible if there are more than seven pages;
- Left and right more buttons will be displayed or hidden according to the current page number, with the fourth page and the last page as the boundary;
- When the page number is larger than page 4, more buttons on the left should be displayed;
- When the page number is less than the fourth from the bottom, more buttons on the right should be displayed.
Left more buttons:
const centerSize = 5; // The page number of the middle button group const jumpSize = 5; {totalPage > centerSize + 2 && current >= centerSize && <li className="more left"
onClick={() => {
setPage(current - jumpSize); OnChange (current - jumpSize); }} ></li>}Copy the code
Right more button:
{
totalPage > centerSize + 2 && current <= totalPage - centerSize + 1
&& <li className="more right"
onClick={() => {
setPage(current + jumpSize);
onChange(current + jumpSize);
}}
></li>
}Copy the code
Finally, the page number button group function is realized.
6.3.4 Step 3: Implement the middle page number button group
The main need to calculate the centerPages array, the calculation logic is the same as Vue:
- If the total number of pages is less than or equal to 7, centerPages is all pages except the first and last pages;
- If the total number of pages is greater than 7, centerPages is an array of pages with two left and two left pages, centered on current.
Calculate centerPages first:
Const centerPages = [];let centerPage = current;
if (current > totalPage - 3) {
centerPage = totalPage - 3;
}
if (current < 4) {
centerPage = 4;
}
if (totalPage <= centerSize + 2) {
for (leti = 2; i < totalPage; i++) { centerPages.push(i); }}else {
for (leti = centerPage - 2; i <= centerPage + 2; i++) { centerPages.push(i); }}Copy the code
Then display it:
{
centerPages.map((page, index) => {
return (
<li
key={index}
className={'number' + (page == current ? ' active' : ' ')}
onClick={() => {
setPage(page); onChange(page); }} >{ page }</li> ); })}Copy the code
Note how lists are rendered ⚠️ :
- React is still wrapped in braces and iterated using JS’s Map method.
- Vue is a list rendering using the V-for directive in an HTML tag.
Because the current page number in Pager can be changed externally (such as the previous/next page button), it is necessary to change current dynamically when defaultCurrent is passed in. This is done by using another React Hook — useEffect — as follows:
// Current useEffect(() => {setPage(defaultCurrent);
});Copy the code
Another thing to note is that more button quick page turns may be out of bounds and need to be displayed, so we have written a limitPage method:
const limitPage = (page) => {
if (page < 1) return 1;
if (page > totalPage) return totalPage;
return page;
}Copy the code
Used in more button events:
Left more buttons:
{
totalPage > centerSize + 2 && current >= centerSize
&& <li className="more left"
onClick={() => {
setPage(limitPage(current - jumpSize)); // Set the new page number after quick page turning onChange(limitPage(current - jumpSize)); }} ></li>}Copy the code
Right more button:
{
totalPage > centerSize + 2 && current <= totalPage - centerSize + 1
&& <li className="more right"
onClick={() => {
setPage(limitPage(current + jumpSize));
onChange(limitPage(current + jumpSize));
}}
></li>
}Copy the code
This completes the React version of the Pager Pager component, with most of the code logic the same except for subtle syntax differences.
The next Angular version of Pager is the same, most of the logic can be reused.
Version 6.4 presents
Angular implements Pager in the same way as Vue/React. It is written in the same way as MVP. It is divided into the following three steps:
- The first step is to start and end page turning
- Step 2 implements quick pagination
- Step 3 implements the paging button group
First implement the first/last page turning function.
6.4.1 Step 1: Implement the first/last page turning logic
Start with the template and write the following code in pager.component.html:
<ul class="x-pager">
<li [ngClass]="{ number: true, active: 1 == current }"
(click)="setPage($event1)"
>1</li>
<li class="more left"></li>
<li class="number" ></li>
<li class="more right"></li>
<li *ngIf="totalPage ! = = 1" [ngClass]="{ number: true, active: totalPage == current }"
(click)="setPage($event, totalPage)"
>{{ totalPage }}</li>
</ul>Copy the code
Then write the concrete logic in pager.component.ts:
import { Component, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: 'x-pager',
templateUrl: './pager.component.html',
styleUrls: ['./pager.component.scss']})export class PagerComponent {
@Input() totalPage: number;
@Input() defaultCurrent: number;
@Output() onChange = new EventEmitter();
current = this.defaultCurrent;
setPage($event, page) { this.current = page; this.onChange.emit(this.current); }}Copy the code
6.4.2 Step 2: Realize the page turning function of more buttons on the left and right
Since the setPage method used to set the page number is already written, you just need to add more left/right buttons to the template:
<li
class="more left"
*ngIf="totalPage > centerSize + 2 && current >= centerSize"
(click)="setPage($event, current - centerSize)"
></li>
<li
class="more right"
*ngIf="totalPage > centerSize + 2 && current <= totalPage - centerSize + 1"
(click)="setPage($event, current + centerSize)"
></li>Copy the code
6.4.3 Step 3: Implement the middle page number button group
Finally is to achieve the page number button group, the key or centerPages array calculation, computing logic can be reused Vue/React. The specific implementation is as follows:
@Input()
get centerPages() {
let centerPage = this.current;
if (this.current > this.totalPage - 3) {
centerPage = this.totalPage - 3;
}
if (this.current < 4) {
centerPage = 4;
}
const centerArr = [];
if (this.totalPage < this.centerSize + 2) {
for (leti = 2; i < this.totalPage; i++) { centerArr.push(i); }}else {
for (leti = centerPage - 2; i <= centerPage + 2; i++) { centerArr.push(i); }}return centerArr;
}Copy the code
Similar to the computed attribute in Vue.
Then use centerPages to render the page number button group:
<li
[ngClass]="{
number: true,
active: page == current
}"
*ngFor="let page of centerPages"
(click)="setPage($event, page)"
>{{ page }}</li>Copy the code
So far, the Pager component of the three major framework has been implemented, and the Pagination component has come to an end.
Finally, let’s summarize the differences between the Vue/React/Angular framework development components.
The framework |
Communicate from the outside in |
Communication from the inside out |
Programming paradigm |
The list of rendering |
Conditions apply colours to a drawing |
event |
Internal state |
Slot definition mode |
Calculate attribute |
Listen for externally passed parameter variables |
Vue |
props |
$emit() |
responsive |
V – for instructions |
V – if instructions |
V-bind: Event (@event) |
data |
<slot> |
computed |
watch |
React |
props |
props |
Function component |
{} the parcel map |
{} Wrap the triplet operator |
onEvent |
useState |
props.children |
Write directly |
useEffect |
Angular |
@Input() |
@Output() emit() |
object-oriented |
* ngFor instruction |
* ngIf instruction |
(event) |
Write directly |
<ng-content> |
@Input() get |
ngOnChanges |
Pagination component source address:
Github.com/kagol/compo…
This article is written with reference to the DevUI pagination component, which source address:
Github.com/DevCloudFE/…
Welcome to the DevUI component library, give us comments and suggestions, also welcome to Star.
Join us
We are DevUI team, welcome to come here and build elegant and efficient man-machine design/R&D system with us. Recruitment email: [email protected].
The text/DevUI Kagol
Previous article recommended
How to Build a Grayscale Publishing Environment
Practice of Micro Front End in Enterprise Application (part 1)
Modularity in Quill, a Modern Rich Text Editor