学习Angular并与Vue.js进行比较 – 实现部分 – 第三部分

继前一篇文章,学习Angular与Vue.js的比较 – 配置篇 – 第2部分

我打算从这里开始写代码。

你首先要做什么?

因为想要比较页面跳转,所以打算创建主页、列表页和详细页这三个页面。

在列表页面和详情页面中展示从API获取的数据。

设计的样子是这样的

Web 1920 – 1.png
Web 1920 – 2.png
Web 1920 – 3.png

不管怎样,跳转页面。

我想首先创建一个空白页面。

在Vue的情况下

<template>
  <div class="top">
    Top
  </div>
</template>

<script>

export default {
  name: 'Top',
}
</script>

<template>
  <div class="list">
    List
  </div>
</template>

<script>

export default {
  name: 'List',
}
</script>
<template>
  <div class="detail">
    Detail
  </div>
</template>

<script>

export default {
  name: 'Detail',
}
</script>

下一步是将组件和过渡联系起来。
URL将设置为以下3个。

    • /        トップページ

 

    • /list     一覧ページ

 

    /detail/[id] 詳細ページ
import Vue from 'vue'
import VueRouter from 'vue-router'
import Top from '@/views/Top.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Top',
    component: Top
  },
  {
    path: '/list',
    name: 'List',
    component: () => import(/* webpackChunkName: "list" */ '@/views/List.vue')
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    component: () => import(/* webpackChunkName: "detail" */ '@/views/Detail.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

便宜起见,在此我们使用了webpackchunk,并不详细解释。


只要每个页面的URL显示如下,就算是成功。

スクリーンショット 2019-11-11 14.10.20.png

如果是Angular的情况

让我们创建一个空页面在Angular中。

在Vue中,我們需要複製文件來建立頁面。但在Angular CLI中,我們可以使用以下命令來生成文件和目錄。

$ ng generate component top # 省略形のコマンド ng g c top
$ ng generate component list
$ ng generate component detal

敲击三个命令后,可以确认已生成了三个包含三个文件的目录。

从顶层目录提取的摘要

<p>top works!</p>
// 空のファイル
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-top',
  templateUrl: './top.component.html',
  styleUrls: ['./top.component.scss']
})
export class TopComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

/src/app/app.module.ts文件中追加了一个需要注意的部分

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TopComponent } from './top/top.component';
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';

