暂时未有相关云产品技术能力~
引言:医疗行业是人们生活中至关重要的领域,而随着人工智能的迅猛发展,阿里云智能图像识别服务(AIGC)作为领军技术,为医疗行业的智能化转型注入了强大动力。本文将探讨AIGC在医疗领域的关键作用,包括影像诊断、疾病预测与监测、智能药物管理以及医疗服务优化等方面的应用案例,展示AIGC如何助力医疗行业实现精准、高效的医疗服务,为人们的健康保驾护航。一、影像诊断:AIGC在影像诊断方面的应用,能够通过对医学影像数据的分析和识别,辅助医生进行快速准确的疾病诊断。例如,AIGC可以帮助医生自动标记和定位病灶,提供可靠的疾病诊断辅助信息,缩短诊断时间,减轻医生的工作负担,并提高诊断的准确性和可靠性。二、疾病预测与监测:AIGC在疾病预测与监测方面的应用,能够通过对患者的临床数据和生理参数进行分析,提供个性化的疾病风险评估和预测。借助AIGC的技术,医生可以更早地发现潜在的疾病风险,并采取相应的预防措施,实现早期干预和治疗,提高疾病的治疗效果和生活质量。三、智能药物管理:AIGC在智能药物管理方面的应用,能够通过对药物标签和药物包装的图像识别,辅助患者正确使用药物,防止药物错误使用和交叉不良反应。此外,AIGC还可以帮助医院和药房自动化管理药物库存,提高药物配送和供应链的效率,确保药物的安全和可追溯性。四、医疗服务优化:AIGC在医疗服务优化方面的应用,能够分析和理解患者的需求,提供个性化的医疗服务。通过对患者的语音、面部表情和姿态等数据进行分析,AIGC可以识别患者的情绪和疼痛程度,并根据个体特征提供相应的情感支持和疼痛管理。此外,AIGC还可以通过自然语言处理技术为患者提供智能化的问诊和咨询服务,缩短就医等待时间,提高医疗资源的利用效率。结论:AIGC作为医疗行业智能化转型的关键技术,通过在影像诊断、疾病预测与监测、智能药物管理以及医疗服务优化等方面的应用,为医疗机构、医生和患者提供了强大的支持和解决方案。随着AIGC技术的不断发展和应用的扩大,医疗行业将迎来更大的变革和创新。医疗机构应积极采用AIGC技术,结合其他人工智能技术和大数据分析手段,推动医疗服务的智能化和个性化,提升医疗质量和效率,为人们的健康保驾护航。通过AIGC的驱动,医疗行业将实现更加精准、高效和可持续的医疗服务,促进人们的健康和福祉。
引言:随着全球城市化进程的加速,智慧城市建设成为了提高城市管理效率、提供优质公共服务的关键。在这一背景下,阿里云智能图像识别服务(AIGC)作为技术先锋,为智慧城市的发展注入了强大的动力。本文将探讨AIGC在智慧城市领域的关键作用,包括智能交通管理、城市安防监控、环境监测与保护以及智能公共服务等方面的应用案例,展示AIGC如何推动着智慧城市的建设与创新。一、智能交通管理:AIGC在智能交通管理方面的应用,通过对交通场景中摄像头捕捉的图像数据进行分析,能够实时监测交通流量、识别违章行为和优化信号控制等。这使得城市交通管理者能够更加高效地调整交通策略,缓解交通拥堵,提升交通运行效率,同时提高交通安全水平,改善市民的出行体验。二、城市安防监控:AIGC在城市安防监控方面的应用,能够识别和分析监控摄像头拍摄的图像,实时监测和预警潜在的安全风险。它可以识别异常行为、犯罪活动和火灾等安全威胁,并及时向相关部门发出警报。通过AIGC的应用,城市安防管理者可以更加快速和精准地响应安全事件,确保城市的安全稳定。三、环境监测与保护:AIGC在环境监测与保护方面的应用,能够对城市环境进行全面的监测和评估。它可以识别空气质量、垃圾分类、水体污染等环境因素,帮助城市管理者及时掌握环境状况,采取相应的环境保护措施,提高城市的生态环境质量,改善居民的生活品质。四、智能公共服务:AIGC在智能公共服务方面的应用,能够提供个性化、精准的公共服务。通过对个人图像的识别和分析,AIGC能够为居民提供便捷的身份验证、智能门禁管理和智能化的服务。例如,在智能化的社区管理中,AIGC可以通过人脸识别技术实现快速、安全的居民身份验证,使得进出小区的流程更加便捷高效。此外,AIGC还可以应用于智能停车管理、智能垃圾分类等公共服务领域,为居民提供更加智能化、个性化的便利。结论:作为智慧城市建设的技术先锋,AIGC在智能交通管理、城市安防监控、环境监测与保护以及智能公共服务等方面的应用,为城市管理者提供了强大的工具和解决方案。随着AIGC技术的不断创新和应用的扩大,智慧城市将迎来更广阔的发展前景。城市管理者应积极采用AIGC技术,结合其他智能技术手段,推动智慧城市的建设与创新,提升城市管理的效率和质量,为居民提供更加智能化、便利化的城市生活体验。通过AIGC的推动,智慧城市将实现可持续发展,为未来城市的繁荣和可持续性做出贡献。
引言:随着全球人口的不断增长和农业生产的挑战,智慧农业被广泛认为是解决粮食安全和可持续农业发展的关键。在这一背景下,阿里云智能图像识别服务(AIGC)作为关键驱动力,为智慧农业的创新与发展提供了强有力的支持。本文将探讨AIGC在智慧农业领域的关键作用,包括农作物识别与管理、病虫害监测与预警、智能灌溉和精准农业等方面的应用案例,展示AIGC如何助力农业行业实现高效、可持续的农业生产。一、农作物识别与管理:AIGC在农作物识别与管理方面的应用,能够准确识别不同类型的农作物,如小麦、水稻、玉米等。通过对农田图像的分析,AIGC可以提供关键的农作物信息,如生长状态、成熟度和病害情况等。这使农业生产者能够及时监测和管理农作物,优化农业生产决策,提高农作物的产量和质量。二、病虫害监测与预警:AIGC在病虫害监测与预警方面的应用,能够识别和分析农作物叶片、果实等图像数据,及时发现并预警病虫害的存在。通过监测农田中的异常图像特征,AIGC能够帮助农业生产者及早采取措施,防止病虫害的扩散,减少农作物损失,提高农业生产的稳定性和可靠性。三、智能灌溉:AIGC在智能灌溉方面的应用,能够通过对土壤湿度、植被覆盖等图像数据的分析,实现对农田的智能化灌溉控制。基于AIGC的预测模型,农业生产者可以合理调控灌溉水量和频率,避免水资源的浪费和过度灌溉,提高灌溉效率,同时降低农业生产的成本。四、精准农业:AIGC在精准农业方面的应用,通过对土壤质量、植被生长情况和作物营养状况等图像数据的分析,实现精准农业的实施。AIGC可以提供准确的农田地块划分和作物覆盖率等信息,帮助农业生产者精确施肥、精准植保和农药使用,最大限度地提高农作物产量和质量,减少资源浪费和环境污染。结论:AIGC作为智慧农业领域的关键驱动力,通过其在农作物识别与管理、病虫害监测与预警、智能灌溉和精准农业等方面的应用,为农业生产提供了高效、可持续的解决方案。随着AIGC技术的不断创新和应用的扩大,智慧农业将迎来更广阔的发展前景。农业生产者应积极采用AIGC技术,结合人工智能和大数据分析等技术手段,推动农业的数字化转型,实现精细化管理和可持续发展,为粮食安全和农业可持续发展做出贡献。
近年来,人工智能(AI)技术在全球范围内蓬勃发展,云计算作为人工智能的基础设施也越来越受到关注。在这个背景下,阿里云的AI计算与应用(AIGC)成为了一个备受瞩目的话题。本文将尽量靠近阿里云的AIGC,介绍其技术、应用和未来发展方向。一、技术阿里云的AIGC采用了一系列先进的技术,包括:1.大规模分布式训练:阿里云的AIGC使用了大规模分布式训练技术,可以支持数以百万计的模型参数训练和优化。这种技术可以大幅提高训练速度和效率,同时也可以降低计算成本。2.模型压缩和优化:阿里云的AIGC采用了模型压缩和优化技术,可以将模型压缩到更小的尺寸,同时也可以对模型进行优化,使其性能更加卓越。3.人工智能编译器:阿里云的AIGC还开发了一种先进的人工智能编译器,可以将预训练的人工智能模型转化为可在云上运行的模型,从而大幅提高了模型的运行效率。二、应用阿里云的AIGC拥有广泛的应用场景,以下是其中一些典型应用:1.语音识别和翻译:阿里云的AIGC可以实现语音识别和自动翻译,为人们提供更加方便、高效和精准的语言服务。2.图像识别和分类:阿里云的AIGC可以实现图像识别和分类,例如人脸识别、物体检测和分类等,为安防、金融等领域提供更加准确的图像分析服务。3.推荐系统:阿里云的AIGC可以为电商、金融等领域提供个性化推荐服务,根据用户历史行为和兴趣爱好等信息,为用户推荐更加符合其需求的商品和服务。4.智能客服:阿里云的AIGC可以为企业提供智能客服服务,通过自然语言处理技术,实现自动问答、智能分析和意图识别等功能,提高客户服务质量和效率。三、未来发展方向随着人工智能技术的不断发展和应用,阿里云的AIGC也将不断完善和创新,以下是其未来发展方向:深度学习模型的研究和开发:阿里云将继续投入深度学习模型的研究和开发,以满足不同应用场景的需求。开放平台的建设:阿里云将继续推进开放平台建设,支持更多开发者和合作伙伴共同开发AI应用和解决方案。边缘计算的推广:阿里云将继续推广边缘计算技术,将AI应用延伸到终端设备,提高数据处理和响应速度。人工智能与各行业融合:阿里云将继续探索人工智能与各行业的融合,推动各行业数字化、智能化升级。总之,阿里云的AIGC在技术、应用和未来发展方向上都具有很大的优势和潜力,这将推动整个人工智能领域不断发展壮大,同时也为人们的生发展起着重要作用。首先,阿里云的AIGC在技术上不断创新和突破,采用了大规模分布式训练、模型压缩和优化、人工智能编译器等先进技术,不断提高模型的训练速度和效率,降低计算成本,使得人工智能技术更好地应用于各行业中。其次,阿里云的AIGC在应用方面也越来越广泛,涵盖了语音识别、图像识别、推荐系统、智能客服等多个领域,为人们提供更加便捷、高效和精准的服务。再次,阿里云的AIGC在未来发展方向上也非常明确和重点突出,将继续深入研究和开发深度学习模型,推进开放平台建设,加强边缘计算推广,并探索人工智能与各行业的融合,推动各行业数字化、智能化升级。综上所述,阿里云的AIGC作为中国云计算领域的代表之一,在技术、应用和未来发展方向上都具有非常重要的作用和意义,将继续引领中国云计算领域的发展。
@[toc]手动序列化小项目Map<String, dynamic> user = JSON.decode(json); print('Howdy, ${user['name']}!'); print('We sent the verification link to ${user['email']}.');手动JSON序列化是指在dart:convert中使用内置的JSON解码器。它将原始JSON字符串传递给JSON。decode()方法,然后在返回的Map<String,dynamic>中找到所需的值。它没有外部依赖项或其他设置,对于小型项目非常方便。随着项目的增长,手动编写序列化逻辑可能变得不可管理且容易出错。如果在访问未提供的JSON字段时输入了错误的字段,代码将在运行时抛出错误。{ "name": "John Smith", "email": "john@example.com" }如果的项目没有很多JSON模型,并且希望快速测试它们,那么手动序列化可能会很方便。在大型和中型项目中使用代码生成代码生成的JSON序列化是指通过外部库自动生成序列化模板。它需要一些初始设置,并运行文件查看器从模型类生成代码。例如,json_Serializable和build_Value就是这样一个库。该方法适用于较大的项目。不需要手写。如果访问带有拼写错误的JSON字段,将在编译时捕获该字段。代码生成的缺点是需要一些初始设置。此外,生成的源文件可能在项目导航器中显得杂乱。class User { final String name; final String email; User(this.name, this.email); User.fromJson(Map<String, dynamic> json) : name = json['name'], email = json['email']; Map<String, dynamic> toJson() => { 'name': name, 'email': email, }; }当有一个中型或大型项目时,可能希望使用代码生成JSON序列化。Flutter中有GSON/Jackson/Moshi吗?简单的答案是否定的这样的库需要使用运行时反射,这在Flutter中是禁用的。反射会干扰Dart的树抖动使用树shaking_,我们可以在发布时“删除”未使用的代码。这可以显著优化应用程序的大小。因为反射默认情况下使用所有代码_树抖动_这将很难工作。这些工具无法知道哪些小部件在运行时没有使用,因此冗余代码很难分割。使用反射时,应用程序大小无法轻松优化。Map userMap = JSON.decode(json); var user = new User.fromJson(userMap); print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}.');进程异步性 return new Scaffold( body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text('Your current IP address is:'), new Text('$_ipAddress.'), spacer, new RaisedButton( onPressed: _getIPAddress, child: new Text('Get IP address'), ), ], ),它调用httpbin.com web服务测试API,然后该API使用的本地IP地址进行响应。请注意,使用了安全网络(HTTPS)。它调用httpbin.comWeb服务测试API。请注意,使用了安全网络请求(HTTPS)运行Flutter创建以创建新的Flutter应用程序将lib/main.art替换为以下内容:@override Widget build(BuildContext context) { var spacer = new SizedBox(height: 32.0); 以下示例显示了如何在Flutter应用程序中解码HTTPS GET请求返回的JSON数据,HTTP API在返回值中使用Dart Futures。我们建议使用async/await语法来调用API。网络呼叫通常遵循以下步骤:创建客户端构造Uri启动请求并等待请求。同时,可以配置请求头和正文。关闭请求并等待响应解码响应内容其中几个步骤使用基于Future的API。上面每个步骤的示例API调用是:其中几个步骤使用了基于未来的API。 @override void initState() { super.initState(); _readCounter().then((int value) { setState(() { _counter = value; }); }); } Future<File> _getLocalFile() async { // get the path to the document directory. String dir = (await getApplicationDocumentsDirectory()).path; return new File('$dir/counter.txt'); } Future<int> _readCounter() async { try { File file = await _getLocalFile(); // read the variable as a string from the file. String contents = await file.readAsString(); return int.parse(contents); } on FileSystemException { return 0; } } 格式化代码自动设置代码格式尽管可以遵循任何喜欢的风格-根据我们的经验,开发团队将:拥有单一、共享的风格此样式由自动格式化强制执行在Android Studio和IntelliJ中自动格式化代码Usage: flutter format <one or more paths> -h, --help Print this usage information.安装Dart插件(请参阅编辑器设置),以在AndroidStudio和IntelliJ中自动格式化代码。要在当前源窗口中自动格式化代码,请右键单击代码窗口并选择使用dartfmt重新格式化代码。也可以使用快捷键来设置代码的格式。自动格式化VS代码中的代码import 'dart:io'; var httpClient = new HttpClient();安装Dart Code插件(请参阅编辑器设置)以自动格式化VS代码中的代码。要在当前源窗口中自动设置代码格式,请右键单击代码窗口并选择“设置文档格式”。也可以使用VS代码快捷键设置代码格式。要在保存文件时自动格式化代码,请将editor.formatOnSave设置设置为true。使用颤振命令自动格式化代码void main() { runApp( new MaterialApp( title: 'Flutter Demo', theme: new ThemeData(primarySwatch: Colors.blue), home: new FlutterDemo(), ), ); } class FlutterDemo extends StatefulWidget { FlutterDemo({Key key}) : super(key: key); @override _FlutterDemoState createState() => new _FlutterDemoState(); }Flutter代码通常涉及构建相当深的树数据结构,例如在构建方法中。为了获得良好的自动格式,我们建议使用可选的尾随逗号。添加尾随逗号很简单:始终在函数、方法和构造函数的参数列表末尾添加尾随逗号,以保留编码。这将帮助自动格式化程序为Flutter样式代码插入适当的换行符。这样,调用代码就不必担心JSON序列化。但是,模型类仍然是必需的。在生产应用程序中,我们希望确保序列化工作正常。在实践中,两个用户。来自Json和User。toJson方法需要适当的单元测试来验证正确的行为。此外,在实际场景中,JSON对象很少如此简单,嵌套的JSON对象也并不少见。尽管其他库可用,但我们在这个tutorial_Serializable包中使用了json。它是一个自动源代码生成器,可以为我们生成JSON序列化模板。由于序列化代码不再由我们手写和维护,我们将在运行时最小化JSON序列化异常的风险。
@[toc]配置混乱默认情况下,flutter不支持Android混淆。如果使用第三方Java或Android库,可能需要减小apk文件的大小或防止代码被反向破解。创建/android/app/proguard-rules.pro文件并添加以下规则:buildTypes { release { signingConfig signingConfigs.debug } }-keep class io.flutter.app.** { *; } -keep class io.flutter.plugin.** { *; } -keep class io.flutter.util.** { *; } -keep class io.flutter.view.** { *; } -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; }上述配置只会混淆Flutter引擎库。任何其他库(如Firebase)都需要添加相应的规则。启用混淆/压缩打开/android/app/build.gradle文件并找到buildTypes块。在发行版配置中,将minimiEnabled和useProguard设置为true,然后将混淆文件指向上一步中创建的文件。android { ... buildTypes { release { signingConfig signingConfigs.release minifyEnabled true useProguard true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }构建发布APK本节介绍如何构建APK版本。如果完成上一节中的签名步骤,APK将被签名。使用命令行:Cd(是的项目目录)运行颤振构建apk(默认情况下,颤振构建包括--release选项)打包发行版APK位于/build/app/outputs/APK/app-release.APK中。在设备上安装分发APK按照以下步骤在连接的Android设备上安装上一步骤中内置的APK使用命令行:使用USB将Android设备连接到PCcd<app dir>。运行颤振安装构建配置检查默认的Gradle build file“build.Gradle”,该文件位于/android/app/中,以验证这些值是否正确,尤其是:DefaultConfig:applicationId:指定始终唯一的(应用程序Id)appid版本代码和版本名称:指定应用程序版本号和版本号字符串。有关详细信息,请参阅版本文档minSdkVersion&targetSdkVersion:以指定应用程序设计运行的最低API级别和API级别。有关更多信息,请参阅发布文档中的API级别部分。storePassword=<password from previous step> keyPassword=<password from previous step> keyAlias=key storeFile=<location of the key store file, e.g. /Users/<user name>/key.jks>当创建一个新的Flutter应用程序时,它有一个默认的启动程序图标。要自定义此图标,请执行以下操作:查看Android启动图标设计指南,然后创建图标。在/android/app/src/main/res/目录中,将图标文件放在一个名为配置限定符的文件夹中。默认的mipmap文件夹演示了正确的命名约定。在AndroidManifest.xml中,更新应用程序标签的android:icon属性以引用上一步骤中的图标(例如,<application android:icon=“@mipmap/ic_launcher”…)。android {要验证图标是否已被替换,请运行应用程序并检查应用程序图标def keystorePropertiesFile = rootProject.file("key.properties") def keystoreProperties = new Properties() keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) android {App Manifest检查默认应用程序清单文件(AndroidManifest.xml文件位于/android/app/src/main/中),并验证这些值是否正确,尤其是:应用程序:编辑应用程序标签,即应用程序的名称。使用权限:如果的应用程序代码不需要Internet访问,请删除android.permission.Internet权限。标准模板包含此标志,以启用Flutter工具和正在运行的应用程序之间的通信。signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile file(keystoreProperties['storeFile']) storePassword keystoreProperties['storePassword'] } } buildTypes { release { signingConfig signingConfigs.release } }检查用户界面Flutter框架使用小部件作为其核心构建块。从控件(文本、按钮、切换等)到布局(居中、填充、行、列等)都是。Inspector是可视化和浏览Flutter等小部件树的强大工具。在以下情况下可能会有所帮助:IntelliJ Flutter Inspector Window现有布局不清楚诊断布局问题IntelliJ Flutter检查器窗口单击Flutter Inspector工具栏上的“选择小部件”,然后单击设备(真机或虚拟机)以选择小部件。选定的小部件将在设备和小部件树中突出显示。选择演示然后,可以在IDE中浏览交互式小部件树,以查看附近的小部件及其字段值。如果要调试布局问题,Widgets树可能不够详细。在这种情况下,单击“渲染树”选项卡以在树中的同一位置查看渲染树。调试布局问题时,关键是查看大小和约束字段。约束沿树向下传递,尺寸向上传递。交换机树
@[toc]Flutter如何调试应用我们上面写了Flutter测试应用,这远远不够,这篇,我们来写一下Flutter如何调试应用:void someFunction(double offset) { debugger(when: offset > 30.0); }在运行应用程序之前,请运行颤振分析来测试代码。该工具(它是darthanalysis工具的包装)将分析代码并帮助查找可能的错误。如果使用IntelliJ的Flutter插件,它将自动启用。Dart ObservatoryDart解析器大量使用代码中的类型注释来帮助跟踪问题。我们鼓励在任何地方使用它们(避免使用var、无类型参数、无类型列表文本等),因为这是跟踪问题的最快方法。使用Dart Observatory(或其他Dart调试器,如IntelliJ IDE中的那些)时,可以使用调试器()语句插入编程断点。要使用此函数,必须添加import“dart:developer”;转到相关文档的顶部。调试器()语句采用可选的when参数,只有当特定条件为真时,才能指定该参数中断import 'package:flutter/material.dart'; void main() { runApp( new MaterialApp( home: new AppHome(), ), ); }Dart print()函数将输出到系统控制台,可以使用flutter日志来查看它(基本上是一个包装器adb-logcat)。如果一次输出太多,Android有时会丢弃一些日志行。为了避免这种情况,可以在Flutter的基础库中使用debugPrint()。这是一个包打印,它将输出限制在一个级别,以避免被Android内核丢弃。I/flutter ( 6559): └MaterialApp(state: _MaterialAppState(1009803148)) I/flutter ( 6559): └ScrollConfiguration() I/flutter ( 6559): └AnimatedTheme(duration: 200ms; state: _AnimatedThemeState(543295893; ticker inactive; Flutter框架中的许多类都有toString实现。根据惯例,这些输出通常包括对象的runtimeType单行输出,通常是表单中的ClassName(有关此实例的更多信息…)。树中使用的一些类还具有toStringDeep,它返回整个子树的多行描述。一些包含toString详细信息的类将实现一个toStringShort,它只返回对象类型或其他非常简短(一个或两个单词)的描述。class AppHome extends StatelessWidget { @override Widget build(BuildContext context) { return new Material( child: new Center( child: new FlatButton( onPressed: () { debugDumpApp(); }, child: new Text('Dump App'), ), ), ); }调试模式断言在开发过程中,强烈建议使用Flutter的“调试”模式,有时也称为“检查”模式。如果使用颤振运行程序。在这种模式下,Dart断言语句被启用,Flutter框架使用它来执行许多运行时检查,以验证是否违反了一些不可变规则。当不可变规则被违反时,它会向控制台报告一些上下文信息,以帮助跟踪问题的根本原因。family: "monospace"; size: 48.0; weight: 900; decoration: double Color(0xffffff00) TextDecoration.underline) I/flutter ( 6559): └MediaQuery(MediaQueryData(size: Size(411.4, 683.4), devicePixelRatio: 2.625, textScaleFactor: 1.0, padding: EdgeInsets(0.0, 24.0, 0.0, 0.0)))要关闭调试模式并使用释放模式,请使用flutterrun--release运行应用程序。这也会关闭Observatory调试器。中间模式可以关闭除Observatory之外的所有调试辅助工具。它被称为“概要模式”,可以使用--profile而不是--release。调试应用程序层Flutter框架的每一层都提供将其当前状态或事件转储到控制台的功能(使用debugPrint)。控件层要转储Widgets库的状态,请调用debugDumpApp()。只要应用程序至少构建了一次(即,在调用build()之后的任何时间),就可以在应用程序不处于构建阶段(即,不在build(方法中调用)的任何时间调用此方法(在调用runApp()之后)。I/flutter : │ creator: [root] I/flutter : │ offset: Offset(0.0, 0.0) I/flutter : │ transform: I/flutter : │ [0] 3.5,0.0,0.0,0.0 I/flutter : │ [1] 0.0,3.5,0.0,0.0 I/flutter : │ [2] 0.0,0.0,1.0,0.0 I/flutter : │ [3] 0.0,0.0,0.0,1.0这是一个“扁平”的树,显示了通过各种构建函数投影的所有小部件(如果在小部件树的根中调用了StringDeepwidget,这就是得到的树)。将看到许多小部件没有出现在应用程序源代码中,因为它们是由框架小部件的build()函数插入的。例如,InkFeature是Material小部件的一个实现细节。I/flutter : ▄▄▄▄▄▄▄▄ Frame 12 30s 437.086ms ▄▄▄▄▄▄▄▄ I/flutter : Debug print: Am I performing this work more than once per frame? I/flutter : Debug print: Am I performing this work more than once per frame? I/flutter : ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀因为当按钮从按下变为释放时调用debugDumpApp(),所以FlatButton对象同时调用setState(),因此它将自己标记为脏。这就是为什么如果查看转储,就会发现特定对象被标记为“脏”。还可以查看哪些手势收听者已注册;在这种情况下,将列出一个GestureDetector,并侦听“轻敲”手势(“轻敲”由TapGesture检测器的toStringShort函数输出)如果编写自己的小部件,则可以通过重写debugFillProperties()来添加信息。将DiagnosticsProperty对象作为方法参数并调用父方法。toString方法使用此函数来填充小部件描述信息。I/flutter : SemanticsNode(0; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)) I/flutter : ├SemanticsNode(1; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)) I/flutter : │ └SemanticsNode(2; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4); canBeTapped) I/flutter : └SemanticsNode(3; Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)) I/flutter : └SemanticsNode(4; Rect.fromLTRB(0.0, 0.0, 82.0, 36.0); canBeTapped; "Dump App")如果试图调试布局问题,Widgets层树可能不够详细。在这种情况下,可以通过调用debugDumpRenderTree()来转储渲染树。就像debugDumpApp()一样,除了在布局或绘图阶段,可以随时调用此函数。一般来说,从帧回调或事件处理程序调用它是最好的解决方案。要调用debugDumpRenderTree(),需要添加import包:flatter/rendering。部分';到源文件。$ flutter run --trace-startup --profile要收集有关Flutter应用程序启动所需时间的详细信息,可以在运行Flutter运行时使用跟踪启动和配置文件选项。
测试应用实例_Flutter await tester.pumpWidget( new StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return new MaterialApp( home: new Material( child: new Center( child: new Slider( key: sliderKey, value: value, onChanged: (double newValue) { setState(() { value = newValue; }); }, ), ), ), ); }, )应用的功能越多,手动测试就越困难。一整套自动化测试将帮助确保应用程序在发布前正确执行,同时保持功能和错误修复速度。有很多种自动化测试。总结如下:单元测试:测试单个函数、方法或类。例如,被测试单元的外部依赖性通常是模拟的,例如package:mockito。单元测试通常不会读取/写入磁盘、呈现到屏幕或从运行测试的进程外部接收用户操作。单元测试的目标是验证逻辑单元在各种条件下的正确性。 await tester.tap(find.byKey(sliderKey)); expect(value, equals(0.5));小部件测试:(在其他UI框架中称为组件测试)用于测试的单个小部件。测试小部件涉及多个类,需要为测试环境提供适当的小部件生命周期上下文。例如,它应该能够接收和响应用户操作和事件,执行布局,并实例化子部件。因此,Widget测试比单元测试更全面。然而,就像单元测试一样,小部件测试环境被一个比完整UI系统简单得多的实现所取代。小部件测试的目标是验证小部件的UI外观和交互是否符合预期。 testWidgets('my first widget test', (WidgetTester tester) async { var sliderKey = new UniqueKey(); var value = 0.0;集成测试:测试整个应用程序或应用程序的大部分。通常,集成测试可以在真实设备或操作系统模拟器(如iOS模拟器或Android模拟器)上运行。测试中的应用程序通常与测试驱动程序代码隔离,以避免结果偏差。集成测试的目标是验证应用程序作为一个整体是否正确运行,以及它所包含的所有小部件是否按预期相互集成。还可以使用集成测试来验证应用程序的性能。import 'package:test/test.dart'; void main() { test('my first unit test', () { var answer = 42; expect(answer, 42); }); }一些Flutter库(如dart:ui)在独立dart VM附带的dart SDK中不可用。此颤振测试命令允许在本地Dart VM中运行测试,并使用颤振引擎而无需首页(UI将不会显示)。使用此命令,可以运行任何测试,无论它是否取决于Flutter库。使用package:test编写Flutter单元测试。用于编写单元测试的package:test文档在这里。dev_dependencies: flutter_test: sdk: flutter即使的测试本身没有显式导入到flatter_test中,因为测试框架本身在后台使用它。要运行测试,请从项目目录(而不是测试子目录)运行fluttertesttest/unit _ test.dart要运行所有测试,请从项目目录运行颤振测试。集成测试Flutter的是:命令行A包:flatter_ driver(API)两者都允许:为集成测试创建指导应用程序,编写测试,运行测试import 'package:flutter_driver/driver_extension.dart'; void main() { // 启用扩展 enableFlutterDriverExtension(); }集成测试是一个简单的包:测试测试。它使用Flutter驱动程序API告诉应用程序要执行什么操作,然后验证应用程序是否执行了此操作。出于兴趣,我们还让测试记录性能时间线。我们创建了一个user_ list_ scrolling_ test.dart测试文件位于my_ app/test_ Driver/down中:void main() { group('scrolling performance test', () { FlutterDriver driver; setUpAll(() async { // 连接app driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) { // 关闭连接 driver.close(); } });构建--目标应用程序并将其安装在设备上启动应用程序在driver/_ list_ scrolling_ test.dart下运行my_ app/test_ User可能想知道该命令如何找到正确的测试文件。flutter drive命令使用约定在与--target应用程序相同的目录中查找具有相同文件名的文件,但带有带有测试后缀的_Test文件。弹性框本身(行和列)的行为是不同的,这取决于它们在给定方向上是有边界的还是无边界的。在边界限制下,它们将尽可能大。它们试图使其子节点在没有边界限制的情况下适应此方向。在这种情况下,不能将子节点的flex属性设置为0以外的任何值(默认值为0)。在小部件库中,这意味着当弹性框位于另一个弹性框或可滚动框内时,不能使用Expanded。如果执行此操作,将收到异常消息。在交叉方向上,例如Column的宽度和Row的高度,它们不能是无边界的,否则它们将无法合理地对齐子节点。 for (int i = 0; i < 5; i++) { await driver.scroll( userList, 0.0, -300.0, new Duration(milliseconds: 300)); await new Future<Null>.delayed(new Duration(milliseconds: 500));这些约束有时是“紧”的,这意味着它们不会为渲染框留出空间来确定其自身的大小(例如,如果最小宽度和最大宽度相同,即宽度很窄)。主要示例是App小部件,它是RenderView类中包含的一个小部件。 for (int i = 0; i < 5; i++) { await driver.scroll( userList, 0.0, 300.0, new Duration(milliseconds: 300)); await new Future<Null>.delayed(new Duration(milliseconds: 500)); }应用程序构建函数返回的子小部件的渲染框被分配了一个约束,迫使它精确地填充应用程序的内容区域(通常是整个屏幕)。Flutter中的许多框,特别是那些只包含一个子控件的框,会将其约束传递给其子控件。这意味着,如果在应用程序渲染树的根处嵌套一些框,则所有子节点都受这些渲染框的约束。 summary.writeSummaryToFile('stocks_scroll_perf', pretty: true); summary.writeTimelineToFile('stocks_scroll_perf', pretty: true);
@[toc]热重载要热重新加载Flutter应用程序:从受支持的IntelliJ IDE或终端窗口运行应用程序。物理机和虚拟机都可以运行。修改项目中的Dart文件。大多数类型的代码更改都可以重新加载;class myWidget extends StatelessWidget { Widget build(BuildContext context) { return new GestureDetector(onTap: () => print('T')); } }Flutter的热重新加载功能可以帮助快速轻松地测试、构建用户界面、添加功能和修复错误,而无需重新启动应用程序。热重新加载是通过将更新的源代码文件注入正在运行的Dart虚拟机(VM)来实现的。在虚拟机用新的字段和函数更新类之后,Flutter框架将自动重建小部件树,以便可以快速查看更改的效果。Performing hot reload... Reloaded 1 of 448 libraries in 2,777ms.如果使用IntelliJ IDE,请选择“全部保存”(cmd-s/ctrl-s),或单击工具栏上的“热重新加载”按钮。如果使用命令行flutter run运行应用程序,请在终端窗口中输入r成功热重新加载后,将在控制台中看到类似以下内容的消息。class myWidget extends StatefulWidget { @override State createState() => new myWidgetState(); } class myWidgetState { ... ... }状态热重新加载Flutter的热重新加载功能(有时称为有状态热重新加载)保留了应用程序的状态。此设计允许仅查看最近更改的效果,而不放弃当前状态。例如,如果应用程序需要用户登录,则可以在导航层次结构中修改并重新加载页面,而无需重新输入登录凭据。状态保持不变,这通常是所需的行为。myWidget is not a subtype of StatelessWidget如果代码更改影响应用程序的状态(或其依赖关系),则应用程序必须使用的数据可能与从头开始执行的数据不完全一致。在热过载和完全重新启动后,结果可能是不同的行为。例如,如果将StatelessWidget类更改为StatefulWidget(反之亦然),则热重新加载后应用程序的先前状态将保持不变。但是,此状态可能与新更改不兼容。静态字段被延迟初始化运行应用程序后,如果进行以下更改:final sampleTable = [ new Table("T1"), new Table("T2"), new Table("T3"), new Table("T4"), ];在Dart中,静态字段被延迟初始化。这意味着第一次运行Flutter应用程序并读取静态字段时,其初始值将设置为初始表达式的结果。全局变量和静态字段被视为状态,因此在热重新加载期间不会重新初始化。如果更改全局变量和静态字段的初始化器,则需要完全重新启动以查看更改。const foo = 1; final bar = foo; void onClick(){ print(foo); print(bar); }然后热重新加载,现在打印2和1。对常量字段值的更改始终会重新加载,但静态字段初始值设定项语句不会重新运行(初始值可能是表达式的值)。从概念上讲,常量字段被视为别名而不是状态。final bar = foo;当一组更改需要完全重新启动才能生效时,Dart VM将检测初始化程序更改并标记。以上示例中的大多数初始化工作都会触发标记机制,但不适用于以下情况:const bar = foo;即使热重新加载操作看似成功且未引发异常,但某些代码更改可能在刷新的UI中不可见。更改应用程序的main()方法后,这种行为很常见。作为一般规则,如果修改的代码位于根小部件的构建方法的下游,则热重载将按预期运行。但是,如果由于重新构建小部件树,修改后的代码不会被重新执行,那么在热重新加载后,将看不到其效果。都import 'package:flutter/material.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { Widget build(BuildContext context) { return new GestureDetector( onTap: () => print('tapped')); } }在完全重新启动后,程序从头开始执行main()的新版本,并构建一个小部件树以显示文本“Hello”。但是,如果在更改后重新加载应用程序,main()将不会被重新执行,未修改的实例MyApp将被用作新构建的小部件树的根。热重新加载后,结果不会改变。 class Color { Color(this.i, this.j); final Int i; final Int j; }
packages思维即使软件包未在Pub。对于未用于公共发布的特殊插件或尚未准备好发布的软件包,可以使用其他依赖选项:dependencies: flutter: sdk: flutter路径依赖性:Flutter应用程序可以通过文件系统依赖插件的路径依赖性。路径可以是相对路径,也可以是绝对路径。例如,要依赖位于应用程序相邻目录中的插件“plugin1”,请使用以下语法依赖项:plugin1:path:/plugin1/Git依赖性:还可以依赖存储在Git存储库中的包。如果软件包位于仓库的根目录中,请使用以下语法:dependencies:plugin1:git:url:git://github.com/flutter/plugin1.gitimport 'package:flutter/material.dart'; import 'package:css_colors/css_colors.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( home: new DemoPage(), ); } } class DemoPage extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( body: new Container(color: CSSColors.orange) ); } }Git依赖于文件夹中的包:默认情况下,Pub假设包位于Git存储库的根目录中。如果不是这样,可以使用path参数指定位置,例如:dependencies:package1:git:url:git://github.com/flutter/packages.git路径:packages/package1最后,可以使用ref参数来修复对特定gitcommit、分支或标记的依赖关系。当pubspec.yaml以速记方式添加包时,plugin1:这被解释为plugin1,即可以使用任何版本的包。为了确保包在更新后可以正常使用,我们建议使用以下格式之一指定版本范围:范围限制:指定最小和最大版本号,例如依赖项:url_launcher:'>=0.1.2<0.2.0'对范围限制使用插入符号语法:类似于常规范围限制。相关性:集合:“^0.1.2”import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( home: new DemoPage(), ); } } class DemoPage extends StatelessWidget { launchURL() { launch('https://flutter.io'); } @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new RaisedButton( onPressed: launchURL, child: new Text('Show Flutter homepage'), ), ), ); } }过程实现步骤1:创建包要创建插件包,请使用--template=plugin参数执行flutter创建使用--org选项指定的组织并使用反向域名表示法。该值用于生成的Android和iOS代码中的各种包和包标识符。$flutter创建--org com.example--template=plugin hello这将在hello/文件夹下创建一个包含以下特殊内容的插件项目:Lib/你好。part:插件包的Dart APIAndroid/src/main/java.com/yourcompany/hello/HelloPlugin.java:插件包API的Android实现Ios/类/HelloPlugin。m: 插件包API的ios实现示例/:一个Flutter应用程序,它依赖于插件来解释如何使用它默认情况下,插件项目对iOS代码使用Objective-C,对Android代码使用Java。如果喜欢Swift或Kotlin,可以使用-i或-a指定iOS或Android的语言。$ flutter create --template=plugin -i swift -a kotlin helloAndroid平台代码我们建议使用Android Studio编辑Android代码。在AndroidStudio中编辑Android平台代码之前,首先确保代码至少构建了一次(例如,从IntelliJ运行示例应用程序或在终端上执行cd hello/example;flutter build apk)下一个$ flutter packages pub publish --dry-run启动Android Studio在“欢迎使用Android Studio”对话框中,选择“导入项目”,或在菜单栏“文件>新建>导入项目…”中,然后选择hello/example/android/build.gradle文件在“渐变同步”对话框中,选择“确定”在“Android Gradle插件更新”对话框中,选择“不再介意我参与此项目”插件的Android平台代码位于hello/java.com中。的公司。hello/HelloPlugindependencies: url_launcher: ^0.4.2android { // lines skipped dependencies { provided rootProject.findProject(":url_launcher") } }如果some_包声明了上述依赖项,other_包声明一个url_启动程序版本类似于“0.4.5”或“^0.4.0”,pub将能够自动解决问题。类似的评论适用于插件包对Gradle模块和Cocoa pods的平台特定依赖性。即使某些软件包和其他软件包声明了不兼容的urls_Launcher版本,该版本可能仍然是_启动器以兼容的方式工作。可以通过向hello包的pubspec.yaml文件添加依赖重写语句来强制使用特定版本来处理冲突:在hello/pubspec.yaml中强制使用url版本0.4.3_ Launcher:dependencies: some_package: other_package: dependency_overrides: url_launcher: '0.4.3'如果冲突的依赖项不是一个包,而是一个Android特定的库,如番石榴,那么依赖项重写声明必须添加到Gradle构造逻辑中。configurations.all { resolutionStrategy { force 'com.google.guava:guava:23.0-android' } }创建Flutter平台客户端应用程序的State类具有当前应用程序状态。我们需要扩展它以保持当前的功率首先,我们建立渠道。我们使用MethodChannel调用一个方法来返回电池电量。通道的客户端和主机通过通道构造函数中传递的通道名称进行连接。单个应用程序中使用的所有频道名称必须是唯一的;我们建议在频道名称中添加一个唯一的“域名前缀”,例如samples.flatter.io/pattern。import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; ... class _MyHomePageState extends State<MyHomePage> { static const platform = const MethodChannel('samples.flutter.io/battery'); // Get battery level. }接下来,我们调用通道上的方法,指定通过字符串标识符调用方法getBatteryLevel。调用可能失败-例如,如果平台不支持平台API(例如,在模拟器中运行时),我们将invokeMethod调用包装在try-catch语句中。我们使用返回的结果更新setState中的用户界面状态batteryLevel。 String _batteryLevel = 'Unknown battery level.'; Future<Null> _getBatteryLevel() async { String batteryLevel; try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { _batteryLevel = batteryLevel; }); }使用Java添加Android平台特定的实现首先在Android Studio中打开Flutter应用程序的Android部分:启动Android Studio选择“文件>打开…”导航到Flutter应用程序目录,选择android文件夹,然后单击“确定”在java目录中打开MainActivity.java接下来,在onCreate中创建MethodChannel并设置MethodCallHandler。确保使用与Flutter客户端中使用的频道名称相同的频道名称。import io.flutter.app.FlutterActivity; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "samples.flutter.io/battery"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( new MethodCallHandler() { @Override public void onMethodCall(MethodCall call, Result result) { // TODO } }); } }最后,我们完成了前面添加的onMethodCall方法。我们需要处理名为getBatteryLevel的平台方法,因此我们检查它是否是调用参数中的getBattery Level。这个平台方法的实现只需要调用我们在上一步中编写的Android代码,并使用响应参数返回成功和错误响应。如果调用了未知方法,我们还将通知返回
盒约束flutter: assets: - assets/my_icon.png - assets/background.png在Flutter中,小部件由其底层RenderBox对象渲染。渲染框受其父对象的约束,并在这些约束下调整自身大小。约束包括最小宽度、最大宽度和高度;尺寸由特定的宽度和高度组成。通常,根据小部件如何处理其约束,有三种类型的框:尽可能大。例如,“Center”和“ListView”的渲染框遵循子部件的大小。例如,“变换”和“不透明度”渲染框。指定尺寸。例如,图像和文本的渲染框一些小部件(如Container)将根据构造函数参数而变化。默认情况下,容器占用尽可能多的空间,但如果为其指定宽度,它将采用指定的值。import 'dart:async' show Future; import 'package:flutter/services.dart' show rootBundle; Future<String> loadAsset() async { return await rootBundle.loadString('assets/config.json'); }其他,如行和列(弹性框),将根据给它们的约束而变化,如下面的“flex”部分所述。这些约束有时是“紧”的,这意味着它们不会为渲染框留出空间来确定其自身的大小(例如,如果最小宽度和最大宽度相同,即宽度很窄)。主要示例是App小部件,它是RenderView类中包含的一个小部件:应用程序构建函数返回的子小部件的渲染框被分配了一个约束,迫使它精确地填充应用程序的内容区域(通常是整个屏幕)。Flutter中的许多框,特别是那些只包含一个子控件的框,会将其约束传递给其子控件。这意味着,如果在应用程序渲染树的根处嵌套一些框,则所有子节点都受这些渲染框的约束。有些框是放松的,有“最大”约束,但没有“最小”约束。例如,中心。 new AssetImage('icons/heart.png', package: 'my_icons')文本输入widgetclass ExampleWidget extends StatefulWidget { ExampleWidget({Key key}) : super(key: key); @override _ExampleWidgetState createState() => new _ExampleWidgetState(); } TextField是最常用的文本输入小部件默认情况下,TextField具有下划线装饰。通过将InputDecoration设置为装饰属性,可以添加标签、图标、提示文本和错误文本。要完全删除装饰(包括下划线和为标签保留的空间),请将装饰明确设置为空白。TextFormField包装一个TextField并将其集成到表单中。需要提供一个验证函数来检查用户的输入是否满足某些限制(例如,电话号码),或者在希望将TextField与其他FormField集成时使用TextFormField。class _ExampleWidgetState extends State<ExampleWidget> { final TextEditingController _controller = new TextEditingController(); @override Widget build(BuildContext context) { return new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new TextField( controller: _controller, decoration: new InputDecoration( hintText: 'Type something', ), ), new RaisedButton( onPressed: () { showDialog( context: context, child: new AlertDialog( title: new Text('What you typed'), content: new Text(_controller.text), ), ); }, child: new Text('DONE'), ), ], ); } }assets资产部分的颤振部分指定应用程序中应包含的文件。每个资产都由相对于pubspec.yaml文件位置的显式路径标识。资产的申报顺序无关紧要。资产的实际目录可以是任何文件夹(本例中为资产)。在构建过程中,Flutter将资产放在一个称为资产包的特殊归档中,应用程序可以在运行时读取该归档。资产变体构建过程支持资产变体的概念:不同版本的资产可以在不同的上下文中显示。当在pubspec.yaml的assets部分中指定资产路径时,在构建过程中,将在相邻子目录中找到任何同名文件。然后,这些文件将与指定的资产一起包含在资产包中。import 'dart:async' show Future; import 'package:flutter/services.dart' show rootBundle; Future<String> loadAsset() async { return await rootBundle.loadString('assets/config.json'); }加载资产应用程序可以通过AssetBundle对象访问其资产。有两种主要方法允许从资产包加载字符串/文本(loadString)或图像/二进制(load)。加载文本资源每个Flutter应用程序都有一个rootBundle对象,可以轻松访问主资源包。可以直接使用包中的全局静态rootBundle对象:flutter/services。部件以加载资产。但是,建议使用DefaultAssetBundle获取当前BuildContext的AssetBundle。此方法不使用应用程序构建的默认资产捆绑包,但允许父小部件在运行时替换不同的资产捆绑包(这对于本地化或测试场景非常有用)。 new AssetImage('icons/heart.png', package: 'my_icons')通常,可以使用DefaultAssetBundle。of()从应用程序运行时加载资产(如JSON文件)。可以使用rootBundle直接在Widget上下文外加载这些资产,或者当AssetBundle的句柄不可用时,Widget build(BuildContext context) { // ... return new DecoratedBox( decoration: new BoxDecoration( image: new DecorationImage( image: new AssetImage('graphics/background.png'), // ... ), // ... ), ); // ... }
对于手势的运用指针表示用户与设备屏幕交互的原始数据。有四种类型的指针事件PointerDownEvent指针触摸屏幕上的特定位置PointerMoveEvent指针从屏幕上的一个位置移动到另一个位置PointerUpEvent指针停止触摸屏幕PointerCancelEvent指针的输入事件不再针对此应用程序(事件取消) Widget build(BuildContext context) { return new GestureDetector( onTap: _handleTap, child: new Container( child: new Center( child: new Text( _active ? 'Active' : 'Inactive', style: new TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: new BoxDecoration( color: _active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } }当按下指针时,框架将在应用程序上执行_点击测试_,以确定指针与屏幕相交的位置存在哪些小部件。然后,指针按下事件(以及该指针的后续事件)被分发到_命中测试_找到的最里面的小部件。从那里,这些事件将在小部件树中弹出。这些事件将从最里面的小部件分发到小部件根路径上的所有小部件。没有取消或停止冒泡过程的机制。要直接从窗口小部件层侦听指针事件,请使用侦听器窗口小部件。然而,一般来说,考虑使用手势(如下所述)。要直接从小部件层侦听指针事件,可以使用Listenerwidgets。单独指针手势表示可以从多个单独指针事件(甚至多个单独的指针)中识别的语义动作(如敲击、拖动和缩放)。一个完整的手势可以调度多个事件,对应于手势的生命周期(例如,拖动开始、拖动更新和拖动结束):class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => new _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return new Container( child: new TapboxC( active: _active, onChanged: _handleTapboxChanged, ), ); } } TaponTapDown指针已在特定位置与屏幕接触onTapUp指针停止在特定位置接触屏幕onTap tap事件触发onTapCancel之前由指针触发的onTapDown不会触发tap事件双击DoubleTap可在同一位置连续两次快速点击屏幕长按onLongPress指针可在同一位置长时间与屏幕保持接触垂直拖动onVerticalDragStart指针已接触屏幕,并可能开始垂直移动。onVerticalDragUpdate指针已与屏幕接触并垂直移动OnVerticalDragEnd先前与屏幕接触且垂直移动的指针不再与屏幕接触,并在停止触摸屏幕时以特定速度移动水平拖动onHorizontalDragStart指针已触摸屏幕,并可能开始水平移动onHorizontalDragUpdate指针接触屏幕并已水平移动onHorizontalDragEnd指针(以前接触屏幕并水平移动)不再接触屏幕,并在停止触摸屏幕时以特定速度移动要从控件层监视手势,请使用手势检测器class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Text('Hello World', style: new TextStyle(fontSize: 32.0)), ), ); } }如果使用“材质组件”,大多数小部件都会响应轻敲或手势。例如,IconButton和FlatButton响应按下(轻击),ListView响应滑动事件以触发滚动。如果不使用这些小部件,但希望在单击时具有“泼墨”效果,则可以使用InkWell。消歧在屏幕上的指定位置可能有多个手势检测器。当指针事件流通过并试图识别特定手势时,所有这些手势检测器都会监听指针事件流。手势检测器小部件确定它是哪个手势。当屏幕上有多个给定指针的手势识别器时,框架通过向每个识别器添加“手势竞争场”来确定所需的手势。“手势比赛场”使用以下规则来确定哪个手势获胜在任何时候,识别器都可以宣布失败并离开“手势比赛场”。如果“比赛场地”中只剩下一个标识符,则该标识符为获胜者class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { //... Widget buttonSection = new Container( child: new Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ buildButtonColumn(Icons.call, 'CALL'), buildButtonColumn(Icons.near_me, 'ROUTE'), buildButtonColumn(Icons.share, 'SHARE'), ], ), ); //... }在任何时候,识别器都可以宣布胜利,这将导致胜利,而所有剩余的识别器都将失败例如,当消除水平和垂直拖动的歧义时,两个识别器在接收到指针向下事件时进入“手势竞争场”。识别器观察指针移动事件。如果用户将指针水平移动超过一定数量的逻辑像素,则水平识别器将宣布胜利,手势将被解释为水平拖动。类似地,如果用户垂直移动超过一定数量的逻辑像素,则垂直识别器将宣布获胜。当仅水平(或垂直)拖动识别器时,“手势竞争场”是有益的。在这种情况下,“手势竞争领域”中只有一个识别器,水平拖动将立即被识别,这意味着第一个水平移动的像素可以被视为拖动,用户不需要等待进一步的手势消歧。依赖包中的字体如果包中定义的字体在内部使用,则在创建文本样式时还应指定包参数,如上面的示例所示。包还可以提供字体文件,而无需输入pubspec Yaml。这些文件应位于包的lib/文件夹中。字体文件不会自动绑定到应用程序,应用程序可以在声明字体时选择性地使用这些字体。假设a_包中有一个包: flutter: fonts: - family: Raleway fonts: - asset: assets/fonts/Raleway-Regular.ttf - asset: packages/my_package/fonts/Raleway-Medium.ttf weight: 500family是字体的名称。可以在TextStyle的fontFamily属性中使用它资产相对于pubspec yaml文件的路径这些文件包含字体中的字形轮廓。构建应用程序时,这些文件将包含在应用程序的资产包中。可以设置字体的粗细、倾斜和其他样式weight属性指定字体的粗细。值范围是100到900(100的倍数)的整数。这些值对应于FontWeight,可用于TextStyle的FontWeight属性样式指定字体是斜体还是普通字体。相应的值为斜体和普通值。这些值对应于fontStyle可用于TextStyle的fontStyle TextStyle属性import 'package:flutter/material.dart'; const String words1 = "Almost before we knew it, we had left the ground."; const String words2 = "A shining crescent far beneath the flying vessel."; const String words3 = "A red flair silhouetted the jagged edge of a wing."; const String words4 = "Mist enveloped the ship three hours out from port."; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Fonts', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new FontsPage(), ); } }在Flutter应用程序中使用字体需要两个步骤。首先在pubspec.yaml中声明它们,以确保它们包含在应用程序中。
@[toc]FlutterFlutter是谷歌的移动UI框架,可以在IOS和Android上快速构建高质量的本地用户界面。Flutter可以使用现有代码。在世界上,Flutter正被越来越多的开发人员和组织使用,Flutter是完全免费和开源的。这也是构建未来Google Fuchsia应用程序的主要方式。import 'package:flutter/material.dart'; void main() { runApp( new Center( child: new Text( 'Hello, world!', textDirection: TextDirection.ltr, ), ), ); }runApp函数接受给定的小部件,并使其成为小部件树的根。在本例中,小部件树由两个小部件组成:Center(及其子小部件)和Text。框架强制根小部件覆盖整个屏幕,这意味着文本“Hello,world”将位于屏幕中心。需要在text实例中指定文本显示的方向。使用MaterialApp时,将自动设置文本方向,稍后将进行演示。编写应用程序时,通常会创建新的小部件。这些小部件是无状态无状态小部件或有状态有状态小部件。具体选择取决于小部件是否需要管理某些状态。小部件的主要工作是实现一个构建函数来构建自己。小部件通常由一些较低级别的小部件组成。Flutter框架将依次构建这些小部件,直到构建最低级别的子小部件。这些最低级别的小部件通常是RenderObject,它将计算和描述小部件的几何结构。MaterialFlutter提供了许多小部件来帮助构建遵循Material Design的应用程序。Material应用程序从MaterialApp小部件开始,它在应用程序的根位置创建一些有用的小部件,包括一个Navigator,它管理由字符串标识的小部件堆栈(即页面路由堆栈)。导航器允许应用程序在页面之间平滑过渡。是否使用MaterialApp是完全可选的,但使用它是一个很好的做法。Scaffold是Material中主要的布局组件.import 'package:flutter/material.dart'; void main() { runApp(new MaterialApp( title: 'Flutter Tutorial', home: new TutorialHome(), )); } class TutorialHome extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( leading: new IconButton( icon: new Icon(Icons.menu), tooltip: 'Navigation menu', onPressed: null, ), title: new Text('Example title'), actions: <Widget>[ new IconButton( icon: new Icon(Icons.search), tooltip: 'Search', onPressed: null, ), ], ), body: new Center( child: new Text('Hello, world!'), ), floatingActionButton: new FloatingActionButton( tooltip: 'Add', // used by assistive technologies child: new Icon(Icons.add), onPressed: null, ), ); } }更完整的示例让我们考虑一个更完整的示例,它将上面介绍的概念结合在一起。让我们假设一个显示各种待售产品并维护购物车的购物应用程序。让我们先定义ShoppingListItem:class Product { const Product({this.name}); final String name; } typedef void CartChangedCallback(Product product, bool inCart); class ShoppingListItem extends StatelessWidget { ShoppingListItem({Product product, this.inCart, this.onCartChanged}) : product = product, super(key: new ObjectKey(product)); final Product product; final bool inCart; final CartChangedCallback onCartChanged; Color _getColor(BuildContext context) { return inCart ? Colors.black54 : Theme.of(context).primaryColor; } TextStyle _getTextStyle(BuildContext context) { if (!inCart) return null; return new TextStyle( color: Colors.black54, decoration: TextDecoration.lineThrough, ); } @override Widget build(BuildContext context) { return new ListTile( onTap: () { onCartChanged(product, !inCart); }, leading: new CircleAvatar( backgroundColor: _getColor(context), child: new Text(product.name[0]), ), title: new Text(product.name, style: _getTextStyle(context)), ); } }ShoppingListItem小部件是无状态的。它将在构造函数中接收的值存储在最终成员变量中,然后在构建函数中使用它们。例如,inCart布尔值表示在两种视觉呈现效果之间切换:一种使用当前主题的主色,另一种使用灰色。当用户单击列表项时,小部件不会直接修改其inCart值。相反,小部件将调用其父小部件赋予它的onCartChanged回调函数。此模式允许在小部件层次结构中存储更高的状态,从而使状态持续更长时间。在极端情况下,存储并传递给runApp应用程序的小部件的状态将持续整个生命周期。当父项收到onCartChanged回调时,父项将更新其内部状态,这将触发父项使用新的inCart值重新生成ShoppingListItem的新实例。尽管父ShoppingListItem在重建时创建了一个新实例,但操作成本很低,因为Flutter框架会将新构建的小部件与先前构建的小组件进行比较,并且只将差异应用于底层RenderObject。Key可以使用该键控制在重新构建小部件时框架将匹配哪些其他小部件。默认情况下,框架根据其runtimeType和显示顺序进行匹配。当使用键时,框架要求两个小部件具有相同的键和runtimeType。键对于构建相同类型的小部件的多个实例非常有用。例如,ShoppingList构建了足够的ShoppingListItem实例来填充其可见区域:如果没有密钥,则当前生成中的第一个项将始终与上一个生成中的首个项同步。即使在语义上,如果列表中的第一个项目滚动到屏幕外,它在窗口中也不再可见。通过将列表中的每个项目指定为“语义”键,无限列表可以更有效,因为框架将使项目与匹配的语义键同步,从而具有相似(或相同)的视觉外观。此外,语义同步条目意味着在有状态的子窗口小部件中,保留状态将附加到相同的语义条目,而不是附加到相同编号位置的条目。class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { Widget titleSection = new Container( padding: const EdgeInsets.all(32.0), child: new Row( children: [ new Expanded( child: new Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ new Container( padding: const EdgeInsets.only(bottom: 8.0), child: new Text( 'Oeschinen Lake Campground', style: new TextStyle( fontWeight: FontWeight.bold, ), ), ), new Text( 'Kandersteg, Switzerland', style: new TextStyle( color: Colors.grey[500], ), ), ], ), ), new Icon( Icons.star, color: Colors.red[500], ), new Text('41'), ], ), ); //... }将第一行文本放入容器,然后在底部添加8个像素。列中的第二个子项(也是文本)显示为灰色。标题行中的最后两项是红星图标和文本“41”。将整行放在容器中,沿每条边填充32个像素
@[toc]js级别的差异主要来自两个方面:hook系统(不考虑类)和ecma-ast差异hook系统。钩子系统的api更多地用于纯函数组件注入状态和生命周期。在这两个方面,Svelte提供的解决方案是不同的。由于预运行编译,Svelte编译器扫描所有与UI相关的状态并注入黑魔法,使得状态的使用与变量声明和赋值一样简单。基本上,开发人员不需要太在意所谓的副作用;因此,一些钩子接口在Svelte框架上是冗余的。然而,考虑到大量的钩子接口,我们选择了内置的svelte钩子来简化转换过程中的转换逻辑。SVelte钩子是一组基于SVelte的钩子接口,通过对react钩子进行基准测试来实现,这些钩子在使用中基本相同。<script> import Nested from './Nested.svelte'; </script> <style> p { color: purple; font-family: 'Comic Sans MS', cursive; font-size: 2em; } </style> <p>These styles...</p> <Nested/>ecma ast差异ecma ast difference babel提供的解析是基于estree的,但同时一些类型也在此基础上进行了改进。有关具体差异,请参阅此处的babel解析器[1]。细化的数据类型有助于我们进行转换推断,因此我们没有使用babel来提供estree插件,并且在转换之后,ast再次被平滑。CSS转换比上述两部分的转换简单得多。React样式是标准css,Svelte样式也是标准css。但是,它将增加一定的编译能力。可以理解,它是标准css的超集,可以直接使用。然而,为了平滑jsx和Svelte html在自定义组件的类选择器中的差异,我们仍然在编译阶段进行了一些转换,这里不再展开。<script> let count = 0; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button>细化后的体积差异<script> import { quintOut } from 'svelte/easing'; import { fade, draw, fly } from 'svelte/transition'; import { expand } from './custom-transitions.js'; import { inner, outer } from './shape.js'; let visible = true; </script> <style> svg { width: 100%; height: 100%; } path { fill: white; opacity: 1; } label { position: absolute; top: 1em; left: 1em; } .centered { font-size: 20vw; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); font-family: 'Overpass'; letter-spacing: 0.12em; color: #676778; font-weight: 400; } .centered span { will-change: filter; } </style> {#if visible} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 103 124"> <g out:fade="{{duration: 200}}" opacity=0.2> <path in:expand="{{duration: 400, delay: 1000, easing: quintOut}}" style="stroke: #ff3e00; fill: #ff3e00; stroke-width: 50;" d={outer} /> <path in:draw="{{duration: 1000}}" style="stroke:#ff3e00; stroke-width: 1.5" d={inner} /> </g> </svg> <div class="centered" out:fly="{{y: -20, duration: 800}}"> {#each 'SVELTE' as char, i} <span in:fade="{{delay: 1000 + i * 150, duration: 800}}" >{char}</span> {/each} </div> {/if} <label> <input type="checkbox" bind:checked={visible}> toggle me </label> <link href="https://fonts.googleapis.com/css?family=Overpass:100,400" rel="stylesheet"> 不可能100%准确地将运行时jsx编译成静态html,弱类型语言的变量跟踪不可靠,非本地逻辑控制语法不能在编译器中逐一枚举;目前,在转换工作中仍然存在许多与编译相关的问题,但这些问题可以通过一些插件来补充并逐步改进。大型项目包装量的现状不容乐观。Svelte可以通过预运行编译按需打包整个框架来有效地减少包容量,但编译产品本身没有优势。页面的UI交互越复杂,编译产品就越大。此外,对框架的依赖程度越高,整体包装量的优势就会消失;此外,我们的转换器为编译增加了一定的复杂性,以平滑差异,因此仍有很大的空间来优化编译产品的数量。没有性能,没有前端。我们仍然缺乏关于性能的数据,但我们也从一些第三方文章中了解到,Svelte的整体性能并不是瓶颈。理论上,通过编译实现数据驱动的DOM是简单而有效的。理论上,脱离虚拟DOM也会提高内存性能;但是,我们将单独查看性能。预运行编译的思想不仅适用于框架,也适用于组件,这也会带来很多好处。调试模式它可以与{@debug…}一起使用以替换控制台。log(…)。每当指定变量的值发生变化时,它都会记录这些变量的值。如果打开devtools,代码执行将在{@debug…}语句的位置暂停。它接受单个变量名:<script> let user = { firstname: 'Ada', lastname: 'Lovelace' }; </script> {@debug user} <h1>Hello {user.firstname}!</h1>on:事件名可以使用的修改器有:PreventDefault:调用事件。preventDefault()在程序运行之前StopPropagation:调用事件StopProparation()以防止事件到达下一个标记被动:提高了触摸/滚轮事件的滚动性能(Svelte将在适当的情况下自动添加)capture:表示其程序是在捕获阶段触发的,而不是通过冒泡一次:程序运行一次后删除自身可以连接修饰符,例如:单击|once|capture={…}。如果未为使用的on:命令事件指定特定值,则意味着组件将负责转发事件,这意味着组件的用户可以监听事件。<form on:submit|preventDefault={handleSubmit}> </form><svelte:options><svelte:options>标记为组件提供编译器选项。有关详细信息,请参阅。选项包括:Immutable={true}-从不使用变量数据,因此编译器可以很容易地检查等式以确定值是否已更改。不可变={false}-默认选项。Svelte在处理可变对象值更改时趋于保守。Accessors={true}-向组件的属性添加getter和setter。访问器={false}-默认值。命名空间=“…”-让组件使用命名空间。最常见的是“svg”。Tag=“…”-将此组件编译为自定义标记时使用的名称。<svelte:options tag="my-custom-element"/>此onMount函数用作回调,在组件安装到DOM后立即执行。它必须在组件初始化期间调用(但不一定在组件内部;可以从外部模块调用)。OnMount不在内部运行。<script> import { onMount } from 'svelte'; onMount(() => { const interval = setInterval(() => { console.log('beep'); }, 1000); return () => clearInterval(interval); }); </script>setContext使用指定的键将任何上下文对象与当前组件相关联。然后,通过getContext函数将上下文应用于组件的子级(包括带有槽的内容)。与生命周期函数一样,它必须在组件初始化期间调用。<script> import { setContext } from 'svelte'; setContext('answer', 42); </script>
SvelteSvelte用于构建快速Web应用程序。Svelte与React和Vue一样,致力于让轻松构建灵活、交互式的用户界面。不同之处在于,Svelte在构建时将代码转换为更好的JavaScript,而不是在运行时解释和执行代码。这意味着不必支付框架本身的性能成本,并且在首次加载时不会有额外的性能损失。可以使用Svelte编写整个应用程序,也可以使用Svlte逐步重构现有代码;也可以只输出一个独立的组件(不强制附加框架本身),并在任何框架中使用它。<script> let name = 'world'; </script> <h1>Hello world!</h1>在大括号中,我们可以编写任意JavaScript。然后可以尝试将名称更改为name。toUpperCase()以使问候更加强烈。注意:看起来正在创建一个全局变量,但实际上,name仍然限于组件的局部变量,也就是说,无法访问全局对象(Window)中组件中声明的name变量。<script> let src = 'tutorial/image.gif'; </script> <img>样式对于特定的函数,Svelte仍然有相应的运行时代码,例如组件逻辑、if/else切换逻辑、循环逻辑等。但是在编译时,如果没有使用函数,则相应的代码根本不会编译成结果。就像Babel中未使用的函数的助手不会被引入一样,就像使用lodash或RxJS时,只有相应的函数被选择性地引入一样。<img src={src} alt="A man dances.">基于这两个特性,Svelte应用程序的最终代码量可能非常小。例如,它的TodoMVC min+gzip仅晚了3kb。然而,Svelte并非没有潜在问题:(1) 尽管简单演示中的代码量确实很小,但同一组件模板通过这种隐式操作生成的代码量将大于vdom渲染函数的代码量,并且多个组件中会有大量重复代码(尽管gzip可以缓解这个问题,但解析和求值是不可避免的)。项目中的组件越多,代码量的差异就越小。同时,它并不是广告中所说的“没有运行时”,而是根据代码按需导入。使用的函数越多,Svelte需要包含的运行时代码就越多。很难说它在实际生产项目中会有多大的规模优势。(2) Svelte在大型应用程序中的性能仍有待观察,尤其是在大量动态内容和嵌套组件的情况下。它的更新策略决定了它还需要一个类似于React的shouldComponentUpdate的机制来防止过度更新。<style> p { color: purple; font-family: 'Comic Sans MS', cursive; font-size: 2em; } </style> <p>这是一段话。</p>嵌套组件将整个应用程序放在一个组件中是不切实际的。相反,我们可以从其他文件导入组件,并将其作为包含元素包含。请注意,即使Nested Svelte具有<p>元素,App的Svelte风格也不受影响。还要注意,组件名称Nested是大写的。此约定用于使我们能够区分用户定义的组件和常规HTML标记。<script> import Nested from './Nested.svelte'; </script>前端通常,字符串以纯文本形式插入,这意味着<和>等字符没有特殊含义。但有时需要将HTML直接呈现到组件中。例如,正在阅读的单词现在存在于markdown文件中,该文件以HTML的形式包含在本页中:<script> let string = `this string contains some <strong>HTML!!!</strong>`; </script> <p>{string}</p>Svelte在将{@html…}中的表达式插入DOM之前不会清理它们。换言之,如果使用此功能,则必须手动从不受信任的源中转义HTML,否则可能会使用户暴露于XSS攻击。一个App实例还需要配置文本编辑器。如果正在使用VS代码,请安装Svelte扩展。否则,请按照本指南中的说明配置文本编辑器,以便。svelte文件与相同。html来突出显示语法。然后,一旦项目建立,就可以轻松地使用Svelte组件。编译器将每个组件转换为一个常规JavaScript类-只需导入并使用new来实例化:import App from './App.svelte'; const app = new App({ target: document.body, props: { answer: 42 } });有时,检查流经应用程序的数据是有用的。一种解决方案是使用控制台。标记内的log(…)。但是,如果要暂停执行,可以将{@debug…}标记与要检查的值的逗号分隔列表一起使用:<script> let user = { firstname: 'Ada', lastname: 'Lovelace' }; </script> {@debug user} <h1>Hello {user.firstname}!</h1>笔记可以在组件中使用HTML注释。以svelte ignore开头的内容将被注释掉,直到在注释结束标记处结束。一般来说,带注释的内容包含可访问性(a11y,一些有助于提高可访问性的信息)信息,因此请仅在有充分理由时使用。 <!-- 这是一句注释! --> <h1>Hello world</h1>如果键是一个表达式,它必须是唯一的,以标识列表中的每个列表项,以便Svelte可以更改列表中任何位置的数据,而不是在末尾添加或删除数据。密钥可以是任何对象,但建议使用字符串和数字,因为它们允许在对象本身更改时保留标识。{#each items as { id, name, qty }, i (id)} <li>{i + 1}: {name} x {qty}</li> {/each} {#each objects as { id, ...rest }} <li><span>{id}</span><MyComponent {...rest}/></li> {/each} {#each items as [id, ...rest]} <li><span>{id}</span><MyComponent values={rest}/></li> {/each}它可以与{@debug…}一起使用以替换控制台。日志(…)。每当指定变量的值发生变化时,它都会记录这些变量的值。如果打开devtools,代码执行将在{@debug…}语句的位置暂停。它接受单个变量名。可以使用的修改器有:PreventDefault:调用事件。preventDefault()在程序运行之前StopPropagation:调用事件。stopPropagation()以防止事件到达下一个标记被动:提高了触摸/滚轮事件的滚动性能(Svelte将在适当的情况下自动添加)捕获:表示其程序是在捕获阶段触发的,而不是通过冒泡触发的一次:程序运行一次后删除自身可以连接修饰符,例如:单击|once|capture={…}。如果未为使用的on:命令事件指定特定值,则意味着组件将负责转发事件,这意味着组件的用户可以监听事件。<script> let counter = 0; function increment() { counter = counter + 1; } function track(event) { trackEvent(event) } </script> <button on:click={increment} on:click={track}>Click me!</button>
Sunburst要创建日出图表,需要在系列配置项中声明一系列类型“sunburst”,并在树结构中声明其数据:var option = { series: { type: 'sunburst', data: [{ name: 'A', value: 10, children: [{ value: 3, name: 'Aa' }, { value: 5, name: 'Ab' }] }, { name: 'B', children: [{ name: 'Ba', value: 4 }, { name: 'Bb', value: 2 }] }, { name: 'C', value: 3 }] } };它由多层环图组成。在数据结构方面,内圈是外圈的父节点。因此,它不仅可以像饼图一样表示部分和整体的比例,还可以像矩形树形图一样表示层次关系。默认情况下,使用全局调色板指定最内层的颜色,其他层的颜色与其父元素相同。在日出图表中,扇区块的颜色可以通过以下三种方式设置:串联。data每个扇区块的样式在itemStyle中设置;在系列中。levels设置itemStyle中每个层的样式;在系列中,整个日出图表的样式设置为itemStyle。以上三者的优先级从高到低,即系列。数据已配置itemStyle的扇区块将覆盖系列。级别ItemStyle和ItemStyle的系列设置。接下来,我们将整体颜色设置为灰色“#aaa”,最里面的颜色设置为蓝色“蓝色”,Aa和B颜色设置为红色“红色”。`在这里插入代码片 type: 'sunburst', data: [{ name: 'A', value: 10, children: [{ value: 3, name: 'Aa', itemStyle: { color: 'red' } }, { value: 5, name: 'Ab' }] }, { name: 'B', children: [{ name: 'Ba', value: 4 }, { name: 'Bb', value: 2 }], itemStyle: { color: 'red' } }, { name: 'C', value: 3 }], itemStyle: { color: '#aaa' },日出图是一个层次结构。为了便于配置相同的图层样式,我们提供了级别配置项。它是一个数组,其中项0表示向下钻取数据后返回到上层的图形,每个后续项表示从圆心到外层的级别。例如,如果我们没有数据钻取功能,并且希望将最里面的扇区块的颜色设置为红色,将文本设置为蓝色,我们可以如下设置。当鼠标移动到扇区块时,日出图表支持突出显示相关数据块。可以设置highlightPolicy,包括以下突出显示方法:“descendant”(默认):突出显示鼠标移动的扇区块及其后代元素;“祖先”:突出显示鼠标所在的扇区块及其祖先元素;“self”:仅突出显示鼠标所在的扇区块;“无”:其他元素不会被淡化。上面提到的“高亮显示”将使用鼠标所在扇区块的强调样式;对于其他相关的扇区块,将使用高光样式。通过这种方式,可以很容易地实现突出显示相关数据的需要。具体而言,对于配置项:itemStyle: { color: 'yellow', borderWidth: 2, emphasis: { color: 'red' }, highlight: { color: 'orange' }, downplay: { color: '#ccc' } }拖拽convertToPixel API用于将数据转换为像素坐标,从而可以获得每个点的位置,并绘制这些点。myChart。在语句convertToPixel('grid',dataItem)中,第一个参数'grid'表示dataItem在网格组件(即直角坐标系)中转换。所谓的“像素坐标”是指以容器的dom元素的左上角为零点的基于像素的坐标系中的坐标。注意,这应该在第一个setOption之后完成,也就是说,只有在网格初始化convertToPixel('grid',dataItem)之后才能调用myChart使用此代码,可以拖动多个点。接下来,为每个点添加拖动响应事件:myChart.setOption({ graphic: echarts.util.map(data, function (dataItem, dataIndex) { return { // 'circle' 表示这个 graphic element 的类型是圆点。 type: 'circle', shape: { // 圆点的半径。 r: symbolSize / 2 }, position: myChart.convertToPixel('grid', dataItem), // 这个属性让圆点不可见(但是不影响他响应鼠标事件)。 invisible: true, // 这个属性让圆点可以被拖拽。 draggable: true, // 把 z 值设得比较大,表示这个圆点在最上方,能覆盖住已有的折线图的圆点。 z: 100, ondrag: echarts.util.curry(onPointDragging, dataIndex) }; }) });声明一个 graphic component,里面有若干个 type 为 'circle' 的 graphic elements。 这里使用了 echarts.util.map 这个帮助方法,其行为和 Array.prototype.map 一样,但是兼容 es5 以下的环境。用 map 方法遍历 data 的每项,为每项生成一个圆点。用 transform 的方式对圆点进行定位。position: [x, y] 表示将圆点平移到 [x, y] 位置。这里使用了 convertToPixel 这个 API 来得到每个圆点的位置。此圆点的拖拽的响应事件,在拖拽过程中会不断被触发。下面介绍详情。 这里使用了 echarts.util.curry 这个帮助方法,意思是生成一个与 onPointDragging功能一样的新的函数,只不过第一个参数永远为此时传入的 dataIndex 的值。myChart.setOption({ ..., tooltip: { // 表示不使用默认的『显示』『隐藏』触发规则。 triggerOn: 'none', formatter: function (params) { return 'X: ' + params.data[0].toFixed(2) + '<br>Y: ' + params.data[1].toFixed(2); } } });此时,完成了拖动的基本功能。但是,如果要在拖动过程中进一步实时查看拖动点的数据值更改,可以使用工具提示组件实时显示该值。然而,Tooltip有其默认的“显示”和“隐藏”触发规则,这在我们的拖放场景中不适用。因此,我们还需要手动自定义工具提示的“显示”和“隐藏”行为。myChart.setOption({ graphic: echarts.util.map(data, function (item, dataIndex) { return { type: 'circle', ..., onmousemove: echarts.util.curry(showTooltip, dataIndex), onmouseout: echarts.util.curry(hideTooltip, dataIndex), }; }) }); function showTooltip(dataIndex) { myChart.dispatchAction({ type: 'showTip', seriesIndex: 0, dataIndex: dataIndex }); } function hideTooltip(dataIndex) { myChart.dispatchAction({ type: 'hideTip' }); }在 mouseover 的时候显示,在 mouseout 的时候隐藏。 此时,完成了拖动的基本功能。但是,如果要在拖动过程中进一步实时查看拖动点的数据值更改,可以使用工具提示组件实时显示该值。然而,Tooltip有其默认的“显示”和“隐藏”触发规则,这在我们的拖放场景中不适用。因此,我们还需要手动自定义工具提示的“显示”和“隐藏”行为。
在微信小程序中使用 ECharts关于微信小程序的项目创建。创建项目后,可以用新项目完全替换weixin项目下载的电子商务/图表,然后修改代码;或者只需将ec画布目录复制到新项目,然后进行相应的调整。如果采用完全替换的方法,则project.config json中的appid将替换为公共平台上应用的项目id。pages目录下的每个文件夹都是一个页面。可以根据情况删除不必要的页面,然后单击应用程序删除json中的相应页面。如果只复制ec画布目录,则可以参考pages/bar目录中多个文件的写入方法。现在,让我们详细解释一下。{ "usingComponents": { "ec-canvas": "../../ec-canvas/ec-canvas" } }首先,在pages/bar目录中创建以下文件:index.js、index。json、索引。wxml、索引。wxss在应用程序中,“pages/bar/index”被添加到json页面中。指数json配置如下:<view class="container"> <ec-canvas id="mychart-dom-bar" canvas-id="mychart-bar" ec="{{ ec }}"></ec-canvas> </view>此配置的功能是允许我们选择在wxml中使用的pages/bar/index组件。请注意,路径的相对位置应正确写入。如果目录结构与本例中的目录结构相同,则应按上述方式进行配置。指数在wxml中,我们创建了一个<ec canvas>组件function initChart(canvas, width, height) { const chart = echarts.init(canvas, null, { width: width, height: height }); canvas.setChart(chart); var option = { ... }; chart.setOption(option); return chart; } Page({ data: { ec: { onInit: initChart } } });目前支持的内容:工具提示相片多个zlevel层此外,还有一些bug尚未修复,其中一些需要小程序团队的在线支持,但不影响基本使用。已知错误包括:Android平台:转换的问题(它会影响关系图两端的标记位置和日出图的文本位置)IOS平台:透明度稍深的问题IOS平台:渐变显示在定义区域之外三维可视化<script src="lib/echarts.min.js"></script> <script src="lib/echarts-gl.min.js"></script>在介绍了ECharts和ECharts GL之后,我们将首先声明一个基本的三维笛卡尔坐标系,用于绘制常见的统计图,如三维散点图、直方图和曲面图。在ECharts中,我们有一个网格组件来提供一个矩形区域来放置二维笛卡尔坐标系,以及笛卡尔坐标系上的x轴(xAxis)和y轴(yAxis)。对于三维笛卡尔坐标系,我们在GL中提供网格3D组件来划分三维笛卡尔空间,并将xAxis3D、yAxis3D、zAxis3D放置在网格3D上。var option = { grid3D: {}, xAxis3D: {}, yAxis3D: {}, zAxis3D: {} }需要注意的是我们不能跟 grid 一样省略 grid3D,默认情况下, x, y, z 分别是从 0 到 1 的数值轴在声明了笛卡尔坐标系之后,我们尝试使用程序生成的正态分布数据在这个三维笛卡尔坐标系中绘制散点图。以下代码用于生成正常分布的数据。不必关心这段代码是如何工作的,只需知道它生成了一个三维正态分布数据并将其放入数据数组中即可。function makeGaussian(amplitude, x0, y0, sigmaX, sigmaY) { return function (amplitude, x0, y0, sigmaX, sigmaY, x, y) { var exponent = -( ( Math.pow(x - x0, 2) / (2 * Math.pow(sigmaX, 2))) + ( Math.pow(y - y0, 2) / (2 * Math.pow(sigmaY, 2))) ); return amplitude * Math.pow(Math.E, exponent); }.bind(null, amplitude, x0, y0, sigmaX, sigmaY); } // 创建一个高斯分布函数 var gaussian = makeGaussian(50, 0, 0, 20, 20); var data = []; for (var i = 0; i < 1000; i++) { // x, y 随机分布 var x = Math.random() * 100 - 50; var y = Math.random() * 100 - 50; var z = gaussian(x, y); data.push([x, y, z]); }option = { grid3D: {}, xAxis3D: {}, yAxis3D: {}, zAxis3D: { max: 100 }, series: [{ type: 'scatter3D', data: data }] }得到如下结果:默认情况下,前三列(即收入、预期寿命和人口)将分别位于x、y和z轴上。使用encode属性,我们还可以将指定列的数据映射到指定的坐标轴,从而节省大量繁琐的数据转换代码。例如,如果我们将x轴替换为国家,将y轴替换为年份,将z轴替换为收入,我们可以看到不同国家在不同年份的人均收入分布。当然,除了visualMap组件,还可以使用其他ECharts内置组件,并充分利用这些组件的交互效果,例如性别。还可以将此示例与3D散点图和散点矩阵结合使用,以实现一系列2D和3D混搭。在实现GL时,我们尽量减少WebGL和Canvas之间的差异,以便GL的使用更加方便和自然。在笛卡尔坐标系中显示其他类型的三维图表除了散点图,我们还可以使用GL在三维笛卡尔坐标系上绘制其他类型的三维图表。例如,在前面的示例中,将scatter3D类型更改为bar3D可以将其转换为三维直方图。最后,我们经常被问到如何使用ECharts绘制只有二维数据的三维直方图。总的来说,我们不建议这样做,因为这种不必要的三维直方图很容易造成错误的表达。有关详细信息,请参阅直方图指南中的说明。然而,如果有一些其他因素使得有必要绘制三维直方图,也可以使用GL。
Canvas渲染使用 Canvas 渲染器(默认)等价于:使用 SVG 渲染器 var chart = echarts.init(containerDom, null, {renderer: 'canvas'}); var chart = echarts.init(containerDom); var chart = echarts.init(containerDom, null, {renderer: 'svg'});在大多数浏览器侧图库中,将选择SVG或画布进行渲染。对于绘制图表,这两种技术通常是可替换的,并且具有类似的效果。然而,在某些情况下,它们的性能和能力是不同的。因此,他们的选择成为一个长期存在的话题,不容易有一个标准答案。Apache EChartsTM从一开始就使用Canvas绘制图表(除了使用VML for IE8-)。ECharts v4.0发布了SVG渲染器,它提供了一个新的选择。通过在初始化图表实例时将渲染器参数设置为“canvas”或“svg”,可以方便地指定渲染器。首先向 echarts 注册 SVG 字符串或解析过的 SVG DOM,引用注册过的底图。$.get('map/organ.svg', function (svg) { echarts.registerMap('organ_diagram', {svg: svg}); var chart = echarts.init(document.getElementById('main'))。 chart.setOption({ geo: [{ map: 'organ_diagram', ... }] }); });一般来说,Canvas更适合绘制具有大量图形元素的图表(通常由大量数据导致)(例如热图表、地理坐标系或平行坐标系上的大型折线图或散点图),也适合实现一些视觉效果。然而,在许多场景中,SVG具有重要的优势:它具有较低的内存消耗(这对于移动终端尤为重要)、略高的渲染性能,并且在用户使用浏览器内置的缩放功能时不会模糊。我们可以根据硬件和软件环境、数据量和功能需求综合考虑选择哪种渲染器。import 'zrender/lib/svg/svg';在良好的硬件和软件环境以及少量数据(如PC上的业务报告)的情况下,可以使用两个渲染器,而不会产生太多混淆。在环境较差且性能问题需要优化的场景中,可以通过实验确定使用哪个渲染器。例如,我有以下经历:当需要创建许多ECharts实例并且浏览器容易崩溃(可能是因为Canvas的数量超过了手机的容量)时,可以使用SVG渲染器进行改进。粗略地说,如果图表在低端Android机器上运行,或者我们使用一些特定的图表,例如水球图表,SVG渲染器可能会更好。当数据量较大且有许多交互时,可以选择画布渲染器。我们强烈欢迎开发人员就他们的经验和场景向我们提供反馈,以帮助我们更好地优化。注意:除了某些特殊渲染可能依赖于画布:例如眩光尾迹效果、混合效果的热图等,SVG支持的大多数功能。SVG 底图如果我们想控制SVG中的某些元素或允许某些元素交互,我们首先需要在SVG中标记这些元素:向这些元素添加名称属性(在下文中,我们将添加了名称属性的元素称为“命名元素”)。许多功能(如select、emphasis、focus plus、label、labelLayout和工具提示)取决于元素的命名。对于以下示例,我们只将name属性name=“named_rect”添加到左侧SVG路径:<?xml version="1.0" encoding="utf-8"?> <svg xmlns="http://www.w3.org/2000/svg" version="1.2" fill-rule="evenodd" xml:space="preserve"> <path name="named_rect" d="M 0,0 L 0,100 100,100 100,0 Z" fill="#765" /> <path d="M 150,0 L 150,100 250,100 250,0 Z" fill="#567" /> </svg>option = { geo: { map: 'some_svg', regions: [{ name: 'element_name_1', itemStyle: { ... } }, { name: 'element_name_2', itemStyle: { ... } }] } };虽然SVG元素样式(如颜色、字体、线宽等)可以直接在SVG文件中定义,但ECharts还支持在选项中为命名元素定制样式,这可以提供很多方便。您可以在geo中设置样式。itemStyle或系列映射。itemStyle(还包括emphasis.itemStyle,select.itemStyle、blur.itemStyle和regions[i].itemStyle以及regions[i]。emphasis.itemStyle,regions[i].select.iitemStyle和regions[i]。select.item Style和region[i]。blur.iitemStyle)。您也可以在此处删除某些命名元素的默认样式(例如,在设置emphasis.itemStyle.color:null后,当鼠标悬停时填充颜色不会改变)。myChart.on('geoselectchanged', function (params) { var selectedNames = params.allSelected[0].name; console.log('selected', selectedNames); });此外,在使用序列图时,还可以使用visualMap组件为命名元素指定样式。参见烹饪牛的例子。注意:只有这些命名元素才能在itemStyle中设置样式:矩形、圆形、直线、椭圆、多边形、多段线和路径。option = { geo: { map: 'some_svg', emphasis: { label: { show: false } } } };虽然可以在SVG中直接定义/来显示文本标签,但ECharts也支持使用geo。标签或序列图。label可在基础地图上设置标签。默认情况下,在鼠标悬停时启用标签功能。'name1' 是一个 SVG 元素的名字。// myChart.on('click', { geoIndex: 0, name: 'name1' }, function (params) { console.log(params); });如果使用geo BoundingCoords作为边界矩形。否则,如果设置了,[0,0,width,height]将用作边界矩形。(如果仅设置了宽度或高度,则仅使用[0,width]或[0,height]。)。否则,如果设置了,则viewBox将用作边界矩形。否则,从整个SVG的所有元素的并集获得最终的边界矩形。如果地理中心或地理缩放,则从上述1~4中获得的边界矩形将相应地进行变换。确认边界矩形后,它将被放置在相应的地理视图端口中:如果使用layoutCenter和layoutSize,则边界矩形将放置在地理视图端口的中心并尽可能填充(保持纵横比)。如果使用顶部、右侧、底部和左侧,则边界矩形将被拉伸,并且地理视图端口将被完全填充。option = { geo: { map: 'some_svg' }, series: { type: 'effectScatter', coordinateSystem: 'geo', geoIndex: 0, data: [ // SVG local coords. [488.2358421078053, 459.70913833075736], [770.3415644319939, 757.9672194986475], [1180.0329284196291, 743.6141808346214], ] } };
移动端适配满足多个查询时的优先级:请注意,可以同时满足多个查询,并且它们都将由mergeOption合并,mergeOption稍后由merge定义(即更高的优先级)。默认查询:如果媒体中有一项不写入查询,则表示“默认值”。也就是说,如果不符合所有规则,则采用此选项。容器尺寸实时变化时的注意事项:在许多情况下,不需要通过拖动任意改变容器DOM节点的大小,而是根据不同的终端设置几个典型的大小。但是,如果容器DOM节点需要能够通过拖动任意更改其大小,那么在当前使用它时应该注意这一点:如果配置项出现在查询选项中,那么它也必须出现在其他查询选项中。否则,它无法返回到原始状态。(左/右/上/下/宽/高不受此限制。) { query: { maxWidth: 500 // 当容器宽度小于 500 时。 }, option: { legend: { right: 10, // legend 放置在右侧中间。 top: '15%', orient: 'vertical' // 纵向布局。 }, series: [ // 两个饼图上下布局。 { radius: [20, '50%'], center: ['50%', '30%'] }, { radius: [30, '50%'], center: ['50%', '75%'] } ] } }, ... ]复合选项中的媒体不支持合并也就是说,当设置了setOption(rawOption)时,如果rawOption是一个复合选项(包括媒体列表),则新的rawOption媒体列表不会与旧媒体列表合并,而是简单地替换。当然,baseOption通常仍会与旧选项合并。事实上,很少有场景需要使用“复合选项”多次设置该选项。我们建议的做法是,在使用mediaQuery时,对第一个setOption使用“复合选项”,而对后面的setOption只使用“原子选项”,也就是说,只使用setOption更改baseOption。{ minWidth: 200, maxHeight: 300, minAspectRatio: 1.3 }现在支持三个属性:width、height和aspectRatio。每个属性的前缀都可以是min或max。例如,minWidth:200表示“大于或等于200px宽度”。这两个属性写在一起表示“和”。例如,{minWidth:200,maxHeight:300}表示“宽度大于或等于200px,高度小于或等于300px”。选项:由于媒体中的选项是原子选项,理论上,可以编写任何选项配置项。但通常情况下,我们只写布局定位,例如拦截上面示例中的一些查询选项:option = { // 这里是基本的『原子option』。 title: {...}, legend: {...}, series: [{...}, {...}, ...], ..., media: [ // { query: {...}, // 这里写规则。 option: { // 这里写此规则满足下的option。 legend: {...}, ... } }, { query: {...}, // 第二个规则。 option: { // 第二个规则对应的option。 legend: {...}, ... } }, { // option: { // legend: {...}, ... } } ] };这里定义了 media query 的逐条规则。这条里没有写规则,表示『默认』,即所有规则都不满足时,采纳这个option。数据和维度ECharts中的数据通常存储在系列数据中。根据图表的类型,数据的具体形式可能略有不同。例如,它可以是线性表、树或图形。但它们都有一个共同点:它们是“数据项”的集合。每个数据项包含“值”和其他信息(如有必要)。每个数据值可以是单个数值(一维)或数组(多维)。例如,series最常见的数据形式是“线性表”,即常见的数组:这里每一个项就是数据项(dataItem),这是数据项的数据值(value)series: { data: [ { value: 2323, // itemStyle: {...} }, 1212, // 2323, // 4343, 3434 ] }也可以直接是 dataItem 的 value,这更常见。每个 value 都是『一维』的。分段视觉映射组件分段视觉映射组件(visualMapPiecewise)有三种模式:连续数据的平均分割:根据visualMap Peerswise SplitNumber自动平均分割成几个块。连续数据的自定义分割:根据visualMap Piecewise Pieces定义每个块的范围。离散数据(分类数据):类别在visualMap pricewise Categories中定义。可视化映射模式的配置由于这是“数据”到“视觉元素”的映射,因此可以在visualMap中指定数据的“哪个维度”(参见visualMap.dimension),以映射到哪个“视觉元素“(参见visualMap.inRange和visualMap.outOfRange)。option = { visualMap: [ { type: 'piecewise', min: 0, max: 5000, dimension: 3, seriesIndex: 4, inRange: { color: ['blue', '#121122', 'red'], symbolSize: [30, 100] }, outOfRange: { // 选中范围外的视觉配置 symbolSize: [30, 100] } }, ... ] };series.data 的第四个维度(即 value[3])被映射,series.data 的第四个维度(即 value[3])被映射。事件和行为myChart.on('click', function (params) { console.log(params.name); });在ECharts中,事件可以分为两种类型:一种是用户用鼠标单击时触发的事件,或者是悬停图表的图形;另一种是用户使用可以交互的组件后触发的行为事件,例如切换图例开关时触发的“legendseletchanged”事件(请注意,切换图例开关不会触发“legendselect tchchanged”事件),缩放数据区域时触发的”datazoom“事件。myChart.on('click', function (params) { if (params.componentType === 'markPoint') { if (params.seriesIndex === 5) { } } else if (params.componentType === 'series') { if (params.seriesType === 'graph') { if (params.dataType === 'edge') { } else { } } } });点击到了 markPoint 上,点击到了 index 为 5 的 series 的 markPoint 上。点击到了 graph 的 edge(边)上。点击到了 graph 的 node(节点)上。在ECharts中,几乎所有的组件交互都会触发相应的事件。事件文档中列出了常见事件和事件对应参数。
transform 进行数据转换数据转换是这样一个公式:outData=f(inputData)。F是转换方法,例如filter、sort、region、boxplot、cluster、aggregate(todo)等。有了数据转换功能,我们至少可以做到以下几点:将数据分成多个部分,并在不同的饼图中显示它们。执行一些数据统计操作并显示结果。使用一些数据可视化算法来处理数据并显示结果。数据排序。删除或直接选择数据项。 series: [{ type: 'pie', radius: 50, center: ['25%', '50%'], // 这个饼图系列,引用了 index 为 `1` 的 dataset 。也就是,引用了上述 // 2011 年那个 "filter" transform 的结果。 datasetIndex: 1 }, { type: 'pie', radius: 50, center: ['50%', '50%'], datasetIndex: 2 }, { type: 'pie', radius: 50, center: ['75%', '50%'], datasetIndex: 3 }] };在大多数情况下,转换只需要输出一个数据。然而,也有一些场景需要输出多个数据,每个数据可以由不同的系列或数据集使用。例如,在内置的“boxplot”转换中,除了boxplot系列所需的数据外,还将生成异常值,并可以使用散点图系列进行显示。例如我们提供配置数据集FromTransformResult,例如:option: { dataset: [{ source: [ ... ] // 原始数据 }, { // 几个 transform 被声明成 array ,他们构成了一个链, // 前一个 transform 的输出是后一个 transform 的输入。 transform: [{ type: 'filter', config: { dimension: 'Product', value: 'Tofu' } }, { type: 'sort', config: { dimension: 'Year', order: 'desc' } }] }], series: { type: 'pie', // 这个系列引用上述 transform 的结果。 datasetIndex: 1 } }当使用转换时,有时我们将无法显示结果,而且我们不知道哪里出了问题。因此,这里提供了一个配置项转换。打印方便调试。此配置项仅在开发环境中生效。option = { dataset: [{ source: [ ... ] }, { transform: { type: 'filter', config: { ... } print: true } }], ... }配置为 true 后, transform 的结果,会被 console.log 打印出来。数据转换器“排序”还具有一些附加功能:可以将多个维度一起排序。请参见下面的示例。整理如下:默认值是按数值排序。其中,“可转换为数值的字符串”也被转换为数值,并与其他数值一起按大小排序。其他“无法转换为数值的字符串”也可以按字符串排序。此功能在这种情况下很有用:具有相同标记的数据项被分组在一起,特别是当多个维度被排序在一起时。option = { dataset: [{ dimensions: ['name', 'age', 'profession', 'score', 'date'], source: [ [' Hannah Krause ', 41, 'Engineer', 314, '2011-02-12'], ['Zhao Qian ', 20, 'Teacher', 351, '2011-03-01'], [' Jasmin Krause ', 52, 'Musician', 287, '2011-02-14'], ['Li Lei', 37, 'Teacher', 219, '2011-02-18'], [' Karle Neumann ', 25, 'Engineer', 253, '2011-04-02'], [' Adrian Groß', 19, 'Teacher', null, '2011-01-16'], ['Mia Neumann', 71, 'Engineer', 165, '2011-03-19'], [' Böhm Fuchs', 36, 'Musician', 318, '2011-02-24'], ['Han Meimei ', 67, 'Engineer', 366, '2011-03-12'], ] }, { transform: { type: 'sort', config: [ // 对两个维度按声明的优先级分别排序。 { dimension: 'profession', order: 'desc' }, { dimension: 'score', order: 'desc' } ] } }], series: { type: 'bar', datasetIndex: 1 }, ... };当对“数值和可以转换为数值的字符串”和“不能转换为数值”进行排序时,或者当它们与“其他类型的值”进行比较时,它们不知道如何比较自己。然后我们将“后者”称为“不可比”,并可以设置不可比:'min'|'max',以指定“不可比较”在该比较中是最大还是最小,以便它们可以产生比较结果。例如,此设置的目的是确定空值(如null、undefined、NaN、“”、“-”)是否位于排序的开始或结束。type SortTransform = { type: 'filter'; config: OrderExpression | OrderExpression[]; }; type OrderExpression = { dimension: DimensionName | DimensionIndex; order: 'asc' | 'desc'; incomparable?: 'min' | 'max'; parser?: 'time' | 'trim' | 'number'; }; type DimensionName = string; type DimensionIndex = number;filter:可以使用'time'|'trim'|'number',就像在数据转换器“filter”中一样。如果要对时间进行排序(例如,值是JSDate实例或时间字符串,如“2012-03-12 11:13:54”),我们需要声明解析器:“time”。如果我们需要对后缀值进行排序(例如“33%”、“16px”),我们需要声明parser:'number'。dataZoomdataZoom组件用于在轴上执行“数据窗口缩放”和“数据窗口平移”操作。可以使用dataZoom xAxisIndex或dataZoom YAxisIndex指定dataZoom控件的一个或多个数字轴。同时可以有多个dataZoom组件,这些组件起到共同控制的作用。控制相同编号轴的组件将自动链接。下面的示例将详细解释。dataZoom的工作原理是通过“数据过滤”来达到“数据窗口缩放”的效果。数据过滤模式的不同设置具有不同的效果。dataZoom。过滤器模式。目前,dataZoom支持两种类型的数据窗口范围设置:百分比形式:dataZoomStart和dataZoom.end。绝对数字形式:dataZoomStartValue和dataZoom.endValue。option = { xAxis: { type: 'value' }, yAxis: { type: 'value' }, dataZoom: [ { // 这个dataZoom组件,默认控制x轴。 type: 'slider', // 这个 dataZoom 组件是 slider 型 dataZoom 组件 start: 10, // 左边在 10% 的位置。 end: 60 // 右边在 60% 的位置。 } ], series: [ { type: 'scatter', // 这是个『散点图』 itemStyle: { opacity: 0.8 }, symbolSize: function (val) { return val[2] * 40; }, data: [["14.616","7.241","0.896"],["3.958","5.701","0.955"],["2.768","8.971","0.669"],["9.051","9.710","0.171"],["14.046","4.182","0.536"],["12.295","1.429","0.962"],["4.417","8.167","0.113"],["0.492","4.771","0.785"],["7.632","2.605","0.645"],["14.242","5.042","0.368"]] } ] }除了图表之外,ApacheEChartsTM还提供了许多交互式组件。例如:图例组件图例、标题组件、可视化映射组件visualMap、数据区域缩放组件dataZoom、时间轴组件。除了图表之外,ApacheEChartsTM还提供了许多交互式组件。例如:图例组件图例、标题组件、可视化映射组件visualMap、数据区域缩放组件dataZoom、时间轴组件。
dataset 管理数据提供一份数据。 声明一个 X 轴,类目轴(category)。默认情况下,类目轴对应到声明多个 bar 系列,默认情况下,每个系列会自动对应到 dataset 的每一列。option = { legend: {}, tooltip: {}, dataset: { // source: [ ['product', '2015', '2016', '2017'], ['Matcha Latte', 43.3, 85.8, 93.7], ['Milk Tea', 83.1, 73.4, 55.1], ['Cheese Cocoa', 86.4, 65.2, 82.5], ['Walnut Brownie', 72.4, 53.9, 39.1] ] }, //dataset 第一列。 xAxis: {type: 'category'}, // 声明一个 Y 轴,数值轴。 yAxis: {}, // series: [ {type: 'bar'}, {type: 'bar'}, {type: 'bar'} ] }Apache EChartsTM 4开始支持数据集组件进行单独的数据集声明,以便数据可以单独管理,由多个组件重用,并且可以基于数据指定数据到可视化的映射。这可以在许多场景中带来方便。在ECharts 4之前,数据只能以“series”形式声明,option = { xAxis: { type: 'category', data: ['Matcha Latte', 'Milk Tea', 'Cheese Cocoa', 'Walnut Brownie'] }, yAxis: {}, series: [ { type: 'bar', name: '2015', data: [89.3, 92.1, 94.4, 85.4] }, { type: 'bar', name: '2016', data: [95.8, 89.4, 91.2, 76.9] }, { type: 'bar', name: '2017', data: [97.7, 83.1, 92.5, 78.1] } ] }该方法的优点是直观、易于理解,适用于某些特殊图表类型的特定数据类型定制。然而,缺点是为了匹配这种数据输入形式,通常需要有一个数据处理过程,并将数据分割设置为每个系列(和类别轴)。此外,这不利于多个系列共享一个数据,也不利于基于原始数据的图表类型和系列的映射排列。因此,ECharts 4提供了一个单独声明数据的数据集组件,这带来了以下效果:它可以接近数据可视化的常见思维模式:(I)提供数据,(II)指定数据到视觉的映射,从而形成图表。数据和其他配置可以分开。数据始终在变化,其他配置始终不变。分离易于单独管理。数据可以被多个系列或组件重用。对于具有大量数据的场景,不必为每个系列创建一个数据。支持更常见的数据格式,如二维数组、对象数组等,在一定程度上避免用户转换为数据格式。 用 dimensions 指定了维度的顺序。直角坐标系中, 默认把第一个维度映射到 X 轴上,第二个维度映射到 Y 轴上。如果不指定 dimensions,也可以通过指定 series.encode,完成映射。option = { legend: {}, tooltip: {}, dataset: { dimensions: ['product', '2015', '2016', '2017'], source: [ {product: 'Matcha Latte', '2015': 43.3, '2016': 85.8, '2017': 93.7}, {product: 'Milk Tea', '2015': 83.1, '2016': 73.4, '2017': 55.1}, {product: 'Cheese Cocoa', '2015': 86.4, '2016': 65.2, '2017': 82.5}, {product: 'Walnut Brownie', '2015': 72.4, '2016': 53.9, '2017': 39.1} ] }, xAxis: {type: 'category'}, yAxis: {}, series: [ {type: 'bar'}, {type: 'bar'}, {type: 'bar'} ] };一目了然,可以进行以下映射:指定数据集的列或行是否映射到一系列图形。您可以使用SeriesLayoutBy属性。默认值是按列映射。series: { // 注意维度序号(dimensionIndex)从 0 开始计数,第三列是 dimensions[2]。 encode: {x: 2, y: 4}, ... }指定维度映射规则:如何将数据集维度(“维度”表示行/列)映射到坐标轴(如X和Y轴)、工具提示、标签、图形元素大小和颜色(visualMap)。您可以使用series配置encode属性和visualMap组件(如果需要映射颜色大小等视觉维度)。在上面的示例中,ECharts没有提供这种映射配置,因此ECharts将根据最常见的理解执行默认映射:X坐标轴声明为类别轴,默认情况下将自动对应于数据集源中的第一列;三列图表系列,逐一对应数据集源中的每个后续列。series: { encode: {x: 2, y: 4}, seriesLayoutBy: 'row', ... }在系列中设置的 dimensions 会更优先采纳。可以在 type 中指定维度类型。可以简写为 string,表示维度名。var option1 = { dataset: { dimensions: [ {name: 'score'}, // 'amount', // {name: 'product', type: 'ordinal'} ], source: [...] }, ... }; var option2 = { dataset: { source: [...] }, series: { type: 'line', // dimensions: [ null, // 可以设置为 null 表示不想设置维度名 'amount', {name: 'product', type: 'ordinal'} ] }, ... };在大多数常见的图表中,数据可以以二维表格的形式描述。广泛使用的数据表软件(如MS Excel、Numbers)或关系数据数据库是二维表。它们的数据可以导出为JSON格式并输入到数据集。在源代码中,在许多情况下可以省略一些数据处理步骤。如果数据导出到csv文件,则可以使用一些csv工具(如dsv或PapaParse)将csv转换为JSON。在JavaScript的常见数据传输格式中,二维数组可以直观地存储二维表。前面的示例都由二维数组表示。除了二维数组,数据集还支持以下键值数据格式,这些格式也非常常见。但是,seriesLayoutBy参数在此类格式中不受支持。按行的 key-value 形式(对象数组),这是个比较常见的格式。 按列的 key-value 形式。dataset: [{ // source: [ {product: 'Matcha Latte', count: 823, score: 95.8}, {product: 'Milk Tea', count: 235, score: 81.4}, {product: 'Cheese Cocoa', count: 1042, score: 91.2}, {product: 'Walnut Brownie', count: 988, score: 76.9} ] }, { // source: { 'product': ['Matcha Latte', 'Milk Tea', 'Cheese Cocoa', 'Walnut Brownie'], 'count': [823, 235, 1042, 988], 'score': [95.8, 81.4, 91.2, 76.9] } }]目前,并非所有图表都支持数据集。支持数据集的图表包括直线、条形图、饼图、扫描仪、效应扫描仪、平行线、烛台、地图、基金和自定义。未来将有更多图表可供支持。最后,给出一个例子。多个图表共享具有链接交互的数据集。
代码分割// app.js import { add } from './math.js'; console.log(add(16, 26)); // 42大多数React应用程序将使用Webpack、Rollup或Browserify等构建工具来打包文件。打包是将文件引入并合并到单个文件中,最后形成一个“包”的过程。然后在页面上引入捆绑包,整个应用程序可以一次加载function add(a, b) { return a + b; } console.log(add(16, 26)); // 42小心:最后,包文件将与上面的示例非常不同。如果使用的是Create React App、Next Js、Gatsby或类似工具,将拥有一个可直接用于打包的Webpack配置。如果不使用这些工具,则需要自己配置它们。例如,查看Webpack文档中的安装和入门教程。分割打包是一项伟大的技术,但随着应用程序的增长,代码包也会随之增长。尤其是在集成大型第三方库的情况下。需要注意代码包中包含的代码,以避免由于容量过大而导致加载时间过长。为了避免创建大型代码包,在早期阶段考虑这个问题并划分代码包是一个不错的选择。代码分区是Webpack、Rollup和Browserify(factor bundle)等打包程序支持的技术,它可以创建多个包并在运行时动态加载它们。应用程序的代码分段可以帮助“懒惰地加载”当前用户所需的内容,并可以显著提高应用程序的性能。虽然它不会减少应用程序的总代码量,但可以避免加载用户永远不需要的代码,并减少在初始加载时需要加载的代码量。import { add } from './math'; console.log(add(16, 26));import("./math").then(math => { console.log(math.add(16, 26)); });当Webpack解析此语法时,它将自动拆分代码。如果使用CreateReact应用程序,则该功能已被开箱即用,可以立即使用该功能。下一个Js在没有配置的情况下也支持此功能。如果自己配置Webpack,可能需要阅读Webpack的代码拆分指南。的Webpack配置应该与此类似。当使用Babel时,应该确保Babel能够解析动态导入语法,而不是转换它。对于这个需求,需要babel插件语法导入插件。import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }当组件首次呈现时,此代码将自动导入包含OtherComponent组件的包。反应Lazy接受需要动态调用import()的函数。它必须返回一个promise,这需要解析dealout导出的React组件。然后,应该在Suspense组件中呈现懒惰组件,以便我们可以在等待加载懒惰组件时使用优雅的降级(如加载指示符)。import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }回退属性接受在加载组件期间要显示的任何React元素。可以将Suspend组件放置在延迟加载组件上方的任何位置。甚至可以用一个Suspend组件包装多个延迟加载组件。import React, { Suspense } from 'react'; import MyErrorBoundary from './MyErrorBoundary'; const OtherComponent = React.lazy(() => import('./OtherComponent')); const AnotherComponent = React.lazy(() => import('./AnotherComponent')); const MyComponent = () => ( <div> <MyErrorBoundary> <Suspense fallback={<div>Loading...</div>}> <section> <OtherComponent /> <AnotherComponent /> </section> </Suspense> </MyErrorBoundary> </div> );基于路由的代码分割决定在哪里引入代码分段需要一些技巧。需要确保所选位置可以均匀地拆分代码包,而不会影响用户体验。一个好的选择是从路由开始。大多数web用户习惯于在页面之间进行加载和切换过程。还可以选择重新渲染整个页面,这样用户在渲染时就不必与页面上的其他元素交互。以下是如何在应用程序中使用React的示例。第三方库(如lazy和React Router)用于配置基于路由的代码拆分。import React, { Suspense, lazy } from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; const Home = lazy(() => import('./routes/Home')); const About = lazy(() => import('./routes/About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> </Switch> </Suspense> </Router> );反应Lazy当前仅支持默认导出。如果要导入的模块使用命名导出,则可以创建一个中间模块作为默认模块重新导出。这确保了树抖动不会出错,并且不会引入不必要的组件。反应Lazy和Suspend技术尚不支持服务器端渲染。如果想在使用服务器端渲染的应用程序中使用它,我们建议使用可加载组件。它有一个很棒的服务器端渲染和打包指南。
@[toc]创建对于每个测试,我们通常都希望将React树呈现给附加到文档的DOM元素。这很重要,因为它可以接收DOM事件。测试完成后,我们需要“清理”并从文档中卸载树。import { unmountComponentAtNode } from "react-dom"; let container = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement("div"); document.body.appendChild(container); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); container.remove(); container = null; });一种常见的方法是使用一对beforeEach和afterEach块来保持它们运行并隔离测试本身的影响。act(() => { // 渲染组件 }); // 进行断言这有助于在使用应用程序时使测试运行更接近真实用户体验。这些示例的其余部分使用act()进行这些保证。可能会发现直接使用act()有点过于冗长。为了避免一些样板代码,可以使用React测试库。这些助手使用act()函数封装。 import React from "react"; export default function Hello(props) { if (props.name) { return <h1>你好,{props.name}!</h1>; } else { return <span>嘿,陌生人</span>; } }数据获取可以使用假数据模拟请求,而不是在所有测试中调用真实的API。使用“假”数据模拟数据采集可以防止由于后端不可用而导致的不稳定测试,并使其运行更快。注意:可能仍然希望使用“端到端”框架运行测试子集,以显示整个应用程序是否协同工作。 import React, { useState, useEffect } from "react"; export default function User(props) { const [user, setUser] = useState(null); async function fetchUserData(id) { const response = await fetch("/" + id); setUser(await response.json()); } useEffect(() => { fetchUserData(props.id); }, [props.id]); if (!user) { return "加载中..."; } return ( <details> <summary>{user.name}</summary> <strong>{user.age}</strong> 岁 <br /> 住在 {user.address} </details> ); }import React from "react"; import { render, unmountComponentAtNode } from "react-dom"; import { act } from "react-dom/test-utils"; import User from "./user"; let container = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement("div"); document.body.appendChild(container); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); container.remove(); container = null; }); it("渲染用户数据", async () => { const fakeUser = { name: "Joni Baez", age: "32", address: "123, Charming Avenue" }; jest.spyOn(global, "fetch").mockImplementation(() => Promise.resolve({ json: () => Promise.resolve(fakeUser) }) ); // 使用异步的 act 应用执行成功的 promise await act(async () => { render(<User id="123" />, container); }); expect(container.querySelector("summary").textContent).toBe(fakeUser.name); expect(container.querySelector("strong").textContent).toBe(fakeUser.age); expect(container.textContent).toContain(fakeUser.address); // 清理 mock 以确保测试完全隔离 global.fetch.mockRestore(); });某些模块在测试环境中可能无法正常工作,或者对测试本身不重要。用虚拟数据模拟这些模块,更容易为代码编写测试。多渲染器在极少数情况下,可能在使用多个渲染器的组件上运行测试。例如, 可能正在内部使用ReactDOM的react测试渲染器组件上运行快照测试。在子组件中渲染以渲染某些内容。在此场景中,可以使用与其渲染器对应的动作()来包装更新。import { act as domAct } from "react-dom/test-utils"; import { act as testAct, create } from "react-test-renderer"; // ... let root; domAct(() => { testAct(() => { root = create(<App />); }); }); expect(root).toMatchSnapshot();计时器代码可能使用基于计时器的函数(如setTimeout)来安排将来的更多工作。在此示例中,多选面板等待选择并向前移动。如果在5秒内没有选择,则超时:import React, { useEffect } from "react"; export default function Card(props) { useEffect(() => { const timeoutID = setTimeout(() => { props.onSelect(null); }, 5000); return () => { clearTimeout(timeoutID); }; }, [props.onSelect]); return [1, 2, 3, 4].map(choice => ( <button key={choice} data-testid={choice} onClick={() => props.onSelect(choice)} > {choice} </button> )); }我们可以使用Jest的计时器模拟为这个组件编写测试,并测试它可能处于的不同状态。import React from "react"; import { render, unmountComponentAtNode } from "react-dom"; import { act } from "react-dom/test-utils"; import Card from "./card"; jest.useFakeTimers(); let container = null; beforeEach(() => { // 创建一个 DOM 元素作为渲染目标 container = document.createElement("div"); document.body.appendChild(container); }); afterEach(() => { // 退出时进行清理 unmountComponentAtNode(container); container.remove(); container = null; }); it("超时后应选择 null", () => { const onSelect = jest.fn(); act(() => { render(<Card onSelect={onSelect} />, container); }); // 提前 100 毫秒执行 act(() => { jest.advanceTimersByTime(100); }); expect(onSelect).not.toHaveBeenCalled(); // 然后提前 5 秒执行 act(() => { jest.advanceTimersByTime(5000); }); expect(onSelect).toHaveBeenCalledWith(null); }); it("移除时应进行清理", () => { const onSelect = jest.fn(); act(() => { render(<Card onSelect={onSelect} />, container); }); act(() => { jest.advanceTimersByTime(100); }); expect(onSelect).not.toHaveBeenCalled(); // 卸载应用程序 act(() => { render(null, container); }); act(() => { jest.advanceTimersByTime(5000); }); expect(onSelect).not.toHaveBeenCalled(); }); it("应接受选择", () => { const onSelect = jest.fn(); act(() => { render(<Card onSelect={onSelect} />, container); }); act(() => { container .querySelector("[data-testid='2']") .dispatchEvent(new MouseEvent("click", { bubbles: true })); }); expect(onSelect).toHaveBeenCalledWith(2); });只能在某些测试中使用假计时器。上面,我们调用jestUseFakeTimers()来启用它们。它们提供的主要优点是,的测试实际上不需要等待5秒才能执行,并且不需要使组件代码更复杂地进行测试。
@[toc]JXcore 打包node.Js是面向服务器端和网络应用程序的开源跨平台运行环境。JXcore是一个支持多线程的节点。对于js发行版,您可以在多个线程中安全运行,而无需对现有代码进行任何更改。安装命令如下: $ curl https://raw.githubusercontent.com/jxcore/jxcore/master/tools/jx_install.sh | bash包含以下文件:drwxr-xr-x 2 root root 4096 Nov 13 12:42 images -rwxr-xr-x 1 root root 30457 Mar 6 12:19 index.htm -rwxr-xr-x 1 root root 30452 Mar 1 12:54 index.js drwxr-xr-x 23 root root 4096 Jan 15 03:48 node_modules drwxr-xr-x 2 root root 4096 Mar 21 06:10 scripts drwxr-xr-x 2 root root 4096 Feb 15 11:56 style载入:使用JXcore编译后,我们可以使用以下命令执行生成的jx二进制文件:$ node index.js command_line_arguments模块系统node.js文件相互调用,Node.js提供了一个简单的模块系统。模块是一个节点。js应用程序的基本组件、文件和模块是一对一的。换句话说,节点js文件是一个模块,可以是JavaScript代码、JSON或编译的C/C++扩展。exports.world = function() { console.log('Hello World'); }helloJs通过exports对象将世界作为模块的访问接口Js通过require('./hello')加载模块,然后可以直接访问Js中exports对象的成员函数hello。有时我们只想将一个对象封装成以下格式的模块://hello.js function Hello() { var name; this.setName = function(thyName) { name = thyName; }; this.sayHello = function() { console.log('Hello ' + name); }; }; module.exports = Hello;这样就可以直接获得这个对象了://main.js var Hello = require('./hello'); hello = new Hello(); hello.setName('BYVoid'); hello.sayHello(); 模块接口中唯一的变化是使用模块Exports=Hello替换exportsworld=function(){}当外部引用此模块时,其接口对象是要输出的Hello对象本身,而不是原始导出。var http = require("http"); ... http.createServer(...);node.Js附带一个名为http的模块。我们在代码中请求它,并将返回值分配给一个局部变量。这将我们的局部变量转换为一个对象,该对象包含http模块提供的所有公共方法。node.Js的require方法中的文件搜索策略如下:因为节点js中有四种类型的模块(原生模块和三个文件模块)。尽管require方法非常简单,但内部加载非常复杂,并且它们的加载优先级不同。LOAD_AS_DIRECTORY(X) 1. 如果 X/package.json 是一个文件, a. 解析 X/package.json, 并查找 "main" 字段。 b. let M = X + (json main 字段) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X) LOAD_NODE_MODULES(X, START) 1. let DIRS=NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. DIRS = DIRS + DIR d. let I = I - 1 5. return DIRSnet在UNIX上,本地域也称为UNIX域。参数路径是文件系统路径名。它从sizeof(sockaddr_un.sun_path)-1被截断,其长度从91到107字节不等,具体取决于操作系统。Linux上的典型值为107,macOS上为103。路径受与创建的文件相同的命名约定和权限检查的约束。它将在文件系统中可见,并将持续到取消链接时。在Windows上,本地域通过命名管道实现。路径必须是?\Pipe或\Pipe是入口。路径允许任何字符,但以下字符可能会对管道名称进行某些处理,例如解析..Sequence。但是,管道空间是平坦的。管道不会持续,并且在关闭最后一个参照时将被删除。不要忘记在转义JavaScript字符串时使用双反斜杠来指定路径,net.createServer().listen( path.join('\\\\?\\pipe', process.cwd(), 'myctl'));server.address()如果在IP套接字上侦听,将返回操作系统报告的绑定IP地址、地址系列和服务端口。在查找操作系统分配的地址时,查找指定的端口非常有用。返回具有端口、系列和地址属性的对象:{port:12346,系列:“IPv4”,地址:“127.0.0.1”}对于侦听管道或UNIX域套接字的服务器,名称将作为字符串返回const server = net.createServer((socket) => { socket.end('goodbye\n'); }).on('error', (err) => { // handle errors here throw err; }); // grab an arbitrary unused port. server.listen(() => { console.log('opened server on', server.address()); });停止服务器以接受和创建新连接并保留现有连接此功能是异步的。当所有连接都关闭并且服务器响应['close'][]事件时,服务器将最终关闭。一旦发生'close',将调用可选的回调函数。与此事件不同,如果服务器在关闭时未打开,则将使用错误作为唯一参数。server.on('error', (e) => { if (e.code === 'EADDRINUSE') { console.log('Address in use, retrying...'); setTimeout(() => { server.close(); server.listen(PORT, HOST); }, 1000); } });所有listen()方法都可以传入backlog参数,以指定要连接的队列的最大长度。
@[toc]多进程Js以单线程模式运行,但它使用事件驱动来处理并发,这有助于我们在多核cpu系统上创建多个子进程,从而提高性能。const child_process = require('child_process'); for(var i=0; i<3; i++) { var workerProcess = child_process.spawn('node', ['support.js', i]); workerProcess.stdout.on('data', function (data) { console.log('stdout: ' + data); }); workerProcess.stderr.on('data', function (data) { console.log('stderr: ' + data); }); workerProcess.on('close', function (code) { console.log('子进程已退出,退出码 '+code); }); }每个子进程始终有三个流对象:子stdin、子。标准输出和子标准错误它们可以共享父进程的stdio流,也可以是独立的分流流对象。节点提供子进程_进程模块用于创建子进程。方法如下:child_process.exec(command[, options], callback)exec-child_进程。Exec使用子进程执行命令,缓存子进程的输出,并以回调函数参数的形式返回子进程的结果。派生子进程。Spawn使用指定的命令行参数创建新进程。fork-child进程。Fork是一种特殊形式的派生(),用于在子进程中运行的模块。例如,fork('./son.js')相当于派生('node',['./sonjs'])。与派生方法不同,fork将在父进程和子进程之间建立通信管道,用于进程之间的通信。const fs = require('fs'); const child_process = require('child_process'); for(var i=0; i<3; i++) { var workerProcess = child_process.exec('node support.js '+i, function (error, stdout, stderr) { if (error) { console.log(error.stack); console.log('Error code: '+error.code); console.log('Signal received: '+error.signal); } console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); }); workerProcess.on('exit', function (code) { console.log('子进程已退出,退出码 '+code); }); }常用工具util.callbackify(original)将异步异步函数(或返回值为Promise的函数)转换为遵循异常优先回调样式的函数,例如,将(err,value)=>…callback作为最后一个参数。在回调函数中,第一个参数是拒绝的原因(如果Promise被解析为null),第二个参数是要解析的值。const util = require('util'); async function fn() { return 'hello world'; } const callbackFunction = util.callbackify(fn); callbackFunction((err, ret) => { if (err) throw err; console.log(ret); });回调函数异步执行,并具有异常堆栈错误跟踪。如果回调函数引发异常,则进程将触发“uncaughtException”异常。如果未捕获,则进程将退出。Null作为回调函数中的参数具有特殊意义。如果回调函数的第一个参数是promise拒绝的原因,并且具有返回值,并且该值可以转换为布尔值false,则该值将封装在Error对象中,并且可以通过属性reason获得。function fn() { return Promise.reject(null); } const callbackFunction = util.callbackify(fn); callbackFunction((err, ret) => { err && err.hasOwnProperty('reason') && err.reason === null; // true });当 Promise 被以 null 拒绝时,它被包装为 Error 并且原始值存储在 reason 中。我们定义了一个基本对象base和从base继承的子对象。Base有三个在构造函数中定义的属性,原型中定义的一个函数inherits实现继承。操作结果如下:base Hello base { name: 'base', base: 1991, sayHello: [Function] } sub { name: 'sub' }Sub只继承Base在原型中定义的函数,而在构造函数中创建的Base属性和sayHello函数不被Sub继承。同时,原型中定义的属性将不会被合并。日志将作为对象的属性输出。如果我们删除objSub sayHello();该行中的注释将显示:var util = require('util'); function Person() { this.name = 'byvoid'; this.toString = function() { return this.name; }; } var obj = new Person(); console.log(util.inspect(obj)); console.log(util.inspect(obj, true)); 'keylog' 事件当此代理管理的连接生成或接收密钥材料时(通常在握手完成之前,但不一定),将触发密钥日志事件。此密钥材料可以存储用于调试,因为它允许解密捕获的TLS流量。它可以为每个套接字触发多次。如果我们编写一段js代码并在浏览器中运行,我们就是在进行前端开发。若我们编写一段js代码并在node中运行它,我们就是在进行后端开发。作为JavaScript运行环境,node。js提供了基本的函数和API:(许多框架都是从node.js派生的)Express框架(快速构建web应用程序)Electron框架(快速构建跨平台桌面应用程序)Restify框架(快速构建API接口项目)读写数据库,创建实用的命令行工具以帮助前端开发TLS/SSLtls模块使用OpenSSL提供传输层安全性(tls)和安全套接字层(SSL):加密流通信。TLS/SSL是一种公钥/私钥基础设施。每个客户端和服务器都需要一个私钥。创建私钥的方法如下:openssl genrsa -out ryans-key.pem 2048openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem \ -certfile ca-cert.pem -out agent5.pfxTLS协议允许客户端协商TLS会话的某些方法内容。然而,会话协商需要服务器响应的资源。这使其成为拒绝服务攻击的潜在媒介。var ciphers = tls.getCiphers(); console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...]“正向保密”或“完全正向保密-完全正向保密”协议描述了密钥协商(如密钥交换)方法的特点。事实上,这意味着即使服务器的密钥是危险的,通信也只能被一类人窃听,他们必须尝试获取为每个会话生成的密钥对。通过在每次握手期间随机生成用于密钥协商的密钥对(而不是针对所有会话使用一个密钥)来实现完全前向保密。实现这项技术(提供完全前向保密)的方法被称为“短暂”。var tls = require('tls'); var fs = require('fs'); var options = { key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem'), // This is necessary only if using the client certificate authentication. requestCert: true, // This is necessary only if the client uses the self-signed certificate. ca: [ fs.readFileSync('client-cert.pem') ] }; var server = tls.createServer(options, function(socket) { console.log('server connected', socket.authorized ? 'authorized' : 'unauthorized'); socket.write("welcome!\n"); socket.setEncoding('utf8'); socket.pipe(socket); }); server.listen(8000, function() { console.log('server bound'); });
child_processchild_进程模块提供派生子进程的功能。它与popen(3)相似但不相同。此函数主要由[child_process.spown()]函数提供:const { spawn } = require('child_process'); const ls = spawn('ls', ['-lh', '/usr']); ls.stdout.on('data', (data) => { console.log(`输出:${data}`); }); ls.stderr.on('data', (data) => { console.log(`错误:${data}`); }); ls.on('close', (code) => { console.log(`子进程退出码:${code}`); });const { exec } = require('child_process'); exec('my.bat', (err, stdout, stderr) => { if (err) { console.error(err); return; } console.log(stdout); }); // 文件名带有空格的脚本: const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true }); // 或: exec('"my script.cmd" a b', (err, stdout, stderr) => { // ... });child_进程模块还提供一些其他同步和异步可选功能。每个函数都是基于[child_process.spawn()]或[child_process.spawnSync()]实现的。[child_process.exec()]:派生一个shell并在该shell上运行命令。完成后,stdout和stderr将传递给回调函数。const { spawn } = require('child_process'); const grep = spawn('grep', ['ssh']); grep.on('close', (code, signal) => { console.log(`子进程收到信号 ${signal} 而终止`); }); // 发送 SIGHUP 到进程 grep.kill('SIGHUP');[child_process.execFile()]:类似于[child_procedure.exec()],但命令是直接派生的,而不首先派生shell。[child_process.f分叉()]:派生一个新的NodeJs进程,并通过建立IPC通信通道调用指定的模块,这允许父进程和子进程相互发送信息。[child_process.execSync()]:[child_procedure.exec()]的同步函数阻止节点J的事件循环。[child_process.execFileSync()]:[child_cess.execFile()]的同步函数阻止节点J的事件循环。options.detached在Windows上,设置选项如果分离为true,则子进程可以在父进程退出后继续运行。子进程有自己的控制台窗口。一旦启用了子进程,就不能禁用它。在非Windows平台上,如果选项if detached设置为true,则子进程将成为新进程组和会话的领导者。请注意,子进程可以在父进程退出后继续运行,无论它们是否分离。详见setsid(2)。const fs = require('fs'); const { spawn } = require('child_process'); const out = fs.openSync('./out.log', 'a'); const err = fs.openSync('./out.log', 'a'); const subprocess = spawn('prg', [], { detached: true, stdio: [ 'ignore', out, err ] }); subprocess.unref();默认情况下,父进程将等待分离的子进程退出。为了防止父进程等待给定的子进程,可以使用子进程Unref()方法。这将导致父进程的事件循环排除子进程的引用计数,使父进程独立于子进程退出,除非在子进程和父进程之间建立了IPC通道。使用分离选项启动长时间运行的进程时,除非提供了未连接到父进程的stdio配置,否则父进程退出后,该进程将不会在后台继续运行。如果继承了父进程的stdio,则子进程将保持与控制终端的连接。例如,对于长时间运行的进程,为了忽略父进程的终止,父进程的stdio文件描述符被分离并忽略:const { spawn } = require('child_process'); const subprocess = spawn(process.argv[0], ['child_program.js'], { detached: true, stdio: 'ignore' }); subprocess.unref();如果未传递信号,[ChildProcess]对象可能会触发['error']事件。向已退出的子进程发送信号不是错误,但可能会产生不可预测的后果。特别是,如果一个进程的PID被重新分配给另一个进程,则该信号将被发送给该进程,这可能会导致意外的结果。注意,当函数被称为kill时,发送给子进程的信号实际上可能不会终止该进程。'use strict'; const { spawn } = require('child_process'); const subprocess = spawn( 'sh', [ '-c', `node -e "setInterval(() => { console.log(process.pid, 'is alive') }, 500);"` ], { stdio: ['inherit', 'inherit', 'inherit'] } ); setTimeout(() => { subprocess.kill(); // 不会终止 shell 中的 node 进程 }, 2000);一旦套接字被传递给子进程,父进程就无法再跟踪套接字何时被销毁。若要显示此属性,连接属性将变为空。发生这种情况时,不建议使用。最大连接数。建议子进程中的任何消息处理程序都应验证套接字是否存在,因为连接在发送到子进程期间可能会关闭。cipher.update用数据更新密码。如果给定inputEncoding参数,其值必须为“utf8”、“ascii”或“latin1”,并且数据参数是使用指定编码的字符串。如果未给定inputEncoding参数,则数据必须为Buffer、TypedArray或DataView。如果数据是Buffer、TypedArray或DataView,则忽略inputEncoding。OutputEncoding指定加密数据的输出格式,可以是“latin1”、“base64”或“hex”。如果指定了outputEncoding,则返回具有指定编码的字符串。如果未提供outputEncoding,将返回Buffer。密码。update()方法可以用新数据多次调用,直到调用密码Final()。['cipher.final()'][]调用密码。update()。final()将抛出错误。const crypto = require('crypto'); const decipher = crypto.createDecipher('aes192', 'a password'); let decrypted = ''; decipher.on('readable', () => { const data = decipher.read(); if (data) decrypted += data.toString('utf8'); }); decipher.on('end', () => { console.log(decrypted); // Prints: some clear text data }); const encrypted = 'ca981be48e90867604588e75d04feabb63cc007a8f8ad89b10616ed84d815504'; decipher.write(encrypted, 'hex'); decipher.end();const crypto = require('crypto'); const fs = require('fs'); const decipher = crypto.createDecipher('aes192', 'a password'); const input = fs.createReadStream('test.enc'); const output = fs.createWriteStream('test.js'); input.pipe(decipher).pipe(output);返回所有剩余的解密内容。如果outputEncoding参数是“latin1”、“ascii”或“utf8”之一,则返回字符串。如果未提供输出编码,则返回Buffer。一旦解密了final()方法,decipher对象就不能再用于解密数据。正在尝试调用解密。final()多次导致抛出错误。const crypto = require('crypto'); const assert = require('assert'); // Generate Alice's keys... const alice = crypto.createDiffieHellman(2048); const aliceKey = alice.generateKeys(); // Generate Bob's keys... const bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator()); const bobKey = bob.generateKeys(); // Exchange and generate the secret... const aliceSecret = alice.computeSecret(bobKey); const bobSecret = bob.computeSecret(aliceKey); // OK assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));错误的冒泡和捕获节点。Js支持几种机制来冒泡和处理应用程序运行时发生的错误。如何报告和处理这些错误完全取决于错误的类型和调用的API的样式。所有JavaScript错误都将被视为异常。将立即生成异常,并使用标准JavaScript抛出机制抛出错误。这些都是使用JavaScript语言提供的try/catch语句处理的。try { const m = 1; const n = m + z; } catch (err) { // 在这里处理错误。 }任何使用JavaScript的抛出机制都会导致异常。异常必须用try/catch处理,否则Node.js进程将立即退出。除了少数例外,同步API(任何不接受回调函数的阻塞方法,如[fs.readFileSync])将使用throw来报告错误。异步API中的错误可以通过几种方式报告:大多数异步方法都接受回调函数,该函数接受Error对象作为第一个参数。如果第一个参数不是null,而是一个Error实例,则表示发生了错误,应进行处理。
@[toc]线性容器类线性容器类包括List、ArrayList、LinkedList、Vector、Deque、Queue和Stack。底层主要通过阵列实现。线性容器类API充分考虑了数据访问的速度,并在运行时通过一条指令实现了添加、删除、修改和查询等操作。列表List可用于构造单向链表对象,即尾部节点只能从头部节点访问。根据泛型定义,List在内存中的存储位置可以是不连续的。可以通过get/set接口修改存储的元素。用于添加、删除、修改和查询列表的API如下:declare class List<T> { constructor(); length: number; add(element: T): boolean; get(index: number): T; has(element: T): boolean; getIndexOf(element: T): number; removeByIndex(index: number): T; remove(element: T): boolean; getLastIndexOf(element: T): number; forEach(callbackfn: (value: T, index?: number, List?: List<T>) => void, thisArg?: Object): void; convertToArray(): Array<T>; isEmpty(): boolean; [Symbol.iterator](): IterableIterator<T>; // 省略部分API }LinkedListLinkedList可用于构造双向链表对象,该对象可以在节点处向前或向后遍历list。根据一般定义,LinkedList可以存储在内存中不连续的位置。可以通过get/set和其他接口修改存储的元素。LinkedList添加、删除、修改和查询的API如下:declare class LinkedList<T> { constructor(); length: number; add(element: T): boolean; insert(index: number, element: T): void; get(index: number): T; addFirst(element: T): void; removeFirst(): T; removeLast(): T; has(element: T): boolean; getIndexOf(element: T): number; removeByIndex(index: number): T; remove(element: T): boolean; removeFirstFound(element: T): boolean; removeLastFound(element: T): boolean; getLastIndexOf(element: T): number; getFirst(): T; getLast(): T; set(index: number, element: T): T; forEach(callbackfn: (value: T, index?: number, LinkedList?: LinkedList<T>) => void, thisArg?: Object): void; clear(): void; [Symbol.iterator](): IterableIterator<T>; // 省略部分API }媒体查询语法调用媒体查询接口matchMediaSync()设置媒体查询条件和查询结果的回调函数,并在相应条件的回调函数中更改页面布局或实现业务逻辑。matchMediaSync()方法的条件参数的语法格式如下:[media-type] [and|not|only] [(media-feature)]媒体类型:媒体类型。参数支持屏幕,这意味着查询条件基于媒体查询的屏幕相关参数。而|not|only|或:媒体逻辑运算符,用于形成复杂的媒体查询。它也可以通过逗号(,)组合,如下表所示。二维码组件二维码的使用在生活中随处可见,比如扫码加好友、扫码骑车、扫码支付。ArkUI开发框架提供了生成QR码的RQCode组件。本节简要介绍其用途。QRCode('Hello, OpenHarmony') .width(70) .height(70)color:设置二维码颜色,默认黑色。backgroundColor:设置二维码背景色。简单样例如下所示:QRCode('Hello, OpenHarmony') .width(70) .height(70) .color(Color.Red) QRCode('Hello, OpenHarmony') .width(70) .height(70) .color(Color.Pink) .backgroundColor('#aabbcc')手势密码PatternLock使用PatternLock时,它接收PatternLockController类型的控制器,用于控制组件的状态,例如重置解锁状态。一个简单的例子如下:@Entry @Component struct PatternLockTest { build() { Column({space: 10}) { PatternLock(this.patternLockController) } .width('100%') .height('100%') .padding(10) } }SideLength:设置组件的宽度和高度。默认值为300vp,最小值可以设置为0。CircleRadius:设置栅格点的半径。默认值为14vp。RegularColor:将宫殿点的填充颜色设置为“未选择”状态。默认值为黑色。SelectedColor:在“选定”状态下设置宫点的填充颜色。默认值为黑色。ActiveColor:将子宫网格点的填充颜色设置为“活动”状态。默认值为黑色。pathColor:设置连接的颜色。默认值为蓝色。PathStrokeWidth:设置线条的宽度。默认值为34vp,最小值可以设置为0。自动重置:设置用户在完成输入后是否可以再次触摸屏幕以重置组件状态。如果设置为true,用户可以通过触摸模式密码锁来重置组件状态(清除之前的输入效果);如果设置为false,在用户离开屏幕完成输入后,再次触摸图案密码锁(包括圆点)将无法更改之前的输入状态。@Entry @Component struct PatternLockTest { @State passwords: Number[] = [] @State message: string = '请验证密码' private patternLockController: PatternLockController = new PatternLockController() build() { Column({space: 10}) { Text(this.message) .textAlign(TextAlign.Center) .fontSize(22) PatternLock(this.patternLockController) .sideLength(200) // 设置宽高 .circleRadius(7) // 设置圆点半径 .regularColor(Color.Red) // 设置圆点颜色 .pathStrokeWidth(10) // 设置连线粗细 .backgroundColor(Color.Pink)// 设置背景色 .autoReset(true) // 支持用户在完成输入后再次触屏重置组件状态 .onPatternComplete((input: Array<number>) => { if (input == null || input == undefined || input.length < 5) { this.message = "密码长度至少为5位数。"; return; } if (this.passwords.length > 0) { if (this.passwords.toString() == input.toString()) { this.passwords = input this.message = "密码设置成功" } else { this.message = '密码输入错误' } } else { this.passwords = input this.message = "密码输入错误" } }) Button('重置密码') .onClick(() => { this.passwords = []; this.message = '请验证手势密码'; this.patternLockController.reset(); }) } .width('100%') .height('100%') .padding(10) } }运行结果如下:
@[toc]commonEvent定义介绍发布:发送公共事件,事件表示事件名称。PublishAsUser:发送指定用户的公共事件。CreateSubscriber:创建事件的订户。订阅:订阅事件,可以是公共事件或自定义事件。取消订阅:取消订阅事件。一旦取消,将不会接收后续事件。@哦。commonEvent模块提供了一个简单的API。首先,创建一个订阅服务器来接收事件,然后开始订阅事件,最后取消订阅。要发布事件,可以直接调用publish()方法来发布事件。如果事件与订阅者订阅的事件类型匹配,则会将其回调给订阅者。简单步骤如下:declare namespace commonEvent { function publish(event: string, callback: AsyncCallback<void>): void; function publish(event: string, options: CommonEventPublishData, callback: AsyncCallback<void>): void; function publishAsUser(event: string, userId: number, callback: AsyncCallback<void>): void; function publishAsUser(event: string, userId: number, options: CommonEventPublishData, callback: AsyncCallback<void>): void; function createSubscriber(subscribeInfo: CommonEventSubscribeInfo, callback: AsyncCallback<CommonEventSubscriber>): void; function createSubscriber(subscribeInfo: CommonEventSubscribeInfo): Promise<CommonEventSubscriber>; function subscribe(subscriber: CommonEventSubscriber, callback: AsyncCallback<CommonEventData>): void; function unsubscribe(subscriber: CommonEventSubscriber, callback?: AsyncCallback<void>): void; }限制与约束@ohos.reminderAgent 模块里提供了发布后台代理提醒和取消后台代理提醒的相关 API,部分 API 如下所示:declare namespace reminderAgent { // 发送后台代理提醒 function publishReminder(reminderReq: ReminderRequest, callback: AsyncCallback<number>): void; // 取消后台代理提醒 function cancelReminder(reminderId: number, callback: AsyncCallback<void>): void; // 获取所有后台代理提醒 function getValidReminders(callback: AsyncCallback<Array<ReminderRequest>>): void; // 取消所有后台代理提醒 function cancelAllReminders(callback: AsyncCallback<void>): void; // 省略部分API }发布提醒:发布后台代理提醒。ReminderRequest参数描述如下:ReminderType:设置后台提醒类型,支持以下三种类型:提醒_类型_计时器:倒计时提醒。使用此类型时,需要使用ReminderRequestTimer配置reminderReq参数。提醒_类型_日历:日历提醒。使用此类型时,需要使用ReminderRequestCalendar配置reminderReq参数。提醒_类型_警报:闹钟提醒。使用此类型时,reminderReq参数需要配置为ReminderRequestAlarm。ActionButton:弹出提醒通知栏中显示的按钮(可选参数,支持0/1/2按钮)。WantAgent:单击通知后要跳转到的目标功能信息。maxScreenWantAgent:提醒目标包在到达时跳转。如果设备正在使用,则会弹出一个通知框。RingDuration:表示振铃持续时间。SnoozeTimes:表示延迟提醒的次数。TimeInterval:执行延迟提醒间隔。标题:表示警报标题。内容:表示提醒内容。ExpiredContent:表示提醒过期后要显示的内容。snoozeContent:表示提醒延迟时要显示的内容。NotificationId:表示提醒使用的通知的ID号。具有相同ID号的提醒将被覆盖。插槽类型:表示提醒的插槽类型。取消提醒:取消后台代理提醒。GetValidReminders:获取当前应用程序设置的所有有效(未过期)提醒。取消所有提醒:取消当前应用程序中的所有提醒。private cancelAllTimer() { reminderAgent.cancelAllReminders((error, data) => { if(!error) { // 取消成功 } }) }思路如下:专用startTimer(){让计时器={reminderType:reminderAgent.reminderType.REMINDER_TYPE_TIMER,触发器时间(秒):10}提醒代理。publishReminder(计时器,(error,reminderId)=>{如果(reminderId){// 发送成功,如果需要取消该提醒,这要使用该提醒ID}})}原子化服务代码简析创建原子化服务项目后,系统将默认在条目的js目录和FormAbility目录中生成页面模板和服务管理。页面模板是标准的原子化卡片布局,页面表示页面集,索引表示特定模块。服务管理为服务状态更改提供回调,其中可以更新卡数据。<div class="container"> <stack> // 堆叠式布局 <div class="container-img"> <image src="/common/widget.png" class="bg-img"/> // 设备卡片背景 </div> <div class="container-inner"> <text class="title"> // 设置标题 {{ $t('strings.title_immersive') }} </text> <text class="detail_text" onclick="routerEvent"> // 设备内容,点击时触发routerEvent事件 {{ $t('strings.detail_immersive') }} </text> </div> </stack> </div>class 为 "detail_text" 的 text 组件添加了 onclick 事件,当点击该组件时触发 routerEvent 的事件回调。routerEvent 在 index.json 文件中配置,布局代码演示效果如下:CanvasArkUI开发框架提供了Canvas Canvas组件,以支持我们对自绘各种图形的需求。Canvas特定绘图委托CanvasRenderingContext2D执行。CanvasRenderingContext2D提供了一系列与绘画相关的方法。作者简单地将CanvasRenderingContext2D理解为画笔,画笔绘制的内容显示在Canvas画布上。本课程主要介绍CanvasRenderingContext2D的使用。interface CanvasInterface { (context?: CanvasRenderingContext2D): CanvasAttribute; } 上下文:创建Canvas组件时,需要一个CanvasRenderingContext2D实例,该实例负责在Canvas上绘制特定内容,如文本、图片和各种形状。@Entry @Component struct Index { // 初始化RenderingContextSettings并设置为抗锯齿 private setting: RenderingContextSettings = new RenderingContextSettings(true); // 初始化CanvasRenderingContext2D private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.setting); build() { Column() { Canvas(this.context) // 设置Canvas所需要的context .size({width: '100%', height: "100%"}) // 设置Canvas的宽高 .onReady(() => { // 监听回调,在回调内执行绘画操作 this.context.fillRect(10, 10, 130, 40); // 以(10, 10)为起点坐标,画矩形,默认黑色 }) } .width('100%') .height('100%') } }
APIreact-spring库中与动画相关的API支持两种不同的使用渲染道具和react钩子的方法。接下来,我们将介绍react hook的一些动画相关API:react spring hook创建的spring动画的基本配置可以通过usestate、useref等进行外部控制,以更改动画。动画过渡属性(从、到)也可以设置为状态控制,但无法实现过渡效果,并且变换非常严格动画定义API(负责创建动画)Usespring:创建一个简单的动画弹簧使用弹簧:创建一组同时执行的弹簧Usetrail:创建一组按顺序执行的弹簧(创建的弹簧属性一致)使用转换:在生命周期发生变化时添加动画,如已安装/未安装的组件Usechain:用于自定义spring执行顺序动画绘制API(负责动画执行)动画:是实现react spring动画效果的基础。通过上述API生成的所有弹簧必须通过动画标记才能绘制动画(动画的执行器)插值:将spring中执行过程的转换属性值与动画XXX绑定(添加到属性,如style\classname)、数据映射或属性值修改(绑定动画和执行器)进行比较默认配置配置:弹簧属性的默认值(影响动画的速度、加速度、延迟和错误等基本属性)(官方示例)to 属性可以是异步函数(过渡效果从上到下依次实现){ to: async (next: any, cancel: any) => { await next({ opacity: 1, color: '#ffaaee', fontSize: 30 }); await next({ opacity: 0.5, color: 'rgb(14,26,19)', fontSize: 20 }); }, } to 属性可以是数组(过渡效果从左到右依次实现){ to: [{opacity: 1, color: '#ffaaee'}, {opacity: 0, color: 'rgb(14,26,19)'}], }to属性的值可以与其他属性平级(props的其他属性){ from: { o: 0.3, xyz: [0, 0, 0], color: 'red' }, o: 1, xyz: [10, 20, 5], color: 'green', reverse: visible, }useSpringUsespring用于创建单独的动画,这是API其余部分的基础。它接收包含动画属性的对象或返回值为对象作为参数的箭头函数参数为object接收usespringprops类型的对象返回animatedvalue类型的对象可以通过外部usestate控制动画属性的更改。通过ref修改状态并调整重置和其他属性将执行相应的动画config.default { mass: 1, tension: 170, friction: 26 } config.gentle { mass: 1, tension: 120, friction: 14 } config.wobbly { mass: 1, tension: 180, friction: 12 } config.stiff { mass: 1, tension: 210, friction: 20 } config.slow { mass: 1, tension: 280, friction: 60 } config.molasses { mass: 1, tension: 280, friction: 120 }参数是一个箭头函数,返回对象arrow函数返回usespringprops类型的对象返回[animationvalue,set,stop]的数组动画属性的更改只能通过set函数重新传递。可以使用“停止”方法提前结束动画返回值为对象时 useSpring返回值为数组 useSprings useTrailxxx 为from和to属性中返回的属性interface AnimatedValue { [key in (form & to)]: AnimatedInterpolation; } type AnimatedValues = Array<AnimatedValue>;animated 组件在react弹簧挂钩中使用动画相对简单。您可以直接使用animated XXX导出要从animated中使用的HTML标记,然后将spring直接分配给属性,例如style无需附加任何属性,直接使用传入from和to的值作为style的属性。springs当生成spring动画数组时,通过map解构获取单个spring,然后将值添加给animated。通过interpolate获取传入的参数,返回新的style属性const spring = useSpring({...}) <animated.div style={spring}></animated.div> <animated.div style={{transfrom: spring.xxx.interpolate(x=>`translate2d(${x}px, 0)`)}}></animated.div> const springs = useSpring(5, [{...}]) // springs.map((spring, index) => (<animated.div key={index} style={{...spring}} />))} springs.map((spring, index) => (<animated.div key={index} style={{transfrom: spring.xxx.interpolate(x=>`translate2d(${x}px, 0)`)}} />))}UseTransitionProps 生命周期动画属性与返回值from的功能与initial的功能相同,initial是enter的起始值。但是,“初始”仅在首次加载组件时有效,而“自”在每次装入组件时有效。初始优先级较高interface UseTransitionProps { // 动画弹簧基本属性 config?: SpringConfig | ((item: TItem) => SpringConfig) // 如果为true,key值相同的Spring将被重用 unique?: boolean default false // 动画开始前的延迟(毫秒),每次进入/更新和离开时加起来 trail?: number // 动画开始的起始属性(每次mounted是enter的起始值) from?: InferFrom<DS> | ((item: TItem) => InferFrom<DS>) // 组件mounted的动画属性 enter?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>) // 组件unmounted的动画属性 leave?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>) // 组件的属性更新时的动画属性(Item的值变化时,在enter之后启动,可以通过hook控制state变化) update?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>) // 动画初始值,第一次加载的值(只有第一次mounted有效) initial?: InferFrom<DS> | ((item: TItem) => InferFrom<DS>) | null // 在组件销毁时调用 onDestroyed?: (isDestroyed: boolean) => void } interface UseTransitionResult { // items的单个项,如果items是boolean则为boolean的值 item: TItem // key值,如果不设置为null,则默认key=>key自动生成 key: string // 当前值状态 inital、enter、leave、update state: State // 动画属性 props: AnimatedValue }
React的三大特性声明式编程命令式编程与声明式编程:简而言之,命令式编程是告诉计算机通过代码做什么。声明式编程是通过代码告诉计算机你想做什么,这样计算机就可以知道怎么做。生活中的一个例子是:命令编程:我想喝一杯冰可乐,然后我会对我周围的XXX说:“XXX,去厨房,打开冰箱,拿出一瓶冰可乐,打开并寄给我。”声明式编程:我想喝一杯冰可乐,然后我会对我周围的XXX说:“XXX,我想喝杯冰可乐。”我不在乎他是如何得到冰可乐的,他是如何发送的,他到底是在楼下买的还是放在冰箱里的。我只关心我对冰可乐的需求是否满足。以代码为例:const container = document. getElementById ( "container" ); const btn = document.createElement ("button"); btn.className = "btn red " ; btn.textContent = "Demo" ; btn.onclick = function ( ) { if ( this.classList.contains ( "red" ) ) { this.classList.remove( "red" ); this.classList.add ( "blue" ); }else { this.classList.remove( "blue" ); this.classList.add ( "red" ); } }; container.appendChild( btn); 如果我想在界面上显示一个按钮,然后单击该按钮,按钮的类将被更改。Dom编程编写的代码是命令式编程:首先,需要命令浏览器。第一步是找到ID为容器的节点,然后创建一个按钮元素,然后向按钮添加一个类名,然后添加一个单击事件,最后将按钮添加到容器节点。整个过程的每一步都是必不可少的,一步一步地告诉浏览器该做什么。class Button extends React. Component { state = { color: "red" }; handleChange =()=> { const color = this.state.color == "red" ? "blue" : "red" ;this.setState({ color }); }; render( ) { return ( <div id="container"> <button className={ `btn ${this.state.color}` } onclick={this.handleChange} > Demo </button> </div> ); } } 为了实现相同的功能,使用声明式编程进行react要简单得多。首先,我们定义一个按钮组件。在render函数中,通过返回类似于HTML的数据结构,我们告诉react我想要呈现一个按钮,它是一个具有ID容器的子节点。按钮上的类名会动态更改。单击按钮时,类将更改。这就是全部。至于何时执行渲染,如何将其渲染到页面,以及单击按钮后如何更新类名,您无需关心。您只需要告诉react您希望当前UI处于何种状态。组件化React提供了一个新的语法扩展JSX。JSX创造性地结合了呈现逻辑和UI逻辑,这种组合在react中称为组件。一个页面由多个组件组成,甚至整个应用程序都可以被视为一个组件,它只是最大的组件。组件可以逐层嵌套。一个组件可以由多个组件组成。大部件由许多小部件组成,这些小部件也可以由较小部件组成。同一部件可用于不同的地方。一次学会,随处编写这句话的意思不是说你可以写你想写的东西,也不是说你想写一次就可以在任何地方运行,而是说你可以使用react语法在很多地方编写代码,比如用react DOM编写网页,用react native编写移动客户端应用,用React360开发VR界面。react的灵活性取决于其自身的定位。React是一个用于构建用户界面的JS库。对于react,这里的用户界面是一个抽象的虚拟用户界面,实际上是一个描述页面状态的数据结构。网页、移动客户端页面和VR界面都是用户界面。只要使用相应的渲染器,就可以在不同的平台上显示正确的UI界面。一般来说,我们可以将react的执行结果想象为视频文件数据。在不同的播放器设备中,我们通过转换器将视频编译成不同的格式,让它们在不同播放器上正常播放。因此,在web端编写react时,我们需要另外引入react DOM来进行渲染。虚拟DOM的创建创建虚拟DOM的两种方法纯JS模式(通常不使用,太麻烦)JSX模式(简单方便,最终由Babel翻译成JS,与JS编写的结果相同)虚拟Dom和真实DomReact提供了一些API来创建一个“特殊”==通用JS对象==const VDOM = React.createElement('xx',{id:'xx'},'xx')///依次为标签名,标签属性和标签内容在编码时,我们只需要操作react的虚拟DOM相关数据,react就会转换成真实的DOM虚拟DOM概述:本质上为object类型的对象(一般对象)虚拟DOM是“轻”的,而真实DOM是“重”的,因为虚拟DOM在react中内部使用,并且不需要在真实DOM上有太多属性虚拟DOM对象最终将通过react转换为真实DOM,并显示在页面上模块与组件,模块化与组件化的理解模块理解:外部提供特定功能的JS程序通常是JS文件为什么拆分为模块:因为随着业务逻辑的增加,代码变得越来越复杂功能:取JS,简化JS编写,提高JS运行效率组件理解:用于实现本地函数(HTML/CSS/JS/image等)的代码和资源的集合为什么一个接口的功能如此复杂,以至于不可能将其写在一块中?它应该写成碎片,然后放在一起功能:重用编码,简化项目编码,提高运营效率模块化当一个应用的js都是以模块来编写,这个应用就是一个模块化的应用组件化当应用是以多组件的方式实现,这个应用就是一个组件化的应用Person.propTypes = { name: React.PropTypes.string.isRequired, age: React.PropTypes.number }使用prop-types库进限制(需要引入prop-types库)Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number. }reateRef创建ref容器myRef = React.createRef() ; <input ref={this.myRef}/>
@[toc]APIreact-spring库中与动画相关的API支持两种不同的使用渲染道具和react钩子的方法。接下来,我们将介绍react hook的一些动画相关API:react spring hook创建的spring动画的基本配置可以通过usestate、useref等进行外部控制,以更改动画。动画过渡属性(从、到)也可以设置为状态控制,但无法实现过渡效果,并且变换非常严格动画定义API(负责创建动画)Usespring:创建一个简单的动画弹簧使用弹簧:创建一组同时执行的弹簧Usetrail:创建一组按顺序执行的弹簧(创建的弹簧属性一致)使用转换:在生命周期发生变化时添加动画,如已安装/未安装的组件Usechain:用于自定义spring执行顺序动画绘制API(负责动画执行)动画:是实现react spring动画效果的基础。通过上述API生成的所有弹簧必须通过动画标记才能绘制动画(动画的执行器)插值:将spring中执行过程的转换属性值与动画XXX绑定(添加到属性,如style\classname)、数据映射或属性值修改(绑定动画和执行器)进行比较默认配置配置:弹簧属性的默认值(影响动画的速度、加速度、延迟和错误等基本属性)(官方示例)to 属性可以是异步函数(过渡效果从上到下依次实现){ to: async (next: any, cancel: any) => { await next({ opacity: 1, color: '#ffaaee', fontSize: 30 }); await next({ opacity: 0.5, color: 'rgb(14,26,19)', fontSize: 20 }); }, } to 属性可以是数组(过渡效果从左到右依次实现){ to: [{opacity: 1, color: '#ffaaee'}, {opacity: 0, color: 'rgb(14,26,19)'}], }to属性的值可以与其他属性平级(props的其他属性){ from: { o: 0.3, xyz: [0, 0, 0], color: 'red' }, o: 1, xyz: [10, 20, 5], color: 'green', reverse: visible, }useSpringUsespring用于创建单独的动画,这是API其余部分的基础。它接收包含动画属性的对象或返回值为对象作为参数的箭头函数参数为object接收usespringprops类型的对象返回animatedvalue类型的对象可以通过外部usestate控制动画属性的更改。通过ref修改状态并调整重置和其他属性将执行相应的动画config.default { mass: 1, tension: 170, friction: 26 } config.gentle { mass: 1, tension: 120, friction: 14 } config.wobbly { mass: 1, tension: 180, friction: 12 } config.stiff { mass: 1, tension: 210, friction: 20 } config.slow { mass: 1, tension: 280, friction: 60 } config.molasses { mass: 1, tension: 280, friction: 120 }参数是一个箭头函数,返回对象arrow函数返回usespringprops类型的对象返回[animationvalue,set,stop]的数组动画属性的更改只能通过set函数重新传递。可以使用“停止”方法提前结束动画返回值为对象时 useSpring返回值为数组 useSprings useTrailxxx 为from和to属性中返回的属性interface AnimatedValue { [key in (form & to)]: AnimatedInterpolation; } type AnimatedValues = Array<AnimatedValue>;animated 组件在react弹簧挂钩中使用动画相对简单。您可以直接使用animated XXX导出要从animated中使用的HTML标记,然后将spring直接分配给属性,例如style无需附加任何属性,直接使用传入from和to的值作为style的属性。springs当生成spring动画数组时,通过map解构获取单个spring,然后将值添加给animated。通过interpolate获取传入的参数,返回新的style属性const spring = useSpring({...}) <animated.div style={spring}></animated.div> <animated.div style={{transfrom: spring.xxx.interpolate(x=>`translate2d(${x}px, 0)`)}}></animated.div> const springs = useSpring(5, [{...}]) // springs.map((spring, index) => (<animated.div key={index} style={{...spring}} />))} springs.map((spring, index) => (<animated.div key={index} style={{transfrom: spring.xxx.interpolate(x=>`translate2d(${x}px, 0)`)}} />))}UseTransitionProps 生命周期动画属性与返回值from的功能与initial的功能相同,initial是enter的起始值。但是,“初始”仅在首次加载组件时有效,而“自”在每次装入组件时有效。初始优先级较高interface UseTransitionProps { // 动画弹簧基本属性 config?: SpringConfig | ((item: TItem) => SpringConfig) // 如果为true,key值相同的Spring将被重用 unique?: boolean default false // 动画开始前的延迟(毫秒),每次进入/更新和离开时加起来 trail?: number // 动画开始的起始属性(每次mounted是enter的起始值) from?: InferFrom<DS> | ((item: TItem) => InferFrom<DS>) // 组件mounted的动画属性 enter?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>) // 组件unmounted的动画属性 leave?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>) // 组件的属性更新时的动画属性(Item的值变化时,在enter之后启动,可以通过hook控制state变化) update?: InferFrom<DS> | InferFrom<DS>[] | ((item: TItem) => InferFrom<DS>) // 动画初始值,第一次加载的值(只有第一次mounted有效) initial?: InferFrom<DS> | ((item: TItem) => InferFrom<DS>) | null // 在组件销毁时调用 onDestroyed?: (isDestroyed: boolean) => void } interface UseTransitionResult { // items的单个项,如果items是boolean则为boolean的值 item: TItem // key值,如果不设置为null,则默认key=>key自动生成 key: string // 当前值状态 inital、enter、leave、update state: State // 动画属性 props: AnimatedValue }
@[toc]React的三大特性声明式编程命令式编程与声明式编程:简而言之,命令式编程是告诉计算机通过代码做什么。声明式编程是通过代码告诉计算机你想做什么,这样计算机就可以知道怎么做。生活中的一个例子是:命令编程:我想喝一杯冰可乐,然后我会对我周围的XXX说:“XXX,去厨房,打开冰箱,拿出一瓶冰可乐,打开并寄给我。”声明式编程:我想喝一杯冰可乐,然后我会对我周围的XXX说:“XXX,我想喝杯冰可乐。”我不在乎他是如何得到冰可乐的,他是如何发送的,他到底是在楼下买的还是放在冰箱里的。我只关心我对冰可乐的需求是否满足。以代码为例:const container = document. getElementById ( "container" ); const btn = document.createElement ("button"); btn.className = "btn red " ; btn.textContent = "Demo" ; btn.onclick = function ( ) { if ( this.classList.contains ( "red" ) ) { this.classList.remove( "red" ); this.classList.add ( "blue" ); }else { this.classList.remove( "blue" ); this.classList.add ( "red" ); } }; container.appendChild( btn); 如果我想在界面上显示一个按钮,然后单击该按钮,按钮的类将被更改。Dom编程编写的代码是命令式编程:首先,需要命令浏览器。第一步是找到ID为容器的节点,然后创建一个按钮元素,然后向按钮添加一个类名,然后添加一个单击事件,最后将按钮添加到容器节点。整个过程的每一步都是必不可少的,一步一步地告诉浏览器该做什么。class Button extends React. Component { state = { color: "red" }; handleChange =()=> { const color = this.state.color == "red" ? "blue" : "red" ;this.setState({ color }); }; render( ) { return ( <div id="container"> <button className={ `btn ${this.state.color}` } onclick={this.handleChange} > Demo </button> </div> ); } } 为了实现相同的功能,使用声明式编程进行react要简单得多。首先,我们定义一个按钮组件。在render函数中,通过返回类似于HTML的数据结构,我们告诉react我想要呈现一个按钮,它是一个具有ID容器的子节点。按钮上的类名会动态更改。单击按钮时,类将更改。这就是全部。至于何时执行渲染,如何将其渲染到页面,以及单击按钮后如何更新类名,您无需关心。您只需要告诉react您希望当前UI处于何种状态。组件化React提供了一个新的语法扩展JSX。JSX创造性地结合了呈现逻辑和UI逻辑,这种组合在react中称为组件。一个页面由多个组件组成,甚至整个应用程序都可以被视为一个组件,它只是最大的组件。组件可以逐层嵌套。一个组件可以由多个组件组成。大部件由许多小部件组成,这些小部件也可以由较小部件组成。同一部件可用于不同的地方。一次学会,随处编写这句话的意思不是说你可以写你想写的东西,也不是说你想写一次就可以在任何地方运行,而是说你可以使用react语法在很多地方编写代码,比如用react DOM编写网页,用react native编写移动客户端应用,用React360开发VR界面。react的灵活性取决于其自身的定位。React是一个用于构建用户界面的JS库。对于react,这里的用户界面是一个抽象的虚拟用户界面,实际上是一个描述页面状态的数据结构。网页、移动客户端页面和VR界面都是用户界面。只要使用相应的渲染器,就可以在不同的平台上显示正确的UI界面。一般来说,我们可以将react的执行结果想象为视频文件数据。在不同的播放器设备中,我们通过转换器将视频编译成不同的格式,让它们在不同播放器上正常播放。因此,在web端编写react时,我们需要另外引入react DOM来进行渲染。虚拟DOM的创建创建虚拟DOM的两种方法纯JS模式(通常不使用,太麻烦)JSX模式(简单方便,最终由Babel翻译成JS,与JS编写的结果相同)虚拟Dom和真实DomReact提供了一些API来创建一个“特殊”==通用JS对象==const VDOM = React.createElement('xx',{id:'xx'},'xx')///依次为标签名,标签属性和标签内容在编码时,我们只需要操作react的虚拟DOM相关数据,react就会转换成真实的DOM虚拟DOM概述:本质上为object类型的对象(一般对象)虚拟DOM是“轻”的,而真实DOM是“重”的,因为虚拟DOM在react中内部使用,并且不需要在真实DOM上有太多属性虚拟DOM对象最终将通过react转换为真实DOM,并显示在页面上模块与组件,模块化与组件化的理解模块理解:外部提供特定功能的JS程序通常是JS文件为什么拆分为模块:因为随着业务逻辑的增加,代码变得越来越复杂功能:取JS,简化JS编写,提高JS运行效率组件理解:用于实现本地函数(HTML/CSS/JS/image等)的代码和资源的集合为什么一个接口的功能如此复杂,以至于不可能将其写在一块中?它应该写成碎片,然后放在一起功能:重用编码,简化项目编码,提高运营效率模块化当一个应用的js都是以模块来编写,这个应用就是一个模块化的应用组件化当应用是以多组件的方式实现,这个应用就是一个组件化的应用Person.propTypes = { name: React.PropTypes.string.isRequired, age: React.PropTypes.number }使用prop-types库进限制(需要引入prop-types库)Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number. }reateRef创建ref容器myRef = React.createRef() ; <input ref={this.myRef}/>
@[toc]数据流方向如果以前研究过angular,应该知道双向数据绑定的概念。指令为ng模式。Angular是双向数据流,父组件和子组件之间的通信相对方便。但有时,我们不希望儿子改变父亲的价值观。我们只能使用它,不能改变它。react最明显的一点是,Vue中的表单控件也有一个V型双向数据绑定指令。当父组件传递值时,可以直接传递列表。但是,它对于子组件是只读的。尝试修改后,将报告错误。当不考虑Redux和flux等状态管理框架时,上述方法通常用于父子组件通信。与jQuery结合首先,这两个可以一起使用。React只关心挂载的根元素root。其他元素不关心。做你喜欢做的事。换句话说,jQuery不操作根中的元素事实上,这个问题没有实际意义,技术选择本身也有问题,一个主张控制DOM,另一个不建议控制DOM。 <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script>alert($)</script>也许你想试试。好的,试试看打开索引。公共文件夹下的html,并将其删除而不添加注释。PropTypes类型检查JS是一种基于面向对象思想的语言,而不是真正的面向对象语言。它在类型检测方面并不完美,这是typescript起火的原因之一。当父组件将数据传输到子组件时,对要传输的属性值没有限制。自由度太高。这在工作中是完全不允许的。对于大型项目,缺乏必要的校准,后期维护极其困难。import PropTypes from 'prop-types'类外部使用:xxx.propTypes={ p1:PropTypes.string.isRequired, p2:PropTypes.func, p3:PropTypes.number }生命周期生命周期就像从出生到死亡的过程。在react中,这些生命周期钩子函数非常有用,我们将在许多场合遇到它们。此外,值得注意的是,react的每个组件都具有上述所有周期函数,而不仅仅是根组件或父组件。严格来说,生命周期函数将在从组件渲染到组件销毁的过程中的某个点自动执行,因此不必担心。但渲染是特殊的。react的所有生命周期函数都有默认实现,但render除外。import fetchJsonp from 'fetch-jsonp' fetchJsonp('/users.jsonp') .then(function(response) { return response.json() }).then(function(json) { console.log('parsed json', json) }).catch(function(ex) { console.log('parsing failed', ex) }) 换句话说,不能在组件中编写任何周期函数,也不能编写构造函数,但不能编写渲染。 <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script>alert($)</script>父->子组件传值最基本的事情是传递属性的值。上述父->子值转移结束,todo_该项是自定义的,保证与子项接收的名称相同子组件需要以this.props的形式接收。xxx,这是这个道具。todo_项目子组件<noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script>alert($)</script>在上面的示例中,当父组件传递值时,可以直接传递列表。但是,它对于子组件是只读的。尝试修改后,将报告错误。当不考虑Redux和flux等状态管理框架时,上述方法通常用于父子组件通信。极致快vs高效率前者需要压缩,而后者不需要前者删除注释,而后者不删除前者不需要热更新,而后者需要热更新测试工具:不需要前者,需要后者语法检查工具:不需要前者,需要后者项目初始化项目名称不必与我的相同。只要接通并启动它NPM I-G纱线(可选命令)此外,如果已在全球范围内安装了纱线,则可以从纱线开始安装过程可能有点慢,请稍候类组件和函数组件的编写在react中,一切都是一个组件。传统布局的头部、左侧导航、主要内容和尾部可以视为组件并独立维护。它就像积木。小部件最终形成小房子。React组件分为两种类型,函数组件和类组件,这两种组件都使用。相反,此类组件更常见,但如果组件中不涉及业务逻辑,则更好地使用功能组件。下面是一个例子来说明两者之间的区别。function Header(){ return <h1>我是头部</h1>; } 仔细观察后,会发现应用程序组件也是一个功能组件。此外,组件以大写字母定义和使用。事实上,在react中,区分组件和HTML标记的方法是case。大写字母作为组件进行解析,小写字母作为HTML标记进行解析。顺便说一句,不管是单标签还是双标签。这取决于个人习惯。function App() { return ( <Header/> ); } 真正的关键字定义类从ES6开始,使用类。接下来,用类组件重写上述应用程序函数组件,以获得相同的效果,并输出Hello world。import React, {Component} from 'react' class App extends Component{ render(){ return ( <div> hello world </div> ) } } export default App;在react中,图片的介绍是特别的,分为本地图片介绍和网络图片参考网络图片引用与之前相同,IMG-tag-SRC属性写入图片链接地址无法以这种方式导入本地映像。它需要以路径的形式导入,并具有alt属性
@[toc]qiankun框架由于主应用程序和微应用程序可以独立于技术堆栈,所以qiankun只是一个面向用户的类似jQuery的库。需要调用几个API来完成应用程序的微前端转换。同时,由于qiankunHTML入口和沙盒的设计,微应用程序的访问与使用iframe一样简单。解耦微前端的核心目标是将巨石应用程序分解为几个松散耦合的、可以自主的微应用程序,而乾坤的许多设计都遵循这一原则,例如HTML输入、沙盒和应用程序间通信。只有这样,才能确保微应用真正具有自主开发和自主运行的能力。微应用信息注册微应用信息注册后,一旦浏览器URL发生变化,将自动触发qiankun的匹配逻辑。所有与activerule规则匹配的微应用程序将被插入到指定的容器中,并依次调用微应用程序公开的生命周期钩子。$ yarn add qiankun # 或者 npm i qiankun -S如果微应用程序与路由没有直接关联,还可以选择手动加载微应用程序:import { loadMicroApp } from 'qiankun'; loadMicroApp({ name: 'app', entry: '//localhost:7100', container: '#yourContainer', });微应用1.微应用分为网页包构建和非网页包构建项目。使用webpack的微应用程序(主要是Vue、react和angular)需要执行以下操作:注意:运行时的publicpath和构建时的publippath是不同的,不能等价地替换它们。2.微应用程序建议使用历史模式路由。需要设置路由基础。该值与其activerule相同。3.引入公共路径。js在条目文件的顶部,修改和导出三个生命周期函数。4.修改webpack打包以允许跨域和UMD打包开发环境。主要修改为上述四项,可能会根据项目的不同情况而变化。例如,项目是索引HTML,所有其他文件都是单独部署的。这意味着在构建时已将publicpath设置为完整路径。运行时不需要修改publicpath(可以保存第一步)。添加公共路径。SRC目录中的js:if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }设置历史模式路由的基础:<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>这个条目是JS的文件索引。为了避免根ID#root与其他DOM之间的冲突,需要限制搜索范围。import './public-path'; import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; function render(props) { const { container } = props; ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root')); } if (!window.__POWERED_BY_QIANKUN__) { render({}); } export async function bootstrap() { console.log('[react16] react app bootstraped'); } export async function mount(props) { console.log('[react16] props from main framework', props); render(props); } export async function unmount(props) { const { container } = props; ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); }非 webpack 构建的微应用一些非webpack构建的项目,如jQuery项目和JSP项目,可以根据此进行处理。<script src="//yourhost/entry.js" entry></script>在访问之前,请确保项目中的图片、音频和视频资源能够正常加载。如果这些资源的地址是完整路径(例如https://qiankun.umijs.org/logo.png)是的,很好。如果它们是相对路径,则需要首先将这些资源上载到服务器并使用完整路径。在 entry js 里声明 lifecycles:((global) => { global['purehtml'] = { bootstrap: () => { console.log('purehtml bootstrap'); return Promise.resolve(); }, mount: () => { console.log('purehtml mount'); return render($); }, unmount: () => { console.log('purehtml unmount'); return Promise.resolve(); }, }; })(window);访问非常简单。只需要声明一个脚本来导出相应的生命周期。const packageName = require('./package.json').name; 除了在代码中暴露相应的生命周期钩子之外,为了使主应用程序能够正确识别微应用程序暴露的一些信息,微应用程序的打包工具需要添加以下配置:module.exports = { output: { library: `${packageName}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, }, };实现思路1.预加载资源如果在应用程序注册配置中存在需要预加载的应用程序,则应在初始化的同时加载这些应用程序。初始化路由根据配置的路由规则匹配当前页面路径,并查找当前有效的应用程序信息。popstate监控页面路由变化,并根据路由变化获取当前有效应用3.代理一些窗口事件。由于每个应用程序可能会绑定一些窗口事件,因此它会劫持窗口Addeventlistener,后者记录每个应用程序绑定的事件,以便在以后切换路由时清除这些事件。4.加载资源目标页面,分析各种资源,然后加载并执行5.在每个应用程序执行之前记录全局变量,记录当前全局变量,然后在卸载应用程序时清除所有全局变量,以避免影响下一个应用程序的执行。qiankun沙盒模型我们知道所有全局方法、全局变量/常量和全局对象都属于窗口对象,这些全局方法和对象可能会造成JS污染。它可以在加载子系统之前创建窗口对象的快照(副本),然后在卸载子系统时恢复快照。也就是说,它可以确保每次子系统运行时,都是一个新的窗口对象环境。实际上,一般原则是记录子系统运行过程中添加、修改和删除的窗口对象的属性和方法,然后在卸载子系统时恢复这些操作。应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法export async function mount(props) { ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root')); }应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例export async function unmount(props) { ReactDOM.unmountComponentAtNode( props.container ? props.container.querySelector('#root') : document.getElementById('root'), ); }
@[toc]layui的优缺点详解优点:Layui作为国内开源的前端UI,简单易用,界面简单美观。目标对象是对前端不太了解的后端开发人员,它也适用于后端开发人员。(1) Layui是一个轻量级框架,简单而美观。适用于后端模式的开发,对服务器页面有很好的效果。(2) Layui是后端开发人员的最佳UI框架。它由Dom驱动。只要不涉及交互,layui仍然很好缺点:因为layui是一个个人开源项目,开源时间不长,所以会有很多bug和一些限制。兼容性不强,尤其是在移动端。由于最初的设计意图,layui仅适用于小型项目。即使是小项目也经常遇到许多问题。官方网站对更好的集成技术收费。栅格系统1.使用layui行定义行,例如,2.使用像layui col MD*这样的预设类来定义一组列并将它们放入行中。其中:变量MD表示不同屏幕下的标记(可选值见下文)变量*表示该列占用的12个等分(例如6/12)。可选值为1-12如果多列的“平分线值”之和等于12,则该行刚好满。如果大于12,额外列将自动开始新行。3.该栏可以同时有四种不同的组合,即:XS(超小屏幕,如手机)、Sm(小屏幕,例如平板电脑)、MD(桌面中屏幕)和LG(桌面大屏幕),以呈现更动态和灵活的布局。4.可以向列中添加诸如layui-col-space5和layui-col-md-offset3等预设类,以定义列的间距和偏移。5.最后,在column元素中放置自己的任意元素,以填充内容并完成布局!<body> <div class="layui-container"> <!-- 定义行--> <div class="layui-row"> <!-- 定义列 --> <div class="layui-col-md5" style="background-color: #007DDB;"> 内容5/12 </div> <div class="layui-col-md7" style="background-color: #00F7DE;"> 内容7/12 </div> </div> <div class="layui-row"> <!-- 定义列 --> <div class="layui-col-md4" style="background-color: #EB7350;"> 内容4/12 </div> <div class="layui-col-md4" style="background-color: #FFB800;"> 内容4/12 </div> <!-- 超过12会自动换行 --> <div class="layui-col-md6" style="background-color: #C2C2C2;"> 内容6/12 </div> </div> <div class="layui-row"> <div class="layui-col-xs6 layui-col-sm6 layui-col-md4" style="background-color: pink;"> 移动:6/12 | 平板:6/12 | 桌面:4/12 </div> <div class="layui-col-xs6 layui-col-sm6 layui-col-md4" style="background-color: palegoldenrod;"> 移动:6/12 | 平板:6/12 | 桌面:4/12 </div> <div class="layui-col-xs4 layui-col-sm12 layui-col-md4" style="background-color: peru;"> 移动:4/12 | 平板:12/12 | 桌面:4/12 </div> <div class="layui-col-xs4 layui-col-sm7 layui-col-md8" style="background-color: powderblue;"> 移动:4/12 | 平板:7/12 | 桌面:8/12 </div> <div class="layui-col-xs4 layui-col-sm5 layui-col-md4" style="background-color: palegreen;"> 移动:4/12 | 平板:5/12 | 桌面:4/12 </div> </div> </div> </body> 按钮名称组合原始class=“layui BTN layui BTN main”默认类=“layui BTN”野生类=“layui BTN layui BTN正常”暖色类=“layui BTN layui BTN暖”警告等级=“layui BTN layui BTN危险”使残废Class=“layui BTN layui禁用BTN”菜单菜单是页面的基本元素。我们希望它是通用的,所以它的结构非常灵活。事实上,在基本菜单正式推出之前,垂直导航(layui导航树)已经取代了它的角色,尤其是在管理系统中。虽然它们在本质上都属于“菜单”的范畴,但我们同意,水平菜单称为“导航”,垂直菜单称为正统的“基本菜单”。它将非常有用,可以在许多业务场景中看到。可依赖的模块:dropdown列表类型及其子菜单是相同的。需要注意的是,“可收缩菜单组”的子菜单只需要嵌套一层UL;“水平父子菜单”还需要一组div class=“layui panel”和“layui menu body panel”,以使子菜单更加清晰。<li lay-options="{ id: 100 ,title: 'menu item 1' ,type: '' //支持的类型有:group、parent,具体用法参见上文 ,aaa: '任意参数' }">内容</li> 评分组件费率评分组件在电子商务和o2o平台中特别常见,通常用于评估商家的服务满意度。外形依然小巧自然,功能依然灵活实用。对应于分数的用户定义的内容功能可以给它更多的播放空间。此组件是2.3.0版中的新组件。关闭半星功能:如果十进制值大于0.5,则分数将向上取整。例如,如果分数为3.6,系统将自动将其更改为4小于或等于0.5的小值:分数向下舍入,例如3.2分,系统将自动变为3分如果在关闭半星函数的同时打开文本,将发现的分数将相应地变为整数打开半星功能:无论的十进制值是0.1还是0.9,都统一计划为0.5。打开文本时,可以看到的分数没有变化。settexsettext函数在首次呈现和单击组件时生成回调。我们的默认文本以星形显示。可以根据自己的文本替换我们的默认文本,例如“恨”和“喜欢”。如果用户在没有设置相应文本的情况下选择分数,系统将使用我们的默认文本rate.render({ elem: '#test1' ,setText: function(value){ var arrs = { '1': '极差' ,'2': '差' ,'3': '中等' ,'4': '好' }; this.span.text(arrs[value] || ( value + "星")); } });
@[toc]使用Prototype引用 Prototype要测试JavaScript库,需要在web页面中引用它。要引用库,请使用<script>标记,并将SRC属性设置为库的URL:Prototype提供了使HTML DOM编程更容易的函数。与jQuery类似,prototype也有自己的$()函数。$()函数接受HTML DOM元素的ID值(或DOM元素),并向DOM对象添加新函数。与jQuery不同,prototype不会替换onload()的ready()方法。相反,prototype为浏览器和HTML DOM添加了扩展。在JavaScript中,可以指定一个函数来处理窗口加载事件:function myFunction() { var obj=document.getElementById("h01"); obj.innerHTML="Hello Prototype"; } onload=myFunction;Prototype 方式:function myFunction() { $("h01").insert("Hello Prototype!"); } Event.observe(window,"load",myFunction);引用jQuery要测试JavaScript库,需要在web页面中引用它。要引用库,请使用<script>标记,并将SRC属性设置为库的URL:主要的jQuery函数是$()函数(jQuery函数)。如果将DOM对象传递给此函数,它将返回一个添加了jQuery函数的jQuery对象。JQuery允许通过CSS选择器选择元素。在JavaScript中,可以指定一个函数来处理窗口加载事件:function myFunction() { var obj=document.getElementById("h01"); obj.innerHTML="Hello jQuery"; } onload=myFunction;function myFunction() { $("#h01").html("Hello jQuery"); } $(document).ready(myFunction);其他框架YUI-雅虎用户界面框架涵盖了大量函数库,从简单的JavaScript函数到完整的Internet小部件。ExtJS-用于构建富互联网应用程序的可定制小部件。Dojo-用于DOM操作、事件、小部件等的工具包。script.aculo。美国-用于视觉效果和界面行为的开源JavaScript框架。Uize-小部件、AJAX、DOM、模板等。CDN -内容分发网络如果许多不同的网站使用相同的JavaScript框架,那么将框架清单放在每个页面共享的公共位置是有意义的。CDN(内容交付网络)解决了这个问题。CDN是包含可共享代码库的服务器网络。国内免费CDN资源包括:Staticfile CDN:https://staticfile.org/cdnjs:https://cdnjs.com/<script src="https://cdn.staticfile.org/jquery/3.4.0/jquery.min.js"> </script>使用框架在决定为web页面使用JavaScript框架之前,最好先测试框架。JavaScript框架易于测试。不需要在计算机上安装它们,也没有安装程序。通常,只需要从网页中引用库文件。JavaScript 创建CookieJavaScript可以使用文档Cookie属性来创建、读取和删除Cookie。在JavaScript中,按如下方式创建cookie:document.cookie="username=John Doe";还可以在cookie中添加过期时间(UTC或GMT)。默认情况下,浏览器关闭时会删除Cookie:document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";使用路径参数告诉浏览器cookie的路径。默认情况下,Cookie属于当前页面。document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";Cookie 字符串文件cookie属性看起来像普通的文本字符串,但实际上不是。即使在文档中,完整的cookie字符串也会写入cookie中。重新读取cookie信息时,cookie信息以名称/值对的形式显示。如果设置了新的cookie,则不会覆盖旧cookie。新的cookie将添加到文档cookie中,因此如果重新阅读文档。cookie,将获得以下数据:cookie1=value; cookie2=value;获取 cookie 值的函数function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i=0; i<ca.length; i++) { var c = ca[i].trim(); if (c.indexOf(name)==0) return c.substring(name.length,c.length); } return ""; }cookie名称的参数是CNAME。创建文本变量以检索指定的Cookie:CNAME+“=”。使用分号拆分文档Cookie字符串,并将拆分字符串数组分配给Ca(Ca=document.Cookie.split(“;”)。循环CA数组(I=0;I<CA.length;I++),然后读取数组中的每个值,并删除前后的空格(C=CA[I].Trim())。如果找到cookie(c.indexof(name)==0),则返回cookie的值(c.substring(name.Length,c.Length)。如果未找到cookie,请返回“”。
JavaScript闭包JavaScript变量可以是局部变量或全局变量。私有变量可以使用闭包。全局变量函数可以访问函数内部定义的变量,例如:function myFunction() { var a = 4; return a * a; }在后一个示例中,a是全局变量。在网页中,全局变量属于窗口对象。全局变量可以应用于页面上的所有脚本。在第一个示例中,a是局部变量。局部变量只能在定义它的函数内使用。不适用于其他函数或脚本代码。全局变量和局部变量是两个不同的变量,即使它们具有相同的名称。修改其中一个不会影响另一个的值。如果在声明变量时不使用VaR关键字,则它是全局变量,即使它是在函数中定义的。变量生命周期全局变量的范围是全局的,也就是说,在整个JavaScript程序中,全局变量无处不在。函数内声明的变量仅在函数内工作。这些变量是局部变量,范围是局部的;函数的参数也是局部的,仅在函数内工作。计数器困境想象一下,如果您想对一些值进行计数,并且计数器在所有函数中都可用。您可以使用全局变量设置计数器:var counter = 0; function add() { return counter += 1; } add(); add(); add();运行结果如下:执行add()函数时,计数器值会更改。但问题是,即使没有调用add()函数,页面上的任何脚本都可以更改计数器。如果在函数中声明计数器,则在不调用函数的情况下无法修改计数器的值:本意是想输出 3, 但事与愿违,输出的都是 1function add() { var counter = 0; return counter += 1; } add(); add(); add();上述代码将无法正确输出。每次调用add()函数时,计数器将设置为1。JavaScript嵌入函数可以解决这个问题。JavaScript 内嵌函数所有函数都可以访问全局变量。事实上,在JavaScript中,所有函数都可以访问上一层的范围。JavaScript支持嵌套函数。嵌套函数可以访问上一级的函数变量。在本例中,嵌入式函数plus()可以访问父函数的计数器变量:function add() { var counter = 0; function plus() {counter += 1;} plus(); return counter; }如果我们可以从外部访问plus()函数,我们就可以解决计数器的困境。我们还需要确保counter=0只执行一次。我们需要关闭。JavaScript 闭包var add = (function () { var counter = 0; return function () {return counter += 1;} })(); add(); add(); add();变量add指定函数自调用的返回字值。自调用函数只执行一次。将计数器设置为0。并返回函数表达式。add变量可用作函数。最重要的是,它可以访问函数上方一级作用域中的计数器。这称为JavaScript闭包。它使得函数可以具有私有变量。计数器受匿名函数的作用域保护,只能通过add方法修改。作为一个函数调用myFunction(10, 2) 返回 20function myFunction(a, b) { return a * b; } myFunction(10, 2); 上述函数不属于任何对象。但它始终是JavaScript中的默认全局对象。html中的默认全局对象是html页面本身,因此函数属于html页面。浏览器中的页面对象是浏览器窗口(窗口对象)。上述函数将自动成为窗口对象的函数。Myfunction()和window Myfunction()function myFunction(a, b) { return a * b; } window.myFunction(10, 2);当函数没有被它自己的对象调用时,该函数的值将成为全局对象。在web浏览器中,全局对象是浏览器窗口(窗口对象)。此实例返回的值为窗口对象:function myFunction() { return this; } myFunction(); 当函数作为全局对象调用时,其值将成为全局对象。使用窗口对象作为变量很容易导致程序崩溃。作为函数方法调用函数在JavaScript中,函数是对象。JavaScript函数有其属性和方法。Call()和apply()是预定义的函数方法。可以使用两种方法调用函数。两个方法的第一个参数必须是对象本身。function myFunction(a, b) { return a * b; } myObject = myFunction.call(myObject, 10, 2); 两种方法都使用对象本身作为第一个参数。两者之间的区别是第二个参数:apply在参数数组中传递,也就是说,多个参数组合成一个数组,call作为call的参数传入(从第二个开始)。function myFunction(a, b) { return a * b; } myArray = [10, 2]; myObject = myFunction.apply(myObject, myArray);在JavaScript严格模式下,调用函数时,第一个参数将成为this的值,即使该参数不是对象。在JavaScript非严格模式下,如果第一个参数的值为null或未定义,它将使用全局对象。
JXcore 打包Jxcore是一个支持多线程JS发布版本的节点,基本上不需要对现有代码进行任何更改,可以直接在多线程中运行,具有线程安全性。本文主要介绍jxcore的封装功能。下载jxcore安装包并解压缩。解压目录中提供了JX二进制文件命令。接下来,我们主要使用这个命令。Linux/OSX 安装命令:$ curl https://raw.githubusercontent.com/jxcore/jxcore/master/tools/jx_install.sh | bash如果权限不足,可以使用以下命令:$ curl https://raw.githubusercontent.com/jxcore/jxcore/master/tools/jx_install.sh | sudo bash如果上述步骤正确,将使用以下命令输出版本号信息:$ jx --version v0.10.32如果成功执行上述命令,将生成以下两个文件:指数这是一个中间件文件,包含需要编译的完整项目信息。指数这是一个完整包信息的二进制文件,可以在客户端上运行。drwxr-xr-x 2 root root 4096 Nov 13 12:42 images -rwxr-xr-x 1 root root 30457 Mar 6 12:19 index.htm -rwxr-xr-x 1 root root 30452 Mar 1 12:54 index.js drwxr-xr-x 23 root root 4096 Jan 15 03:48 node_modules drwxr-xr-x 2 root root 4096 Mar 21 06:10 scripts drwxr-xr-x 2 root root 4096 Feb 15 11:56 styleNode.js 的项目运行:$ node index.js command_line_arguments使用 JXcore 编译后,我们可以使用以下命令来执行生成的 jx 二进制文件:$ jx index.jx command_line_argumentsNode.js 多进程我们都知道node JS以单线程模式运行,但它使用事件驱动来处理并发。这有助于我们在多核CPU系统上创建多个子进程,从而提高性能。每个子进程总是有三个流对象:child和stdin,以及child。标准输出和子标准输出它们可以共享父进程的stdio流,也可以是独立的重定向流对象。节点提供child_流程模块用于通过以下方式创建子流程:exec-child_进程。Exec使用子进程执行命令,缓存子进程的输出,并以回调函数参数的形式返回子进程的结果。spawn-child_进程。Spawn使用指定的命令行参数创建新进程。fork-child_进程。Fork是spawn()的一种特殊形式,用于在子进程中运行的模块。例如,fork('./son.JS')等同于spawn('node'['./son.JS')。与spawn方法不同,fork将在父进程和子进程之间建立通信管道,用于进程之间的通信。exec() 方法child_进程。Exec使用子进程执行命令,缓存子进程的输出,并以回调函数参数的形式返回子进程的结果。child_process.exec(command[, options], callback)callback:回调函数,包括三个参数:error、stdout和stderr。exec()方法返回最大缓冲区,等待进程结束,并一次返回缓冲区的内容。onsole.log("进程 " + process.argv[2] + " 执行。" );const fs = require('fs'); const child_process = require('child_process'); for(var i=0; i<3; i++) { var workerProcess = child_process.exec('node support.js '+i, function (error, stdout, stderr) { if (error) { console.log(error.stack); console.log('Error code: '+error.code); console.log('Signal received: '+error.signal); } console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); }); workerProcess.on('exit', function (code) { console.log('子进程已退出,退出码 '+code); }); }执行上述代码,输出结果为:$ node master.js 子进程已退出,退出码 0stdout: 进程 1 执行。stderr: 子进程已退出,退出码 0 stdout: 进程 0 执行。stderr: 子进程已退出,退出码 0 stdout: 进程 2 执行。spawn() 方法child_process.spawn(command[, args][, options])spawn()方法返回一个流(stdout&stderr),当进程返回大量数据时使用该流。一旦进程开始执行,spawn()就开始接收响应。console.log("进程 " + process.argv[2] + " 执行。" );const fs = require('fs'); const child_process = require('child_process'); for(var i=0; i<3; i++) { var workerProcess = child_process.spawn('node', ['support.js', i]); workerProcess.stdout.on('data', function (data) { console.log('stdout: ' + data); }); workerProcess.stderr.on('data', function (data) { console.log('stderr: ' + data); }); workerProcess.on('close', function (code) { console.log('子进程已退出,退出码 '+code); }); }执行以上代码,输出结果为:子进程已退出子进程已退出,退出码 0 stdout: 进程 1 执行。 子进程已退出,退出码 0 stdout: 进程 2 执行。fork 方法child_进程。Fork是spawn()方法的一种特殊形式,用于创建进程。语法格式如下:child_process.fork(modulePath[, args][, options])除了childprocess实例的所有方法之外,返回的对象还具有内置的通信通道。说明如下:Modulepath:string,要在子进程中运行的模块Args:数组字符串参数数组options:对象
@[toc]GET/POST请求在许多情况下,我们的服务器需要处理用户的浏览器,例如表单提交。get/post请求通常用于向服务器提交表单。var http = require('http'); var url = require('url'); var util = require('util'); http.createServer(function(req, res){ res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'}); res.end(util.inspect(url.parse(req.url, true))); }).listen(3000);在浏览器中访问http://localhost:3000/user?name=黎燃&url=并查看返回的结果:我们可以使用URL解析方法来解析URL中的参数。代码如下:var http = require('http'); var url = require('url'); var util = require('util'); http.createServer(function(req, res){ res.writeHead(200, {'Content-Type': 'text/plain'});解析 url 参数 var params = url.parse(req.url, true).query; res.write("网站名:" + params.name); res.write("\n"); res.write("网站 URL:" + params.url); res.end();获取 POST 请求内容post请求的内容都在请求正文中,http Serverrequest没有属性content作为请求正文,因为等待发送请求正文可能是一项耗时的任务。例如,在上传文件时,我们可能不需要注意请求主体的内容。恶意post请求将极大地消耗服务器的资源,因此node JS默认情况下不会解析请求体。var http = require('http'); var querystring = require('querystring'); var util = require('util');定义了一个post变量,用于暂存请求体的信息var post = ''; 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中req.on('data', function(chunk){ post += chunk; });在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。 req.on('end', function(){ post = querystring.parse(post); res.end(util.inspect(post)); });Web 应用架构Web服务器通常指的是Web服务器,它指的是驻留在Internet上的特定类型计算机的程序。web服务器的基本功能是提供web信息浏览服务。它只需要支持HTTP协议、HTML文档格式和URL,并与客户端的web浏览器协作。大多数web服务器支持服务器端脚本语言(PHP、python、Ruby)等,并通过脚本语言从数据库获取数据,然后将结果返回到客户端浏览器。目前,最流行的三种web服务器是Apache、nginx和IIS。节点。JS提供了一个HTTP模块。HTTP模块主要用于构建HTTP服务器和客户端。使用HTTP服务器或客户端函数时必须调用HTTP模块。代码如下:var http = require('http');var http = require('http'); var fs = require('fs'); var url = require('url');创建服务器http.createServer( function (request, response) { 解析请求,包括文件名var pathname = url.parse(request.url).pathname;输出请求的文件名console.log("Request for " + pathname + " received.");从文件系统中读取请求的文件内容 fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err);HTTP 状态码: 404 : NOT FOUND,Content Type: text/htmlresponse.writeHead(404, {'Content-Type': 'text/html'}); }else{ 响应文件内容 response.write(data.toString()); 发送响应数据response.end();控制台会输出以下信息console.log('Server running at http://127.0.0.1:8080/');使用 Node 创建 Web 客户端Node需要引入HTTP模块来创建web客户端和客户端JS文件var http = require('http');用于请求的选项var options = { host: 'localhost', port: '8080', path: '/index.html' };处理响应的回调函数var callback = function(response){不断更新数据 var body = ''; response.on('data', function(data) { body += data; });数据接收完成console.log(body);向服务端发送请求var req = http.request(options, callback); req.end();
Buffer 缓冲区JavaScript语言本身只有字符串数据类型,没有二进制数据类型。但是,在处理TCP流或文件流时必须使用二进制数据。因此,在node JS中,定义了一个缓冲区类来创建用于存储二进制数据的缓冲区。const buf = Buffer.from('runoob', 'ascii'); 在node JS中,缓冲区类是与node内核一起发布的核心库。缓冲库是node JS带来的一种存储原始数据的方法,它允许节点JS。console.log(buf.toString('hex')); console.log(buf.toString('base64'));原始数据存储在buffer类的实例中。缓冲区类似于整数数组,但它对应于V8堆内存之外的一段原始内存。写入缓冲区写入 Node 缓冲区的语法如下所示:buf.write(string[, offset[, length]][, encoding])string - 写入缓冲区的字符串。offset - 缓冲区开始写入的索引值,默认为 0 。length - 写入的字节数,默认为 buffer.lengthencoding - 使用的编码。默认为 'utf8' 。buf = Buffer.alloc(256); len = buf.write("www.runoob.com"); console.log("写入字节数 : "+ len);输出为:$node main.js写入字节数 : 14buf.write(string[, offset[, length]][, encoding])根据参数偏移量和指定的编码方法将参数字符串数据写入缓冲区。偏移量的默认值为0,默认编码方法为utf8。长度是要写入的字符串的字节大小。返回数字类型,指示写入了多少8位字节流。如果缓冲区没有足够的空间容纳整个字符串,它将只写入部分字符串。默认情况下,长度是缓冲区长度-偏移量此方法似乎无法写入某些字符。buf.writeDoubleBE(value, offset[, noAssert])根据传递的偏移量和指定的endian格式将值写入缓冲区。注意:值必须是有效的64位双精度值。如果参数noassert为真,则不会验证值和偏移参数。这意味着该值可能太大,或者偏移量可能超过缓冲区的末尾,从而导致丢弃该值。默认值为false。从流中读取数据var fs = require("fs"); var data = '';创建可读流var readerStream = fs.createReadStream('input.txt');设置编码为 utf8。readerStream.setEncoding('UTF8');处理流事件 --> data, end, and errorreaderStream.on('data', function(chunk) { data += chunk; }); readerStream.on('end',function(){ console.log(data); }); readerStream.on('error', function(err){ console.log(err.stack); }); console.log("程序执行完毕");管道流管道为输出流到输入流提供了一种机制。通常我们使用它从一个流中获取数据并将其传递给另一个流。创建一个可读流var readerStream = fs.createReadStream('input.txt');创建一个可写流var writerStream = fs.createWriteStream('output.txt');管道读写操作,读取 input.txt 文件内容,并将内容写入到 output.txt 文件中。readerStream.pipe(writerStream);链式流链接是一种将输出流连接到另一个流并创建多个流操作链的机制。链流通常用于管道操作。接下来,我们使用管道和链来压缩和解压缩文件。var fs = require("fs"); var zlib = require('zlib');压缩 input.txt 文件为 input.txt.gzfs.createReadStream('input.txt') .pipe(zlib.createGzip()) .pipe(fs.createWriteStream('input.txt.gz'));执行上述操作后,我们可以看到输入Txt压缩文件input.Txt.gz。接下来,让我们解压文件并创建解压JS文件,代码如下:var fs = require("fs"); var zlib = require('zlib');解压 input.txt.gz 文件为 input.txtfs.createReadStream('input.txt.gz') .pipe(zlib.createGunzip()) .pipe(fs.createWriteStream('input.txt'));模块系统以便启用节点的文件。js相互调用,节点。js提供了一个简单的模块系统。模块是JS应用程序的基本组件,文件和模块是一一对应的。换句话说,node JS文件是一个模块。该文件可以是JavaScript代码、JSON或编译的C/C++扩展。hello.jsfunction Hello() { var name; this.setName = function(thyName) { name = thyName; }; this.sayHello = function() { console.log('Hello ' + name); }; }; 模块接口中唯一的变化是使用模块Exports=Hello,而不是Exports world=function(){}当模块被外部引用时,其接口对象是要输出的Hello对象本身,而不是原始导出。var Hello = require('./hello'); hello = new Hello(); hello.setName('BYVoid'); hello.sayHello();
@[toc]一.TypeScript 鸭子类型Duck类型是一种动态类型和多态形式。在这种风格中,对象的有效语义不是通过从特定类继承或实现特定接口来确定的,而是通过“当前方法和属性的集合”来确定的。var object_name = { key1: "value1", // 标量 key2: "value", key3: function() { // 函数 }, key4:["content1", "content2"] //集合 }在duck类型中,重点是对象的行为可以做什么,而不是对象所属的类型。例如,在不使用duck类型的语言中,我们可以编写一个函数,该函数接受“duck”类型的对象并调用其“walk”和“call”方法。在使用duck类型的语言中,这样的函数可以接受任何类型的对象并调用其“go”和“call”方法。如果这些需要调用的方法不存在,则会引发运行时错误。具有这种正确的“go”和“call”方法的任何对象都可以被函数接受。此行为导致上述表达式,并将此确定类型的方法命名为鸭子类型。interface IPoint { x:number y:number } function addPoints(p1:IPoint,p2:IPoint):IPoint { var x = p1.x + p2.x var y = p1.y + p2.y return {x:x,y:y} } // 正确 var newPoint = addPoints({x:3,y:4},{x:5,y:1}) // 错误 var newPoint2 = addPoints({x:1},{x:4,y:3})二.TypeScript 命名空间名称空间最明确的目的之一是解决重复名称的问题。假设班上有两个叫小明的学生。为了清楚地区分他们,我们必须使用他们名字之外的一些附加信息,例如他们的姓氏(王晓明、李晓明)或他们父母的名字。命名空间定义标识符的可见范围。一个标识符可以在多个名称空间中定义,它在不同名称空间中的含义是无关的。这样,任何标识符都可以在新名称空间中定义,并且它们不会与任何现有标识符冲突,因为现有定义在其他名称空间中。typescript中的命名空间由命名空间定义。语法格式如下:namespace SomeNameSpaceName { export interface ISomeInterfaceName { } export class SomeClassName { } }上面定义了一个名称空间somenamespacename。如果我们需要在外部调用somenamespacename中的类和接口,我们需要向类和接口添加export关键字。要调用另一个命名空间,语法格式为:SomeNameSpaceName.SomeClassName;如果命名空间位于单独的typescript文件中,则应使用三个斜杠///引用它。语法格式如下:/// <reference path = "SomeFileName.ts" />namespace Drawing { export interface IShape { draw(); } }名称空间支持嵌套,也就是说,可以在另一个名称空间中定义名称空间。namespace Runoob { export namespace invoiceApp { export class Invoice { public calculateDiscount(price: number) { return price * .40; } } } }三.TypeScript 模块typescript模块设计有可替换的组织代码。模块在其自己的范围内执行,而不是在全局范围内,这意味着模块中定义的变量、函数和类在模块外部不可见,除非它们使用导出显式导出。同样,我们必须导入其他模块通过导入导出的变量、函数、类等。这两个模块之间的关系是通过在文件级使用导入和导出来建立的。模块使用模块加载器导入其他模块。在运行时,模块加载器的功能是在执行模块代码之前查找并执行模块的所有依赖项。最常见的JavaScript模块加载器是为node JS和require提供服务。用于web应用程序的js。此外,还有systemjs和webpack。模块导出使用关键字export。语法格式如下:// 文件名 : SomeInterface.ts export interface SomeInterface { // 代码部分 }要在其他文件中使用此模块,需要使用导入关键字进行导入:import someInterfaceRef = require("./SomeInterface");TestShape.js 文件代码为:define(["require", "exports", "./Circle", "./Triangle"], function (require, exports, circle, triangle) { function drawAllShapes(shapeToDraw) { shapeToDraw.draw(); } drawAllShapes(new circle.Circle()); drawAllShapes(new triangle.Triangle()); });四.类型脚本声明文件作为JavaScript的超集,typescript在开发过程中不可避免地引用了其他第三方JavaScript库。虽然可以通过直接引用调用库的类和方法,但不能使用类型脚本功能,如类型检查。为了解决这个问题,我们需要删除这些库中的函数和方法体,只保留导出的类型声明。相反,将生成描述JavaScript库和模块信息的声明文件。通过引用此声明文件,可以借用typescript的各种功能来使用库文件。如果我们想使用第三方库,比如jQuery,我们通常会得到一个ID为foo的元素,如下所示:$('#foo'); // 或 jQuery('#foo');但在typescript中,我们不知道$jQuery是什么:jQuery('#foo'); // index.ts(1,1): error TS2304: Cannot find name 'jQuery'.此时,我们需要使用declare关键字定义其类型,以帮助typescript判断传入的参数类型是否正确:declare var jQuery: (selector: string) => any; jQuery('#foo');declare定义的类型仅用于编译时的检查,并将从编译结果中删除。上述示例的编译结果是:jQuery('#foo');声明文件以 .d.ts 为后缀,例如:runoob.d.ts声明文件或模块的语法格式如下:declare module Module_Name { }Typescript导入声明文件语法格式:/// <reference path = " runoob.d.ts" />
@[toc]一.TypeScript的Map对象类型脚本映射对象。map对象保存键值对,可以记住键的原始插入顺序。任何值(对象或原始值)都可以用作键或值。Map是ES6中引入的新数据结构。Typescript使用地图类型和new关键字创建Map:let myMap = new Map();初始化映射,可以以数组的形式传入键值对:let myMap = new Map([ ["key1", "value1"], ["key2", "value2"] ]); Map对象相关功能和属性:MapClear()–删除映射对象的所有键/值对。MapSet()–设置键值对并返回映射对象。MapGet()–返回与键对应的值。如果不存在,则返回undefined。MapHas()–返回一个布尔值,用于确定映射是否包含与键对应的值。MapDelete()–删除映射中的元素,如果删除成功则返回true,如果删除失败则返回false。MapSize–返回映射对象键/值对的数目。MapKeys()-返回一个迭代器对象,其中包含map对象中每个元素的键。MapValues()–返回一个新的迭代器对象,其中包含map对象中每个元素的值。let nameSiteMapping = new Map();设置 Map 对象nameSiteMapping.set("Google", 1); nameSiteMapping.set("Runoob", 2); nameSiteMapping.set("Taobao", 3);获取键对应的值console.log(nameSiteMapping.get("Runoob"));判断 Map 中是否包含键对应的值console.log(nameSiteMapping.has("Taobao")); console.log(nameSiteMapping.has("Zhihu")); 返回 Map 对象键/值对的数量console.log(nameSiteMapping.size);删除 Runoobconsole.log(nameSiteMapping.delete("Runoob")); console.log(nameSiteMapping);移除 Map 对象的所有键/值对 , 清除 MapnameSiteMapping.clear(); console.log(nameSiteMapping);使用 es6 编译:tsc --target es6 test.ts执行上述JavaScript代码,输出结果为:2 true false 3 true Map { 'Google' => 1, 'Taobao' => 3 } Map {}1.1迭代 Map地图对象中的元素按顺序插入。我们可以迭代map对象,每次迭代都返回[key,value]数组。Typescript用于…Of来实现迭代:let nameSiteMapping = new Map(); nameSiteMapping.set("Google", 1); nameSiteMapping.set("Runoob", 2); nameSiteMapping.set("Taobao", 3);迭代 Map 中的 keyfor (let key of nameSiteMapping.keys()) { console.log(key); }迭代 Map 中的 valuefor (let value of nameSiteMapping.values()) { console.log(value); }迭代 Map 中的 key => valuefor (let entry of nameSiteMapping.entries()) { console.log(entry[0], entry[1]); }使用对象解析for (let [key, value] of nameSiteMapping) { console.log(key, value); }二.TypeScript 联合类型联合类型可以通过管道(|)将变量设置为多种类型。指定值时,可以根据设置的类型指定值。注意:只能指定类型。如果分配了其他类型,将报告错误。创建联合类型的语法格式如下:Type1|Type2|Type3声明一个联合类型:var val:string|number val = 12 console.log("数字为 "+ val) val = "Runoob" console.log("字符串为 " + val)编译上述代码以获得以下JavaScript代码:var val; val = 12; console.log("数字为 " + val); val = "Runoob"; console.log("字符串为 " + val);数字为 12字符串为 Runoobfunction disp(name:string|string[]) { if(typeof name == "string") { console.log(name) } else { var i; for(i = 0;i<name.length;i++) { console.log(name[i]) } } } disp("Runoob") console.log("输出数组....") disp(["Runoob","Google","Taobao","Facebook"])上述代码输出结果为:Runoob输出数组....RunoobGoogleTaobaoFacebook2.1扩展知识对于联合类型数据,主要扩展了以下几点。只能访问公共属性或方法通常,使用关节类型是因为无法确定变量最终值的类型。对于联合类型的变量或参数,如果无法确定其特定类型,则只能访问联合类型中所有类型通用的属性或方法。如果访问特定类型特有的属性或方法,将生成错误。function sayRes(res: number | string) { if (res.length > 0) { // Error: 类型“number”上不存在属性“length”。 } }2.2总结联合类型包含所有可能的变量类型;分配除联合类型变量之外的值将产生错误;在无法确定联合类型变量的最终类型之前,只能访问联合类型通用的属性和方法。
AngularJS 的Scope作用域是HTML(视图)和JavaScript(控制器)之间的链接。作用域是一个具有可用方法和属性的对象。作用域可应用于视图和控制器。如何使用范围,在angularjs中创建控制器时,可以将$scope对象作为参数传递:<div ng-app="myApp" ng-controller="myCtrl"> <h1>{{carname}}</h1> </div> <script> var app = angular.module('myApp', []); app.controller('myCtrl', function($scope) { $scope.carname = "Volvo"; }); </script>将scope对象添加到控制器时,视图(HTML)可以获得这些属性。在视图中,不需要添加$scope前缀,只需添加属性名称,例如:{carname}。angularjs的应用程序组成如下:视图,即HTML。模型,当前视图中可用的数据。控制器,即JavaScript函数,可以添加或修改属性。范围是一个模型。Scope是一个JavaScript对象,具有可在视图和控制器中使用的属性和方法。<div ng-app="myApp" ng-controller="myCtrl"> <input ng-model="name"> <h1>{{greeting}}</h1> <button ng-click='sayHello()'>点我</button> </div> <script> var app = angular.module('myApp', []); app.controller('myCtrl', function($scope) { $scope.name = "Runoob"; $scope.sayHello = function() { $scope.greeting = 'Hello ' + $scope.name + '!'; }; }); </script>Scope 作用范围了解当前使用的范围非常重要。在上述两个示例中,只有一个作用域,因此处理起来相对简单。然而,在大型项目中,HTML DOM中有多个作用域。此时,需要知道哪个范围对应于使用的范围。<div ng-app="myApp" ng-controller="myCtrl"> <ul> <li ng-repeat="x in names">{{x}}</li> </ul> </div> <script> var app = angular.module('myApp', []); app.controller('myCtrl', function($scope) { $scope.names = ["Emil", "Tobias", "Linus"]; }); </script>根范围所有应用程序都有一个$rootscope,它可以作用于ng应用程序指令中包含的所有HTML元素。$rootscope可以在整个应用程序中使用。它是每个控制器中作用域的桥梁。rootscope定义的值可以在每个控制器中使用。<div ng-app="myApp" ng-controller="myCtrl"> <h1>{{lastname}} 家族成员:</h1> <ul> <li ng-repeat="x in names">{{x}} {{lastname}}</li> </ul> </div> <script> var app = angular.module('myApp', []); app.controller('myCtrl', function($scope, $rootScope) { $scope.names = ["Emil", "Tobias", "Linus"]; $rootScope.lastname = "Refsnes"; }); </script>AngularJS 服务(Service)什么是服务?在angularjs中,服务是可以在angularjs应用程序中使用的函数或对象。Angularjs拥有30多个内置服务。有一个$location服务可以返回当前页面的URL地址。var app = angular.module('myApp', []); app.controller('customersCtrl', function($scope, $location) { $scope.myUrl = $location.absUrl(); });为什么要使用服务?在许多服务中,例如$location服务,它可以使用DOM中存在的对象,例如窗口位置对象,但是窗口位置对象在angularjs应用程序中有一定的限制。Angularjs将始终监视应用程序并处理事件更改。Angularjs使用$location服务而不是窗口。location对象更好。var app = angular.module('myApp', []); app.controller('myCtrl', function($scope, $http) { $http.get("welcome.htm").then(function (response) { $scope.myWelcome = response.data; }); });var app = angular.module('myApp', []); app.controller('myCtrl', function($scope, $timeout) { $scope.myHeader = "Hello World!"; $timeout(function () { $scope.myHeader = "How are you today?"; }, 2000); });Angularjs$interval服务对应于JS窗口Setinterval函数。var app = angular.module('myApp', []); app.controller('myCtrl', function($scope, $interval) { $scope.theTime = new Date().toLocaleTimeString(); $interval(function () { $scope.theTime = new Date().toLocaleTimeString(); }, 1000); });要使用自定义服务,需要在定义控制器和设置依赖项时独立添加它:app.service('hexafy', function() { this.myFunc = function (x) { return x.toString(16); } });app.controller('myCtrl', function($scope, hexafy) { $scope.hex = hexafy.myFunc(255); });从对象数组获取值时,可以使用过滤器:<ul> <li ng-repeat="x in counts">{{x | myFormat}}</li> </ul>
基于域校准翻译的人像卡通化模型测评模型描述该任务采用一种全新的域校准图像翻译模型DCT-Net(Domain-Calibrated Translation),利用小样本的风格数据,即可得到高保真、强鲁棒、易拓展的人像风格转换模型,并通过端到端推理快速得到风格转换结果。使用方式和范围使用方式:● 直接推理,在任意真实人物图像上进行直接推理;使用范围:● 包含人脸的人像照片,人脸分辨率大于100x100,总体图像分辨率小于3000×3000,低质人脸图像建议预先人脸增强处理。目标场景:● 艺术创作、社交娱乐、隐私保护场景,自动化生成卡通肖像。如何使用在ModelScope框架上,提供输入图片,即可以通过简单的Pipeline调用来使用人像卡通化模型。代码范例import cv2from modelscope.hub.snapshot_download import snapshot_downloadfrom modelscope.pipelines import pipelinemodel_dir = snapshot_download('damo/cv_unet_person-image-cartoon_compound-models', cache_dir='.')img_cartoon = pipeline('image-portrait-stylization', model=model_dir)result = img_cartoon('input.png')cv2.imwrite('result.png', result['output_img'])print('finished!')模型局限性以及可能的偏差● 低质/低分辨率人脸图像由于本身内容信息丢失严重,无法得到理想转换效果,可预先采用人脸增强模型预处理图像解决;● 小样本数据涵盖场景有线,人脸暗光、阴影干扰可能会影响生成效果。训练数据介绍训练数据从公开数据集(COCO等)、互联网搜索人像图像,并进行标注作为训练数据。● 真实人脸数据FFHQ常用的人脸公开数据集,包含7w人脸图像;● 卡通人脸数据,互联网搜集,100+张模型推理流程预处理● 人脸关键点检测● 人脸提取&对齐,得到256x256大小的对齐人脸推理● 为控制推理效率,人脸及背景resize到指定大小分别推理,再背景融合得到最终效果;● 亦可将整图依据人脸尺度整体缩放到合适尺寸,直接单次推理引用如果该模型对你有所帮助,请引用相关的论文:@inproceedings{men2022domain, title={DCT-Net: Domain-Calibrated Translation for Portrait Stylization}, author={Men, Yifang and Yao, Yuan and Cui, Miaomiao and Lian, Zhouhui and Xie, Xuansong}, journal={ACM Transactions on Graphics (TOG)}, volume={41}, number={4}, pages={1--9}, year={2022}}测评原图:人像卡通化模型:
Angularjs表达式Angularjs使用表达式将数据绑定到HTMLAngularjs表达式使用双括号编写:{表达式}。angularjs表达式将数据绑定到HTML,类似于ng bind指令。Angularjs将在写入表达式的位置“输出”数据。Angularjs表达式与JavaScript表达式非常相似:它们可以包含文本、运算符和变量。实例{5+5}或{firstname+“”+LastName}}<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.staticfile.org/angular.js/1.4.6/angular.min.js"></script> </head> <body> <div ng-app> <p>黎燃表达式{{ 5 + 5 }}</p> </div> </body> </html> Angularjs数字类似于JavaScript数字:<div ng-app="" ng-init="quantity=1;cost=5"> <p>总价: {{ quantity * cost }}</p> </div>使用相同的NG bind实例:<div ng-app="" ng-init="quantity=1;cost=5"> <p>总价: <span ng-bind="quantity * cost"></span></p> </div>Angularjs表达式和JavaScript表达式与JavaScript表达式类似,angularjs表达式可以包含字母、运算符和变量。与JavaScript表达式不同,angularjs表达式可以用HTML编写。与JavaScript表达式不同,angularjs表达式不支持条件判断、循环和异常。与JavaScript表达式不同,angularjs表达式支持过滤器。Angularjs指令Angularjs使用称为指令的新属性扩展HTML。Angularjs通过内置指令向应用程序添加函数。Angularjs允许自定义指令。angularjs指令是一个扩展的HTML属性,前缀为ng-。ng app指令初始化angularjs应用程序。ng init指令初始化应用程序数据。ng模型指令将元素值(例如输入字段的值)绑定到应用程序。<div ng-app="" ng-init="firstName='John'"> <p>在输入框中尝试输入:</p> <p>姓名:<input type="text" ng-model="firstName"></p> <p>你输入的为: {{ firstName }}</p> </div>ng-app指令告诉angularjs<div>元素是angularjs应用程序的“所有者”。数据绑定上面示例中的{firstname}表达式是angularjs数据绑定表达式。angularjs中的数据绑定将angularjs表达式与angularjs数据同步。{{firstname}}通过ng model=“firstname”同步。在下一个示例中,两个文本字段由两个ng模型指令同步:<div ng-app="" ng-init="quantity=1;price=5"> <h2>价格计算器</h2> 数量: <input type="number" ng-model="quantity"> 价格: <input type="number" ng-model="price"> <p><b>总价:</b> {{ quantity * price }}</p> </div>ng-repeat 指令用在一个对象数组上:Ng应用程序指令ng app指令定义angularjs应用程序的根元素。加载网页时,ng app指令将自动引导(自动初始化)应用程序。稍后,将了解ng app如何通过值(例如ng app=“mymodule”)连接到代码模块。<div ng-app="" ng-init="names=[ {name:'Jani',country:'Norway'}, {name:'Hege',country:'Sweden'}, {name:'Kai',country:'Denmark'}]"> <p>循环对象:</p> <ul> <li ng-repeat="x in names"> {{ x.name + ', ' + x.country }} </li> </ul> </div>Ng初始化指令ng init指令定义angularjs应用程序的初始值。通常,不使用ng init。将用控制器或模块替换它。Ng模型教学ng模型指令将HTML元素绑定到应用程序数据。ng模型指令还可以:为应用程序数据(数字、电子邮件、必填项)提供类型验证。为应用程序数据提供状态(无效、脏、触摸、错误)。为HTML元素提供CSS类。将HTML元素绑定到HTML表单。Ng重复指令ng repeat指令为集合(数组)中的每个项克隆一次HTML元素。创建自定义指令除了angularjs的内置指令外,我们还可以创建自定义指令。可以使用。用于添加自定义指令的指令函数。要调用自定义指令,需要向HTML元素添加自定义指令名称。驼峰方法用于将指令命名为runoobdirective,但在使用它时需要将其拆分为“-”。Runoob指令:<body ng-app="myApp"> <runoob-directive></runoob-directive> <script> var app = angular.module("myApp", []); app.directive("runoobDirective", function() { return { template : "<h1>自定义指令!</h1>" }; }); </script> </body>可以通过以下方式调用指令:素名属性类名笔记<runoob-directive></runoob-directive><div runoob-directive></div><div class="runoob-directive"></div><!-- directive: runoob-directive -->通过添加restrict属性并将值设置为“a”,可以设置指令只能通过属性调用:var app = angular.module("myApp", []); app.directive("runoobDirective", function() { return { restrict : "A", template : "<h1>自定义指令!</h1>" }; });极限值可以是以下值:E用作元素名称A用作属性C用作类名M用作注释restrict的默认值是ea,也就是说,可以通过元素名和属性名调用指令。
Redis管道技术Redis是一种基于客户机-服务器模型和请求/响应协议的TCP服务。这意味着请求通常将遵循以下步骤:客户端向服务器发送查询请求,并侦听套接字返回,通常处于阻塞模式,等待服务器响应。服务器处理该命令并将结果返回给客户端。Redis管道技术允许客户端在服务器没有响应时继续向服务器发送请求,最后一次读取所有服务器的响应。实例要查看redis管道,只需启动redis实例并输入以下命令:$(echo -en "PING\r\n SET runoobkey redis\r\nGET runoobkey\r\nINCR visitor\r\nINCR visitor\r\nINCR visitor\r\n"; sleep 10) | nc localhost 6379 +PONG +OK redis :1 :2 :3在上面的示例中,我们使用ping命令检查redis服务是否可用。然后我们将runoobkey的值设置为redis,然后我们得到runoobkey的值,并使访问者自动增加三倍。在返回的结果中,我们可以看到这些命令一次提交到redis服务,最后一次读取所有服务器的响应管道技术最显著的优势是提高redis服务的性能。一些测试数据require 'rubygems' require 'redis' def bench(descr) start = Time.now yield puts "#{descr} #{Time.now-start} seconds" end def without_pipelining r = Redis.new 10000.times { r.ping } end def with_pipelining r = Redis.new r.pipelined { 10000.times { r.ping } } end bench("without pipelining") { without_pipelining } bench("with pipelining") { with_pipelining }在接下来的测试中,我们将使用redis的Ruby客户端来支持流水线技术的特性,并测试流水线技术的提速效果。在局域网中的Mac OS X系统上执行的上述简单脚本的数据表明,在开启管道操作后,往返延迟已显著改善。Redis分区是将数据划分为多个redis实例的过程,因此每个实例只保存密钥的子集。分区的优势通过使用多台计算机的内存总和,我们可以构建一个更大的数据库。通过多核和多台计算机,我们可以扩展我们的计算能力;通过多台计算机和网络适配器,我们可以扩展网络带宽。分区不足redis的一些功能在分区方面表现不佳:通常不支持涉及多个键的操作。例如,当两个集合映射到不同的redis实例时,您无法对这两个集合执行交集操作。不能使用涉及多个密钥的Redis事务。使用分区时,数据处理更加复杂。例如,您需要处理多个rdb/aof文件,并备份来自多个实例和主机的持久文件。添加或删除容量也很复杂。大多数redis集群支持在运行时添加和删除节点的透明数据平衡,但其他系统(如客户端分区和代理)不支持此功能。然而,一种叫做预硬化的技术是有帮助的。分区类型Redis有两种类型的分区。假设有四个redis实例R0、R1、R2、R3和多个键代表用户,例如user:1和user:2。对于给定的键,有许多不同的方法来选择该键存储在哪个实例中。换句话说,有不同的系统将密钥映射到redis服务。范围分区最简单的分区方法是按范围,即将特定范围内的对象映射到特定的redis实例。例如,ID为0到10000的用户将保存到实例R0,ID为10001到20000的用户将保存到R1,依此类推。该方法是可行的,可以在实践中使用。缺点是有一个从区间范围到实例的映射表。此表需要管理。同时,它还需要各种对象的映射表,这通常不是redis的好方法。哈希分区另一种分区方法是哈希分区。这适用于任何键,并且不需要是对象名称:该形式与以下描述一样简单:使用哈希函数将密钥转换为数字,例如,使用CRC32哈希函数。在键foobar上执行CRC32(foobar)将输出一个类似于93024922的整数。取这个整数的模并将其转换为0到3之间的数字,然后这个整数可以映射到四个redis实例之一。93024922%4=2,这意味着键foobar应该保存在R2实例中。注:模运算是除法的余数,在许多编程语言中通常由%运算符实现。
@[toc]Redis 协议的高性能解析器虽然redis协议很容易阅读和实现,但它可以以类似于二进制协议的性能实现。Resp使用前缀长度传输大容量数据,因此它不需要扫描负载以查找JSON之类的特殊字符,也不需要引用需要发送到服务器的负载。批次和多批次长度可以使用代码进行处理,这些代码对每个字符执行单个操作,并同时扫描CR字符,例如以下C代码:Resp使用前缀长度传输多行数据,因此它不需要扫描负载以查找JSON之类的特殊字符,也不需要引用需要发送到服务器的负载。可以用代码处理多行和多行长度。该代码对每个字符执行单个操作,并同时扫描CR字符。#include <stdio.h> int main(void) { unsigned char *p = "$123\r\n"; int len = 0; p++; while(*p != '\r') { len = (len*10)+(*p - '0'); p++; } /* Now p points at '\r', and the len is in bulk_len. */ printf("%d\n", len); return 0; }识别第一个CR后,您可以跳过它和下面的LF,而无需任何处理。然后,可以使用不以任何方式检查有效负载的单个读取操作来读取大容量数据。最后,剩余的Cr和LF字符将在不进行任何处理的情况下丢弃。Redis协议的性能与二进制协议相当。更重要的是,它很容易在大多数高级语言中实现,从而减少了客户端软件中的错误数量。C语言实现,虽然C有助于redis的性能,但语言不是核心因素。纯内存输入/输出。与其他基于磁盘的数据库相比,redis的纯内存操作具有天然的性能优势。输入/输出多路复用,基于epoll/select/kqueue等输入/输出多路复用技术,实现高吞吐量的网络输入/输出。在单线程模型中,一个线程不能使用多个内核,但另一方面,它避免了多线程频繁切换上下文和锁等同步机制的开销。单线程对于DB,CPU通常不是瓶颈,因为大多数请求不是CPU密集型的,而是i/o密集型的。对于redis,如果不考虑rdb/aof等持久化方案,redis是一个完整的纯内存操作,执行速度非常快。因此,这部分操作通常不是性能瓶颈。redis真正的性能瓶颈在于网络i/o,即客户端和服务端之间的网络传输延迟。因此,redis选择单线程i/o多路复用来实现其核心网络模型。避免过多的上下文切换开销在多线程调度过程中,需要在CPU之间切换线程上下文,上下文切换涉及一系列寄存器替换,如程序计数器、堆栈指针和程序状态字、程序堆栈重置,甚至CPU缓存和TLB快速表的替换。如果在进程内进行多线程切换,则效果更好,因为单个进程内的多线程共享进程地址空间,因此,线程上下文比进程上下文小得多。如果是跨进程调度,则需要切换整个进程地址空间。如果是单线程,则可以避免进程中频繁切换线程的开销,因为程序始终在进程中的单线程中运行,并且不存在多线程切换的场景。期望的多线程编程与实际的多线程编程相比:网络模型Redis协议通过TCP,客户端和Redis实例保持双工连接,如下图所示:序列化实现序列化的实现相对简单。在执行前一个命令后,同一个连接发送第二个请求。如下图所示:单连接吞吐量 = 1 / (2*网络延迟 + 服务器处理时间 + 客户端处理时间)redis对单个请求的处理时间(10微秒)通常比LAN的延迟小1个数量级。因此,在串行模式下,单个连接在网络上等待的时间最多,服务器的处理能力没有得到充分利用。
Bootstrap是用于快速开发web应用程序和网站的前端框架。Bootstrap基于HTML、CSS和JavaScript。基本结构bootstrap提供了包含网格系统、链接样式和背景的基本结构。这将在bootstrap的基本结构中详细解释。CSS:bootstrap具有以下特性:全局CSS设置、定义基本HTML元素样式、可扩展类和高级网格系统。这将在引导CSS部分详细解释。组件:引导包含十多个可重用组件,用于创建图像、下拉菜单、导航、警告框、弹出框等。这将在布局组件部分详细解释。JavaScript插件:bootstrap包含十多个自定义jQuery插件。您可以直接或逐个包含所有插件。实例<div class="container"> <div class="jumbotron"> <h1>黎燃我的第一个 Bootstrap 页面</h1> <p>黎燃</p> </div> <div class="row"> <div class="col-sm-4"> <h3>Column 1</h3> <p>黎燃</p> <p>黎燃</p> </div> <div class="col-sm-4"> <h3>Column 2</h3> <p>黎燃</p> <p>黎燃</p> </div> <div class="col-sm-4"> <h3>Column 3</h3> <p>黎燃</p> <p>黎燃</p> </div> </div> </div>下载 Bootstrap可以下载 Bootstrap 的最新版本 http://getbootstrap.com/ 预编译的 Bootstrap如上图,可以看到已编译的CSS和JS(bootstrap.),以及已编译的压缩CSS和JS(bootstrap.min.)。它还包括字形的字体,这是一个可选的引导主题。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>在线尝试 Bootstrap 实例</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <h1>Hello, world!</h1> </body> </html>超大屏幕bootstrap支持的另一个特性是jumbotron。顾名思义,这个组件可以增加标题的大小,并为登录页的内容添加更多的边距。使用jumbotron的步骤如下:创建with类。用于<div>的Jumbotron容器。除了较大的字体外,字体重量减少到200。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Bootstrap 实例 - 超大屏幕(Jumbotron)</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="jumbotron"> <h1>欢迎登陆页面!</h1> <p>这是一个超大屏幕(Jumbotron)的实例。</p> <p><a class="btn btn-primary btn-lg" role="button"> 学习更多</a> </p> </div> </div> </body> </html>运行结果如下:为了获得全宽无圆角的大屏幕,请使用。Jumbotron类以外的所有。容器类,如下:<div class="jumbotron"> <div class="container"> <h1>欢迎登陆页面!</h1> <p>这是一个超大屏幕(Jumbotron)的实例。</p> <p><a class="btn btn-primary btn-lg" role="button"> 学习更多</a> </p> </div> </div>结果如下:欢迎登陆页面!这是一个超大屏幕(Jumbotron)的实例。
jQuery Password ValidationjQuery密码验证插件扩展了jQuery验证插件,并提供了两个组件:用于评估密码相关因素的函数:例如,大小写字母的混合、字符(数字、特殊字符)的混合、长度以及与用户名的相似性(可选)。用户定义的验证插件方法,使用评估函数显示密码强度。可以本地化显示的文本。可以简单地自定义强度显示的外观,本地化消息显示,并将其集成到现有表单中。如果要使用密码验证插件,请在输入中添加一个类“密码”,并在需要显示表单的位置添加显示强度的基本标记:<form id="register"> <label for="password">Password:</label> <input class="password" name="password" id="password" /> <div class="password-meter"> <div class="password-meter-message"> </div> <div class="password-meter-bg"> <div class="password-meter-bar"></div> </div> </div> </form>对表单应用 Validate 插件:$(document).ready(function() { $("#register").validate(); });可以重载验证程序Passwordrating实现了不同的评估方法。或过载$。验证器。对消息进行密码分级以提供其他消息,例如本地化。Password Validation示例演示<!doctype html> <html> <head> <meta charset="utf-8"> <title>Makes "field" required to be the same as #other</title> <link rel="stylesheet" href="https://jqueryvalidation.org/files/demo/site-demos.css"> </head> <body> <form id="myform"> <label for="password">Password</label> <input id="password" name="password" /> <br/> <label for="password_again">Again</label> <input class="left" id="password_again" name="password_again" /> <br> <input type="submit" value="Validate!"> </form> <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script> <script src="https://jqueryvalidation.org/files/dist/jquery.validate.min.js"></script> <script src="https://jqueryvalidation.org/files/dist/additional-methods.min.js"></script> <script> // just for the demos, avoids form submit jQuery.validator.setDefaults({ debug: true, success: "valid" }); $( "#myform" ).validate({ rules: { password: "required", password_again: { equalTo: "#password" } } }); </script> </body> </html>运行结果如下:深入理解JSONPJsonp(带填充的JSON)是JSON的“使用模式”,它使网页能够从其他域名(网站)获取数据,即跨域读取数据。为什么我们需要一种特殊的技术(jsonp)来访问来自不同领域(网站)的数据?这是因为同源策略。同源策略是Netscape提出的一种著名的安全策略。现在,所有支持JavaScript的浏览器都将使用此策略。服务器的Jsonp格式数据如果客户想要访问。假设客户希望返回数据:[customername1”,“customername2]。实际返回给客户机的数据显示为:回调函数([“customername1”、“customername2]”)。PHP代码中的服务器端文件jsonp是:<?php header('Content-type: application/json'); //获取回调函数名 $jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']); //json数据 $json_data = '["customername1","customername2"]'; //输出jsonp格式的数据 echo $jsoncallback . "(" . $json_data . ")"; ?>客户端实现 callbackFunction 函数<script type="text/javascript"> function callbackFunction(result, methodName) { var html = '<ul>'; for(var i = 0; i < result.length; i++) { html += '<li>' + result[i] + '</li>'; } html += '</ul>'; document.getElementById('divCustomers').innerHTML = html; } </script>页面展示<div id="divCustomers"></div>参数value: 要编码的值。该函数只对 UTF-8 编码的数据有效。options:由以下常量组成的二进制掩码 JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR。json_decodejson_ String:要解码的json字符串,必须是UTF-8编码数据Assoc:参数为true时,返回数组;如果为false,则返回该对象。深度:指定递归深度的整数类型参数选项:二进制掩码,目前只支持JSON_ BIGINT_ AS_ STRING如何解码 JSON 数据:<?php $json = '{"a":1,"b":2,"c":3,"d":4,"e":5}'; var_dump(json_decode($json)); var_dump(json_decode($json, true)); ?>
一.为什么要学习layui?它是一个开源的Web UI解决方案,采用自己的经典模块化规范,遵循原生HTML/CSS/JS开发模式。它很容易使用。它的风格简单轻巧,而组件优雅而丰富。从源代码到使用方法的每一个细节都经过精心雕琢,非常适合快速开发的web界面。Layui不同于那些基于MVVM的前端框架,但它并不违背这条道路,而是相信回归自然的方式。准确地说,它更面向后端开发人员。不需要参与前端工具,只需面对浏览器本身,让您需要的所有元素和交互都来自这里。二.目录结构目录结构如下:├─css //css目录 │ │─modules //模块 css 目录(一般如果模块相对较大,我们会单独提取,如下:) │ │ ├─laydate │ │ └─layer │ └─layui.css //核心样式文件 ├─font //字体图标目录 └─layui.js //核心库三.CDN 方式引入UNPKG 和 CDNJS 均为第三方开源免费的 CDN,通过 NPM/GitHub 实时同步。UNPKG 引入示例<!-- 引入 layui.css --> <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css"> <!-- 引入 layui.js --> <script src="//unpkg.com/layui@2.6.8/dist/layui.js">四.经典的模块化”Layui被定义为“经典的模块化”,并不是为了炫耀她有多么优秀,而是为了刻意避免当前JS社区的主流解决方案,并尝试以尽可能简单的方式解释效率!所谓的经典在于她对回归自然的痴迷。以当前浏览器普遍认可的方式组织模块!我们相信,这对中国的大多数程序员来说是一个很好的指南,可以帮助他们在未来从旧时代过渡到新标准。因此,layui本身并不完全遵循AMD时代。准确地说,她试图建立自己的模型:layui 模块的定义(新 js 文件)layui.define([mods], function(exports){ //…… exports('mod', api); }); //layui 模块的使用 layui.use(['mod1', 'mod2'], function(args){ var mod = layui.mod1; //…… });有早期AMD的影子,但不受commonjs的规章制度限制。Layui认为这种轻量级组织比webpack更适合大多数场景。因此,她坚持采用经典的模块化,正是为了让人们避免复杂的工具配置,回归简单,安静高效地编织原始的HTML/CSS/JS。然而,layui不是像requirejs那样的模块加载器,而是一个UI解决方案。与bootstrap的不同之处在于,layui结合了自己对经典模块化的理解。这使在layui组织的框架内更好地用更易维护的代码编织丰富的用户界面。五.建立模块入口可以根据layui的模块规范创建条目文件,并使用layui use()加载条目文件,如下所示:存放新模块的目录,注意,不是 layui 的模块目录:<script src="./layui/layui.js"></script> <script> layui.config({ base: '/res/js/modules/' }).use('index'); </script>上述指数为指数。js在/RES/js/modules/目录中。其内容应如下:layui.define(['layer', 'form'], function(exports){ var layer = layui.layer ,form = layui.form; layer.msg('Hello World'); exports('index', {}); });注意,这里是模块输出的核心,模块名必须和 use 时的模块名一致。六.管理扩展模块除了使用layui的内置模块外,还需要加载扩展模块(可以简单地理解为符合layui模块规范的JS文件)。我们假设许多扩展模块存储在项目中,如下所示://mod1.js layui.define('layer', function(exports){ //… exports(mod1, {}); }); //mod2.js,假设依赖 mod1 和 form layui.define(['mod1', 'form'], function(exports){ //… exports(mod2, {}); }); //mod3.js //… //main.js 主入口模块 layui.define('mod2', function(exports){ //… exports('main', {}); });在某些模块依赖性之后,还可以将上述扩展模块组合到一个文件中进行加载。我们可以使用gulp将上述扩展模块(如mod1、mod2、MOD3和main)合并到一个模块文件中:main.js。此时,只需加载它:<script src="./layui/layui.js"></script> <script> layui.config({ base: '/res/js/modules/' //你的扩展模块所在目录 }).use('main'); // </script>这里的 main 模块包含了 mod1、mod2、mod3 等等扩展模块如我们所见,我们最多只需要加载两个JS文件:layui,js和main.js。这将显著减少对静态资源的请求。七.全局配置在使用该模块之前,请全局配置一些参数,尽管大多数情况下不需要这样做。因此,我们目前提供的全局配置项很少,这也是为了减少一些不必要的工作,并使使用尽可能简单。当前支持的全局配置项如下:layui.config({ dir: '/res/layui/' ,version: false ,debug: false ,base: '' });layui.js 所在目录(如果是 script 单独引入 layui.js,无需设定该参数)一般可无视一般用于更新模块缓存,默认不开启。设为 true 即让浏览器不缓存。也可以设为一个固定的值,如:201610用于开启调试模式,默认 false,如果设为 true,则JS模块的节点会保留在页面设定扩展的 layui 模块的所在目录,一般用于外部模块扩展如果对layui JS本身执行动态加载和其他特殊场景的看法是正确的,那么上述由config设置的layui-the-dir参数将变得无效。它将在加载某些组件的依赖文件(CSS)后执行。此时,我们可以动态加载layui JS定义了一个我们事先约定的全局对象:<script> var LAYUI_GLOBAL = { dir: '/res/layui/' }; </script>八.定义模块使用此方法,可以在新的JS文件中定义layui模块。参数mods是可选的,用于声明模块依赖的模块。回调是加载模块后的回调函数。它返回一个exports参数以输出模块的接口。layui.define(function(exports){ //do something exports('demo', { msg: 'Hello Demo' }); });与requirejs最大的区别在于接口输出。Exports是一个接受两个参数的函数。第一个参数是模块名,第二个参数是模件接口。声明上述模块后,可以在外部使用它。演示将在layui对象下注册,可以使用VaR demo=layui demo获得模块接口。还可以在定义模块时声明模块所需的依赖项,例如:layui.define(['layer', 'laypage', 'mod1'], function(exports){ //此处 mod1 为你的任意扩展模块 //do something exports('demo', { msg: 'Hello Demo' }); });上述的 layer、laypage 都是 layui 的内置模块。layui - 在每一个细节中,用心与你沟通
2023年07月
2023年05月
2023年01月
2022年12月
2022年11月
2022年10月