英雄之旅安卓是一款由网易游戏开发的角色扮演类手机游戏,于2018年7月正式上线。该游戏以中国神话为背景,以传奇英雄为主题,将传奇故事和神话故事完美地融合在一起,创造出一个全新的神话世界。
在这里,玩家可以体验到不同的神话故事:从封神大战到太古时代的传奇故事;从三皇五帝到唐朝的历史故事。玩家可以选择不同的角色进行游戏:如封神大战中的哪吒、太乙真人、女娲、黄帝、炎帝、伏羲、太上老君等。
此外,《英雄之旅》还有一个独特的特性——“元气”。元气是一种能量形式,它可以用来升级武器装备、开启新副本、升级天书和其他功能。此外,还有一个“元气竞技场”功能:在竞技场中,玩家可以通过PK来赢得元气奖励。
public class HeroJourney { public static void main(String[] args) { // 创建一个新的HeroJourney对象 HeroJourney hj = new HeroJourney(); // 设定剧情胜利条件 hj.setVictoryCondition(); // 创建剧情 hj.createStory(); // 创建NPC hj.createNPC(); // 创建怪物 hj.createMonster(); } public void setVictoryCondition() { } public void createStory() { } public void createNPC() { } public void createMonster() { } }
英雄之旅的 HeroesComponent
目前获取和显示的都是模拟数据。
本节课的重构完成之后,HeroesComponent
变得更精简,并且聚焦于为它的视图提供支持。这也让它更容易使用模拟服务进行单元测试。
要查看本页所讲的范例程序,参阅现场演练 / 下载范例。
组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。
本节课,你将创建一个 HeroService
,应用中的所有类都可以使用它来获取英雄列表。 不要使用 new关键字来创建此服务,而要依靠 Angular 的依赖注入机制把它注入到 HeroesComponent
的构造函数中。
服务是在多个“互相不知道”的类之间共享信息的好办法。你将创建一个 MessageService
,并且把它注入到两个地方。
HeroService
中,它会使用该服务发送消息MessagesComponent
中,它会显示其中的消息。当用户点击某个英雄时,它还会显示该英雄的 ID。使用 Angular CLI 创建一个名叫 hero
的服务。
ng generate service hero
该命令会在 src/app/hero.service.ts
中生成 HeroService
类的骨架,代码如下:
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class HeroService {
constructor() { }
}
注意,这个新的服务导入了 Angular 的 Injectable
符号,并且给这个服务类添加了 @Injectable()
装饰器。 它把这个类标记为依赖注入系统的参与者之一。HeroService
类将会提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖。 目前它还没有依赖,但是很快就会有了。
@Injectable()
装饰器会接受该服务的元数据对象,就像 @Component()
对组件类的作用一样。
HeroService
可以从任何地方获取数据:Web 服务、本地存储(LocalStorage)或一个模拟的数据源。
从组件中移除数据访问逻辑,意味着将来任何时候你都可以改变目前的实现方式,而不用改动任何组件。这些组件不需要了解该服务的内部实现。
这节课中的实现仍然会提供模拟的英雄列表。
导入 Hero
和 HEROES
。
import { Hero } from "./hero";
import { HEROES } from "./mock-heroes";
添加一个 getHeroes
方法,让它返回模拟的英雄列表。
getHeroes(): Hero[] {
return HEROES;
}
你必须先注册一个服务提供者,来让 HeroService
在依赖注入系统中可用,Angular 才能把它注入到 HeroesComponent
中。所谓服务提供者就是某种可用来创建或交付一个服务的东西;在这里,它通过实例化 HeroService
类,来提供该服务。
为了确保 HeroService
可以提供该服务,就要使用注入器来注册它。注入器是一个对象,负责当应用要求获取它的实例时选择和注入该提供者。
默认情况下,Angular CLI 命令 ng generate service
会通过给 @Injectable()
装饰器添加 providedIn: "root"
元数据的形式,用根注入器将你的服务注册成为提供者。
@Injectable({
providedIn: "root",
})
当你在顶层提供该服务时,Angular 就会为 HeroService
创建一个单一的、共享的实例,并把它注入到任何想要它的类上。在 @Injectable
元数据中注册该提供者,还能允许 Angular 通过移除那些完全没有用过的服务来进行优化。
现在 HeroService
已经准备好插入到 HeroesComponent
中了。
这是一个过渡性的代码范例,它将会允许你提供并使用
HeroService
。
打开 HeroesComponent
类文件。
删除 HEROES
的导入语句,因为你以后不会再用它了。转而导入 HeroService
。
import { HeroService } from "../hero.service";
把 heroes
属性的定义改为一句简单的声明。
heroes: Hero[] = [];
往构造函数中添加一个私有的 heroService
,其类型为 HeroService
。
constructor(private heroService: HeroService) {}
这个参数同时做了两件事:1. 声明了一个私有 heroService
属性,2. 把它标记为一个 HeroService
的注入点。
当 Angular 创建 HeroesComponent
时,依赖注入系统就会把这个 heroService
参数设置为 HeroService
的单例对象。
创建一个方法,以从服务中获取这些英雄数据。
getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
你固然可以在构造函数中调用 getHeroes()
,但那不是最佳实践。
让构造函数保持简单,只做最小化的初始化操作,比如把构造函数的参数赋值给属性。构造函数不应该做任何事。它当然不应该调用某个函数来向远端服务(比如真实的数据服务)发起 HTTP 请求。
而是选择在 ngOnInit 生命周期钩子中调用 getHeroes()
,之后 Angular 会在构造出 HeroesComponent
的实例之后的某个合适的时机调用 ngOnInit()
。
ngOnInit(): void {
this.getHeroes();
}
刷新浏览器,该应用仍运行的一如既往。显示英雄列表,并且当你点击某个英雄的名字时显示出英雄详情视图。
HeroService.getHeroes()
的函数签名是同步的,它所隐含的假设是 HeroService
总是能同步获取英雄列表数据。而 HeroesComponent
也同样假设能同步取到 getHeroes()
的结果。
this.heroes = this.heroService.getHeroes();
这在真实的应用中几乎是不可能的。现在能这么做,只是因为目前该服务返回的是模拟数据。不过很快,该应用就要从远端服务器获取英雄数据了,而那天生就是异步操作。
HeroService
必须等服务器给出响应,而 getHeroes()
不能立即返回英雄数据,浏览器也不会在该服务等待期间停止响应。
HeroService.getHeroes()
必须具有某种形式的异步函数签名。
这节课,HeroService.getHeroes()
将会返回 Observable
,部分原因在于它最终会使用 Angular 的 HttpClient.get
方法来获取英雄数据,而 HttpClient.get()
会返回 Observable
。
Observable
是 RxJS 库中的一个关键类。
在稍后的 HTTP 教程中,你就会知道 Angular HttpClient
的方法会返回 RxJS 的 Observable
。这节课,你将使用 RxJS 的 of()
函数来模拟从服务器返回数据。
打开 HeroService
文件,并从 RxJS 中导入 Observable
和 of
符号。
import { Observable, of } from "rxjs";
把 getHeroes()
方法改成这样:
getHeroes(): Observable<Hero[]> {
const heroes = of(HEROES);
return heroes;
}
of(HEROES)
会返回一个 Observable<Hero[]>
,它会发出单个值,这个值就是这些模拟英雄的数组。
在 HTTP 教程中,你将会调用
HttpClient.get<Hero[]>()
它也同样返回一个 Observable<Hero[]>
,它也会发出单个值,这个值就是来自 HTTP 响应体中的英雄数组。
HeroService.getHeroes
方法之前返回一个 Hero[]
,现在它返回的是 Observable<Hero[]>
。
你必须在 HeroesComponent
中也向本服务中的这种形式看齐。
找到 getHeroes
方法,并且把它替换为如下代码(和前一个版本对比显示):
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
Observable.subscribe()
是关键的差异点。
上一个版本把英雄的数组赋值给了该组件的 heroes
属性。这种赋值是同步的,这里包含的假设是服务器能立即返回英雄数组或者浏览器能在等待服务器响应时冻结界面。
当 HeroService
真的向远端服务器发起请求时,这种方式就行不通了。
新的版本等待 Observable
发出这个英雄数组,这可能立即发生,也可能会在几分钟之后。然后,subscribe()
方法把这个英雄数组传给这个回调函数,该函数把英雄数组赋值给组件的 heroes
属性。
使用这种异步方式,当 HeroService
从远端服务器获取英雄数据时,就可以工作了。
这一节将指导你:
MessagesComponent
,它在屏幕的底部显示应用中的消息。MessageService
,用于发送要显示的消息。MessageService
注入到 HeroService
中。HeroService
成功获取了英雄数据时显示一条消息。使用 CLI 创建 MessagesComponent
。
ng generate component messages
CLI 在 src/app/messages
中创建了组件文件,并且把 MessagesComponent
声明在了 AppModule
中。
修改 AppComponent
的模板来显示所生成的 MessagesComponent
。
<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>
你可以在页面的底部看到来自的 MessagesComponent
的默认内容。
使用 CLI 在 src/app
中创建 MessageService
。
ng generate service message
打开 MessageService
,并把它的内容改成这样。
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
该服务对外暴露了它的 messages
缓存,以及两个方法:add()
方法往缓存中添加一条消息,clear()
方法用于清空缓存。
在 HeroService
中导入 MessageService
。
import { MessageService } from "./message.service";
修改这个构造函数,添加一个私有的 messageService
属性参数。Angular 将会在创建 HeroService
时把 MessageService
的单例注入到这个属性中。
constructor(private messageService: MessageService) { }
这是一个典型的“服务中的服务”场景:你把
MessageService
注入到了 HeroService
中,而 HeroService
又被注入到了 HeroesComponent
中。
修改 getHeroes()
方法,在获取到英雄数组时发送一条消息。
getHeroes(): Observable<Hero[]> {
const heroes = of(HEROES);
this.messageService.add("HeroService: fetched heroes");
return heroes;
}
MessagesComponent
可以显示所有消息,包括当 HeroService
获取到英雄数据时发送的那条。
打开 MessagesComponent
,并且导入 MessageService
。
import { MessageService } from "../message.service";
修改构造函数,添加一个 public 的 messageService
属性。Angular 将会在创建 MessagesComponent
的实例时 把 MessageService
的实例注入到这个属性中。
constructor(public messageService: MessageService) {}
这个 messageService
属性必须是公共属性,因为你将会在模板中绑定到它。
Angular 只会绑定到组件的公共属性。
把 CLI 生成的 MessagesComponent
的模板改成这样。
<div *ngIf="messageService.messages.length">
<h2>Messages</h2>
<button type="button" class="clear"
(click)="messageService.clear()">Clear messages</button>
<div *ngFor="let message of messageService.messages"> {{message}} </div>
</div>
这个模板直接绑定到了组件的 messageService
属性上。
详情 |
|
---|---|
*ngIf
|
只有在有消息时才会显示消息区。 |
*ngFor
|
在一系列 |
Angular 事件绑定 |
把按钮的 |
当你把 最终代码 某一页的内容添加到 messages.component.css
中时,这些消息会变得好看一些。
下面的例子展示了当用户点击某个英雄时,如何发送和显示一条消息,以及如何显示该用户的选取历史。当你学到后面的路由一章时,这会很有帮助。
import { Component, OnInit } from "@angular/core";
import { Hero } from "../hero";
import { HeroService } from "../hero.service";
import { MessageService } from "../message.service";
@Component({
selector: "app-heroes",
templateUrl: "./heroes.component.html",
styleUrls: ["./heroes.component.css"]
})
export class HeroesComponent implements OnInit {
selectedHero?: Hero;
heroes: Hero[] = [];
constructor(private heroService: HeroService, private messageService: MessageService) { }
ngOnInit(): void {
this.getHeroes();
}
onSelect(hero: Hero): void {
this.selectedHero = hero;
this.messageService.add(`HeroesComponent: Selected hero id=${hero.id}`);
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
刷新浏览器,页面显示出了英雄列表。滚动到底部,就会在消息区看到来自 HeroService
的消息。点击 Clear messages 按钮,消息区不见了。
下面是本页所提到的源代码。
import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";
import { Hero } from "./hero";
import { HEROES } from "./mock-heroes";
import { MessageService } from "./message.service";
@Injectable({
providedIn: "root",
})
export class HeroService {
constructor(private messageService: MessageService) { }
getHeroes(): Observable<Hero[]> {
const heroes = of(HEROES);
this.messageService.add("HeroService: fetched heroes");
return heroes;
}
}
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
import { Component, OnInit } from "@angular/core";
import { Hero } from "../hero";
import { HeroService } from "../hero.service";
import { MessageService } from "../message.service";
@Component({
selector: "app-heroes",
templateUrl: "./heroes.component.html",
styleUrls: ["./heroes.component.css"]
})
export class HeroesComponent implements OnInit {
selectedHero?: Hero;
heroes: Hero[] = [];
constructor(private heroService: HeroService, private messageService: MessageService) { }
ngOnInit(): void {
this.getHeroes();
}
onSelect(hero: Hero): void {
this.selectedHero = hero;
this.messageService.add(`HeroesComponent: Selected hero id=${hero.id}`);
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
import { Component, OnInit } from "@angular/core";
import { MessageService } from "../message.service";
@Component({
selector: "app-messages",
templateUrl: "./messages.component.html",
styleUrls: ["./messages.component.css"]
})
export class MessagesComponent implements OnInit {
constructor(public messageService: MessageService) {}
ngOnInit() {
}
}
<div *ngIf="messageService.messages.length">
<h2>Messages</h2>
<button type="button" class="clear"
(click)="messageService.clear()">Clear messages</button>
<div *ngFor="let message of messageService.messages"> {{message}} </div>
</div>
h2 {
color: #A80000;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
.clear {
color: #333;
background-color: #eee;
margin-bottom: 12px;
padding: 1rem;
border-radius: 4px;
font-size: 1rem;
}
.clear:hover {
color: white;
background-color: #42545C;
}
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
import { HeroesComponent } from "./heroes/heroes.component";
import { HeroDetailComponent } from "./hero-detail/hero-detail.component";
import { MessagesComponent } from "./messages/messages.component";
@NgModule({
declarations: [
AppComponent,
HeroesComponent,
HeroDetailComponent,
MessagesComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [
// no need to place any providers due to the `providedIn` flag...
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>
HeroService
类中
HeroService
注册为该服务的提供者,以便在别处可以注入它
HeroService
中获取数据的方法提供了一个异步的函数签名
Observable
以及 RxJS 库
of()
方法返回了一个模拟英雄数据的可观察对象 (Observable<Hero[]>
)
ngOnInit
生命周期钩子中调用 HeroService
方法,而不是构造函数中
MessageService
,以便在类之间实现松耦合通讯
HeroService
连同注入到它的服务 MessageService
一起,注入到了组件中AngularJS XMLHttpRequest我们可以使用AngularJS 内置的 $http 服务直接同外部进行通信。$http 服务只是简单的封装了浏览器原生...
进程本节介绍Node.js的process(过程)对象,它提供有关当前Node.js过程的信息和控制。process是全局对象,能够在任意位置对其进...
global-modifying-module.d.tsglobal-plugin.d.tsglobal.d.tsmodule-class.d.tsmodule-function.d.tsmodule-plugin.d.tsmodule.d...
1. Iterator(遍历器)的概念JavaScript 原有的表示“集合”的数据结构,主要是数组( Array )和对象( Object ),ES6 又添加...
ECMAScript 6 教程导读ES6:全称ECMAScript 6.0ECMAScript 是JavaScript语言的国际标准,JavaScript是ECMAScript的实现。ES6经过...
HTML DOM 简介 HTML DOM 定义了访问和操作 HTML 文档的标准。您应该具备的基础知识 在您开始学习HTMLDOM之前,您需要对以下内容...
描述导入指令,导入SASS或SCSS文件。 它直接需要导入文件名。 在SASS中导入的所有文件将在单个CSS文件中组合。当我们使用 @impor...
描述 @for 指令允许您在循环中生成样式。计数器变量用于设置每次迭代的输出。 @for 指令有两种类型,如下表所示:序号关键字和描...
描述本节介绍了Less 中函数的使用。LESS 映射具有值操作的 JavaScript 代码,并使用预定义的函数来操纵样式表中的 HTML 元素。它...
实例使用 meter 元素展示给定的数据范围:meter value="2" min="0" max="10"2 out of 10/meterbrmeter value="0.6"60%/meter浏览...