@NgModule({
  declarations: [
    AppComponent,
+    TopComponent,
+    ListComponent,
+    DetailComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

通过将组件文件加载到声明中,可以在组件内部使用所需的文件,从而可以使用其他组件。


接下来,我们将编写用于转移的路由文件。

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
+ import { TopComponent } from './top/top.component';
+ import { ListComponent } from './list/list.component';
+ import { DetailComponent } from './detail/detail.component';


const routes: Routes = [
+  {
+    path: '',
+    component: TopComponent
+  },
+  {
+    path: 'list',
+    component: ListComponent
+  },
+  {
+    path: 'detail/:id',
+    component: DetailComponent
+  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }


这里与Vue几乎没有什么不同。

只要在URL上访问并确认屏幕上显示下方的图片,就表示成功了。

スクリーンショット 2019-11-12 18.10.19.png

CSS布局

由于文件创建已经完成,所以我们将开始进行CSS布局。

首先,我们需要编写normalize.scss和共通CSS来解决不同浏览器之间的布局差异。

Vue的情况下

安装normalize.scss

yarn add normalize.scss
or
npm install normalize.scss

导入 normalize.scss 文件

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

+ <script>
+ import 'normalize.scss/normalize.scss'
+
+ export default{
+   name: 'App'
+ }
+ </script>

写上通用CSS

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
import 'normalize.scss/normalize.scss'

export default{
  name: 'App'
}
</script>

<style>
body {
  font-family: YuGothic,'Yu Gothic','Hiragino Kaku Gothic ProN','ヒラギノ角ゴ ProN W3','メイリオ', + Meiryo,'MS ゴシック',sans-serif;
  font-weight: 400;
  font-size: 12px;
}
</style>

在Angular的情况下

normalize.scss的安装方式不变,故省略不提。

Angular的环境配置文件中存在一个名为angular.json的东西,我想要在node_modules文件夹中安装的文件中使用scss进行读取。

"node_modules/normalize.scss/normalize.scss

将其追加到styles中。

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "sample-angular": {
      ...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "styles": [
+               "node_modules/normalize.scss/normalize.scss",
              "src/styles.scss"
            ],
            ...
}

为了设定Angular的共通样式,可以准备styles.scss文件,并在此处加载normalize.scss并编写共通样式描述。

@import '~normalize.scss';

body {
  font-family: YuGothic,'Yu Gothic','Hiragino Kaku Gothic ProN','ヒラギノ角ゴ ProN W3','メイリオ', + Meiryo,'MS ゴシック',sans-serif;
  font-weight: 400;
  font-size: 12px;
}

这样一切准备都完成了。

创建首页

创建首页
首页的结构是

准备可供共同使用的组件,并将其加载到首页上。

共通组件
– 按钮
– 头部

对于Vue而言

按钮组件应使用props从父组件接收数据,以便进行数据传递。

<template>
  <router-link :to="path" class="button">{{ text }}</router-link>
</template>

<script>

export default {
  name: 'BaseButton',
  props: {
    /**
     * @param text buttonに表示させる文字列
     * @param path 遷移先のURLのパス
     */
    text: {
      type: String,
      required: true
    },
    path: {
      type: String,
      required: true
    }
  }
}
</script>

<style lang="scss" scoped>
.button{
  display: block;
  font-size: 22px;
  width: 400px;
  height: 70px;
  padding: 25px;
  box-sizing: border-box;
  color: #fff;
  background: #000;
  font-weight: 700;
  text-align: center;
  line-height: 1;
  transition: all .3s ease;
  border: 1px solid transparent;
  &:hover {
    background-color: #fff;
    border-color: #000;
    color: #000;
  }
}
</style>

在中文中,Header组件使用router-link-active来为跳转页面应用CSS类。

<template>
  <header class="header">
    <nav>
      <ul class="navList">
        <li class="navItem">
          <router-link to="/" class="navLink" exact>トップ</router-link>
        </li>
        <li class="navItem">
          <router-link to="/list" class="navLink" exact>商品一覧</router-link>
        </li>
      </ul>
    </nav>
  </header>
</template>

<script>

export default {
  name: 'TheHeader',
}
</script>

<style lang="scss" scoped>
.header{
  padding: 60px 0;
}

.navList{
  display: flex;
  justify-content: center;
}
.navLink{
  color: #ccc;
  font-weight: 700;
  line-height: 1;
}

.navItem{
  padding: 0 20px;
  &:not(:first-child){
    border-left: 1px solid #000;
  }
}

.router-link-active{
  color: #000;
}
</style>

在顶部使用 import 导入共通组件文件,通过 components 编写以在 Vue 模板中使用。

<template>
  <div class="top">
    <TheHeader></TheHeader>
    <div class="titleWrap">
      <h1 class="title">それっぽいトップページ</h1>
      <p class="subTitle">KAKKO II EIGO NO TEKISUTO GA HAIRU</p>
      <p class="subTitleBg">KAKKO II EIGO<br>NO TEKISUTO GA HAIRU</p>
    </div>
    <div class="button">
      <BaseButton text="商品を見る" path="/list"></BaseButton>
    </div>
  </div>
</template>

<script>
import TheHeader from '@/components/TheHeader'
import BaseButton from '@/components/BaseButton'

export default {
  name: 'Top',
  components: {
    TheHeader,
    BaseButton
  }
}
</script>

<style lang="scss" scoped>
.titleWrap{
  position: relative;
  margin-top: 200px;
}
.title{
  font-size: 120px;
  line-height: 1;
  font-weight: 700;
  text-align: center;
}
.subTitle{
  margin-top: 30px;
  font-size: 20px;
  text-align: center;
}
.subTitleBg{
  position: absolute;
  top: 70px;
  left: 50%;
  z-index: -1;
  font-size: 60px;
  line-height: 1.05;
  color: #eee;
  text-align: center;
  transform: translateX(-50%);
}

.button{
  width: 400px;
  margin: 150px auto 0;
}
</style>

在App中添加重置CSS后,首页完成了。

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
import 'normalize.scss/normalize.scss'

export default{
  name: 'App'
}
</script>

<style>
body {
  font-family: YuGothic,'Yu Gothic','Hiragino Kaku Gothic ProN','ヒラギノ角ゴ ProN W3','メイリオ', Meiryo,'MS ゴシック',sans-serif;
  font-weight: 400;
  font-size: 12px;
  height: 100%;
  background: #FCFCFC;
}
h1, p, ul{
  margin: 0;
}
ul{
  list-style-type: none;
  padding-left: 0;
}
a{
  text-decoration: none;
}
</style>

在Angular的情况下

在Angular中,虽然几乎是相似的,但为了方便使用共享文件,我想创建一个shared文件夹来管理组件。

首先,使用cli命令创建用于管理的文件夹。

ng generate module shared # 省略形はng g m shared

生成的文件

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ]
})
export class SharedModule { }

本次将组件文件作为共享文件进行管理,
考虑将来可能还会将其他功能进行共享化,
因此创建一个share/component文件夹,以便更方便地进行管理。

ng generate module shared/component

生成的文件

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ]
})
export class ComponentModule { }

