第六章 数据结构
注:学习使用,禁止转载
数据结构概览
编写一个可维护的APP最难的方面之一是管理数据.在你的应用程序中,有很多种方式去获取数据.
- AJAX请求
- Websocket
- Indexdb
- LocalStorage
- Service Workers
- 等等
管理数据难的问题有:
- 我们怎么聚合这些不同来源的数据到一个一致的系统中
- 我们怎样才能避免意外的副作用引起的错误
- 我们怎么组织代码,使得对于其他参与者来说是清晰可见的
- 当数据变化的时候,怎么让我们的应用程序尽快做出反应
许多年来,MVC是一种标准的数据结构模式:Model包含领域模型逻辑,View负责显示数据,Controller将他们连接起来.
但是问题是,我们熟知的MVC模式不能直接很好的用在web开发中.
这里产生了一些新的数据结构的注意.
- MVW / Two-way data binding:Model-View-Whatever是angular1的默认结构.$scope提供了双向绑定,当数据变化的时候,整过应用程序会分享相同的数据.
- Flux,使用一个非双向数据结构.在Flux中,Stores保存数据,Views渲染数据,Actions在Stores中改变数据.这里有更多的方式设置Flux,但是这种方式是单向的,更容易理解.
- Observables,观察者模式.Observables给我们一个数据流,我们订阅数据流,然后执行操作去反应变化. 在JavaScript中,RxJs是最出名的反应式编程架构,它给了我们更多的能力,去组合操作进数据流.
angular2中的数据结构
对于数据结构的使用,angular2是非常灵活的.一个数据策略在这个项目用,不代表另外的项目就用这个数据策略.所以,angular2没有指定一个技术堆栈,而是可以使用在我们选用的任何数据结构之上.
这个的好处就是使得angular2可以使用在任何地方.不好的就是你必须自己选定一种模式.
别担心,我们不会让你自己来选择.在接下来的部分,我们会详细讲解使用这些模式怎么构建应用程序.
第一部分 Observables
Observables and RxJS
在angular中,我们可以使用Observables作为我们的数据结构,使用Observables组织我们的数据叫着Reactive Programming,反应式编程.
但是Observables是什么,Reactive Programming是怎么样的.Reactive Programming是一种跟异步数据流工作的方式.Observables是一种保存数据的方式,它可以使我们实现Reactive Programming.但是我怀疑,这样讲可能不清晰.所以,接下来让我们看看那例子.
注意:需要具备的知识
我想指出的是,这不是一本关于反应式编程的书,这里有一些反应式编程方面的书籍,我们将会在下面列出来.
考虑到这一章是一个将angular与RxJs结合的书,而不是详细介绍RxJs的书.
在这一章,我们会解释我们需要用到的RxJs的概念和API.但是记住,如果你对于RxJs还是新手,你需要阅读其他的治疗,以便去细致的了解.
学习反应式编程和RxJs
如果你刚开始学习RxJs,我建议你先读一下这篇文章.
- The introduction to Reactive Programming you’ve been missing46 by Andre Staltz
当你对RxJs有点了解的时候,下面的连接可以帮助你了解更多:
- Which static operators to use to create streams?
- Which instance operators to use on streams?
- RxMarbles - Interactive diagrams of the various operations on streams
在这章中,我们会提供RxJs的文档链接给你.RxJs的文档有很多的例子,解释了怎么组合不同的流和操作.
我会给你一个警告:开始学习RxJs的时候是比较难的.但是相信我,你可以掌握它,并且从中受益良多.
下面有一些关于RxJs的讨论可能对你有利:
- Promise发送单一的值,作为流发送多个值.
- 原始编程是拉数据,而反应式编程是推数据
- RxJs是函数式编程
- 流是可以组合的
聊天应用程序概览
在本章中,我们使用RxJs构建一个聊天应用程序.界面如下:
在这个例子中,我们会提供一些机器人去和你聊天.打开代码,并运行:
cd code/rxjs/chatnpm installnpm run go
然后打开浏览器输入:http://localhost:8080
注意关于这个应用程序的一些事情:
- 你可以点击threads可以切换聊天人
- 机器人会根据你的输入发送信息给你
- 顶部的未读消息数量是异步的
让我们看看这个应用程序的结构:
- 3个顶层angular 组件
- 3个model
- 3个service
Components 组件
我们可以将应用程序分解为3个组件
- ChatNavBar包含未读消息数
- ChatThreads聊天机器人列表
- ChatWindow展示与当前聊天对象的聊天信息
Models 模型
这个应用程序有三个model
- User存储聊天参与人的信息
- Message存储单条信息
- Thread存储聊天对象的聊天信息
Services 服务
在这个应用程序中,每一个model对应一个service,service是具有两类功能的单例:
- 提供我们应用程序可以订阅的数据流
- 提供增加或删除数据的操作
比如UserService:
- 发送当前用户的流
- 提供一个设置当前用户的方法
总结
在顶层,应用程序的数据结构是简单直接的.
- service维护发送模型的流
- components订阅流并根据最近的值渲染它们
比如,ChatThreads组件监听来自于ThreadService的最近thread列表.ChatWindow订阅最近消息列表.
在本章的其余部分,我们会深入分析怎样使用angular2 和RxJs来实现这个应用程序.
实现模型
User
code/rxjs/chat/app/ts/models.ts
export class User {id: string;constructor(public name: string,public avatarSrc: string) {this.id = uuid();}
}
Thread
code/rxjs/chat/app/ts/models.ts
id: string;lastMessage: Message;name: string;avatarSrc: string; constructor(id?: string,name?: string,avatarSrc?: string) {this.id = id || uuid();this.name = name;this.avatarSrc = avatarSrc;}
}
注意,在我们的模型中,存储了lastMessage的引用,它让我们去显示最近的消息.
Message
export class Message {id: string;sentAt: Date;isRead: boolean;author: User;text: string;thread: Thread;constructor(obj?: any) {this.id = obj && obj.id || uuid();this.isRead = obj && obj.isRead || false;this.sentAt = obj && obj.sentAt || new Date();this.author = obj && obj.author || null;this.text = obj && obj.text || null;this.thread = obj && obj.thread || null;}
}
上面要注意的是,我们的构造函数使用any类型,我们可以传递我们需要的参数进去,就可以新建一个Message,比如只传递id,或者isRead都可以.
实现UserService
UserService提供了一个存储当前用户的地方,并且当当前用户改变的时候,通知应用程序.
我们需要做的第一件事情是创建一个Typescript类,并使用@Injectable 注解该类,使其可注入.
code/rxjs/chat/app/ts/services/UserService.ts
@Injectable()
export class UserService {
可注入意思是可以将当前服务注入到其他的组件中.
currentUser
接下来,我们安装一个流,我们将会使用它去管理当前用户.
code/rxjs/chat/app/ts/services/UserService.ts
currentUser: Subject<User> = new BehaviorSubject<User>(null);
这里有一些事情需要解释:
- 我们定义了一个Subject流的实例变量
- 更具体的说,它是一个BehaviorSubject流,包含User对象
- 然而,这个流的第一个值是null
如果你没有使用过RxJs,你可能不知道Subject和BehaviorSubject是什么.你可以认为Subject是一个可读写流.
其结果之一是,因为消息会被立刻发送,一个新的订阅者可能会有丢失该消息的风险,BehaviorSubject可以解决这个问题.
BehaviorSubject有一个特定的属性来存储最近的值.这意味着任务订阅该流的人都会接收到最新的值,这个是很重要的,它意味着当参与者变化的时候,任何其他的订阅者都知道当前用户是谁.
设置一个新用户
当当前用户改变的时候,我们需要发布一个新的用户给流,我们可以使用两种方式来做这个事情.
直接增加一个新用户给流
最简单的方式就是直接新建一个用户给流,如下:
userService.subscribe((newUser) => {console.log('New User is: ', newUser.name);
})
// => New User is: originalUserName
let u = new User('Nate', 'anImgSrc'); userService.currentUser.next(u);
// => New User is: Nate
创建一个setCurrentUser(newUser: User)函数
另外一种方式就是可以创建一个新的函数setCurrentUser(newUser: User).如下:
code/rxjs/chat/app/ts/services/UserService.ts
public setCurrentUser(newUser: User): void {this.currentUser.next(newUser);
}
注意到了吗?我们始终使用currentUser的next方法,为什么还要做这一步呢?
这个是为了从流中解耦.通过使用currentUser包装方法使得其他的客户可以调用,而不会动我们里面的代码.
在这里,我不会直接建议你使用哪一种,但是对于一个大型应用程序来说,解耦是很重要的.
UserService.ts
UserService像下面这样.
code/rxjs/chat/app/ts/services/UserService.ts
import {Injectable, bind} from '@angular/core';
import {Subject, BehaviorSubject} from 'rxjs';
import {User} from '../models';/*** UserService manages our current user*/
@Injectable()
export class UserService {// `currentUser` contains the current usercurrentUser: Subject<User> = new BehaviorSubject<User>(null);public setCurrentUser(newUser: User): void {this.currentUser.next(newUser);}
}export var userServiceInjectables: Array<any> = [bind(UserService).toClass(UserService)
];
MessagesService
在这个应用程序中,所有的消息都是通过MessagesService管理.
比起UserService来说,MessageService有更多的流,这里有5个流,3个数据管理流和2个行为流
三个数据管理流是:
- newMessages 发送每个新消息一次
- messages 发送当前消息列表
- updates 在messages上执行操作
newMessages流
newMessages流仅仅发送一次新消息
code/rxjs/chat/app/ts/services/MessagesService.ts
@Injectable()
export class MessagesService {
// a stream that publishes new messages only once newMessages: Subject<Message> = new Subject<Message>();
如果你希望,你可以定义一个帮助函数,实现添加新消息.
code/rxjs/chat/app/ts/services/MessagesService.ts
addMessage(message: Message): void {this.newMessages.next(message);
}
它可能是有帮助的,有一个流从thread中所有消息,而不是参与用户.比如,考虑Echo Bot:
当我们实现Echo Bot的时候,不希望有一个无限循环,或者重复以前的信息.
为了实现这个功能,我们可以订阅newMessage流,并且过滤掉所有信息,具有这些条件的:
- 这个thread的消息
- 不是被机器人输入的
你可以认为这个就是说我希望我与某个机器人的聊天信息,除了机器人说的之外:
code/rxjs/chat/app/ts/services/MessagesService.ts
messagesForThreadUser(thread: Thread, user: User): Observable<Message> {return this.newMessages.filter((message: Message) => {// belongs to this threadreturn (message.thread.id === thread.id) &&// and isn't authored by this user(message.author.id !== user.id);
});
}
messages流
当newMessages发送一个新消息的时候,messages发送最近消息的数组
code/rxjs/chat/app/ts/services/MessagesService.ts
// `messages` is a stream that emits an array of the most up to date messages messages: Observable<Message[]>;
考虑messages怎么获取,在讨论之前,我们需要先讨论updates流和一个新的模式:操作流
操作流
这里:
- 我们会保存存储最近消息列表的消息流(messages)状态
- 我们会使用一个update流应用到messages流上
你可以使用这种方式理解:放入update流上的任何函数,都会改变当前消息流列表.这个函数应该是接收一个消息列表,并且返回一个新的消息列表.让我们看看接口代码:
code/rxjs/chat/app/ts/services/MessagesService.ts
interface IMessagesOperation extends Function {(messages: Message[]): Message[];
}
让我们定义我们的update流
code/rxjs/chat/app/ts/services/MessagesService.ts
// `updates` receives _operations_ to be applied to our `messages`
// it's a way we can perform changes on *all* messages (that are currently
// stored in `messages`)
updates: Subject<any> = new Subject<any>();
记住,updates接收一个应用于我们消息列表的操作.但是我们怎么连接起来?我们可以像这样:
code/rxjs/chat/app/ts/services/MessagesService.ts
constructor() {this.messages = this.updates// watch the updates and accumulate operations on the messages.scan((messages: Message[],operation: IMessagesOperation) => {return operation(messages);},initialMessages)
这个代码介绍了一个新的流函数:scan.如果你对函数式编程很熟悉,那么它跟reduce很像.它对数组中的每一个元素运行一次这个函数,并最后累积起来.scan有什么特殊的呢?它会立刻发送每一个元素的结果,而不会等待所有操作完成,这个正式我们需要的.
当我们调用this.updates.scan时,我们会创建一个新的流,它被updates订阅,在每一个部分,我们会获得:
- 我们已经完成的messages
- new操作会被应用
最后,我们返回new message[]
共享一个流
关于流的一个信息就是它们默认是不共享的.意思就是,如果一个订阅者从流中读取了一个值,这个值就会永远消失.在我们的例子中,我们希望,1. 在许多订阅者中共享流. 2. 对于来迟的订阅者,发送最近的流.
为了做这个事情,我们有两个操作:publishReplay和refCount.
- publishReplay让我们可以在多个订阅者中共享流,并且对于将来的订阅者返回一个消息数量n
- refCount使得发送的数量容易被使用
code/rxjs/chat/app/ts/services/MessagesService.ts
// watch the updates and accumulate operations on the messages.scan((messages: Message[],operation: IMessagesOperation) => {return operation(messages);},initialMessages)// make sure we can share the most recent list of messages across anyone// who's interested in subscribing and cache the last known list of// messages.publishReplay(1).refCount();
向messages流中增加message
我们可以像下面这样增加:
var myMessage = new Message(/* params here... */);
updates.next( (messages: Message[]): Message[] => {return messages.concat(myMessage);
})
上面,我们给updates流增加了一个操作.messages订阅了该流,所以当增加的时候,它会将其添加到自己的消息列表中去.
上面的方法有一个问题,我们每次都会去调用这个方法.其实我们可以像下面这样做:
Data Architecture with Observables - Part 1: Services 170addMessage(newMessage: Message) {updates.next( (messages: Message[]): Message[] => {return messages.concat(newMessage);})
}
使用下面的调用就可以了:
var myMessage = new Message(/* params here... */);
MessagesService.addMessage(myMessage);
方法很好,但是它不是反应式的.部分的,由于创建消息的行为不能与其他流组合(同时这个方法也绕过了NewMessage流).
创建一个新消息的反应式方式可能是有一个流,它接收一个消息列表并且增加到这个列表中去.
又一次,如果你没有这样思考的话,这个方式有点新.下面是你怎么实现它:
- 我们创建一个叫create的action stream-行为流(action stream只是用来区别它的功能,本质上它还是一个Subject流)
code/rxjs/chat/app/ts/services/MessagesService.ts
// action streams
create: Subject<Message> = new Subject<Message>();
- 接下来,在我们的构造器中配置create流
code/rxjs/chat/app/ts/services/MessagesService.ts
this.create.map( function(message: Message): IMessagesOperation {return (messages: Message[]) => {return messages.concat(message);
};
})
这个map方法有点像JavaScript数组的map方法,它针对每一个元素运行这个函数并返回函数返回值.
这个流会发送一个接收一个消息列表并且添加这个消息到消息列表的方法.
现在,我们有了create流,我们需要将其挂到updates流上去,通过subscribe完成这个功能.
code/rxjs/chat/app/ts/services/MessagesService.ts
this.create.map( function(message: Message): IMessagesOperation {return (messages: Message[]) => {return messages.concat(message);
}; }).subscribe(this.updates);
上面的意思是我们updates流订阅去监听create流.这个意思就是说,如果create流接收到一个消息,它会发送将会被updates流接收的一个IMessagesOperation操作然后message将会被增加到messages里面去.
下面的图描述了这个场景:
好主意,它意味着我们可以获得下面的东西:
- 来自于messages的消息列表
- 在当前列表上面执行操作updates的方式
- 一个易于使用的create流,它可以添加到我们的updates流上去.
在这个代码中,如果你想获取最近的消息列表,你仅仅需要去获取messages流.
但是有一个问题,我们始终没有将这个流连接到newMessages流上.
要是有一种方便的方法做这个事情就好了,事实证明是有的,如下:
code/rxjs/chat/app/ts/services/MessagesService.ts
this.newMessages.subscribe(this.create);
现在,场景如下:
现在,我们的工作流完成了.这是一个两全其美的办法:如果我们需要订阅单个信息,可以通过newMessages流,如果我们想要最近的消息列表,我们可以订阅messages流.
MessagesService全部.
import {Injectable, bind} from '@angular/core';
import {Subject, Observable} from 'rxjs';
import {User, Thread, Message} from '../models';let initialMessages: Message[] = [];interface IMessagesOperation extends Function {(messages: Message[]): Message[];
}@Injectable()
export class MessagesService {// a stream that publishes new messages only oncenewMessages: Subject<Message> = new Subject<Message>();// `messages` is a stream that emits an array of the most up to date messagesmessages: Observable<Message[]>;// `updates` receives _operations_ to be applied to our `messages`// it's a way we can perform changes on *all* messages (that are currently // stored in `messages`)updates: Subject<any> = new Subject<any>();// action streamscreate: Subject<Message> = new Subject<Message>();markThreadAsRead: Subject<any> = new Subject<any>();constructor() {this.messages = this.updates// watch the updates and accumulate operations on the messages.scan((messages: Message[],operation: IMessagesOperation) => {return operation(messages);},initialMessages)// make sure we can share the most recent list of messages across anyone// who's interested in subscribing and cache the last known list of// messages.publishReplay(1).refCount();// `create` takes a Message and then puts an operation (the inner function)// on the `updates` stream to add the Message to the list of messages.//// That is, for each item that gets added to `create` (by using `next`)// this stream emits a concat operation function.//// Next we subscribe `this.updates` to listen to this stream, which means// that it will receive each operation that is created//// Note that it would be perfectly acceptable to simply modify the// "addMessage" function below to simply add the inner operation function to// the update stream directly and get rid of this extra action stream// entirely. The pros are that it is potentially clearer. The cons are that// the stream is no longer composable.this.create.map( function(message: Message): IMessagesOperation {return (messages: Message[]) => {return messages.concat(message);};}).subscribe(this.updates);this.newMessages.subscribe(this.create);// similarly, `markThreadAsRead` takes a Thread and then puts an operation// on the `updates` stream to mark the Messages as readthis.markThreadAsRead.map( (thread: Thread) => {return (messages: Message[]) => {return messages.map( (message: Message) => {// note that we're manipulating `message` directly here. Mutability// can be confusing and there are lots of reasons why you might want// to, say, copy the Message object or some other 'immutable' hereif (message.thread.id === thread.id) {message.isRead = true;}return message;});};}).subscribe(this.updates);}// an imperative function call to this action streamaddMessage(message: Message): void {this.newMessages.next(message);}messagesForThreadUser(thread: Thread, user: User): Observable<Message> {return this.newMessages.filter((message: Message) => {// belongs to this threadreturn (message.thread.id === thread.id) &&// and isn't authored by this user(message.author.id !== user.id);});}
}export var messagesServiceInjectables: Array<any> = [bind(MessagesService).toClass(MessagesService)
];
试试
如果还没有试,这里我们提供了一个方式去感受MessagesService是怎么工作的.测试在test/services/MessagesService.spec.ts.
:fa-info-circle:为了运行测试,打开终端,运行下列代码:
cd /path/to/code/rxjs/chat//<--yourpathwillvary
npm install
karma start
首先,让我们创建一些要使用的模型例子:
code/rxjs/chat/test/services/MessagesService.spec.ts
import {Message, User, Thread} from '../../app/ts/models'; describe('MessagesService', () => {it('should test', () => {let user: User = new User('Nate', '');let thread: Thread = new Thread('t1', 'Nate', '');let m1: Message = new Message({author: user,text: 'Hi!',thread: thread
});
接下来,让我们订阅流:
code/rxjs/chat/test/services/MessagesService.spec.ts
text: 'Bye!',thread: thread});let messagesService: MessagesService = new MessagesService();// listen to each message indivdually as it comes inmessagesService.newMessages.subscribe( (message: Message) => {console.log('=> newMessages: ' + message.text);});// listen to the stream of most current messagesmessagesService.messages.subscribe( (messages: Message[]) => {console.log('=> messages: ' + messages.length);});messagesService.addMessage(m1);messagesService.addMessage(m2);
注意,虽然我们首先订阅newMessages,并且newMessages被addMessage直接调用,但是我们的messages首先打印出信息.这是因为Messages订阅newMessages早于我们测试中的初始化(当MessageService被初始化时).你的代码不能依赖于这个顺序,但是了解它是有必要的.
ThreadsService
ThreadsService中,我们会定义各自发送的四个流:
- 当前线程的map
- 当前线程的有序列表,newnewest-firt(时间最新的在最前)
- 当前选择线程
- 当前选择线程的消息列表
线程的map
code/rxjs/chat/app/ts/services/ThreadsService.ts
import {Injectable, bind} from '@angular/core';
import {Subject, BehaviorSubject, Observable} from 'rxjs';
import {Thread, Message} from '../models';
import {MessagesService} from './MessagesService';
import * as _ from 'underscore';@Injectable()
export class ThreadsService {// `threads` is a observable that contains the most up to date list of threadsthreads: Observable<{ [key: string]: Thread }>;
注意这个流将会发送一个map,key为线程id,value为线程本身.
为了保存当前线程列表,我们创建一个threads流关联MessageService.messages流.
code/rxjs/chat/app/ts/services/ThreadsService.ts
this.threads = messagesService.messages
每一次addMessage增加时,messages会发送一个数组,我们将会检查每个message,并且返回我们想要的thread列表.
注意,上面我们每次发送一个新的列表.因为可能下一次可能删除一些消息(比如离开会话).因为我们每次都会重新计算thread的列表,正常情况下,如果没有消息,我们会删除thread列表.
在线程列表中,我们会显示最近聊天的thread.
如下:
为了做这个事情,我们会存储每个thread的最近消息列表,通过比较sentAt时间获取最新的thread
code/rxjs/chat/app/ts/services/ThreadsService.ts
// Cache the most recent message for each thread
let messagesThread: Thread = threads[message.thread.id];
if (!messagesThread.lastMessage ||
messagesThread.lastMessage.sentAt < message.sentAt) { messagesThread.lastMessage = message;
}
});
return threads; });
结合起来,threads像下面这样:
code/rxjs/chat/app/ts/services/ThreadsService.ts
this.threads = messagesService.messages.map( (messages: Message[]) => {let threads: {[key: string]: Thread} = {};// Store the message's thread in our accumulator `threads`messages.map((message: Message) => {threads[message.thread.id] = threads[message.thread.id] ||message.thread;// Cache the most recent message for each threadlet messagesThread: Thread = threads[message.thread.id];if (!messagesThread.lastMessage ||messagesThread.lastMessage.sentAt < message.sentAt) {messagesThread.lastMessage = message;}});return threads;});
实施ThreadsService
code/rxjs/chat/test/services/ThreadsService.spec.ts
import {it,describe
} from '@angular/core/testing';import {MessagesService, ThreadsService} from '../../app/ts/services/services';
import {Message, User, Thread} from '../../app/ts/models';
import * as _ from 'underscore';describe('ThreadsService', () => {it('should collect the Threads from Messages', () => {let nate: User = new User('Nate Murray', '');let felipe: User = new User('Felipe Coury', '');let t1: Thread = new Thread('t1', 'Thread 1', '');let t2: Thread = new Thread('t2', 'Thread 2', '');let m1: Message = new Message({author: nate,text: 'Hi!',thread: t1});let m2: Message = new Message({author: felipe,text: 'Where did you get that hat?',thread: t1});let m3: Message = new Message({author: nate,text: 'Did you bring the briefcase?',thread: t2});let messagesService: MessagesService = new MessagesService();let threadsService: ThreadsService = new ThreadsService(messagesService);threadsService.threads.subscribe( (threadIdx: { [key: string]: Thread }) => {let threads: Thread[] = _.values(threadIdx);let threadNames: string = _.map(threads, (t: Thread) => t.name).join(', ');console.log(`=> threads (${threads.length}): ${threadNames} `);});messagesService.addMessage(m1);messagesService.addMessage(m2);messagesService.addMessage(m3);// => threads (1): Thread 1// => threads (1): Thread 1// => threads (2): Thread 1, Thread 2});
});
thread的有序列表,最新消息在前面
让我们创建一个新的thream.它返回有序的thread列表,基于时间.orderedThreads.
code/rxjs/chat/app/ts/services/ThreadsService.ts
// `orderedThreads` contains a newest-first chronological list of threads orderedThreads: Observable<Thread[]>;
接下来,在构造器中,我们定义了orderedThreads,它订阅了threads,并且返回有序列表.
code/rxjs/chat/app/ts/services/ThreadsService.ts
this.orderedThreads = this.threads .map((threadGroups: { [key: string]: Thread }) => {
let threads: Thread[] = _.values(threadGroups);return _.sortBy(threads, (t: Thread) => t.lastMessage.sentAt).reverse();});
当前选择的thread – currentThread
我们的应用程序需要知道当前选择的是thread,这个让我们知道:
- 在消息窗口中应该显示那个thread
- 在thread列表中,应该标识那个thread为当前thread
让我们创建一个存储当前thread的BehaviorSubject流 – currentThread
code/rxjs/chat/app/ts/services/ThreadsService.ts
// `currentThread` contains the currently selected threadcurrentThread: Subject<Thread> =
new BehaviorSubject<Thread>(new Thread());
首先,我们分配一个空的thread作为默认值.这里就不需要进一步的配置设置了.
设置当前thread
为了设置当前thread,我们可以
- 通过next直接提交一个新的thread
- 给它增加一个新的辅助方法
让我们定义一个帮助函数,setCurrentThread,我们可以使用它设置thread.
code/rxjs/chat/app/ts/services/ThreadsService.ts
setCurrentThread(newThread: Thread): void {this.currentThread.next(newThread);
}
将当前thread标记为已读
我们想要去跟踪未读消息的数量,当我们切换到一个thread的时候,我们希望标记该thread的所有消息为已读。我们需要去做下面这些内容:
- messagesService.makeThreadAsRead接收一个thread,然后将该thread对应的所有消息标记为已读
- 我们的currentThread代表当前thread发送一个单一的thread
所以我们需要做的就是将他们组合在一起。
code/rxjs/chat/app/ts/services/ThreadsService.ts
this.currentThread.subscribe(this.messagesService.markThreadAsRead);
当前thread(currentThread)的消息列表(currentThreadMessages)
既然我们已经有选择了当前thread,我们要确保我们能够显示该thread的消息列表。
实现这个效果比表面看起来要复杂得多,我们像下面这样去实现:
var theCurrentThread:Thread;this.currentThread.subscribe((thread:Thread) => {theCurrentThread = thread;
});this.currentThreadMessages.map((mesages:Message[]) => {return _.filter(messages,(message:Message) => {return message.thread.id == theCurrentThread.id;})});
这种方法有什么问题呢?如果currentThread改变了,currentThreadMessages不知道,因此,我们会得到一个过时的curentMessages。
现在如果我们改变它,将消息列表存储为一个变量,让该变量去订阅currentThread的变化,但是现在又有另一个问题,我们仅仅知道currentThread变化了,但是当有新的消息进来的时,我们并不知道。
怎么解决这个问题呢?
事实证明,RxJs有一组操作,我们可以使用它们来组合多个流。在这个例子中,我们希望“不管是currentThreadmessageService.messages改变,我们都希望去发射一些东西”,我们使用combineLatest操作符来做这个事情。
code/rxjs/chat/app/ts/services/ThreadsService.ts
this.currentThreadMessages = this.currentThread.combineLatest(messagesService.messages,(currentThread: Thread, messages: Message[]) => {
当我们结合两个流的时候,那个先到是不保证的。所以我们需要去检查是否有我们需要的内容,不然我们只能得到一个空的列表。
现在,我们有了currentThread和messages,我们只需要去找到我们感兴趣的消息就可以了。
code/rxjs/chat/app/ts/services/ThreadsService.ts
this.currentThreadMessages = this.currentThread.combineLatest(messagesService.messages,(currentThread: Thread, messages: Message[]) => {if (currentThread && messages.length > 0) {return _.chain(messages).filter((message: Message) =>(message.thread.id === currentThread.id))
另一个细节,因为我们已经知道了当前thread的消息,所以将当前thread的消息标记为已读就很方便了。
code/rxjs/chat/app/ts/services/ThreadsService.ts
return _.chain(messages).filter((message: Message) =>(message.thread.id === currentThread.id)) .map((message: Message) => {message.isRead = true;return message; }) .value();
将所有的东西组合起来,我们的currentThreadMessages像下面这样:
code/rxjs/chat/app/ts/services/ThreadsService.ts
this.currentThreadMessages = this.currentThread.combineLatest(messagesService.messages,(currentThread: Thread, messages: Message[]) => {if (currentThread && messages.length > 0) {return _.chain(messages).filter((message: Message) =>(message.thread.id === currentThread.id)).map((message: Message) => {message.isRead = true;return message; }).value();} else {return [];}});
完整的ThreadsService
import {Injectable, bind} from '@angular/core';
import {Subject, BehaviorSubject, Observable} from 'rxjs';
import {Thread, Message} from '../models';
import {MessagesService} from './MessagesService';
import * as _ from 'underscore';@Injectable()
export class ThreadsService {// `threads` is a observable that contains the most up to date list of threadsthreads: Observable<{ [key: string]: Thread }>;// `orderedThreads` contains a newest-first chronological list of threadsorderedThreads: Observable<Thread[]>;// `currentThread` contains the currently selected threadcurrentThread: Subject<Thread> =new BehaviorSubject<Thread>(new Thread());// `currentThreadMessages` contains the set of messages for the currently// selected threadcurrentThreadMessages: Observable<Message[]>;constructor(public messagesService: MessagesService) {this.threads = messagesService.messages.map( (messages: Message[]) => {let threads: {[key: string]: Thread} = {};// Store the message's thread in our accumulator `threads`messages.map((message: Message) => {threads[message.thread.id] = threads[message.thread.id] ||message.thread;// Cache the most recent message for each threadlet messagesThread: Thread = threads[message.thread.id];if (!messagesThread.lastMessage ||messagesThread.lastMessage.sentAt < message.sentAt) {messagesThread.lastMessage = message;}});return threads;});this.orderedThreads = this.threads.map((threadGroups: { [key: string]: Thread }) => {let threads: Thread[] = _.values(threadGroups);return _.sortBy(threads, (t: Thread) => t.lastMessage.sentAt).reverse();});this.currentThreadMessages = this.currentThread.combineLatest(messagesService.messages,(currentThread: Thread, messages: Message[]) => {if (currentThread && messages.length > 0) {return _.chain(messages).filter((message: Message) =>(message.thread.id === currentThread.id)).map((message: Message) => {message.isRead = true;return message; }).value();} else {return [];}});this.currentThread.subscribe(this.messagesService.markThreadAsRead);}setCurrentThread(newThread: Thread): void {this.currentThread.next(newThread);}}export var threadsServiceInjectables: Array<any> = [bind(ThreadsService).toClass(ThreadsService)
];
数据模型总结
我们的数据模型和服务,我们已经具备了编写组件的一切基础。在下一章,我们会编写三个主要的组件。
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- github前端面试题1
2016/7/20 Myblog/ FrontendDeveloperQuestions/ QuestionsandAnswers at master markyun/MyblogGitHub https://github.com/markyun/Myblog/ tree/master/FrontendDeveloperQuestions/ QuestionsandAnswers 1/26 My‐blog / Front‐end‐Developer‐Questions / Questions-…...
2024/3/19 5:09:29 - Vue-apollo 请求 由于缓存问题不发请求问题 页面数据不更新 需要 禁止缓存
...
2024/3/17 14:05:38 - 做完双眼皮手术脸很干
...
2024/3/24 6:24:48 - 一年后双眼皮会变薄
...
2024/3/16 12:49:08 - 双眼皮做好还可以开眼角吗
...
2024/3/16 0:17:50 - 双眼皮切线太原
...
2024/3/15 22:38:03 - 双眼皮割的薄
...
2024/3/21 3:02:14 - Angular——事件
类型句法类别插值 属性 属性 类 样式 content_copy{{expression}} [target]"expression" bind-target"expression" 从数据源 到查看目标 的单向方式事件 content_copy(target)"statement" on-target"statement" 从视图目标 到数据源…...
2024/3/15 22:38:02 - 简单实现angular2组件双向绑定
直接献上代码父组件 import { Component, OnInit } from angular/core;Component({selector: ngx-input,templateUrl: <p>双向绑定</p>{{pvalue}} <input type"radio" [attr.value]"pvalue" [checked]"pvalue" (change)"pva…...
2024/3/15 22:38:00 - 简单实现angular2组件双向绑定(转)
直接献上代码 父组件 import { Component, OnInit } from angular/core;Component({selector: ngx-input,templateUrl: <p>双向绑定</p>{{pvalue}} <input type"radio" [attr.value]"pvalue" [checked]"pvalue" (change)"pv…...
2024/3/13 16:01:40 - 双眼皮埋线一般可以保持多久
...
2024/3/29 10:07:31 - 双眼皮埋线能在家里做吗
...
2024/3/22 17:41:49 - 【Angular 学习】--- 常用命令的通用关键参数解释
1.ng serve --host (self) 指定本地Server绑定的域名,默认值:localhost.如果希望使用self来访问你的站点,须加入以上参数 2.ng serve --hmr 注意开启之后,只是在angular-cli里的webpack添加必要的扩展,等价于webpack…...
2024/3/24 3:19:02 - Angular13 Angular2发送PUT请求在后台接收不到参数
1 问题描述 利用angular2发送PUT请求时,后端接收不到参数 2 问题诊断 前段参数格式问题,后端获取参数的方法不对 3 解决问题 angular前段:将所有参数编程JSON字符串形式springBoot后端:在控制方法中的形参中添加RequesBody注解&am…...
2024/3/19 18:57:28 - 双眼皮埋线几年做一次
...
2024/3/28 4:07:17 - 双眼皮几个月恢复宽度
...
2024/3/15 22:38:01 - 没拆线前双眼皮太宽
...
2024/3/15 22:37:56 - 埋线双眼皮有黑印吗
...
2024/3/19 11:48:42 - 埋线双眼皮修复印
...
2024/3/19 3:12:17 - 埋线双眼皮可以拿出来吗
...
2024/3/15 22:37:53
最新文章
- Python 垃圾回收和弱引用(Weakref)
Python中的赋值语句是建立变量名与对象的引用关系,多个变量可以引用同一个对象,当对象的引用数归零时,可能会被当作垃圾回收。而弱引用即可以引用对象,又不会阻止对象被当作垃圾回收,因此这个特性非常适合用在缓存场景…...
2024/3/29 21:49:51 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 包子凑数(蓝桥杯,闫氏DP分析法)
题目描述: 小明几乎每天早晨都会在一家包子铺吃早餐。 他发现这家包子铺有 N 种蒸笼,其中第 i 种蒸笼恰好能放 Ai 个包子。 每种蒸笼都有非常多笼,可以认为是无限笼。 每当有顾客想买 X 个包子,卖包子的大叔就会迅速选出若干笼…...
2024/3/29 3:00:18 - 贪心算法C++
一、分发饼干 1、题目: 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块…...
2024/3/29 14:03:16 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/3/29 18:08:39 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/3/29 18:08:34 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/3/29 2:45:46 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/3/29 16:26:39 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/3/29 5:19:52 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/3/29 18:08:00 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/3/29 11:11:56 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/3/29 1:13:26 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/3/29 8:28:16 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/3/29 7:41:19 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/3/29 18:07:15 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/3/29 9:57:23 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/3/29 0:49:46 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/3/29 18:06:57 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/3/29 17:27:19 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/3/29 18:06:36 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/3/29 18:06:22 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/3/28 18:26:34 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/3/29 18:06:01 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/3/28 20:09:10 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57