Angular 4.x 自定义表单控件
当我们打算自定义表单控件前,我们应该先考虑一下以下问题:
是否已经有相同语义的 native (本机) 元素?如:
<input type="number">
如果有,我们就应该考虑能否依赖该元素,仅使用 CSS 或渐进增强的方式来改变其外观/行为就能满足我们的需求?
如果没有,自定义控件会是什么样的?
我们如何让它可以访问 (accessible)?
在不同平台上自定义控件的行为是否有所不同?
自定义控件如何实现数据验证功能?
可能还有很多事情需要考虑,但如果我们决定使用 Angular 创建自定义控件,就需要考虑以下问题:
如何实现 model -> view 的数据绑定?
如何实现 view -> model 的数据同步?
若需要自定义验证,应该如何实现?
如何向DOM元素添加有效性状态,便于设置不同样式?
如何让控件可以访问 (accessible)?
该控件能应用于 template-driven 表单?
该控件能应用于 model-driven 表单?
(备注:主要浏览器上 HTML 5 当前辅助功能支持状态,可以参看 - HTML5 Accessibility)
Creating a custom counter
现在我们从最简单的 Counter 组件开始,具体代码如下:
counter.component.ts
import { Component, Input } from '@angular/core';@Component({selector: 'exe-counter',template: `<div><p>当前值: {{ count }}</p><button (click)="increment()"> + </button><button (click)="decrement()"> - </button></div>`
})
export class CounterComponent {@Input() count: number = 0;increment() {this.count++;}decrement() {this.count--;}
}
app.component.ts
import { Component, OnInit } from '@angular/core';@Component({selector: 'exe-app',template: `<exe-counter></exe-counter>`,
})
export class AppComponent { }
app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';import { CounterComponent } from './couter.component';
import { AppComponent } from './app.component';@NgModule({imports: [BrowserModule],declarations: [AppComponent, CounterComponent],bootstrap: [AppComponent],schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }
很好,CounterComponent 组件很快就实现了。但现在我们想在 Template-Driven
或 Reactive
表单中使用该组件,具体如下:
<!-- this doesn't work YET -->
<form #form="ngForm"><exe-counter name="counter" ngModel></exe-counter><button type="submit">Submit</button>
</form>
现在我们还不能直接这么使用,要实现该功能。我们要先搞清楚 ControlValueAccessor
,因为它是表单模型和DOM 元素之间的桥梁。
Understanding ControlValueAccessor
当我们运行上面示例时,浏览器控制台中将输出以下异常信息:
Uncaught (in promise): Error: No value accessor for form control with name: 'counter'
那么,ControlValueAccessor
是什么?那么你们还记得我们之前提到的实现自定义控件需要确认的事情么?其中一个要确认的事情就是,要实现 Model -> View,View -> Model 之间的数据绑定,而这就是我们 ControlValueAccessor 要处理的问题。
ControlValueAccessor 是一个接口,它的作用是:
把 form 模型中值映射到视图中
当视图发生变化时,通知 form directives 或 form controls
Angular 引入这个接口的原因是,不同的输入控件数据更新方式是不一样的。例如,对于我们常用的文本输入框来说,我们是设置它的 value
值,而对于复选框 (checkbox) 我们是设置它的 checked
属性。实际上,不同类型的输入控件都有一个 ControlValueAccessor
,用来更新视图。
Angular 中常见的 ControlValueAccessor 有:
DefaultValueAccessor - 用于
text
和textarea
类型的输入控件SelectControlValueAccessor - 用于
select
选择控件CheckboxControlValueAccessor - 用于
checkbox
复选控件
接下来我们的 CounterComponent 组件需要实现 ControlValueAccessor
接口,这样我们才能更新组件中 count 的值,并通知外界该值已发生改变。
Implementing ControlValueAccessor
首先我们先看一下 ControlValueAccessor
接口,具体如下:
// angular2/packages/forms/src/directives/control_value_accessor.ts
export interface ControlValueAccessor {writeValue(obj: any): void;registerOnChange(fn: any): void;registerOnTouched(fn: any): void;setDisabledState?(isDisabled: boolean): void;
}
writeValue(obj: any):该方法用于将模型中的新值写入视图或 DOM 属性中。
registerOnChange(fn: any):设置当控件接收到 change 事件后,调用的函数
registerOnTouched(fn: any):设置当控件接收到 touched 事件后,调用的函数
setDisabledState?(isDisabled: boolean):当控件状态变成
DISABLED
或从DISABLED
状态变化成ENABLE
状态时,会调用该函数。该函数会根据参数值,启用或禁用指定的 DOM 元素。
接下来我们先来实现 writeValue()
方法:
@Component(...)
class CounterComponent implements ControlValueAccessor {...writeValue(value: any) {this.counterValue = value;}
}
当表单初始化的时候,将会使用表单模型中对应的初始值作为参数,调用 writeValue()
方法。这意味着,它会覆盖默认值0,一切看来都没问题。但我们回想一下在表单中 CounterComponent 组件预期的使用方式:
<form #form="ngForm"><exe-counter name="counter" ngModel></exe-counter><button type="submit">Submit</button>
</form>
你会发现,我们没有为 CounterComponent 组件设置初始值,因此我们要调整一下 writeValue() 中的代码,具体如下:
writeValue(value: any) {if (value) {this.count = value;}
}
现在,只有当合法值 (非 undefined、null、"") 写入控件时,它才会覆盖默认值。接下来,我们来实现 registerOnChange()
和 registerOnTouched()
方法。registerOnChange() 可以用来通知外部,组件已经发生变化。registerOnChange() 方法接收一个 fn
参数,用于设置当控件接收到 change 事件后,调用的函数。而对于 registerOnTouched() 方法,它也支持一个 fn
参数,用于设置当控件接收到 touched 事件后,调用的函数。示例中我们不打算处理 touched
事件,因此 registerOnTouched() 我们设置为一个空函数。具体如下:
@Component(...)
class CounterComponent implements ControlValueAccessor {...propagateChange = (_: any) => {};registerOnChange(fn: any) {this.propagateChange = fn;}registerOnTouched(fn: any) {}
}
很好,我们的 CounterComponent 组件已经实现了ControlValueAccessor 接口。接下来我们需要做的是在每次count 的值改变时,需要调用 propagateChange() 方法。换句话说,当用户点击了 +
或 -
按钮时,我们希望将新值传递到外部。
@Component(...)
export class CounterComponent implements ControlValueAccessor {...increment() {this.count++;this.propagateChange(this.count);}decrement() {this.count--;this.propagateChange(this.count);}
}
是不是感觉上面代码有点冗余,接下来我们来利用属性修改器,重构一下以上代码,具体如下:
counter.component.ts
import { Component, Input } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';@Component({selector: 'exe-counter',template: `<p>当前值: {{ count }}</p><button (click)="increment()"> + </button><button (click)="decrement()"> - </button>`
})
export class CounterComponent implements ControlValueAccessor {@Input() _count: number = 0;get count() {return this._count;}set count(value: number) {this._count = value;this.propagateChange(this._count);}propagateChange = (_: any) => { };writeValue(value: any) {if (value !== undefined) {this.count = value;}}registerOnChange(fn: any) {this.propagateChange = fn;}registerOnTouched(fn: any) { }increment() {this.count++;}decrement() {this.count--;}
}
CounterComponent 组件已经基本开发好了,但要能正常使用的话,还需要执行注册操作。
Registering the ControlValueAccessor
对于我们开发的 CounterComponent 组件来说,实现 ControlValueAccessor 接口只完成了一半工作。要让 Angular 能够正常识别我们自定义的 ControlValueAccessor
,我们还需要执行注册操作。具体方式如下:
步骤一:创建 EXE_COUNTER_VALUE_ACCESSOR
import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';export const EXE_COUNTER_VALUE_ACCESSOR: any = {provide: NG_VALUE_ACCESSOR,useExisting: forwardRef(() => CounterComponent),multi: true
};
友情提示:想了解 forwardRef 和 multi 的详细信息,请参考 Angular 2 Forward Reference 和 Angular 2 Multi Providers 这两篇文章。
步骤二:设置组件的 providers 信息
@Component({selector: 'exe-counter',...providers: [EXE_COUNTER_VALUE_ACCESSOR]
})
万事俱备只欠东风,我们马上进入实战环节,实际检验一下我们开发的 CounterComponent
组件。完整代码如下:
counter.component.ts
import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';export const EXE_COUNTER_VALUE_ACCESSOR: any = {provide: NG_VALUE_ACCESSOR,useExisting: forwardRef(() => CounterComponent),multi: true
};@Component({selector: 'exe-counter',template: `<div><p>当前值: {{ count }}</p><button (click)="increment()"> + </button><button (click)="decrement()"> - </button></div>`,providers: [EXE_COUNTER_VALUE_ACCESSOR]
})
export class CounterComponent implements ControlValueAccessor {@Input() _count: number = 0;get count() {return this._count;}set count(value: number) {this._count = value;this.propagateChange(this._count);}propagateChange = (_: any) => { };writeValue(value: any) {if (value) {this.count = value;}}registerOnChange(fn: any) {this.propagateChange = fn;}registerOnTouched(fn: any) { }increment() {this.count++;}decrement() {this.count--;}
}
Using it inside template-driven forms
Angular 4.x 中有两种表单:
Template-Driven Forms - 模板驱动式表单 (类似于 Angular 1.x 中的表单 )
Reactive Forms - 响应式表单
了解 Angular 4.x Template-Driven Forms 详细信息,请参考 - Angular 4.x Template-Driven Forms。接下来我们来看一下具体如何使用:
1.导入 FormsModule 模块
app.module.ts
import { FormsModule } from '@angular/forms';@NgModule({imports: [BrowserModule, FormsModule],...
})
export class AppModule { }
2.更新 AppComponent
2.1 未设置 CounterComponent 组件初始值
app.component.ts
import { Component, OnInit } from '@angular/core';@Component({selector: 'exe-app',template: `<form #form="ngForm"><exe-counter name="counter" ngModel></exe-counter></form><pre>{{ form.value | json }}</pre>`,
})
export class AppComponent { }
友情提示:上面示例代码中,form.value 用于获取表单中的值,json 是 Angular 内置管道,用于执行对象序列化操作 (内部实现 - JSON.stringify(value, null, 2))。若想了解 Angular 管道详细信息,请参考 - Angular 2 Pipe。
2.2 设置 CounterComponent 组件初始值 - 使用 [ngModel] 语法
import { Component, OnInit } from '@angular/core';@Component({selector: 'exe-app',template: `<form #form="ngForm"><exe-counter name="counter" [ngModel]="outerCounterValue"></exe-counter></form><pre>{{ form.value | json }}</pre>`,
})
export class AppComponent { outerCounterValue: number = 5;
}
2.3 设置数据双向绑定 - 使用 [(ngModel)] 语法
import { Component, OnInit } from '@angular/core';@Component({selector: 'exe-app',template: `<form #form="ngForm"><p>outerCounterValue value: {{outerCounterValue}}</p><exe-counter name="counter" [(ngModel)]="outerCounterValue"></exe-counter></form><pre>{{ form.value | json }}</pre>`,
})
export class AppComponent { outerCounterValue: number = 5;
}
Using it inside reactive forms
了解 Angular 4.x Reactive (Model-Driven) Forms 详细信息,请参考 - Angular 4.x Reactive Forms。接下来我们来看一下具体如何使用:
1.导入 ReactiveFormsModule
app.module.ts
import { ReactiveFormsModule } from '@angular/forms';@NgModule({imports: [BrowserModule, ReactiveFormsModule],...
})
export class AppModule { }
2.更新 AppComponent
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';@Component({selector: 'exe-app',template: `<form [formGroup]="form"><exe-counter formControlName="counter"></exe-counter></form><pre>{{ form.value | json }}</pre>`,
})
export class AppComponent {form: FormGroup;constructor(private fb: FormBuilder) { }ngOnInit() {this.form = this.fb.group({counter: 5 // 设置初始值});}
}
友情提示:上面代码中我们移除了 Template-Driven 表单中的 ngModel 和 name 属性,取而代之是使用 formControlName 属性。此外我们通过 FormBuilder 对象提供的
group()
方法,创建 FromGroup 对象,然后在模板中通过[formGroup]="form"
的方式实现模型与 DOM 元素的绑定。关于 Reactive Forms 的详细信息,请参考 Angular 4.x Reactive Forms 。
最后我们在来看一下,如何为我们的自定义控件,添加验证规则。
Adding custom validation
在 Angular 4.x 基于AbstractControl自定义表单验证 这篇文章中,我们介绍了如何自定义表单验证。而对于我们自定义控件来说,添加自定义验证功能 (限制控件值的有效范围:0 <= value <=10),也很方便。具体示例如下:
1.自定义 VALIDATOR
1.1 定义验证函数
export const validateCounterRange: ValidatorFn = (control: AbstractControl): ValidationErrors => {return (control.value > 10 || control.value < 0) ?{ 'rangeError': { current: control.value, max: 10, min: 0 } } : null;
};
1.2 注册自定义验证器
export const EXE_COUNTER_VALIDATOR = {provide: NG_VALIDATORS,useValue: validateCounterRange,multi: true
};
2.更新 AppComponent
接下来我们更新一下 AppComponent 组件,在组件模板中显示异常信息:
@Component({selector: 'exe-app',template: `<form [formGroup]="form"><exe-counter formControlName="counter"></exe-counter></form><p *ngIf="!form.valid">Counter is invalid!</p><pre>{{ form.get('counter').errors | json }}</pre>`,
})
CounterComponent 组件的完整代码如下:
counter.component.ts
import { Component, Input, forwardRef } from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS,AbstractControl, ValidatorFn, ValidationErrors, FormControl
} from '@angular/forms';export const EXE_COUNTER_VALUE_ACCESSOR: any = {provide: NG_VALUE_ACCESSOR,useExisting: forwardRef(() => CounterComponent),multi: true
};export const validateCounterRange: ValidatorFn = (control: AbstractControl): ValidationErrors => {return (control.value > 10 || control.value < 0) ?{ 'rangeError': { current: control.value, max: 10, min: 0 } } : null;
};export const EXE_COUNTER_VALIDATOR = {provide: NG_VALIDATORS,useValue: validateCounterRange,multi: true
};@Component({selector: 'exe-counter',template: `<div><p>当前值: {{ count }}</p><button (click)="increment()"> + </button><button (click)="decrement()"> - </button></div>`,providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})
export class CounterComponent implements ControlValueAccessor {@Input() _count: number = 0;get count() {return this._count;}set count(value: number) {this._count = value;this.propagateChange(this._count);}propagateChange = (_: any) => { };writeValue(value: any) {if (value) {this.count = value;}}registerOnChange(fn: any) {this.propagateChange = fn;}registerOnTouched(fn: any) { }increment() {this.count++;}decrement() {this.count--;}
}
除了在 CounterComponent 组件的 Metadata 配置自定义验证器之外,我们也可以在创建 FormGroup
对象时,设置每个控件 (FormControl) 对象的验证规则。需调整的代码如下:
counter.component.ts
@Component({selector: 'exe-counter',...,providers: [EXE_COUNTER_VALUE_ACCESSOR] // 移除自定义EXE_COUNTER_VALIDATOR
})
app.component.ts
import { validateCounterRange } from './couter.component';
...export class AppComponent {...ngOnInit() {this.form = this.fb.group({counter: [5, validateCounterRange] // 设置validateCounterRange验证器});}
}
自定义验证功能我们已经实现了,但验证规则即数据的有效范围是固定 (0 <= value <=10),实际上更好的方式是让用户能够灵活地配置数据的有效范围。接下来我们就来优化一下现有的功能,使得我们开发的组件更为灵活。
Making the validation configurable
我们自定义 CounterComponent 组件的预期使用方式如下:
<exe-counterformControlName="counter"counterRangeMax="10"counterRangeMin="0">
</exe-counter>
首先我们需要更新一下 CounterComponent 组件,增量 counterRangeMax 和 counterRangeMin 输入属性:
@Component(...)
class CounterInputComponent implements ControlValueAccessor {...@Input() counterRangeMin: number;@Input() counterRangeMax: number;...
}
接着我们需要新增一个 createCounterRangeValidator()
工厂函数,用于根据设置的最大值 (maxValue) 和最小值 (minValue) 动态的创建 validateCounterRange()
函数。具体示例如下:
export function createCounterRangeValidator(maxValue: number, minValue: number) {return (control: AbstractControl): ValidationErrors => {return (control.value > +maxValue || control.value < +minValue) ?{ 'rangeError': { current: control.value, max: maxValue, min: minValue }} : null;}
}
在 Angular 4.x 自定义验证指令 文章中,我们介绍了如何自定义验证指令。要实现指令的自定义验证功能,我们需要实现 Validator
接口:
export interface Validator {validate(c: AbstractControl): ValidationErrors|null;registerOnValidatorChange?(fn: () => void): void;
}
另外我们应该在检测到 counterRangeMin
和 counterRangeMax
输入属性时,就需要调用 createCounterRangeValidator()
方法,动态创建 validateCounterRange()
函数,然后在 validate()
方法中调用验证函数,并返回函数调用后的返回值。是不是有点绕,我们马上看一下具体代码:
import { Component, Input, OnChanges, SimpleChanges, forwardRef } from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, Validator,AbstractControl, ValidatorFn, ValidationErrors, FormControl
} from '@angular/forms';...export const EXE_COUNTER_VALIDATOR = {provide: NG_VALIDATORS,useExisting: forwardRef(() => CounterComponent),multi: true
};export function createCounterRangeValidator(maxValue: number, minValue: number) {return (control: AbstractControl): ValidationErrors => {return (control.value > +maxValue || control.value < +minValue) ?{ 'rangeError': { current: control.value, max: maxValue, min: minValue } } : null;}
}@Component({selector: 'exe-counter',template: `<div><p>当前值: {{ count }}</p><button (click)="increment()"> + </button><button (click)="decrement()"> - </button></div>`,providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})
export class CounterComponent implements ControlValueAccessor, Validator,OnChanges {...private _validator: ValidatorFn;private _onChange: () => void;@Input() counterRangeMin: number; // 设置数据有效范围的最大值@Input() counterRangeMax: number; // 设置数据有效范围的最小值// 监听输入属性变化,调用内部的_createValidator()方法,创建RangeValidatorngOnChanges(changes: SimpleChanges): void {if ('counterRangeMin' in changes || 'counterRangeMax' in changes) {this._createValidator();}}// 动态创建RangeValidatorprivate _createValidator(): void {this._validator = createCounterRangeValidator(this.counterRangeMax,this.counterRangeMin);}// 执行控件验证validate(c: AbstractControl): ValidationErrors | null {return this.counterRangeMin == null || this.counterRangeMax == null ? null : this._validator(c);}...
}
上面的代码很长,我们来分解一下:
注册 Validator
export const EXE_COUNTER_VALIDATOR = {provide: NG_VALIDATORS,useExisting: forwardRef(() => CounterComponent),multi: true
};@Component({selector: 'exe-counter',...,providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})
创建 createCounterRangeValidator() 工厂函数
export function createCounterRangeValidator(maxValue: number, minValue: number) {return (control: AbstractControl): ValidationErrors => {return (control.value > +maxValue || control.value < +minValue) ?{ 'rangeError': { current: control.value, max: maxValue, min: minValue } } : null;}
}
实现 OnChanges 接口,监听输入属性变化创建RangeValidator
export class CounterComponent implements ControlValueAccessor, Validator,OnChanges {...@Input() counterRangeMin: number; // 设置数据有效范围的最大值@Input() counterRangeMax: number; // 设置数据有效范围的最小值// 监听输入属性变化,调用内部的_createValidator()方法,创建RangeValidatorngOnChanges(changes: SimpleChanges): void {if ('counterRangeMin' in changes || 'counterRangeMax' in changes) {this._createValidator();}}...
}
调用 _createValidator() 方法创建RangeValidator
export class CounterComponent implements ControlValueAccessor, Validator,OnChanges {...// 动态创建RangeValidatorprivate _createValidator(): void {this._validator = createCounterRangeValidator(this.counterRangeMax,this.counterRangeMin);}...
}
实现 Validator 接口,实现控件验证功能
export class CounterComponent implements ControlValueAccessor, Validator,OnChanges {...// 执行控件验证validate(c: AbstractControl): ValidationErrors | null {return this.counterRangeMin == null || this.counterRangeMax == null ? null : this._validator(c);}...
}
此时我们自定义 CounterComponent 组件终于开发完成了,就差功能验证了。具体的使用示例如下:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';@Component({selector: 'exe-app',template: `<form [formGroup]="form"><exe-counter formControlName="counter" counterRangeMin="5" counterRangeMax="8"></exe-counter></form><p *ngIf="!form.valid">Counter is invalid!</p><pre>{{ form.get('counter').errors | json }}</pre>`,
})
export class AppComponent {form: FormGroup;constructor(private fb: FormBuilder) { }ngOnInit() {this.form = this.fb.group({counter: 5});}
}
以上代码成功运行后,浏览器页面的显示结果如下:
参考资源
thoughtram.io - CUSTOM FORM CONTROLS IN ANGULAR
Angular 4.x 自定义表单控件相关推荐
- Angular学习笔记(五) - 自定义表单控件
本文简单介绍封装使用ngModel实现自定义表单控件的过程. NgModel 相关 NgModel NgModel用于从作用域创建一个FormControl实例,并将它绑定到一个表单控件元素. ngM ...
- Angular: [ControlValueAccessor] 自定义表单控件
Angular: [ControlValueAccessor] 自定义表单控件 我们在实际开发中,通常会遇到各种各样的定制化功能,会遇到有些组件会与 Angular 的表单进行交互,这时候我们一般会从 ...
- 细说 Angular 的自定义表单控件
我们在构建企业级应用时,通常会遇到各种各样的定制化功能,因为每个企业都有自己独特的流程.思维方式和行为习惯.有很多时候,软件企业是不太理解这种情况,习惯性的会给出一个诊断,『你这么做不对,按逻辑应该这 ...
- 细说 Angular 的自定义表单控件 (赞,实用、日期组件)
原文出处:https://m.imooc.com/article/19369 (应该是<Angular从零到一>作者) 我们在构建企业级应用时,通常会遇到各种各样的定制化功能,因为每个企 ...
- Angular19 自定义表单控件
1 需求 当开发者需要一个特定的表单控件时就需要自己开发一个和默认提供的表单控件用法相似的控件来作为表单控件:自定义的表单控件必须考虑模型和视图之间的数据怎么进行交互 2 官方文档 -> 点击前 ...
- vb.net form 最大化按钮 代码_【React】利用antd的form自定义表单控件
由于业务的需求,需要对Form表单进行自定义控件操作 业务需求如下: 首先点击选择按钮---在弹窗中选择产品--将选择好的产品展示在页面上,关于自定义组件的封装网上大牛的方法大多是封装好新的组件,从而 ...
- Antd Form 自定义表单控件
首先我们直接在自定义组件中打印看一下props能得到什么: 可以看到Form.Item向我们的自定义组件内部传递了一个value和一个onChange事件 那么我们可以在自己定义的组件内部维护好一个s ...
- antd自定义form表单控件
用 getFieldDecorator 方法包裹的表单控件会自动添加 value (或由 valuePropName 指定的属性名) 和 onChange (或由 trigger 指定的属性名)属性, ...
- 『ExtJS』表单(一)常用表单控件及内置验证
几点说明 关于ExtJS的表单,我打算分为三个部分来写 常用表单控件及内置验证 -- 这里主要是JS代码 表单行为与Asp.NET页面的消息回复 -- 这里既有JS代码,与有C#代码,我主要是使用As ...
最新文章
- GitHub标星1.2w+,Chrome最天秀的插件都在这里
- Java时间和时间戳的相互转换
- Oracle 实例恢复时 前滚(roll forward) 后滚(roll back) 问题
- MySQL数据库入门———常用基础命令
- Struts(七):action配置文件之通配符映射
- ASP.NET中利用DataList实现图片无缝滚动
- μC/OS-II软件定时器的分析与测试
- MariaDB存储引擎简介
- mongodb 数字 _id_MongoDB学习笔记MongoDB简介及数据类型
- Java死锁示例–如何分析死锁情况
- 红帽linux5.5序列号,Redhat 5 安装序列号及版本说明
- JS-面向对象-函数的使用场景---作为其他变量的值 / 作为对象的属性值 / 作为其他函数的参数 / 作为其他函数的返回值 / 作为构造函数(定义对象)
- 常用类 (三) ----- BigDecimal和BigInteger大数类
- CorelDRAW 2019中文版安装使用教程
- 银行圈巨变!中国建设银行无人银行开业!
- OpenFaceswap 入门教程(1):软件安装篇
- 奇技淫巧玄妙无穷| M1 mac os(苹果/AppleSilicon)系统的基本操作和设置
- 年度盘点 || 2019年十大营销事件
- Python校内实训--第二天星座查询进阶版
- anaconda创建虚拟环境报错
热门文章
- python 会计师事务所_Selenium爬取会计师事务所新闻信息——以中准会计师事务所为例...
- mysql的find in set_mysql中find_in_set()函数的使用及in()用法详解
- linux安装redis清除错误,关于linux redis安装及安装遇到的问题
- 天津商业大学C语言题库,天津商业大学c语言机考改错及解答(含答案)解析.doc
- Python套接字通信实例
- 高斯伪谱法 matlab,Gauss 高斯伪谱法求解的 ,希望对大家有用的!代码比较复杂,但是可以运行。 matlab 263万源代码下载- www.pudn.com...
- 卡内基梅隆计算机专业,详解卡内基梅隆大学计算机学院
- python连接数据库mysql失败_解决python连接mysql报错问题
- html中使用style设置背景
- NB-IOT技术以及物联网安全问题简述