创建文件后, 按照 App 模块← shared 模块← shared/compoent 模块的顺序将它们连接在一起, 以便在 App 组件中使用.

最初,将shared模块文件与app模块关联起来。
关联模块文件是通过将其载入到imports: []中实现的。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TopComponent } from './top/top.component';
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
+ import { SharedModule } from './shared/shared.module';

@NgModule({
  declarations: [
    AppComponent,
    TopComponent,
    ListComponent,
    DetailComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
+    SharedModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

接下来,我们将在shared模块中加载shared/component模块。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ComponentModule } from './component/component.module';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
+    ComponentModule
  ]
})
export class SharedModule { }

下面是一个可能的中文翻译选项:

在某些特殊情况下,当在外部类似于app模块的地方使用已加载的模块时,需要在exports:[]中写下所使用的外部模块文件。因此,需要在exports中逐步记录shared/component模块。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ComponentModule } from './component/component.module';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    ComponentModule
  ],
+  exports: [
+    ComponentModule
+  ]
})
export class SharedModule { }

这样就完成了共享文件夹的创建。

接下来,使用cli命令创建按钮组件和头部组件的文件。

ng generate component shared/component/button # 省略形はng g c shared/component/button
ng g c shared/component/header

当命令成功执行时,将会生成ts、html和scss文件。

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.scss']
})
export class ButtonComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

当成功生成文件后,可以确认自动加载了组件到父模块文件的声明中

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button/button.component';
import { HeaderComponent } from './header/header.component';

@NgModule({
+  declarations: [ButtonComponent, HeaderComponent],
  imports: [
    CommonModule
  ]
})
export class ComponentModule { }

然而,如果保持现状,无法在共享模块中调用,因此需要添加导出命令。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button/button.component';
import { HeaderComponent } from './header/header.component';

@NgModule({
  declarations: [
    ButtonComponent,
    HeaderComponent
  ],
  imports: [
    CommonModule
  ],
+  exports: [
+    ButtonComponent,
+    HeaderComponent
+  ]
})
export class ComponentModule { }

从 shared 模块中正确加载了 button 组件和 header 组件吗?尝试从 top 组件中调用并进行确认。

<app-header></app-header>
<app-button></app-button>

如果在模板中嵌入组件,可以使用组件文件中指定的选择器名称进行调用。


接下来我们将创建一个button组件。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ButtonComponent } from './button/button.component';
import { HeaderComponent } from './header/header.component';
+ import { RouterModule } from '@angular/router';

@NgModule({
  declarations: [
    ButtonComponent,
    HeaderComponent
  ],
  imports: [
    CommonModule,
+    RouterModule
  ],
  exports: [
    ButtonComponent,
    HeaderComponent
  ]
})
export class ComponentModule { }

如果想要使用Vue中的标签进行页面跳转,只需添加标签即可使用,并无特殊需求。

在Angular中,需要在module文件中加载RouterModule模块。

RouterModule不是一个自定义的模块,而是一个在框架中预先提供的模块文件。

<a [routerLink]="path" class="button">{{ text }}</a>

如果加载了module文件,可以通过routerLink=”文件路径”来指定导航目标。在这种情况下,我想添加变量而不是字符串,所以我通过[routerLink]=”变量名包含文件路径”的形式进行数据绑定。

.button{
  display: block;
  font-size: 22px;
  width: 400px;
  height: 70px;
  padding: 25px;
  box-sizing: border-box;
  color: #fff;
  background: #000;
  font-weight: 700;
  text-align: center;
  line-height: 1;
  transition: all .3s ease;
  border: 1px solid transparent;
  &:hover {
    background-color: #fff;
    border-color: #000;
    color: #000;
  }
}

对于scss的详细说明将被省略。

+ import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.scss']
})
export class ButtonComponent implements OnInit {
  /** Vueで言う所のpropsに該当するもの
   * @param text buttonに表示させる文字列
   * @param path 遷移先のURLのパス
  */
+  @Input() text: string;
+  @Input() path: string;

  constructor() { }

  ngOnInit() {
  }

}

在Vue中,通过使用props,可以从父组件传递数据。
在Angular中,通过使用Input,可以传递和接收数据。

import { Input } from '@angular/core';

class Hoge {
  @Input() val: type; // @Input() 変数: 型の名前
}

下一步,创建header组件。

<header class="header">
  <nav>
    <ul class="navList">
      <li class="navItem">
        <a routerLink="/" class="navLink" routerLinkActive="router-link-active" [routerLinkActiveOptions]="{exact: true}">トップ</a>
      </li>
      <li class="navItem">
        <a routerLink="/list" class="navLink" routerLinkActive="router-link-active" [routerLinkActiveOptions]="{exact: true}">商品一覧</a>
      </li>
    </ul>
  </nav>
</header>

与之前的button组件不同,routerLink需要放入一个字符串。

routerLinkActive是一个能够在路由器链接到指定位置时传递class的功能。在routerLinkActive中指定class,可以使指定的class在活动时生效。

[routerLinkActiveOptions]="{exact: true}"

如果上述代码的目标路径正确,它将被设定为激活状态。

只需写入 SCSS。

.header{
  padding: 60px 0;
}

.navList{
  display: flex;
  justify-content: center;
}
.navLink{
  color: #ccc;
  font-weight: 700;
  line-height: 1;
}

.navItem{
  padding: 0 20px;
  &:not(:first-child){
    border-left: 1px solid #000;
  }
}

.router-link-active{
  color: #000;
}
@import '~normalize.scss';

body {
  font-family: YuGothic,'Yu Gothic','Hiragino Kaku Gothic ProN','ヒラギノ角ゴ ProN W3','メイリオ', Meiryo,'MS ゴシック',sans-serif;
  font-weight: 400;
  font-size: 12px;
  height: 100%;
  background: #FCFCFC;
}
h1, p, ul{
  margin: 0;
}
ul{
  list-style-type: none;
  padding-left: 0;
}
a{
  text-decoration: none;
}

下一步是创建首页。
通过父组件向传递数据。

<div class="top">
  <app-header></app-header>
  <div class="titleWrap">
    <h1 class="title">それっぽいトップページ</h1>
    <p class="subTitle">KAKKO II EIGO NO TEKISUTO GA HAIRU</p>
    <p class="subTitleBg">KAKKO II EIGO<br>NO TEKISUTO GA HAIRU</p>
  </div>
  <div class="button">
    <app-button text="商品を見る" path="/list"></app-button>
  </div>
</div>

请写上用于顶部布局的css样式

.titleWrap{
  position: relative;
  margin-top: 200px;
}
.title{
  font-size: 120px;
  line-height: 1;
  font-weight: 700;
  text-align: center;
}
.subTitle{
  margin-top: 30px;
  font-size: 20px;
  text-align: center;
}
.subTitleBg{
  position: absolute;
  top: 70px;
  left: 50%;
  z-index: -1;
  font-size: 60px;
  line-height: 1.05;
  color: #eee;
  text-align: center;
  transform: translateX(-50%);
}

.button{
  width: 400px;
  margin: 150px auto 0;
}

Angular的主页已完成。

总结

编写源代码后,发现作为文章变得非常长。
当我们在代码级别上比较SPA框架时,我们发现它们具有相同的功能。

也许是因为Vue的创作者尤雨溪先生参与了AngularJS的开发,所以这两者的机制可能相似。

我想在下一章中创建商品列表页面和详细页面。

希望这成为最后一章。

在学习Angular的实际应用中与Vue.js进行比较 – 实施篇- 第4部分。

广告
将在 10 秒后关闭
bannerAds