关于typescript:浅谈MaxCompute资源规划管理及评估

简介: 本文次要介绍如何进行MaxCompute存储资源和计算资源的评估及布局治理。 一、MaxCompute资源布局背景介绍MaxCompute资源次要有两类:存储资源、计算资源(蕴含cpu和内存)。存储资源用于存储MaxCompute的库表数据,计算资源用于运行sql、mr等工作。最佳的MaxCompute资源布局计划可能达到以下几个目标:• 数据存储资源足够,既可能存储以后的所有存量库表数据,也可能存储将来一段时间的增量数据;• 计算资源短缺,然而不能节约。计算资源量可能满足所有数据计算工作,且尽可能减少资源节约状况。这样消耗的资源费用起码;• 被解决的数据量微小、消耗计算资源较多的大型工作,可能会将quota group资源组耗尽,造成其余工作无奈获取到计算资源而阻塞。MaxCompute资源布局计划必须可能尽量避免这种状况;• 不同优先级的计算工作可能尽量互不烦扰,无限保障高优先级的工作获取到足够计算资源;• 可能满足时段的差异化资源需要,满足对资源隔离(生产/开发/自助剖析)不同工作负载的能力,防止互相烦扰,同时更大化进步资源使用率。MaxCompute资源布局的最终目标就是可能满足上述几点需要,企业客户耗费最低资源费用的状况下,满足数据存储需要,以及数据处理工作对计算资源的需要。本文内容次要基于阿里私有云MaxCompute环境。私有云和专有云环境的MaxCompute资源布局有比拟大的差别,比方:在私有云环境,存储资源和计算资源是应用整个阿里云区域的资源池,简直不必放心底层到底有多少台服务器进行撑持,能够近乎认为私有云底层的资源池是有限的;然而在专有云环境,整个专有云都是企业客户独享的资源,必须依据存储资源和计算资源量布局服务器数量&&服务器规格。本文次要探讨私有云MaxCompute的资源布局。 二、MaxCompute存储资源布局2.1 计存比在介绍存储计划抉择之前,先说一个罕用的概念:“计存比”。计存比就是计算CU数量和理论存储数量TB的比值。比方资源分配:50CU计算资源,存储数据量是10TB。那么,50CU/10TB=5,计存比=5。 2.2 存储资源布局倡议对于存储资源,MaxCompute提供两种计费形式:• 按量付费:MaxCompute以小时级别采集每个我的项目空间下以后的存储,并以 project 我的项目空间为根本单位,计算我的项目空间当天的存储平均值。而后再乘以单价(元/GB/天),最初的失去每天的存储费用。 • 套餐资源:MaxCompute包年包月套餐蕴含预留的计算资源和存储资源,每种套餐固定计算资源CU量和存储资源。套餐中的存储资源是指每天固定的存储资源,超过的局部另外按量计费。套餐资源目前只反对固定的几个套餐,见下图所示: 阿里云提供的第二种计划,三种套餐的计存比是固定的,而且计存比都在1左右。这种固定资源套餐的计存比偏低,适宜存储量大、计算工作较少的企业客户。固定资源套餐的计算资源CU量是固定的,无奈应答计算资源需求量猛增的状况。比方企业平时的数据批量解决工作能够失常运行,在双11、618等大促流动期间的数据批量解决工作就会呈现重大阻塞。对于存储资源布局,笔者倡议:• 当预估企业客户将来一段时间的数据存储总量比拟大(100TB以上)、计算工作少(计存比小于1.5),抉择阿里云的固定套餐资源;• 当客户须要更加灵便的存储资源空间,同时计算资源CU量不受存储空间限度,倡议抉择按量付费形式。应用多少存储空间,耗费多少存储费用。至于计算资源CU布局,依照企业客户的理论需要,独自进行布局。 三、MaxCompute计算资源布局3.1 MaxCompute计算资源简介对于计算资源布局,笔者首先倡议:在我的项目测试阶段,全副都采纳按量付费形式。因为开发测试阶段,耗费的计算资源CU数量不多,采纳按量付费形式更加便宜。对于MaxCompute计算资源按量付费的计费规定,读者能够具体参考官网文档:https://help.aliyun.com/document_detail/112752.html我的项目开发实现,正式进入到上线阶段,倡议购买包年包月的计算资源CU配额,因为是固定的CU配额,不会在阿里云公共计算资源池去抢占计算资源,能够顺利地为企业客户预留足够的CU资源。计费形式如下所示: 本章节次要介绍我的项目上线之后,如何购买适合的包年包月固定CU数量。对于计算资源布局,本文介绍在我的项目实际中罕用的两种计划:办法1:依照以往教训先确定计存比,而后预估数据容量,最初失去计算计算资源CU量;办法2:抉择在我的项目正式上线前、或者在我的项目正式上线运行一小段时间之后,评估计算资源CU耗费的CPU总时长,而后再依据不同工作独自耗费的CPU时长、工作的优先级、企业客户要求每天所有工作必须在哪个时间段运行实现,综合思考这几个因素,最初失去计算资源CU量的最小最大值,用数学表达式示意就是: 本文分两个章节别离介绍这两种罕用的计算资源预估办法。 3.2依照计存比办法预估计算资源第一步:评估存储容量依照3年我的项目周期计算:存储容量 = 以后数据存量 + 每月预估数据增量*月数。以后数据存量很容易失去,在数据上云实现之后就能够失去以后数据存量。每月预估数据增量须要在数据上云之后两三个月,依据增量总值除以月数,失去每月预估增量平均值。当然,如果还要思考将来数据中台承载更多业务、每月数据增量会变大等因素,能够将以后计算失去的每月预估数据增量值乘以倍数。倡议每半年预估一次存储总容量,而后每半年调整一次计算资源CU量。第二步:预预计存比依照我的项目开发测试阶段、以及上线运行一两个月的状况,能够大略预预计存比。依据理论状况,计存比个别配置2-10。如果客户每天运行的数据批量解决工作很多,且sql程序计算复杂度高,计存比能够抉择10;如果客户每天运行的数据批量解决工作比拟少,且sql程序计算复杂度不高,计算比能够抉择2;如果客户每天运行的数据批量解决工作适中,sql程序计算复杂度也适中,计存比能够抉择2-10之间的适合值。倡议每半年评估一次计存比,而后每半年调整一次计算资源CU量。第三步:预估计算资源CU量依照第一步预估的每半年存储资源总量,联合每半年评估的计存比值,存储资源总量 *计存比 = 计算资源CU总量。 预估失去计算资源CU总量,进而每半年利用该企业主账号调整一次MaxCompute计算资源CU总量。依照计存比预估企业我的项目须要耗费的计算资源CU总量,有很多须要预估的变量,包含数据存储总量、计存比,很可能预估不精确。因而,该办法要求我的项目的技术负责人领有较多的我的项目施行教训,可能在每一步预估都尽可能精确。 3.3依照我的项目理论耗费CU量进行资源划分抉择在我的项目正式上线前、或者在我的项目正式上线运行一小段时间之后,评估计算资源CU耗费的CPU总时长,而后再依据不同工作独自耗费的CPU时长、工作的优先级、企业客户要求每天所有工作必须在哪个时间段运行实现,综合思考这几个因素,最初失去计算资源耗费费用起码的最佳CU数量。 3.3.1查看计算资源耗费状况在进行资源布局之前,须要首先搞清楚过来一段时间MaxCompute计算资源的耗费状况。读者能够参考https://help.aliyun.com/document_detail/135432.html具体介绍如何开明和查看MaxCompute的information_schema信息。MaxCompute元数据表有很多,本文只须要利用到一张表:TASKS_HISTORY。这张元数据表记录了所有MaxCompute 计算工作的资源耗费状况。读者能够参考 https://help.aliyun.com/document_detail/135433.html#title-r2c-tak-zfi具体介绍元数据表TASKS_HISTORY的字段信息,其中最重要的字段信息是:cost_cpu 和 cost_mem,别离示意: 本文次要借助CPU消耗量(也就是上图的cost_cpu字段对应的每个工作耗费的cpu数量)进行计算资源CU数量的布局。须要留神的是,cost_cpu字段的含意:MaxCompute计算工作作业的CPU消耗量。100示意1 cores,比方官网的例子:10 core运行5s,cost_cpu为10100*5=5000)。那么cost_cpu字段示意的是“cpu核数消耗量 100 工作运行工夫秒”。因为cost_cpu依照秒统计,对于理论我的项目评估太过于精密,咱们通常将cost_cpu 除以 100、而后再除以3600,失去cores h (cpu核数 小时)。这样不便评估理论我的项目在规定时间段内运行完所有工作须要quota group资源组的起码计算资源CU数量。如下如所示某个MaxCompute project 的MaxCompute计算工作的资源耗费状况: 3.3.2 布局计算资源CU数量通过3.3.1章节的内容,咱们能够查看到MaxCompute project某一天运行的所有计算工作耗费的CPU核数*小时。计算资源CU数量布局的细则:Step1:首先,计算失去均匀每天运行所有工作耗费的cost_cpu总和(须要除以100,能力失去真正的cpu核数 秒,而后再除以 3600,失去耗费的 “cpu核数 小时”)。举个例子:MaxCompute project均匀每天须要运行1000个工作,这些工作耗费的cost_cpu别离是 W1、W2 …… W1000。那么须要将W1 + W2+ …… + W1000 失去每天运行所有工作耗费的cost_cpu总和Wz。留神:数据中台个别会划分6个MaxCompute project,别离是:• ods_dev:贴源层开发测试project;• ods_prod:贴源层生产project;• cdm_dev:公共层开发测试project;• cdm_prod:公共层生产project;• ads_dev:应用层开发测试project;• ads_prod:应用层生产project;须要将这6个MaxCompute project的所有数据计算工作的cost_cpu相加失去cost_cpu总和Wz。当然,大部分读者应用MaxCompute进行数据处理,并非须要建设数据中台。任何须要应用MaxCompute进行数据处理的利用场景,都能够依照理论划分的MaxCompute project,将这些MaxCompute project涵盖的所有数据处理工作耗费的cost_cpu相加失去总和Wz。Step2:依照上述介绍的阿里云官网详情介绍,cost_cpu须要除以100才是真正耗费的CPU核数。同时,cost_cpu依照秒进行度量,咱们个别会依照小时进行度量。因而,须要将cost_cpu总和Wz除以100、再除以3600,最初失去均匀每天运行所有工作耗费 “cpu核数 *小时”,本文假如这个值为W。Step3:征询客户数据批量解决工作须要在每天的哪些时间段运行实现。举个例子:客户要求在深夜零点之后、凌晨6点之前必须将所有数据批量解决工作运行实现。那么每天可能运行的总时长都是6个小时。本文假如所有工作必须在N个小时运行实现。Step4:利用上述失去的每天所有工作[cpu核数 *小时 / 工作运行时长N个小时],就能够失去该客户的MaxCompute project须要调配的计算资源CU数量的最小值:W/N。W/N的前提是数据处理工作的cost_cpu很稳固,而且在这N个小时内,所有工作都随时在运行,不存在任何闲暇的工夫。然而,理论我的项目可能会因为某些起因导致数据计算工作运行工夫缩短(比方参加计算的数据量减少),相当于W会变大;同时,因为DataWorks/Dataphin调度工作还会产生很多延迟时间、工作获取CU资源也会耽搁很多工夫,这部分延迟时间会加大工作之间运行的工夫距离,真正用于运行工作的工夫会小于N。W/N的分母理论变大、分子理论变小,进而变相地要求减少计算资源,以便让工作获取更多资源进而运行地更加疾速。因而个别状况下,会在上述失去的W/N后果根底上增加一倍。依照上述4个步骤,能够预估计算失去企业能够须要购买的CU数量。 ...

October 15, 2020 · 1 min · jiezi

关于typescript:从零编写-发布一个-VSCode-扩展

年初在 TO-DO 上打算了一个工作,是以解决本身需要为目标,开发一个 VSCode 扩大。 需要最近一个小需要来了,是否在不来到VSCode编辑器的状况下,查看文件或者文件夹的大小。 调研恰好目前扩大市场上没有统计 ???? 文件夹相干的扩大,只有统计 ???? 单个文件的,比方:File Size 所以还是本人造轮子吧 预览 试用从网页装置,Folder Size,或者从扩大商店搜寻 开发疾速入门三个比拟好的入门办法: 浏览官网文档应用官网示例疾速入门浏览同类型扩大源码大家都晓得 VSCode 是用 TypeScript 写的,所以 VSCode 扩大天然是拥抱 TS 的,当然也能够用 JS 编写。 浏览同类型扩大代码的时候,发现大部分的扩大实现的统计文件信息的形式都不太一样,有的简略,有的简单。 其实我这个需要官网文档上的例子齐全就能够 Cover 住,我做的呢,只是整合了我所须要的性能个性,关上/抉择文件的时候,能够在 Status Bar (状态栏)显示格局为:[File | Folder] 这样的文案。这样我就能够在不来到 VSCode 编辑器的状况下统计到了文件及文件夹的信息。 性能实现目前 Folder Size 具备三个小性能: 统计文件大小统计文件夹大小统计文件夹中文件的个数这些性能都是基于 workspace 的事件钩子去触发的,在关上或切换文件、保留文件、敞开文件时触发统计,上面会讲到 API 用法。 调试没玩明确如何用 VSCode 自带的 debug 调试扩大的我,只能用打印内容来调试,VSCode Extension 有几个能够用于打印调试的性能。比方: OutputChannelshowInformationMessageshowTextDocument利用 vsce 工具进行打包为 VSIX 各式的文件,即 VSCode 扩大本地装置格局。也能够将文件发给别人测试。 公布扩大公布须要注册 Azure 账号,VSCode 应用 Azure DevOps 作为扩大市场服务,简略四步: ...

October 13, 2020 · 2 min · jiezi

关于typescript:深入浅出-Typescript-读书笔记

最近开始在我的项目中真正应用 typescript 了,为了补救知识点上的有余,我浏览了「深刻了解 typescript」,上面是我记录的笔记,在此跟大家分享我感觉对于老手比拟重要的一些点。 变量申明和类型申明TS 的变量申明是申明一个变量,比方 const/let/class,其中 class 同时也可用于类型申明。 应用类实现接口应用 implements 关键字能够让接口限度类外部的类型: interface Point { x: number; y: number; z: number; // New member}class MyPoint implements Point { // ERROR : missing member `z` x: number; y: number;} 应用箭头函数做类型注解能够应用箭头函数做类型注解,所以可能会呈现有两个箭头的函数,第一个箭头是类型注解,第二个箭头是函数体自身。 // 一般函数const simple = function(foo: number): string {return foo.toString()};// 箭头函数const simple: (foo: number) => string = (foo) => foo.toString(); 类型断言 as类型断言 as 容许咱们笼罩原有的类型推导。 function handler (event: Event) { let mouseEvent = event as MouseEvent;} ...

October 11, 2020 · 2 min · jiezi

关于typescript:TypeScript-运行时类型检查补充工具

TypeScript是动态类型零碎,在编译时做类型查看。一般而言,如果我的项目所用到的所有库、模块都是基于ts的,那么动态类型曾经能够防止大部分编程层面的类型问题。不过,在一些场景下来,单纯动态类型是无奈解决问题的,局部数据是动静传入到零碎中的,次要蕴含场景如下: 第三方数据源(接口API、本地长久化存储、postMessage等)第三方调用者传参全局状态变更当然,还有其余可能,总之,单纯靠动态类型查看,无奈解决运行时类型问题。因而,我写了tyshemo这个工具。它能够帮忙咱们实现运行时的类型查看。它裸露了很多接口,其中的Ty接口,很适宜在js中作为ts的补充被应用,咱们来看下。 import { Ty } from 'tyshemo'@Ty.decorate.with([Number, Number])class Some { constructor(a, b) { this.x = a + b } @Ty.decorate.with(String) name = 'calc' @Ty.decorate.with([Number], Number) plus(y) { return this.x + y }}const some = new Some(1, 3) // okconst some2 = new Some('1', '3') // throw errorsome.name = 'ooo' // oksome.name = 123 // throw errorconst z = some.plus(2) // okconst z1 = some.plus('3') // throw error咱们能够通过 Ty.decorate.with() 作为装璜器来限定一个类上属性的值类型,办法的参数和返回值类型。 ...

September 27, 2020 · 1 min · jiezi

关于typescript:typescript接口使用总结

接口是一系列形象办法的申明,是一些办法特色的汇合,这些办法都应该是形象的,须要由具体的类去实现,而后第三方就能够通过这组形象办法调用,让具体的类执行具体的办法。 一、初识interface Item { // 定义一个item接口 id: number, title: string}function renderList(item: Item) { console.log(item.id, item.title); // 1 hello}renderList({id: 1, title: 'hello'});PS:为什么以函数为第一个示例呢?次要是接口在日常的应用大多用来做函数参数应用二、接口数据类型演示enum Type { Yes = 1, No = 0}interface Item { id: number, // 数字 title: string, // 字符串 status: boolean, // 布尔值 gender: Object, // 对象 girlfriend: null, // null future: undefined, // undefined favorites: string[], // 字符串数组 plan: [string, number], // 元组 type: Type, // 枚举 callback: ()=>string, // 返回字符串的回调函数 // callback(): string, // 函数的另一种写法 content: any // 任意}function renderList(item: Item): void { console.log(item, item.callback());}renderList({ id: 1, title: 'hello', status: true, gender: {1: '男', 0: '女'}, girlfriend: null, future: undefined, favorites: ['game', 'movie'], plan: ['得分', 100], type: Type.Yes, callback: () => { return '123123'; }, content: '这是一个内容'});三、是否可选默认属性都是必须的,如果要改成可选的加个?就行了 ...

September 22, 2020 · 2 min · jiezi

关于typescript:React-Hooks使用pdfjsdist

零碎页面中有时候会有一些加载pdf资源的性能需要,pdfjs-dist这个插件能够满足要求。解决步奏:申请->转换->加载这里应用的版本是 2.2.228申请有两种,一种能够是Url链接申请。一种是二进制文件。代码上差异不大,业务逻辑解决如下: const winW = (document.querySelector('.pdf-calssname') as HTMLDivElement).getBoundingClientRect().width;const loadingFile = PDFJS.getDocument('your url'); // URLconst laodingFile = PDFJS.getDocument({data:atob(blod)}); //二进制文件//loading....loadingFile.promise.then((pdf)=>{ const nums = pdf.numPages; setpageNum(new Array(nums).fill(1)); // pageNum variable for(let i =1;i<nums;++i){ pdf.getPage(i).then((page:any)=>{ const viewport:any = page.getViewport(i); const scale:any = (winW / viewport.width).toFixed(2); //url cosnt scale:any = (winW /viewport.width *viewport.scale).toFixed(2) // 二进制文件流 const scaledViewport = page.getViewport({scale:Math.max(scale,1)*2}); const canvas:any = document.getElementById('the-canvas'+i); const context = canvas.getContext('2d'); canvas.height = Math.ceil(scaleViewport.height); canvas.width = Math.ceil(scaleViewport.width); const renderContext ={ canvasContext:context, viewport:scaledViewport, } const render:any = page.render(renderContext); render.then((=>{}) }) }},(err)=>{ console.log(err);})局部tsx代码如下: ...

September 21, 2020 · 1 min · jiezi

关于typescript:TypeScript-之父开源是赢得-JavaScript-开发人员的唯一途径

作者:Liam Tung编译:王治治丨公布自:思否编辑部原文链接:https://www.zdnet.com/article... 微软的开源编程语言 TypeScript 是 JavaScript 的一个超集,到往年 12 月就满 10 岁了。 它曾经成长为构建在浏览器中运行的应用程序的首选语言,但早在 2010 年,它不得不在微软公司的文化中抉择本人的形式,过后微软公司依然胆怯开源。 TypeScript 之父 Anders Hejlsberg 是微软的丹麦软件工程师和技术研究员,他在一次媒体采访中形容了 2010 年,在微软首席 Steve Ballmer 的领导下做出的决定 —— 开源策略是博得JavaScript开发人员的惟一路径。 Ballmer 在 2001 年称 Linux 是威逼微软所有知识产权的 "癌症",而在 2010 年,微软的开源依然是高层管理人员的辣手问题。 "Linux被视为对Windows的威逼,而事实证明,它恰恰相反。"Hejlsberg 说。 Ballmer 于 2014 年 8 月卸任 CEO,尔后他发出了这一立场,现在,在微软 CEO 萨提亚-纳德拉(Satya Nadella)的领导下,该公司专一于云计算并酷爱开源,还领有开源代码库 GitHub。 微软其余次要的开源我的项目包含风行的代码编辑器 Visual Studio Code(VS Code)、.NET Code 和 TypeScrip。 自 2012 年正式公布以来,TypeScript 曾经成为浏览器利用前端开发的重要语言,被 Slack、Airbnb 等公司采纳,当然还有用 TypeScript 构建 VS Code 的微软本人。当初,该语言与Java、JavaScript 和 Python 一样,在十大编程语言中稳居一席之地。 ...

September 20, 2020 · 2 min · jiezi

关于typescript:typescript如何在Vue提供this类型推断

Vue中option的类型推断如果大家有用ts写代码,会发现当咱们写组件的option(选项)时,可能很好的提供类型推断,当然前提是你要应用Vue.extend()办法。 具体的应用大家能够参考我写的这篇博客,如何在vue中不借助vue-class-decorator实现ts类型推断。 vue2中应用ts 那vue的类型是如何实现在参数中为this提供类型推断呢? 以下这段代码在javascript可能很好的工作,也很容易了解。然而当咱们切换到ts做动态类型查看时,this并不能很好的工作,那咱们如何让this可能提供类型推断呢? export default { data: { first_name: "Anthony", last_name: "Fu", }, computed: { full_name() { return this.first_name + " " + this.last_name; }, }, methods: { hi() { alert(this.full_name); }, },};this提供类型为了能让this显示的推断类型,咱们能够采取传参的形式 interface Context { $injected: string}function bar(this: Context, a: number) { this.$injected // 这样是能工作的}然而,如果咱们传入Record参数类型(索引对象),这样就会有问题,它(ts)并不能很好提供类型校验了 type Methods = Record<string, (this: Context, ...args:any[]) => any>const methods: Methods = { bar(a: number) { this.$injected // ok }}methods.bar('foo', 'bar') // 没有提醒谬误,因为参数类型曾经变为 `any[]`而且也不能老是让用户,提供参数类型吧!这种体验是十分不敌对的,所以为了实现类型校验,咱们须要寻找另一种办法了。 ThisType在理解了vue的代码之后,发现了ts一个很有用的内置类型 -ThisType ThisType定义:通过ThisType咱们能够在对象字面量中键入this,并提供通过上下文类型管制this类型的便捷形式。它只有在--noImplicitThis的选项下才无效ThisType能够影响所有的嵌套函数,那咱们能够这样写了 type Methods = { double: (a: number) => number deep: { nested: { half: (a: number) => number } }}const methods: Methods & ThisType<Methods & Context> = { double(a: number) { this.$injected // ok return a * 2 }, deep: { nested: { half(a: number) { this.$injected // ok return a / 2 } } }}methods.double(2) // okmethods.double('foo') // errormethods.deep.nested.half(4) // ok能够看到this的类型推断曾经失效了,然而有个毛病还是须要用户去定义方法的接口,那咱们能不能主动推断类型呢? 实现define能够的,通过函数来主动推断类型。 type Options<T> = { methods?: T } & ThisType<T & Context>function define<T>(options: Options<T>) { return options}define({ methods: { foo() { this.$injected // ok }, },})办法曾经能主动推断了,那么接下来,咱们能够接着实现computed和data的类型推断 整个残缺的demo如下: /* ---- Type ---- */export type ExtractComputedReturns<T extends any> = {[key in keyof T]: T[key] extends (...args: any[]) => infer TReturn ? TReturn : never}type Options<D = {}, C = {}, M = {}> = { data: () => D computed: C methods: M mounted: () => void // and other options} & ThisType<D & M & ExtractComputedReturns<C>> // merge them togetherfunction define<D, C, M>(options: Options<D, C, M>) {}/* ---- Usage ---- */define({ data() { return { first_name: "Anthony", last_name: "Fu", } }, computed: { fullname() { return this.first_name + " " + this.last_name }, }, methods: { notify(msg: string) { alert(msg) } }, mounted() { this.notify(this.fullname) },})其实define的原理就是Vue.extend能推断this(上下文类型)的原理了 ...

September 19, 2020 · 1 min · jiezi

关于typescript:初探-TypeScript-类型编程

本文首发于我的博客,转载请注明出处:http://kohpoll.github.io/blog...平时咱们编写 TypeScript 时,次要会应用类型注解(给变量、函数等加上类型束缚),这能够加强代码可读性、防止低级 bug。实际上 TypeScript 的类型零碎设计的十分弱小,弱小到能够独自作为一门编程语言。本文是本人学习 TypeScript 类型编程的一个总结,心愿对你有帮忙。 开始之前本文不会对 TypeScript 的根底语法和应用进行阐明,你能够参考互联网上提供的优良材料: https://www.typescriptlang.or...https://basarat.gitbook.io/ty...启程参考 SCIP 中对于编程语言的形容。一门编程语言应该提供以下机制: 根本表达式。用来示意语言所关怀的最简略的个体。组合的办法。从简略的个体登程结构复合的对象。形象的办法。能将复合对象封装作为独立单元去应用。上面咱们将以这三个方面为线索来摸索 TypeScript 的类型编程。 根本表达式咱们首先来看看类型编程中,定义“变量”的形式: // string、number、boolean 的值能够作为类型应用,称为 literal typetype LiteralS = 'x';type LiteralN = 9;type LiteralB = true;// 根底类型type S = string;// 函数type F = (flag: boolean) => void;// 对象type O = { x: number; y: number; };// tupletype T = [string, number];这里略微补充下 interface 和 type 的区别。 最次要的区别就是 type 能够进行“类型编程”,interface 不行。 interface 能定义的类型比拟局限,就是 object/function/class/indexable: ...

September 17, 2020 · 4 min · jiezi

关于typescript:React路由跳转弹出新窗口传参

业务中呈现了几个场景:有几个公共的页面须要展现固定的业务,须要在新路由中展现;起初需要改变,要求在新窗口中展现业务。 这样带来几个问题:1,路由跳转2,新窗口弹出3,弹出窗口传参用来申请接口数据,如何传递参数 我的项目应用hooks开发首先须要注册路由<Router path={'门路'} exact={true} component={MyComponent}>这外面的exact属性默认为false,如果为true时,须要和路由雷同时能力匹配,有斜杠也是能够匹配。 如果在父路由中加了exact,不能匹配子路由,倡议在子路由中加exact。 在实现的过程中,因为习惯问题,我把这一行代码放在<Switch>中较为靠后的地位,把本人坑了一把,起初查了下,这件事件和路由的匹配规定有关系。1,从上到下匹配, 一旦匹配到了, 就不往下匹配了。谬误页面的配置, 就是下面都无奈匹配到, 就匹配谬误页面。因为开启了严格匹配,为了养成良好的习惯2. 长路由放在短路由后面,这里是说,路由前半部分雷同的状况 `/a/b` 应该放在 `/a` 后面3. 长路由放在含糊匹配的后面 `/a/b` 放在 `/a/:id 匹配好路由后就能够间接在代码外面实现跳转了。首先组件外面要引入react-router-dom要应用props.history.phush()办法还须要用withRouter import {withRouter} from 'react-router-dom'...props.history.push({pathname:'your router',state:{your parameter}})...export default withRouter(memo(MyComponent));跳转路由的参数就从props.history.location.state中获取 如果须要呈现新窗口,路由跳转不实用,须要应用window.open()函数跳转新窗口。这里的Url拼接和新窗口参数获取波及到location,浏览器F12输出location能看到具体构造。 location属性hash 设置或返回从井号 (#) 开始的 URL(锚)。如果地址里没有“#”,则返回空字符串。 host 设置或返回主机名和以后 URL 的端口号。 hostname 设置或返回以后 URL 的主机名。 href 设置或返回残缺的 URL。在浏览器的地址栏上怎么显示它就怎么返回。 pathname 设置或返回以后 URL 的门路局部。 port 设置或返回以后 URL 的端口号,设置或返回以后 URL 的端口号。 protocol 设置或返回以后 URL 的协定,取值为 ‘http:’,’https:’,’file:’ 等等。 search 设置或返回从问号 (?) 开始的 URL(查问局部)。 ...

September 10, 2020 · 1 min · jiezi

关于typescript:JNPF快速开发框架的八大功能介绍

一、代码生成器 代码生成器是JNPF疾速开发框架的最外围的性能之一。它深深切切的把开发人员从繁琐的代码编写工作中解放了进去。以前,如果在软件我的项目开发的过程中,忽然有一个需要要变动,那可能就又得搞好几个月能力稳固公布,而且整个过程中可能还要进行无数次的修修改改,有时候就算是一个小小的细节,就能连带着其余性能的变动,这就是所谓牵一发而动全身。那如果使用JNPF疾速开发框架会是怎么样的呢?那就简略多了。 使用JNPF开发框架的代码生成器,每次批改变动只须要简略的点击下一步,代码即可主动生成。如此,不必一个一个编写代码的开发是不是就很快了! 二、系统管理 在一个软件我的项目中最让人费脑的应该就是权限治理了吧!权限治理背地有很多的权限验证逻辑,当中繁多的治理要求会很让人头疼。当初,使用JNPF疾速开发框架外面的独立权限治理模块,不须要编写简单的代码,只有业务关系写对既可配置好。 三、挪动端使用 想想如果要开发一个APP是不是就感觉费劲费钱,而且还要很长时间能力上线,一个好的idea往往就这样在各种磨耗中夭折了吧! 而JNPF疾速开发框架是基于浏览器的集成开发环境,可视化和智能化的设计,能疾速地实现惯例利用和面向手机 App 的挪动利用开发,这样挪动端开发起来就特地省时省心省力省钱了! 四、IM通信 想要跟上科技时代,通信工具必不可少,最便捷的无非就是挪动端疾速收发音讯。 JNPF的IM通信实现了零阻碍沟通,让企业即刻领有音讯及时送达、群聊、图片、音视频等各性能于一体的办公软件。 五、工作流引擎 面对简单繁多的工作流性能,做起来是不是很头疼,其中的业务逻辑经常会被打断,走不通。当初有了JNPF框架的帮忙,就能够分分钟搞定,审批流转,进度查看,数据分析,简直都能够一步到位。 六、图表使用 图表的作用,次要是让企业用户能够从不同维度对数据进行统计,并以图表的形式对数据进行直观的、可视化的展现。JNPF框架能够实现0代码,几分钟工夫内就配置出各种款式的图表。 七、数据报表 JNPF框架领有弱小的报表设计能力,可能满足不同场景下的报表开发与设计需要,如 Excel 表格、Word 文档、Dashboard 仪表板等。 八、数据导入导出 对于日常办公,罕用也最繁琐的就是数据处理,其中产品信息、客户资料等是成千上万,员工要在零碎里手动录入这些材料,确实麻烦,且容易出错,效率还不高。来看看JNPF的数据导入导出性能吧。 以上这些便是JNPF疾速开发框架的八个次要性能,通过这些性能,企事业单位就能够很方便快捷的开发出本人想要的各种办公治理软件系统,助力企业的高效经营,实现企业的增效降本。

September 4, 2020 · 1 min · jiezi

关于typescript:typescript-梳理

1. 赋值断言类属性应用赋值断言官网示例: class C { foo!: number; // ^ // Notice this exclamation point! // This is the "definite assignment assertion" modifier. constructor() { this.initialize(); } initialize() { this.foo = 0; }}如果去掉foo前面的感叹号会怎么? 报错提醒咱们foo没有初始化,也没有在构造函数中定义;意思就是在构造函数中初始化就不会报错了? 在构造函数中初始化确实不报错,反向证实报错信息很精确。 变量申明应用赋值断言官网示例 let x!: number[];initialize();x.push(4);function initialize() { x = [0, 1, 2, 3];}去掉感叹号 报错信息通知咱们变量x在被赋值之前应用了。 提醒: 下面的报错信息都是建设在tsconfig.json设置strict:true的前提下,在不应用赋值断言的前提下: strictPropertyInitializationstrict变量类属性falsetrue报错不报错falsefalse不报错不报错truefalse不报错报错truetrue报错报错strictPropertyInitialization的粒度更细,变量没有初始化导致报错没有更细粒度的参数去配置。 小结:对于提早初始化,typescript没法推断进去,要通过赋值断言显示指定前面会为其初始化。

September 4, 2020 · 1 min · jiezi

关于typescript:使用-TypeScript-模板字符串类型

我的博客原文:https://blog.gplane.win/posts/ts-template-string-types.html介绍在明天的早些时候,Anders Hejlsberg 在 TypeScript 的仓库中发了一个 Pull Request:Template string types and mapped type as clauses。这个个性预计会在 4.1 版本中可用。 具体给 TypeScript 带来什么新个性,我就不在这里反复说了,Anders Hejlsberg 在 PR 中介绍得很分明。这篇文章次要探讨利用这个模板字符串类型,来对字符串字面量类型进行一些典型的字符串操作。 革除字符串的特定前缀实现与剖析JavaScript 的字符串中有一个实例办法 trimStart,它能够去掉字符串后面的空格字符。咱们将在 TypeScript 的类型层面上实现这个性能。代码如下: type Whitespace = ' ' | '\n' | '\r' | '\t'type TrimStart<S extends string, P extends string = Whitespace> = S extends `${P}${infer R}` ? TrimStart<R, P> : S第 1 行的 Whitespace 类型定义了罕用的一些空格字符。当然 Unicode 中还定义了其它的一些空格字符,咱们依据须要往 Whitespace 这个联结类型前面补充就能够。 第 3 行至第 5 行就是 TrimStart 类型的定义。这个类型须要两个类型参数,其中 S 是要被解决的字符串,P 是要被搜寻并删除的前缀。这两个参数都指定 string 类型作为泛型束缚。另外 TypeScript 容许咱们为类型参数提供一个默认类型,在这里,类型参数 P 的默认类型是 Whitespace,示意在不指定要搜寻哪些字符串的时候,默认搜寻空格字符。 ...

September 1, 2020 · 2 min · jiezi

关于typescript:我写了一个TypeScript虚拟机

我写了一个TypeScript虚拟机: Tser。Github地址:tser-project/tser。 装置应用$ brew tap tser-project/tser && brew install tser;$ tser ./input.ts;为什么要发明Tser?TypeScript(TS)是一个平凡的创造,让咱们在复用JS生态的同时领有了动态类型语言的开发体验。TS实质是一门预编译语言,编译到JS后再应用JS虚拟机执行,因为强依赖于JS,也因而无奈解脱JS的一些顽疾,比方执行效率。而TS自身是动态类型语言,领有确定的数据类型标记,只是在本义为JS时失落了类型标记;如果咱们能间接执行TS程序,而不是先本义为JS再执行,这些数据类型标记能够为程序带来很大的性能晋升。 咱们来看一组性能比照数据,仅比照fib(42)在各个虚拟机或语言中的性能体现(并不能齐全依此来作为性能评估规范;测试条件:同一设施同一状态,编译过程均未应用任何优化)。 语言虚拟机执行耗时(ms)TypeScriptdeno4150JavaScriptv8 / node3859TypeScriptTser2035C++--2106TS技术倒退很快,业界曾经有越来越多的我的项目应用TS开发和重构;抛开眼下去看TS技术的倒退,它的起点会在哪里?会始终停留在一个预编译语言上吗?当TS生态倒退越来越健全,是不是还有必要齐全依靠于JS的生态?业界会不会诞生一个真正的TS虚拟机(Deno并不是)?如果业界有一个稳固且高性能的TS虚拟机,对TS生态是不是一件坏事,会不会将TS推向一个新的高度? 这些问题思考了很久。 <u>TS应该无奈取代JS的生态,然而在某些畛域,TS能够脱离JS生态而独立存在;TS虚拟机是独立TS生态的基石,能够让TS在这些畛域有显著优于JS的运行时体现,并为这些畛域带来理论的业务收益。</u> Tser能做什么?Tser编译性能绝对不高而运行时性能高,更适宜独立后盾服务、Serverless等场景。如果语法反对欠缺,能够运行绝大部分现有的应用TS编写的后盾服务时,这些服务能够因而取得很大的性能晋升。 Tser目前能做的事件还非常少,因为语法反对很不欠缺,仅能反对一些简略脚本的执行,比方应该可能反对一些简略的云函数场景。 Tser技术原理Tser前端依靠于Antlr来生成的语法解析器,而后进行语法树的生成与遍历;后端依靠于LLVM建设,将TS代码编译为LLVM IR,并应用其JIT引擎立刻执行IR。 业界雷同产品: AssemblyScript、StaticScript Tser语法反对变量反对var let const, var与let雷同。 根底类型类型字节 (in 64bit)反对boolean1✔️number4✔️ 同int32int324✔️int648✔️float4✔️double8✔️string--✔️ 暂不反对运算运算符运算符反对+ - * / %✔️++ --✔️+= -= *= /= %=✔️< > <= >=✔️== === != !==✔️ ===与==目前没有区别&& ` `✔️!✔️? :✔️()✔️.✔️逻辑管制语句语句反对if else✔️while do while✔️for✔️switch✔️continue✔️break✔️函数反对绝大部分函数性能,函数嵌套,暂不反对闭包和函数参数。 ClassClass反对继承、多态,反对Class组合,反对动态属性与静态方法,不反对办法重载;Class继承和多态应用相似虚构表的形式来反对,多态反对办法和属性。 内置对象目前内置对象反对力度非常低,仅用于测试。 内置对象办法consoledebug log info warn errorDatenow暂不反对module 内置对象、Event Loop、GC等等。 奉献TserTser是一个微小的工程,很难用一己之力实现。Tser当初还是一个baby,更心愿它能起到一个抛砖引玉的作用,能汇集起一些有能力的人来一起建设。

September 1, 2020 · 1 min · jiezi

关于typescript:typescript笔记2

typescript类型1、函数多参type addType = (x: number, y: number) => numberlet addxy: addType = (ar1, ar2) => ar1 + ar2;// 可选参数type addType2 = (x: number, y: number, z?: number) => numberlet addxyz: addType2;addxyz = (x, y) => x + y;addxyz = (x, y, z) => x + y + z; // 这种函数会笼罩后面的let addx1 = (x: number, y = 3) => { return x + y}addx1(1);addx1(2, 2);// 多个参数let addx2 = (ar1: number, ...args: number[]) => {}2、函数重载function reverse(x: number): number;function reverse(x: string): string;// 函数实体function reverse(x: any) { if (typeof x === 'number') { return Number(x.toString().split('').reverse().join('')); } else if (typeof x === 'string') { return x.split('').reverse().join(''); }}reverse(1)reverse('abc')3、泛型问题:const getArray = (value: any, times: number = 5) => { return new Array(times).fill(value)}console.log(getArray(5, 4).map(item => item.length)) // 因为 value是any,然而lenth只有value是字符串的时候才可用, 然而这里没有报错计划:const getArray2 = <T>(value: T, times: number = 5): T[] => { return new Array(times).fill(value)}console.log(getArray2('abc', 5).map(item => item.length)) // okconsole.log(getArray2(12345, 5).map(item => item.length)) // error// 如果须要value是length属性的interface IwithLength { length: number}const getArray22 = <T extends IwithLength>(value: T, times: number = 5): T[] => { return new Array(times).fill(value)}getArray22(12345, 5);// errorgetArray22('12345', 5);// ok// 多个泛型const getArray3 = <T, U>(x: T, y: U, times = 5): Array<[T, U]> => { return new Array(times).fill([x, y])}console.log(getArray3(1, 2))console.log(getArray3('a', 2))// 与keyof联合const getProps = <T, K extends keyof T>(obj: T, prop: K) => { return obj[prop]}var myObj = { name: 'xing', age: 32}console.log(getProps(myObj, 'name'))console.log(getProps(myObj, 'year')) // error泛型类型type F1 = <T, U>(x: T, y: U, times: number) => Array<[T, U]>let getArray4: <T, U>(x: T, y: U, times: number) => Array<[T, U]>interface F2 { <T, U>(x: T, y: U, times: number): Array<[T, U]>}interface F3<T, U> { (x: T, y: U, times: number): Array<[T, U]> array: [T, U]}function xxx<T, U>(x: T, y: U, times: number): Array<[T, U]> { return [[x, y]]}

August 31, 2020 · 2 min · jiezi

关于typescript:推荐vscode编写typescript的两个插件

因为项目组最近筹备从javascript迁徙到typescript;在应用ts过程中有局部类型定义及代码片段有反复;所以编写了两个vscode插件;如有须要能够查阅。 tools1: JSON转换成typescript的interfacegithub地址: 欢送star 特色从剪切板json数据转换成interface (windows: ctrl+alt+C , Mac : ^+⌥+C) 抉择json数据转换成interface (windows: ctrl+alt+S , Mac : ^+⌥+S) 将json文件转换成interface (windows: ctrl+alt+F , Mac : ^+⌥+F) 下载下面的gift图可能播放较快,有趣味同学能够下载应用:关上vscode插件并搜寻json转ts tools2: vscode-react-typescript-snippetgithub地址: 欢送star 应用ts编写react代码片段。 下载关上vscode插件并搜寻vscode-react-typescript-snippet即可。 反对文件TypeScript (.ts)TypeScript React (.tsx)代码片段TriggerContenttsrcc→react 类式组件tsrcstate蕴含Props, State, 和 constructor的类式组件tsrpcc→react PureComponent组件tsrpfcreact 函数式组件tsdrpfc领有default export的函数式react组件tsrfc无状态的函数式react组件conc→react constructor 办法cwm→componentWillMount 办法ren→render 办法cdm→componentDidMount 办法cwrp→componentWillReceiveProps 办法scu→shouldComponentUpdate 办法cwu→componentWillUpdate 办法cdu→componentDidUpdate 办法cwum→componentWillUnmount 办法sst→this.setState生成bnd→绑定语句met→创立一个办法tscredux→创立一个类式的redux,蕴含connecttsrfredux->创立一个函数式的redux,蕴含connectimt生成一个import语句state 相干tsrcstate import * as React from "react";export interface IAppProps {}export interface IAppState {}export default class App extends React.Component<IAppProps, IAppState> { constructor(props: IAppProps) { super(props); this.state = {}; } render() { return <div></div>; }}functional 相干tsrfc ...

August 25, 2020 · 2 min · jiezi

关于typescript:typeScript-配置文件该怎么写

TypeScript 的学习材料十分多,其中也不乏很多优良的文章和教程。然而目前为止没有一个我特地称心的。起因有: 它们大多数没有一个清晰的主线,而是依照 API 组织章节的,内容在逻辑上比拟零散。大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。大多数内容比拟干燥,趣味性比拟低。都是水灵灵的文字,没有图片,不足可能引起强烈共鸣的例子。因而我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮忙大家建设 TypeScript 世界观。 系列安顿: 上帝视角看 TypeScript(已公布)TypeScript 类型零碎(已公布)types 和 @types 是什么?(已公布)你不晓得的 TypeScript 泛型(万字长文,倡议珍藏)(已公布)TypeScript 配置文件该怎么写?(就是本文)TypeScript 是如何与 React,Vue,Webpack 集成的?TypeScript 练习题目录未来可能会有所调整。留神,我的系列文章根本不会讲 API,因而须要你有肯定的 TypeScript 应用根底,举荐两个学习材料。 深刻了解 TypeScript官网文档联合这两个材料和我的系列教程,把握 TypeScript 不可企及。 接下来,咱们通过几个方面来从宏观的角度来看一下 TypeScript。 <!-- more --> 前言这篇文章是我的 TypeScript 系列的第 5 篇。明天咱们就来看下, TypeScript 的配置文件 tsconfig.json 该如何写。 和 package.json 一样, 它也是一个 JSON 文件。package.json 是包形容文件,对应的 Commonjs 标准,而 tsconfig.json 是最终被 TypeScript Compiler 解析和应用的一个 JSON 文件。 TypeScript Compiler 用这个配置文件来决定如何对我的项目进行编译。 说到编译,不得不提一个出名选手 - babel。 和 TypeScript 相似, 他们都能够将一种语法动态编译成另外一种语法。如果说我想编译一个文件,我只须要通知 babel 我的文件门路即可。 ...

August 25, 2020 · 3 min · jiezi

关于typescript:ES新特性与TypeScriptJS性能优化

一、 var a =[]for(var i=0; i<10; i++) { a[i] = function() { console.log(i) }}a[6]()执行后果: 10 后果剖析:因为var申明的变量,没有块级作用域,可在全局应用,执行a[6]()时,循环体已执行完,每循环一次i的值都会被扭转,且最终i的值为10,所以执行a[i<10]()时都为10 二、 var tmp = 123 if(true) { console.log(tmp) let tmp }执行后果:报错后果剖析: let/const 命令会使区块造成关闭的作用域。若在申明之前应用变量,就会报错。总之,在代码块内,应用 let 命令申明变量之前,该变量都是不可用的。这在语法上,称为 “暂时性死区”( temporal dead zone,简称 TDZ)。 三、 let min = arr.reduce((pre, cur) => { return Math.min(pre, cur)})四、请具体阐明var,let,const三种申明变量形式之间的具体差异 var定义的变量,没有块的概念,能够跨块拜访, 不能跨函数拜访。let定义的变量,只能在块作用域里拜访,不能跨块拜访,也不能跨函数拜访。var申明的变量会挂载在window上,而let和const申明的变量不会同一作用域下let和const不能申明同名变量,而var能够const用来定义常量,应用时必须初始化(即必须赋值),只能在块作用域里拜访,而且不能批改,如果申明的是复合类型数据,能够批改其属性。五、 var a = 10var obj = { a: 20, fn() { setTimeout(() => { console.log(this.a) }) }}obj.fn()执行后果: 20后果剖析:箭头函数的this指向申明时的对象,且调用时this指向不会产生扭转 ...

August 23, 2020 · 2 min · jiezi

关于typescript:游戏开发中的红点提示

前言当咱们的游戏开发进度靠近序幕的时候,不仅要做教学疏导的事件,还有一件对于中大型游戏来说十分重要的事件就是红点提醒。它有别于教学疏导,但也是疏导的作用,指引性更明确,而且不会影响UI外观和体验的晦涩。 开始通过配置两张数据表来记录红点提醒的相干数据。提示信息表,仅表明都有哪些提醒类型。每个红点提醒应用的界面及控件名称。第二列为属于哪个提醒,所以申明为索引类型。比方1来说,就是由GridLayerListViewTest的buttonBack和ItemView的bg两个按钮组成,也就是当有新道具增加时,GridLayerListViewTest的buttonBack和ItemView两个控件都应该有红点。第三列和第四列申明为类名和控件名第五列标识控件是否为道具,因为道具和一般控件的记录形式不同。 读取配置数据,初始化信息。 init() { let redtipData: XlsxData = ModuleManager.dataManager.get(RedTipItemModel.CLASS_NAME) redtipData.forEach((key, data) => { let item = new RedTipItemModel() item.init(key, data) this.redtipMap.set(key, item) }) let redtipStep: XlsxData = ModuleManager.dataManager.get(RedTipStepModel.CLASS_NAME) let indexs = redtipStep.getIndex(Redtip_step_dataEnum.tipID); for (const key in indexs) { if (indexs.hasOwnProperty(key)) { const list = indexs[key]; for (let index = 0; index < list.length; index++) { const stepID = list[index]; let step = new RedTipStepModel() step.init(stepID, redtipStep.getRowData(stepID)) this.redtipMap.get(step.getTipID()).addStep(step) // if (index == 0) { let className = step.getClassName(); let classMap = this.classNameMap.get(className) if (!classMap) { classMap = [] this.classNameMap.set(className, classMap) } classMap.push(step.getTipID()) } } } }初始化函数中次要做了两件事件:一是 产生提醒所对应的界面和控件数据。二是 产生界面对应的提醒id信息。次要是为了疾速断定某个界面是否存在某个提醒 ...

August 21, 2020 · 2 min · jiezi

关于typescript:vuetypscript项目中遇到的问题

问题typescript引入没有申明过的第三方库typescript中应用map forEach filter find遍历用[]取属性时报错Element implicitly has an 'any' type because expression of type 'string' can't b...解答1.申明第三方库有的第三方库是有申明文件的,这时咱们只须要npm install @types/{模块名}没有申明文件的第三方库 在我的项目的src下建一个@type文件夹,在这个文件夹上来编写申明文件例子// main.tsimport { VeRadarChart } from 've-charts'Vue.component('VeRadarChart', VeRadarChart)// @types/definition.d.tsdeclare module 've-charts' { export class VeRadarChart { }}像这样申明一下就能用了 2.用[]获取属性报错// index.vueimport Service from './service.ts'const tService: any = Service// Service不能间接被遍历获取属性,然而tService能够let _promises = this.formItem.map(({service}: any): any => { return tService[service]()})// Promise.all...........xxxx......// service.tsexport default { async getList(params) { return await XXXXXX }}// const.tstype FormItem<T> = { label: T prop: T type: T service: T}export const formItem: Array<FormItem<any>> = [ { label: '投诉类型', prop: 'complainType', type: 'select', service: 'getComplainType' }, { label: '问题维度', prop: 'problemDim', type: 'select', service: 'getProblemDim' }, { label: '用户危险等级', prop: 'riskLevel', type: 'select', service: 'getRiskLevel' }]

August 20, 2020 · 1 min · jiezi

关于typescript:TypeScript-设计模式之观察者模式

一、模式介绍1. 背景介绍在软件系统中常常碰到这类需要:当一个对象的状态产生扭转,某些与它相干的对象也要随之做出相应的变动。这是建设一种对象与对象之间的依赖关系,一个对象产生扭转时将主动告诉其余对象,其余对象将相应做出反馈。 咱们将产生扭转的对象称为察看指标,将被告诉的对象称为观察者,一个察看指标能够对应多个观察者,而且这些观察者之间没有互相分割,之后能够依据须要减少和删除观察者,使得零碎更易于扩大,这就是观察者模式的产生背景。 2. 概念介绍观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态产生扭转时,其相干依赖对象皆失去告诉并被自动更新。观察者模式是一种对象行为型模式。 3. 生存场景在所有浏览器事件(鼠标悬停,按键等事件)都是观察者模式的例子。 另外还有: 如咱们订阅微信公众号“前端自习课”(察看指标),当“前端自习课”群发图文音讯后,所有公众号粉丝(观察者)都会接管到这篇文章(事件),这篇文章的内容是发布者自定义的(自定义事件),粉丝浏览后作出特定操作(如:点赞,珍藏,关注等)。 二、模式特点1. 模式组成在观察者模式中,通常蕴含以下角色: 指标:Subject察看指标:ConcreteSubject观察者:Observer具体观察者:ConcreteObserver2. UML 类图 图片起源:《TypeScript 设计模式之观察者模式》  3. 长处观察者模式能够实现表示层和数据逻辑层的拆散,并升高察看指标和观察者之间耦合度;观察者模式反对简略播送通信,主动告诉所有曾经订阅过的对象;观察者模式合乎“开闭准则”的要求;察看指标和观察者之间的形象耦合关系可能独自扩大以及重用。4. 毛病当一个察看指标有多个间接或间接的观察者时,告诉所有观察者的过程将会破费很多工夫;当察看指标和观察者之间存在循环依赖时,察看指标会触发它们之间进行循环调用,可能导致系统解体。观察者模式短少相应机制,让观察者晓得所察看的指标对象是怎么发生变化的,而仅仅只是晓得察看指标产生了变动。三、应用场景在以下状况下能够应用观察者模式: 在一个形象模型中,一个对象的行为依赖于另一个对象的状态。即当指标对象的状态产生扭转时,会间接影响到观察者的行为;一个对象须要告诉其余对象产生反馈,但不晓得这些对象是谁。须要在零碎中创立一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,能够应用观察者模式创立一种链式触发机制。四、实战示例1. 简略示例定义察看指标接口(Subject)和观察者接口(Observer)// ObserverPattern.ts// 察看指标接口interface Subject { addObserver: (observer: Observer) => void; deleteObserver: (observer: Observer) => void; notifyObservers: () => void;}// 观察者接口interface Observer { notify: () => void;}定义具体察看指标类(ConcreteSubject)// ObserverPattern.ts// 具体察看指标类class ConcreteSubject implements Subject{ private observers: Observer[] = []; // 增加观察者 public addObserver(observer: Observer): void { console.log(observer, " is pushed~~"); this.observers.push(observer); } // 移除观察者 public deleteObserver(observer: Observer): void { console.log(observer, " have deleted~~"); const idx: number = this.observers.indexOf(observer); ~idx && this.observers.splice(idx, 1); } // 告诉观察者 public notifyObservers(): void { console.log("notify all the observers ", this.observers); this.observers.forEach(observer => { // 调用 notify 办法时能够携带指定参数 observer.notify(); }); }}定义具体观察者类(ConcreteObserver)// ObserverPattern.ts// 具体观class ConcreteObserver implements Observer{ constructor(private name: string) {} notify(): void { // 能够解决其余逻辑 console.log(`${this.name} has been notified.`); }}测试代码// ObserverPattern.tsfunction useObserver(): void { const subject: Subject = new ConcreteSubject(); const Leo = new ConcreteObserver("Leo"); const Robin = new ConcreteObserver("Robin"); const Pual = new ConcreteObserver("Pual"); const Lisa = new ConcreteObserver("Lisa"); subject.addObserver(Leo); subject.addObserver(Robin); subject.addObserver(Pual); subject.addObserver(Lisa); subject.notifyObservers(); subject.deleteObserver(Pual); subject.deleteObserver(Lisa); subject.notifyObservers();}useObserver();残缺演示代码如下: ...

August 17, 2020 · 3 min · jiezi

关于typescript:TypeScript-设计模式之观察者模式

一、模式介绍1. 背景介绍在软件系统中常常碰到这类需要:当一个对象的状态产生扭转,某些与它相干的对象也要随之做出相应的变动。这是建设一种对象与对象之间的依赖关系,一个对象产生扭转时将主动告诉其余对象,其余对象将相应做出反馈。 咱们将产生扭转的对象称为察看指标,将被告诉的对象称为观察者,一个察看指标能够对应多个观察者,而且这些观察者之间没有互相分割,之后能够依据须要减少和删除观察者,使得零碎更易于扩大,这就是观察者模式的产生背景。 2. 概念介绍观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态产生扭转时,其相干依赖对象皆失去告诉并被自动更新。观察者模式是一种对象行为型模式。 3. 生存场景在所有浏览器事件(鼠标悬停,按键等事件)都是观察者模式的例子。 另外还有: 如咱们订阅微信公众号“前端自习课”(察看指标),当“前端自习课”群发图文音讯后,所有公众号粉丝(观察者)都会接管到这篇文章(事件),这篇文章的内容是发布者自定义的(自定义事件),粉丝浏览后作出特定操作(如:点赞,珍藏,关注等)。 二、模式特点1. 模式组成在观察者模式中,通常蕴含以下角色: 指标:Subject察看指标:ConcreteSubject观察者:Observer具体观察者:ConcreteObserver2. UML 类图 图片起源:《TypeScript 设计模式之观察者模式》  3. 长处观察者模式能够实现表示层和数据逻辑层的拆散,并升高察看指标和观察者之间耦合度;观察者模式反对简略播送通信,主动告诉所有曾经订阅过的对象;观察者模式合乎“开闭准则”的要求;察看指标和观察者之间的形象耦合关系可能独自扩大以及重用。4. 毛病当一个察看指标有多个间接或间接的观察者时,告诉所有观察者的过程将会破费很多工夫;当察看指标和观察者之间存在循环依赖时,察看指标会触发它们之间进行循环调用,可能导致系统解体。观察者模式短少相应机制,让观察者晓得所察看的指标对象是怎么发生变化的,而仅仅只是晓得察看指标产生了变动。三、应用场景在以下状况下能够应用观察者模式: 在一个形象模型中,一个对象的行为依赖于另一个对象的状态。即当指标对象的状态产生扭转时,会间接影响到观察者的行为;一个对象须要告诉其余对象产生反馈,但不晓得这些对象是谁。须要在零碎中创立一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,能够应用观察者模式创立一种链式触发机制。四、实战示例1. 简略示例定义察看指标接口(Subject)和观察者接口(Observer)// ObserverPattern.ts// 察看指标接口interface Subject { addObserver: (observer: Observer) => void; deleteObserver: (observer: Observer) => void; notifyObservers: () => void;}// 观察者接口interface Observer { notify: () => void;}定义具体察看指标类(ConcreteSubject)// ObserverPattern.ts// 具体察看指标类class ConcreteSubject implements Subject{ private observers: Observer[] = []; // 增加观察者 public addObserver(observer: Observer): void { console.log(observer, " is pushed~~"); this.observers.push(observer); } // 移除观察者 public deleteObserver(observer: Observer): void { console.log(observer, " have deleted~~"); const idx: number = this.observers.indexOf(observer); ~idx && this.observers.splice(idx, 1); } // 告诉观察者 public notifyObservers(): void { console.log("notify all the observers ", this.observers); this.observers.forEach(observer => { // 调用 notify 办法时能够携带指定参数 observer.notify(); }); }}定义具体观察者类(ConcreteObserver)// ObserverPattern.ts// 具体观class ConcreteObserver implements Observer{ constructor(private name: string) {} notify(): void { // 能够解决其余逻辑 console.log(`${this.name} has been notified.`); }}测试代码// ObserverPattern.tsfunction useObserver(): void { const subject: Subject = new ConcreteSubject(); const Leo = new ConcreteObserver("Leo"); const Robin = new ConcreteObserver("Robin"); const Pual = new ConcreteObserver("Pual"); const Lisa = new ConcreteObserver("Lisa"); subject.addObserver(Leo); subject.addObserver(Robin); subject.addObserver(Pual); subject.addObserver(Lisa); subject.notifyObservers(); subject.deleteObserver(Pual); subject.deleteObserver(Lisa); subject.notifyObservers();}useObserver();残缺演示代码如下: ...

August 17, 2020 · 3 min · jiezi

关于typescript:全民加速节解读CDN的应用场景与产品价值

8月12日,全民减速节第二次直播中,阿里云CDN产品专家寒丰进行了《阿里云CDN产品解读》的主题分享,从CDN的趋势、变迁、价值三个方面来论述思考,并对阿里云CDN产品的业务架构和价值进行解读。 当下,互联网的应用服务曾经深刻到公众生存的各个环节,线上流量的竞争曾经成为了企业须要抢占的重要洼地。中国互联网信息中心公布的第45次《中国互联网倒退统计报告》显示,以后超过99%的用户都是应用手机接入互联网,其中,APP分类占比的数据显示,在挪动利用规模排行前四的APP类型包含游戏、日常工具、电子商务、生存服务类,这四大类就曾经占据了57.9%,剩下的40%包含了像社交、教育等,这组数据也进一步验证阐明了挪动互联跟公众生存的非亲非故。 在《中国互联网倒退统计报告》中,有一组数据资料显示:从2018年到2019年,网页总数是呈增长趋势的,动静网页的增长率是大于整体网页增长率,同时,上面还有一个数据是均匀每个页面的字节数在在增长,反映到理论业务中,就是用户对智能的要求会更高,对于交互性实时性诉求更重,同时对整体的服务质量也要求也高了。动静交互的权重在一直地减少,用户交互体验成为越来越重要的环节。 如何进步用户的交互?CDN起到至关重要的作用。CDN绝大部分的能力是进步整个用户的交互晦涩度,为终端的体验带来一个比拟好的晋升。 CDN服务的典型场景CDN服务的几个典型场景如下: 首先,图文音视频基本上存在于所有的互联网业务之中,比方网站上的图片、短视频、长视频等,这些是互联网中比拟常见的一种业务场景。 第二,边缘交互。比拟常见的是边缘卸载,把加密的一些能力在CDN边缘做下沉,缩小核心的耗费,升高核心老本的一个这种大量的投入,缩小资源的节约,进步核心的资源的利用率。 第三,动静交互,当有些内容无奈在边缘解决,这时候就波及到边缘去跟核心做交互,这种就是典型的动静交互的业务场景,须要把终端的用户信息和核心的用户信息进行实时的交互,并且可能反馈到给到终端用户。 第四,各个企业的业务场景不同,它有一些不是非标准化的协定的话,也须要在互联网中去散发,晋升整个业务的交互性,这也须要CDN具备可能去反对非标业务的能力。 阿里云CDN产品发展史阿里云CDN产品正式商用的是在2014年,它是发祥于淘宝自建CDN,在外部应用成熟之后才走向市场的。在2015年,阿里云CDN的用户规模冲破了10万,产品能力受到市场的认可。同时,在2015年,CDN在数据传输平安上进行了布局和冲破。 随着互联网内容状态和交互状态的演进,动静交互要求一直变高,比方电子商务购物过程中的领取登陆类的场景诉求变大,在2016年,阿里云CDN对外开始提供动静交互的服务,帮忙客户进步整个用户交互体验,同年,阿里云CDN也凋谢了局部寰球的服务能力,发展了全球化布局。 在2017年,基于很多客户并没有去把业务内容拆分得很细的状况,CDN须要为他提供一站式的动动态内容减速服务,阿里云CDN衍生出全站减速产品,为了在整个的服务经营体系过程中,更好的去提供业务决策,也推出实时数据分析,让数据价值失去更好体现。 在2018年,阿里云CDN上线反对了iPv6,并且在一些新的协定,如QUIC的反对上进行了技术尝试。 在2019年,阿里云CDN进一步开释边缘算力,公布了ER产品,让企业用户可能在CDN边缘节点下来执行本人的程序,让用户去自定义业务。 阿里云CDN产品业务架构图自从阿里云CDN商业化至今,始终在进行场景化的摸索,致力于笼罩到上述用户的全业务场景。上文所述的整个互联网业务的典型利用场景,反馈到CDN之上的直观感知就是业务的变动,不同的业务其实有不同的流量带宽的体现。 阿里云CDN以场景为导向,为用户提供最适宜的产品服务能力,在资源基座层面,能够提供多变灵便多变、弹性的资源,来应答业务需要;在行业差异化诉求层面,比方金融政务、传统企业、传媒、教育、游戏、医疗等等行业,有对于安全性、数据散发性等不同级别的要求,阿里云CDN能够依据边缘散发、减速传输、平安防护、自定义服务、性能优化、动静选路、iPv6、视频解决、边缘脚本、数据分析等等不同的产品能力进行组合,为行业提供差异化的产品计划。 阿里云CDN次要有三大产品价值第一, 灵便阿里云寰球2800+节点,笼罩了70多个国家和地区,领有13OTb带宽储备。从覆盖率和节点数来看,产品更加靠近用户的终端,对于减速网络的灵便度和切换度至关重要。从带宽的储备来看,如此短缺的带宽储备也能够及时、无效地应答业务的突发。 第二, 疾速便捷阿里云CDN产品能够在控制台下来实现自助开启。用户业务的开明、接入和应用的话,全自动自主操作,不须要其余的第三方的染指。真正的即开即用,在整个管制台上,用户也能够去依据业务诉求去开启和敞开,可能很疾速的去反映到业务上。 第三, 个性化企业业务是具备多样性的,须要依据他的业务本身状况来实现一些自定义的服务能力,甚至于说在依据他的具体的业务场景去制订它的业务状态,这在阿里云CDN产品上能够实现。比方自定义同步,这齐全能够通过CDN控制台去疾速的自助实现。 如何在边缘节点中去实现业务价值晋升?基于遍布寰球的边缘节点资源,如何充沛开释技术能力和算力,帮忙客户晋升价值是阿里云CDN始终在思考的方向。阿里云CDN通过可条件化、可编程化和可边缘化,基于业务多样性下的差异化服务能力,把业务与性能晋升互相交融,实现交互体验的服务效力的最大化。在寰球一体化的背景下,让客户依据本人的业务去建设逻辑、去差异化解决,实现业务收效最大化。 对于一些老旧的业务,客户如果改变核心架构,可能要投入大量的老本和工夫周期,而在CDN侧进行边缘优化,让边缘承载更多的核心的服务能力,让更个性化的一种这种服务的话,更贴近于本人的业务,即可达到成果最佳。 原文链接 本文为阿里云原创内容,未经容许不得转载。

August 17, 2020 · 1 min · jiezi

关于typescript:全民加速节解读CDN的应用场景与产品价值

8月12日,全民减速节第二次直播中,阿里云CDN产品专家寒丰进行了《阿里云CDN产品解读》的主题分享,从CDN的趋势、变迁、价值三个方面来论述思考,并对阿里云CDN产品的业务架构和价值进行解读。 当下,互联网的应用服务曾经深刻到公众生存的各个环节,线上流量的竞争曾经成为了企业须要抢占的重要洼地。中国互联网信息中心公布的第45次《中国互联网倒退统计报告》显示,以后超过99%的用户都是应用手机接入互联网,其中,APP分类占比的数据显示,在挪动利用规模排行前四的APP类型包含游戏、日常工具、电子商务、生存服务类,这四大类就曾经占据了57.9%,剩下的40%包含了像社交、教育等,这组数据也进一步验证阐明了挪动互联跟公众生存的非亲非故。 在《中国互联网倒退统计报告》中,有一组数据资料显示:从2018年到2019年,网页总数是呈增长趋势的,动静网页的增长率是大于整体网页增长率,同时,上面还有一个数据是均匀每个页面的字节数在在增长,反映到理论业务中,就是用户对智能的要求会更高,对于交互性实时性诉求更重,同时对整体的服务质量也要求也高了。动静交互的权重在一直地减少,用户交互体验成为越来越重要的环节。 如何进步用户的交互?CDN起到至关重要的作用。CDN绝大部分的能力是进步整个用户的交互晦涩度,为终端的体验带来一个比拟好的晋升。 CDN服务的典型场景CDN服务的几个典型场景如下: 首先,图文音视频基本上存在于所有的互联网业务之中,比方网站上的图片、短视频、长视频等,这些是互联网中比拟常见的一种业务场景。 第二,边缘交互。比拟常见的是边缘卸载,把加密的一些能力在CDN边缘做下沉,缩小核心的耗费,升高核心老本的一个这种大量的投入,缩小资源的节约,进步核心的资源的利用率。 第三,动静交互,当有些内容无奈在边缘解决,这时候就波及到边缘去跟核心做交互,这种就是典型的动静交互的业务场景,须要把终端的用户信息和核心的用户信息进行实时的交互,并且可能反馈到给到终端用户。 第四,各个企业的业务场景不同,它有一些不是非标准化的协定的话,也须要在互联网中去散发,晋升整个业务的交互性,这也须要CDN具备可能去反对非标业务的能力。 阿里云CDN产品发展史阿里云CDN产品正式商用的是在2014年,它是发祥于淘宝自建CDN,在外部应用成熟之后才走向市场的。在2015年,阿里云CDN的用户规模冲破了10万,产品能力受到市场的认可。同时,在2015年,CDN在数据传输平安上进行了布局和冲破。 随着互联网内容状态和交互状态的演进,动静交互要求一直变高,比方电子商务购物过程中的领取登陆类的场景诉求变大,在2016年,阿里云CDN对外开始提供动静交互的服务,帮忙客户进步整个用户交互体验,同年,阿里云CDN也凋谢了局部寰球的服务能力,发展了全球化布局。 在2017年,基于很多客户并没有去把业务内容拆分得很细的状况,CDN须要为他提供一站式的动动态内容减速服务,阿里云CDN衍生出全站减速产品,为了在整个的服务经营体系过程中,更好的去提供业务决策,也推出实时数据分析,让数据价值失去更好体现。 在2018年,阿里云CDN上线反对了iPv6,并且在一些新的协定,如QUIC的反对上进行了技术尝试。 在2019年,阿里云CDN进一步开释边缘算力,公布了ER产品,让企业用户可能在CDN边缘节点下来执行本人的程序,让用户去自定义业务。 阿里云CDN产品业务架构图自从阿里云CDN商业化至今,始终在进行场景化的摸索,致力于笼罩到上述用户的全业务场景。上文所述的整个互联网业务的典型利用场景,反馈到CDN之上的直观感知就是业务的变动,不同的业务其实有不同的流量带宽的体现。 阿里云CDN以场景为导向,为用户提供最适宜的产品服务能力,在资源基座层面,能够提供多变灵便多变、弹性的资源,来应答业务需要;在行业差异化诉求层面,比方金融政务、传统企业、传媒、教育、游戏、医疗等等行业,有对于安全性、数据散发性等不同级别的要求,阿里云CDN能够依据边缘散发、减速传输、平安防护、自定义服务、性能优化、动静选路、iPv6、视频解决、边缘脚本、数据分析等等不同的产品能力进行组合,为行业提供差异化的产品计划。 阿里云CDN次要有三大产品价值第一, 灵便阿里云寰球2800+节点,笼罩了70多个国家和地区,领有13OTb带宽储备。从覆盖率和节点数来看,产品更加靠近用户的终端,对于减速网络的灵便度和切换度至关重要。从带宽的储备来看,如此短缺的带宽储备也能够及时、无效地应答业务的突发。 第二, 疾速便捷阿里云CDN产品能够在控制台下来实现自助开启。用户业务的开明、接入和应用的话,全自动自主操作,不须要其余的第三方的染指。真正的即开即用,在整个管制台上,用户也能够去依据业务诉求去开启和敞开,可能很疾速的去反映到业务上。 第三, 个性化企业业务是具备多样性的,须要依据他的业务本身状况来实现一些自定义的服务能力,甚至于说在依据他的具体的业务场景去制订它的业务状态,这在阿里云CDN产品上能够实现。比方自定义同步,这齐全能够通过CDN控制台去疾速的自助实现。 如何在边缘节点中去实现业务价值晋升?基于遍布寰球的边缘节点资源,如何充沛开释技术能力和算力,帮忙客户晋升价值是阿里云CDN始终在思考的方向。阿里云CDN通过可条件化、可编程化和可边缘化,基于业务多样性下的差异化服务能力,把业务与性能晋升互相交融,实现交互体验的服务效力的最大化。在寰球一体化的背景下,让客户依据本人的业务去建设逻辑、去差异化解决,实现业务收效最大化。 对于一些老旧的业务,客户如果改变核心架构,可能要投入大量的老本和工夫周期,而在CDN侧进行边缘优化,让边缘承载更多的核心的服务能力,让更个性化的一种这种服务的话,更贴近于本人的业务,即可达到成果最佳。 原文链接 本文为阿里云原创内容,未经容许不得转载。

August 17, 2020 · 1 min · jiezi

关于typescript:TypeScript-类型系统

TypeScript 的学习材料十分多,其中也不乏很多优良的文章和教程。然而目前为止没有一个我特地称心的。起因有: 它们大多数没有一个清晰的主线,而是依照 API 组织章节的,内容在逻辑上比拟零散。大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。大多数内容比拟干燥,趣味性比拟低。都是水灵灵的文字,没有图片,不足可能引起强烈共鸣的例子。因而我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮忙大家建设 TypeScript 世界观。 而本篇文章则是这个系列的开篇。 系列安顿: 上帝视角看 TypeScript(已公布)TypeScript 类型零碎(就是本文)types 和 @types 是什么?你不晓得的 TypeScript 泛型(万字长文,倡议珍藏)(已公布)TypeScript 配置文件该怎么写?TypeScript 是如何与 React,Vue,Webpack 集成的?TypeScript 练习题目录未来可能会有所调整。留神,我的系列文章根本不会讲 API,因而须要你有肯定的 TypeScript 应用根底,举荐两个学习材料。 深刻了解 TypeScript官网文档联合这两个材料和我的系列教程,把握 TypeScript 不可企及。 接下来,咱们通过几个方面来从宏观的角度来看一下 TypeScript。 <!-- more --> 前言上一节的上帝视角看 TypeScript,咱们从宏观的角度来对 Typescript 进行了一个瞻望。之所以把那个放到结尾讲是让大家有一个大体的意识,不想让大家一叶障目。当你对整个宏观层面有了肯定的理解,那么对 Typescript 的了解就不会错太多。相同,一开始就是具体的概念和 API,则很可能会让你丢失都整体的根本判断。 实际上, Typescript 始终在不断更新迭代。一方面是因为当初许下的诺言”Typescript 是 JavaScript 的超集“(JavaScript 的个性你要同步反对,同时也要解决各种新语法带来的不兼容状况)。不单是 ECMA,社区的其余倒退可能也会让 Typescript 很好受。 比方 JSX 的宽泛应用就给 Typescript 泛型的应用带来了影响。 TypeScript 始终处于高速的迭代。除了修复日常的 bug 之外,TypeScript 也在一直公布新的性能,比方最新 4.0.0 beta 版本的标签元祖 的性能就对智能提醒这块很有用。Typescript 在社区倒退方面也做的分外好,以至于它的竞争对手 Flow 被 Typescript 完满击败,这在很大水平上就是因为 Typescript 没有烂尾。现在微软在开源方向的发力是越来越显著了,我很期待微软接下来的体现,让咱们刮目相待。 ...

August 16, 2020 · 3 min · jiezi

关于typescript:TypeScript做开源项目可以怎么玩这些好看又实用的项目告诉你

TypeScript 是一种由微软开发的开源、跨平台的编程语言。它是 JavaScript 的超集,最终会被编译为 JavaScript 代码。TypeScript 起源于应用 JavaScript 开发的大型项目 。因为 JavaScript 语言自身的局限性,难以胜任和保护大型项目开发。因而微软开发了 TypeScript ,使得其可能胜任开发大型项目。 Gitee 上喜爱 TypeScript 的开发者数量也不少,那么明天就为大家举荐一下他们都在用 TypeScript 做些什么乏味的我的项目。 1.ng-alain我的项目作者: ng-alain 开源许可协定: MIT 我的项目地址:https://gitee.com/ng-alain/ng-alain 一个基于 Antd 中后盾前端解决方案,提供更多通用性业务模块,让开发者更加专一于业务。 2.ledge我的项目作者: phodal 开源许可协定: MPL-2.0 我的项目地址:https://gitee.com/phodal/ledge Ledge (from Know-Ledge,意指承载物)常识和工具平台,是 ThoughtWorks 进行的一系列 DevOps 实际、麻利实际、软件开发与测试、精益实际提炼进去的常识体系。它蕴含了各种最佳实际、准则与模式、施行手册、度量、工具,用于帮忙您的企业在数字化时代更好地后退,还有 DevOps 转型。 3.vue-picture-cut我的项目作者: 光年以外 开源许可协定: MIT 我的项目地址:https://gitee.com/light-year/vue-picture-cut 基于vue和typescript开发的一款图片剪裁解决工具。 长处:原生、轻量、应用简略、性能全面、扩展性强。 目前性能:缩放、翻折、旋转、边缘校验、矩形剪裁、任意(椭)圆剪裁。 4.RuiJi.Scraper我的项目作者: 朱平齐 开源许可协定: LGPL-2.1 我的项目地址:https://gitee.com/zhupingqi/RuiJi.Scraper RuiJi Scraper是可视化的网络数据提取浏览器插件,反对目前大部分支流浏览器。 例如以谷歌为内核开发的谷歌浏览器、腾讯浏览器、猎豹浏览器、百度浏览器、360极速浏览器,以及火狐浏览器、微软最新的Edge浏览器。 5.Vimium C我的项目作者: gdh1995 开源许可协定: MIT ...

August 14, 2020 · 1 min · jiezi

关于typescript:TypeScript做开源项目可以怎么玩这些好看又实用的项目告诉你

TypeScript 是一种由微软开发的开源、跨平台的编程语言。它是 JavaScript 的超集,最终会被编译为 JavaScript 代码。TypeScript 起源于应用 JavaScript 开发的大型项目 。因为 JavaScript 语言自身的局限性,难以胜任和保护大型项目开发。因而微软开发了 TypeScript ,使得其可能胜任开发大型项目。 Gitee 上喜爱 TypeScript 的开发者数量也不少,那么明天就为大家举荐一下他们都在用 TypeScript 做些什么乏味的我的项目。 1.ng-alain我的项目作者: ng-alain 开源许可协定: MIT 我的项目地址:https://gitee.com/ng-alain/ng-alain 一个基于 Antd 中后盾前端解决方案,提供更多通用性业务模块,让开发者更加专一于业务。 2.ledge我的项目作者: phodal 开源许可协定: MPL-2.0 我的项目地址:https://gitee.com/phodal/ledge Ledge (from Know-Ledge,意指承载物)常识和工具平台,是 ThoughtWorks 进行的一系列 DevOps 实际、麻利实际、软件开发与测试、精益实际提炼进去的常识体系。它蕴含了各种最佳实际、准则与模式、施行手册、度量、工具,用于帮忙您的企业在数字化时代更好地后退,还有 DevOps 转型。 3.vue-picture-cut我的项目作者: 光年以外 开源许可协定: MIT 我的项目地址:https://gitee.com/light-year/vue-picture-cut 基于vue和typescript开发的一款图片剪裁解决工具。 长处:原生、轻量、应用简略、性能全面、扩展性强。 目前性能:缩放、翻折、旋转、边缘校验、矩形剪裁、任意(椭)圆剪裁。 4.RuiJi.Scraper我的项目作者: 朱平齐 开源许可协定: LGPL-2.1 我的项目地址:https://gitee.com/zhupingqi/RuiJi.Scraper RuiJi Scraper是可视化的网络数据提取浏览器插件,反对目前大部分支流浏览器。 例如以谷歌为内核开发的谷歌浏览器、腾讯浏览器、猎豹浏览器、百度浏览器、360极速浏览器,以及火狐浏览器、微软最新的Edge浏览器。 5.Vimium C我的项目作者: gdh1995 开源许可协定: MIT ...

August 14, 2020 · 1 min · jiezi

关于typescript:typescript-配置-alias

1 装置依赖 npm install --save-dev babel-plugin-module-resolver# yarn add babel-plugin-module-resolver --dev根目录新增.babelrc文件参考以下内容按您我的项目中的须要去批改 { "presets": ["next/babel"], "plugins": [ [ "module-resolver", { "alias": { "@/actions": "./actions", "@/components": "./components", "@/constants": "./constants", "@/pages": "./pages", "@/public": "./public", "@/reducers": "./reducers", "@/utils": "./utils" } } ] ]}批改tsconfig.json文件 { "compilerOptions": { "baseUrl": "./", "paths": { "@components/*": ["./components/*"], "@pages/*": ["./pages/*"], "@public/*": ["./public/*"] } }}留神"baseUrl": "./",不能省去,否则ts报Option 'paths' cannot be used without specifying '--baseUrl' option.谬误 next.js中配置alias也能够参考如上步骤

August 12, 2020 · 1 min · jiezi

关于typescript:Typescript-设计模式之工厂方法

在现实生活中,工厂是负责生产产品的,比方牛奶、面包或礼物等,这些产品满足了咱们日常的生理需求。此外,在日常生活中,咱们也离不开大大小小的零碎,这些零碎是由不同的组件对象形成。 而作为一名 Web 软件开发工程师,在软件系统的设计与开发过程中,咱们能够利用设计模式来进步代码的可重用性、可扩展性和可维护性。在泛滥设计模式当中,有一种被称为工厂模式的设计模式,它提供了创建对象的最佳形式。 工厂模式能够分为三类: 简略工厂模式(Simple Factory Pattern)工厂办法模式(Factory Method Pattern)形象工厂模式(Abstract Factory Pattern)本文阿宝哥将介绍简略工厂模式与工厂办法模式,而形象工厂模式将在后续的文章中介绍,上面咱们先来介绍简略工厂模式。 一、简略工厂模式1.1 简略工厂模式简介简略工厂模式又叫 静态方法模式,因为工厂类中定义了一个静态方法用于创建对象。简略工厂让使用者不必晓得具体的参数就能够创立出所需的 ”产品“ 类,即使用者能够间接生产产品而不须要晓得产品的具体生产细节。 置信对于刚接触简略工厂模式的小伙伴来说,看到以上的形容可能会感觉有点形象。这里为了让小伙伴更好地了解简略工厂模式,阿宝哥以用户买车为例,来介绍一下 BMW 工厂如何应用简略工厂模式来生产????。 在上图中,阿宝哥模仿了用户购车的流程,pingan 和 qhw 别离向 BMW 工厂订购了 BMW730 和 BMW840 型号的车型,接着工厂依照对应的模型进行生产并在生产实现后交付给用户。接下来,阿宝哥将介绍如何应用简略工厂来形容 BMW 工厂生产指定型号车子的过程。 1.2 简略工厂模式实战 定义 BMW 抽象类abstract class BMW { abstract run(): void;}创立 BMW730 类(BMW 730 Model)class BMW730 extends BMW { run(): void { console.log("BMW730 动员咯"); }}创立 BMW840 类(BMW 840 Model)class BMW840 extends BMW { run(): void { console.log("BMW840 动员咯"); }}创立 BMWFactory 工厂类class BMWFactory { public static produceBMW(model: "730" | "840"): BMW { if (model === "730") { return new BMW730(); } else { return new BMW840(); } }}生产并动员 BMW730 和 BMW840const bmw730 = BMWFactory.produceBMW("730");const bmw840 = BMWFactory.produceBMW("840");bmw730.run();bmw840.run();以上代码运行后的输入后果为: ...

August 11, 2020 · 2 min · jiezi

关于typescript:小游戏云开发入门

前言百度:https://q.qq.com/wiki/cloud/base/intro.html QQ:https://q.qq.com/wiki/cloud/base/intro.html WX:https://developers.weixin.qq.com/minigame/dev/wxcloud/basis/getting-started.html 当下云开发比拟火,不过自己并不是很感冒,因为他的益处不言而喻,但毛病也是致命的。益处就是1. 不必本人买服务器,域名认证,不必放心服务器过期;省去了很多工夫。2. 初始容量收费,根本够用。 毛病就是不能跨平台。这个就很伤了,咱们开发一款游戏不可能只上一个平台吧!尽管咱们能够上所有领有云开发能力的平台,然而毕竟没有云开发能力的平台也很多啊! 不过例如世界排行榜这样的性能还是能够用一下的。顶多就是没有云开发能力的平台不显示排行榜性能就是了。 我是用这个云开发能力做了一个比拟残缺的交易零碎。进入这个零碎后所有数据联网获取,来到这个零碎单机玩法。所以没有云开发能力的平台也就只能体验单机局部了。 开始这里我以微信云开发为例: 开明服务这一步要留神的是应用的appId 不能是测试id,否则你的云开发按钮是不可点击的。这一步完结之后你会取得一个环境id。 初始化wx.cloud.init({ env: 'test-x1dzi'})将上一步取得的环境id传入init函数即做好了初始化工作,而且此办法是没有返回值的。 调用云函数wx.cloud.callFunction({ name: 'add', data: { a: 12, b: 19 }}).then(console.log)根本在小程序端的重要局部就这些了。当你看过了几个反对云开发的平台阐明文档后你会发现,他们在小程序端的应用形式其实是一样的。只是命名空间的不同而已。所以在小程序端很容易做好多平台反对的。剩下的就是写云函数了。微信云其实就是nodejs服务器。每一个云函数你能够了解为后端通过路由后调用的函数。只是在调试和上传上的形式上有所不同而已。 这里的调试是比拟好受的,首先用creator打进去的包云函数目录是会被清理的,如果你把函数目录放到build-template中你又没法像在微信开发者工具中一样及时的看成果。又不能在开发者工具中改变一下就复制一份到build-template中。因为我的零碎没那么简单,所以目前就是每次打包后从新下载我须要的云函数。其实能够写个插件,在打包之前把云函数寄存到一个中央,打完包之后再放回来。 开发方式我的开发方式比较简单,间接将小程序端的云函数调用写成一个服务,增加到我的网络框架中,就跟我用长短链接一样应用了。文章地址:https://mp.weixin.qq.com/s/DQuiQejiS6qtBTef_yu0Sw扩大的形式很简略 定义一个新的链接形式。 定义类,实现接口这里的url 就是环境id,协定号就是云函数的名称。对于sendData类中的接管形式能够本人随便更改。 export default class WXCloudService extends Service { /** * 因为init函数无返回值,所以间接告诉链接胜利 * @param url 相当于环境ID * @param port 无用 */ connect(url: string, port?: number) { super.connect(url); console.log("WXCloudService connect url ", url) wx.cloud.init({ env: url }) this.emit(NetConfig.OPEN, url); } sendData(message: SendMessage) { let self = this; let protoID = message.getProtoID(); let data = message.getData(); console.log("WXCloudService sendData protoID ", protoID,' data ',data) wx.cloud.callFunction({ // 需调用的云函数名 name: protoID, // 传给云函数的参数 data: data, success: function (res) { console.log('WXCloudService success res ', res) self.onData(res.result, protoID); }, fail: function (res) { console.log('WXCloudService fail res ', res) self.onError(message); }, complete: function (res) { }, }) } isReady() { return true; }}在工厂中创立 ...

August 9, 2020 · 1 min · jiezi

关于typescript:小游戏云开发入门

前言百度:https://q.qq.com/wiki/cloud/base/intro.html QQ:https://q.qq.com/wiki/cloud/base/intro.html WX:https://developers.weixin.qq.com/minigame/dev/wxcloud/basis/getting-started.html 当下云开发比拟火,不过自己并不是很感冒,因为他的益处不言而喻,但毛病也是致命的。益处就是1. 不必本人买服务器,域名认证,不必放心服务器过期;省去了很多工夫。2. 初始容量收费,根本够用。 毛病就是不能跨平台。这个就很伤了,咱们开发一款游戏不可能只上一个平台吧!尽管咱们能够上所有领有云开发能力的平台,然而毕竟没有云开发能力的平台也很多啊! 不过例如世界排行榜这样的性能还是能够用一下的。顶多就是没有云开发能力的平台不显示排行榜性能就是了。 我是用这个云开发能力做了一个比拟残缺的交易零碎。进入这个零碎后所有数据联网获取,来到这个零碎单机玩法。所以没有云开发能力的平台也就只能体验单机局部了。 开始这里我以微信云开发为例: 开明服务这一步要留神的是应用的appId 不能是测试id,否则你的云开发按钮是不可点击的。这一步完结之后你会取得一个环境id。 初始化wx.cloud.init({ env: 'test-x1dzi'})将上一步取得的环境id传入init函数即做好了初始化工作,而且此办法是没有返回值的。 调用云函数wx.cloud.callFunction({ name: 'add', data: { a: 12, b: 19 }}).then(console.log)根本在小程序端的重要局部就这些了。当你看过了几个反对云开发的平台阐明文档后你会发现,他们在小程序端的应用形式其实是一样的。只是命名空间的不同而已。所以在小程序端很容易做好多平台反对的。剩下的就是写云函数了。微信云其实就是nodejs服务器。每一个云函数你能够了解为后端通过路由后调用的函数。只是在调试和上传上的形式上有所不同而已。 这里的调试是比拟好受的,首先用creator打进去的包云函数目录是会被清理的,如果你把函数目录放到build-template中你又没法像在微信开发者工具中一样及时的看成果。又不能在开发者工具中改变一下就复制一份到build-template中。因为我的零碎没那么简单,所以目前就是每次打包后从新下载我须要的云函数。其实能够写个插件,在打包之前把云函数寄存到一个中央,打完包之后再放回来。 开发方式我的开发方式比较简单,间接将小程序端的云函数调用写成一个服务,增加到我的网络框架中,就跟我用长短链接一样应用了。文章地址:https://mp.weixin.qq.com/s/DQuiQejiS6qtBTef_yu0Sw扩大的形式很简略 定义一个新的链接形式。 定义类,实现接口这里的url 就是环境id,协定号就是云函数的名称。对于sendData类中的接管形式能够本人随便更改。 export default class WXCloudService extends Service { /** * 因为init函数无返回值,所以间接告诉链接胜利 * @param url 相当于环境ID * @param port 无用 */ connect(url: string, port?: number) { super.connect(url); console.log("WXCloudService connect url ", url) wx.cloud.init({ env: url }) this.emit(NetConfig.OPEN, url); } sendData(message: SendMessage) { let self = this; let protoID = message.getProtoID(); let data = message.getData(); console.log("WXCloudService sendData protoID ", protoID,' data ',data) wx.cloud.callFunction({ // 需调用的云函数名 name: protoID, // 传给云函数的参数 data: data, success: function (res) { console.log('WXCloudService success res ', res) self.onData(res.result, protoID); }, fail: function (res) { console.log('WXCloudService fail res ', res) self.onError(message); }, complete: function (res) { }, }) } isReady() { return true; }}在工厂中创立 ...

August 9, 2020 · 1 min · jiezi

关于typescript:代码生成器插件与Creator预制体文件解析

前言之前写过一篇主动生成脚本的工具,然而我给它起名叫半自动代码生成器。之所以称之为半自动,因为我感觉全自动代码生成器应该做到两点:代码生成+主动绑定。之前的工具只做了代码生成,并没有做主动绑定,所以鄙人又花工夫钻研了CocosCreator的预制体文件,实现了主动绑定的能力,并且反对了插件应用形式。本篇内容,不仅仅是宣传本人的插件工具,还会帮忙大家剖析一下Creator的预制体文件格式,使购买插件的同学能够将插件价值最大化,也能让读者对Creator的预制体文件有所理解。Creator 预制体文件格式剖析首先咱们展现一小段预制体文件。[ { "__type__": "cc.Prefab", "_name": "", "_objFlags": 0, "_native": "", "data": { "__id__": 1 }, "optimizationPolicy": 0, "asyncLoadAssets": false, "readonly": false }, { "__type__": "cc.Node", "_name": "LoginView", "_objFlags": 0, "_parent": null, "_children": [ { "__id__": 2 }, { "__id__": 6 }, { "__id__": 18 }, { "__id__": 21 }, { "__id__": 33 }, { "__id__": 42 }, { "__id__": 63 }, { "__id__": 84 } ], "_active": true, "_components": [ { "__id__": 105 }, { "__id__": 106 } ], "_prefab": { "__id__": 107 }, "_opacity": 255, "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 }, "_contentSize": { "__type__": "cc.Size", "width": 960, "height": 640 }, "_anchorPoint": { "__type__": "cc.Vec2", "x": 0.5, "y": 0.5 }, "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [ 480, 320, 0, 0, 0, 0, 1, 1, 1, 1 ] }, "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": 0 }, "_skewX": 0, "_skewY": 0, "_is3DNode": false, "_groupIndex": 0, "groupIndex": 0, "_id": "" }]当你第一次看到这个文件的时候可能会有所纳闷,此文件是一个一维数组的json对象。数组第一个地位是预制体文件的形容能够疏忽。第二个地位的对象就是这个文件根节点对应的属性了。也是最重要的局部。从数据的_children 和_components字段能够看出,引擎是依据__id__这个字段来寻找其余数据的。只不过这个__id__并非你所了解的id,而是数组的索引地位。并且配置中所有波及到寻找其余数据都是用的这种__id__形式。包含给本人的脚本组件绑定节点。 ...

August 7, 2020 · 2 min · jiezi

关于typescript:游戏开发中的新手引导与事件管理系统

前言在游戏开发靠近序幕的时候,大部分的游戏都会接入老手疏导性能,晋升玩家的游戏体验,不至于让玩家进入游戏有冷场或者手足无措的感觉。对于老手疏导的做法预计一百个人有一百种形式,接下来我将分享一下本人的应用形式,并随同一些问题的探讨。 教学疏导1. 应用简略的遮罩聚焦。这种形式能够转移玩家的注意力,毕竟进入游戏的时候内容有很多,然而人的承受能力无限。能够依据配置表设置决定是否有遮罩 2. 将疏导放到最上层,屏蔽触摸事件这种形式比拟省事,也防止了玩家乱点导致的疏导谬误。然而咱们能够通过配置表的形式,决定这一步是否为强制疏导,如果不是那么能够不屏蔽事件。 3. 手指地位如何取得通过配置好的界面节点名称,控件名称间接取得节点。这里边应用的数组的模式配置,是预留的为了实现拖拽教学。让手指从一个地位滑动到另一个地位。 setWidget() { if (this.widget) { return; } let parent = EngineHelper.findChild(this.className, UIManager.instance().getRoot()) if (parent) { if (this.widgetName) { if (this.widgetName == 'this') { this.widget = parent; } else { this.widget = EngineHelper.findChild(this.widgetName, parent) } if (this.index >= 0) { this.widget = this.widget.children[this.index] if (this.index2 >= 0) { this.widget = this.widget.children[this.index2] } } } else { this.widget = parent; } } }而后将节点的坐标通过转换赋值给手指 ...

August 5, 2020 · 2 min · jiezi

关于typescript:游戏开发中的新手引导与事件管理系统

前言在游戏开发靠近序幕的时候,大部分的游戏都会接入老手疏导性能,晋升玩家的游戏体验,不至于让玩家进入游戏有冷场或者手足无措的感觉。对于老手疏导的做法预计一百个人有一百种形式,接下来我将分享一下本人的应用形式,并随同一些问题的探讨。 教学疏导1. 应用简略的遮罩聚焦。这种形式能够转移玩家的注意力,毕竟进入游戏的时候内容有很多,然而人的承受能力无限。能够依据配置表设置决定是否有遮罩 2. 将疏导放到最上层,屏蔽触摸事件这种形式比拟省事,也防止了玩家乱点导致的疏导谬误。然而咱们能够通过配置表的形式,决定这一步是否为强制疏导,如果不是那么能够不屏蔽事件。 3. 手指地位如何取得通过配置好的界面节点名称,控件名称间接取得节点。这里边应用的数组的模式配置,是预留的为了实现拖拽教学。让手指从一个地位滑动到另一个地位。 setWidget() { if (this.widget) { return; } let parent = EngineHelper.findChild(this.className, UIManager.instance().getRoot()) if (parent) { if (this.widgetName) { if (this.widgetName == 'this') { this.widget = parent; } else { this.widget = EngineHelper.findChild(this.widgetName, parent) } if (this.index >= 0) { this.widget = this.widget.children[this.index] if (this.index2 >= 0) { this.widget = this.widget.children[this.index2] } } } else { this.widget = parent; } } }而后将节点的坐标通过转换赋值给手指 ...

August 5, 2020 · 2 min · jiezi

关于typescript:从零开始创建TypeScript项目

1、创立文件夹间接创立typescipt文件夹通过终端创立typescript文件夹mkdir typescriptcd typescript2、进入typescript文件夹,初始化npmnpm init依据提醒回车,这时候文件夹中会生成package.json文件。 3、装置typescript/tslint和NodeJS的类型申明npm install --save-dev typescript tslint @types/nodetslint是代码规定查看工具,放弃代码格调统一。这时候文件夹中就会生成node_modules文件夹 4、配置tsconfig.json{ "compilerOptions": { "lib": ["ES2015"], "module": "commonjs", "outDir": "dist", "sourceMap": true, "strict": true, "target": "es2015" }, "include": [ "src" ]}能够本人新建并配置,或者应用命令初始化,处于typescript目录下,运行以下命令 ./node_modules/.bin/tsc --init根本配置我的项目阐明:include ts文件在哪个文件夹中lib 运行环境包含哪些apimodule ts代码编译成什么模块零碎outDir 编译后的js代码寄存地位stirct 是否开启严格模式target ts编译成js的规范... 有很多配置项具体能够查看其它材料5、配置tslint.json{ "defaultSeverity": "error", "extends": [ "tslint:recommended" ], "jsRules": {}, "rules": { "no-console": false }, "rulesDirectory": []}能够本人新建并配置或者应用命令初始化,处于typescript目录下,运行以下命令 ./node_modules/.bin/tslint --init6、在src目录中创立index.ts文件并输出内容mkdir srctouch src/index.ts当初整体目录如下typescript----node_modules/----src/ ----index.ts----package.json----tsconfig.json----tslint.jsonindex.ts...console.log("hello TypeScript")7、编译./node_modules/.bin/tsc这时候文件夹中就会生成dist/index.js 8、运行node ./dist/index.js如果中断输入“hello Typescript"那么阐明咱们第一个typescript我的项目配置胜利

August 4, 2020 · 1 min · jiezi

关于typescript:上帝视角看-TypeScript

TypeScript 的学习材料十分多,其中也不乏很多优良的文章和教程。然而目前为止没有一个我特地称心的。起因有: 它们大多数没有一个清晰的主线,而是依照 API 组织章节的,内容在逻辑上比拟零散。大多是“讲是什么,怎么用“,而不是”讲为什么,讲原理“。大多数内容比拟干燥,趣味性比拟低。都是水灵灵的文字,没有图片,不足可能引起强烈共鸣的例子。因而我的想法是做一套不同市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,通过通俗易懂的例子和图片来帮忙大家建设 TypeScript 世界观。 而本篇文章则是这个系列的开篇。 系列安顿: 上帝视角看 TypeScript(就是本文)TypeScript 类型零碎什么是 types?什么是 @types?类型推导, 类型断言与类型爱护你不晓得的 TypeScript 泛型(万字长文,倡议珍藏)(已公布)TypeScript 练习题TypeScript 配置文件该怎么写?TypeScript 是如何与 React,Vue,Webpack 集成的?目录未来可能会有所调整。留神,我的系列文章根本不会讲 API,因而须要你有肯定的 TypeScript 应用根底,举荐两个学习材料。 深刻了解 TypeScript官网文档联合这两个材料和我的系列教程,把握 TypeScript 不可企及。 接下来,咱们通过几个方面来从宏观的角度来看一下 TypeScript。 从输入输出上来看如果咱们把 Typescript 编译器看成一个黑盒的话。其输出则是应用 TypeScript 语法书写的文本或者文本汇合。 (文本) 如果几个文本有援用关系,比方 a.ts 依赖 foo.ts 和 bar.ts,其就是一个文本汇合。 (文本汇合) 输入是编译之后的 JS 文件 和 .d.ts 的申明文件。 其中 JS 是未来须要运行的文件,而 .d.ts 申明文件则是 ts 文件中的类型申明,这个类型申明就是你在 ts 文件中申明的类型和 TypeScript 类型推导零碎推导的类型。当然你也能够本人写 .d.ts 申明文件。 ...

August 4, 2020 · 1 min · jiezi

关于typescript:跨引擎游戏框架说明文档

文章比拟扩散,不易查找,故而有此篇。如需购买,请通过公众号——我的服务——源码发售进入。 目录 整体分为与引擎无关的cfw目录和与平台相干的engine目录。 cfw anim: 自定义序列帧动画 属性管理器:多用于战斗中英雄和敌人的各种属性治理。 audio 音频管理器:包含音乐,音效 collide:四叉树碰撞,已做优化。 教程:https://mp.weixin.qq.com/s/Xpf6qgZPSJ2cynNR7-iMYA event:独立于引擎的事件管理器。事件代理,全局事件。 网络:屏蔽http与socket的应用差别。应用协定号+数据的模式传输内容;不便适配各种不同服务器定义的协定格局。编解码应用策略模式,适应各种编解码模式。 教程: https://mp.weixin.qq.com/s/DQuiQejiS6qtBTef_yu0Sw MVC 教程:https://mp.weixin.qq.com/s/9odSAptgWPcjJep0o-LGOQ 教程:https://mp.weixin.qq.com/s/BRqWViFqToHGd3ELtBOdNA 8. 多语言解决 教程:https://mp.weixin.qq.com/s/MAmG4W3bRndoVwc1kLFSHg 9. 对象池封装 教程:https://mp.weixin.qq.com/s/rP2yUdaoKzv4DRflMb1orA 资源管理 教程:https://mp.weixin.qq.com/s/X8_40j0kNanYN6I3wEjURw 教程:https://mp.weixin.qq.com/s/l-gpWDXz1F4J_YlCatNmlw 本地存档的封装,反对密文存档 教程:https://mp.weixin.qq.com/s/TqbUhytw8iJkUaHUl0jCuw 罕用数据结构封装 13. 工夫管理器封装,反对客户端与服务器改正工夫 辅助工具类 UI管理器:单场景,分层治理 教程:https://mp.weixin.qq.com/s/PaE5fdsiC16QzNdzjyeh6A xlxs 数据解析:反对将多个sheet合并应用。 教程:https://mp.weixin.qq.com/s/Vxo4chW2cfrnR9hIi8waog engine AdaptUI:适配刘海屏应用BgScale:适配背景图片或者内容应用ListView: 依据Android的List View思维对ScrollView的优化,挪动顶部不可见时拿到底部反复利用,缩小节点创立个数。教程:https://mp.weixin.qq.com/s/ytjhIJ426BbTHUljBAtRTQ GridLayerListView: 分层治理的ListView,大幅度降低DrawCall,进入公众号回复listview可取得git地址。教程:https://mp.weixin.qq.com/s/w2qYPUB39vUQ-_FYQbqb7g Resolution:屏幕适配策动类,初始化调用即可。附赠工具 game 数据表寄存和导出目录MaliTextureTool etc压缩纹理导出工具,已增加批量导出脚本工具。pngquant 图片压缩工具,已增加批量导出脚本工具。public nodejs 专用类。PVRTexTool pvr压缩纹理导出工具,已增加批量导出脚本工具。xlsx: game 中数据表的导出工具 教程:https://mp.weixin.qq.com/s/MAmG4W3bRndoVwc1kLFSHg AdobeAIRInstaller.exe adobe反对工具BigShear.air,ShoeBox_3.6.5_public.air拆分图集;须要装置AdobeAIRInstaller 。PngSplit.exe 拆分图集bmfont64.exe bmfont制作工具 laya举荐runnable-hiero.jar bmfont制作工具 反对换色,描边runnable-hierotoxml 将runnable-hiero.jar导出的文件转换为xml格局,因为laya只反对xml格局。tinypng.py tiny 批量压缩脚本,近程压缩,须要注册还有数量限度,举荐应用pngquant。轻游戏证书生成.bat 如名称。creator 与 laya 代码生成+主动绑定工具,creator我的项目中以集成插件。 ...

August 2, 2020 · 1 min · jiezi

关于typescript:手写脚本代码太累搞一个生成工具吧

前言在游戏开发中,咱们的开发流程个别是 制作预制体或者场景创立脚本、申明属性拖拽节点设置属性编写逻辑我开发了款半自动代码生成器工具次要是解决第2步的问题;之所以称之为半自动,因为我感觉全自动代码生成器应该做到两点:代码生成(第2步)+主动绑定(第3步)。主动绑定须要改变预制体文件,因为所有人的应用形式不尽相同,呈现的问题会比拟多,我喜爱绝对灵便,束缚比拟少的形式,所以我采纳了拖拽设置和代码设置相结合的形式解决主动绑定的问题。 性能介绍导出与预制体同名的类文件申明属性 如果属性是有效值进行赋值 如果属性是按钮,进行函数监听,并生成监听函数 将生成的类导出到指定目录反对导出指定预制体文件或者目录,主动实别目录的子目录。反对导出Creator 和 Laya 的预制体应用TAG标记是否导出指定名称的属性,带有TAG标记符号的节点才会导出,我TAG是$。如果TAG是有效字符,那么会导出所有名称无效的节点。 creator导出的属性名称前面带有类型,button带有sprite会同时输入。 目录阐明creator_export:creator文件导出目录,这个目录工具会创立并且能够放到其余中央。creator_prefabs: creator文件输出目录,个别会设置为我的项目的预制体文件夹。放到这里只是测试应用。laya_export 和 laya_prefabs :同 creator文件夹。creator_build.bat: window下的运行脚本,实际上就是直行node 并传递两个参数。如果是mac用户能够自行写一个sh脚本。creator_prefab.js: creator文件导出的外围代码。creator_template.txt: creator导出文件的模板文件,实践上就是字符替换。file_util.js : 文件辅助类laya_build.bat,laya_prefab.js,laya_template.txt: 同creator文件。 残缺代码creator导出文件import BaseView from "../../../cfw/mvc/BaseView";const { ccclass, property } = cc._decorator;@ccclassexport default class LoginView extends BaseView { @property({type: cc.Sprite, displayName: "logointro$Sprite"}) logointro$Sprite: cc.Sprite = null; @property({type: cc.Sprite, displayName: "btn_buy_big$Sprite"}) btn_buy_big$Sprite: cc.Sprite = null; @property({type: cc.Button, displayName: "btn_buy_big$Button"}) btn_buy_big$Button: cc.Button = null; onLoad() { if(!this.logointro$Sprite){this.logointro$Sprite = this.findChild("logointro$").getComponent(cc.Sprite)} if(!this.btn_buy_big$Sprite){this.btn_buy_big$Sprite = this.findChild("btn_buy_big$").getComponent(cc.Sprite)} if(!this.btn_buy_big$Button){this.btn_buy_big$Button = this.findChild("btn_buy_big$").getComponent(cc.Button)} this.registerButtonByNode(this.btn_buy_big$Button,this.onbtn_buy_big$ButtonClick) } onbtn_buy_big$ButtonClick(){ } onDestroy(){ }}laya导出文件import BaseView from "../../../cfw/mvc/BaseView";export default class TestView extends BaseView { /** @prop {name:normal, tips:"normal", type:Node, default:null}*/ public normal: Laya.Button = null; /** @prop {name:double, tips:"double", type:Node, default:null}*/ public double: Laya.Button = null; constructor() { super(); } onAwake() { super.onAwake() if(!this.normal){this.normal = this.findChild("normal")} this.registerButtonByNode(this.normal,this.onnormalClick) if(!this.double){this.double = this.findChild("double")} this.registerButtonByNode(this.double,this.ondoubleClick) } onEnable(): void { } onDisable(): void { } onnormalClick(){ } ondoubleClick(){ }}注意事项节点的名称不能有空格,横线不能用引擎曾经应用的变量名结语工具已上传到框架仓库中,有须要的自行拉取,如遇到问题能够微信找我沟通。 ...

July 31, 2020 · 1 min · jiezi

关于typescript:手写脚本代码太累搞一个生成工具吧

前言在游戏开发中,咱们的开发流程个别是 制作预制体或者场景创立脚本、申明属性拖拽节点设置属性编写逻辑我开发了款半自动代码生成器工具次要是解决第2步的问题;之所以称之为半自动,因为我感觉全自动代码生成器应该做到两点:代码生成(第2步)+主动绑定(第3步)。主动绑定须要改变预制体文件,因为所有人的应用形式不尽相同,呈现的问题会比拟多,我喜爱绝对灵便,束缚比拟少的形式,所以我采纳了拖拽设置和代码设置相结合的形式解决主动绑定的问题。 性能介绍导出与预制体同名的类文件申明属性 如果属性是有效值进行赋值 如果属性是按钮,进行函数监听,并生成监听函数 将生成的类导出到指定目录反对导出指定预制体文件或者目录,主动实别目录的子目录。反对导出Creator 和 Laya 的预制体应用TAG标记是否导出指定名称的属性,带有TAG标记符号的节点才会导出,我TAG是$。如果TAG是有效字符,那么会导出所有名称无效的节点。 creator导出的属性名称前面带有类型,button带有sprite会同时输入。 目录阐明creator_export:creator文件导出目录,这个目录工具会创立并且能够放到其余中央。creator_prefabs: creator文件输出目录,个别会设置为我的项目的预制体文件夹。放到这里只是测试应用。laya_export 和 laya_prefabs :同 creator文件夹。creator_build.bat: window下的运行脚本,实际上就是直行node 并传递两个参数。如果是mac用户能够自行写一个sh脚本。creator_prefab.js: creator文件导出的外围代码。creator_template.txt: creator导出文件的模板文件,实践上就是字符替换。file_util.js : 文件辅助类laya_build.bat,laya_prefab.js,laya_template.txt: 同creator文件。 残缺代码creator导出文件import BaseView from "../../../cfw/mvc/BaseView";const { ccclass, property } = cc._decorator;@ccclassexport default class LoginView extends BaseView { @property({type: cc.Sprite, displayName: "logointro$Sprite"}) logointro$Sprite: cc.Sprite = null; @property({type: cc.Sprite, displayName: "btn_buy_big$Sprite"}) btn_buy_big$Sprite: cc.Sprite = null; @property({type: cc.Button, displayName: "btn_buy_big$Button"}) btn_buy_big$Button: cc.Button = null; onLoad() { if(!this.logointro$Sprite){this.logointro$Sprite = this.findChild("logointro$").getComponent(cc.Sprite)} if(!this.btn_buy_big$Sprite){this.btn_buy_big$Sprite = this.findChild("btn_buy_big$").getComponent(cc.Sprite)} if(!this.btn_buy_big$Button){this.btn_buy_big$Button = this.findChild("btn_buy_big$").getComponent(cc.Button)} this.registerButtonByNode(this.btn_buy_big$Button,this.onbtn_buy_big$ButtonClick) } onbtn_buy_big$ButtonClick(){ } onDestroy(){ }}laya导出文件import BaseView from "../../../cfw/mvc/BaseView";export default class TestView extends BaseView { /** @prop {name:normal, tips:"normal", type:Node, default:null}*/ public normal: Laya.Button = null; /** @prop {name:double, tips:"double", type:Node, default:null}*/ public double: Laya.Button = null; constructor() { super(); } onAwake() { super.onAwake() if(!this.normal){this.normal = this.findChild("normal")} this.registerButtonByNode(this.normal,this.onnormalClick) if(!this.double){this.double = this.findChild("double")} this.registerButtonByNode(this.double,this.ondoubleClick) } onEnable(): void { } onDisable(): void { } onnormalClick(){ } ondoubleClick(){ }}注意事项节点的名称不能有空格,横线不能用引擎曾经应用的变量名结语工具已上传到框架仓库中,有须要的自行拉取,如遇到问题能够微信找我沟通。 ...

July 31, 2020 · 1 min · jiezi

关于typescript:游戏开发性能优化之对象池

为什么要应用对象池对象池优化是游戏开发中十分重要的优化形式,也是影响游戏性能的重要因素之一。在游戏中有许多对象在不停的创立与移除,比方角色攻打子弹、特效的创立与移除,NPC的被毁灭与刷新等,在创立过程中十分耗费性能,特地是数量多的状况下。对象池技术能很好解决以上问题,在对象移除隐没的时候回收到对象池,须要新对象的时候间接从对象池中取出应用。长处是缩小了实例化对象时的开销,且能让对象重复应用,缩小了新内存调配与垃圾回收器运行的机会。 Cocos官网文档阐明的应用形式https://docs.cocos.com/creator/manual/zh/scripting/pooling.html 这样的一个对象池,其实严格意义上来说更像是节点池,因为它曾经解决了节点移除等操作。无奈将一般的TS对象放入cc.NodePool 进行治理。那么当咱们须要对一般的TS对象进行治理的时候还是须要本人再写一个对象池。益处就是回收节点的时候不须要对节点做任何操作。将节点增加到场景中时不须要思考是否存在的问题,间接addChild就能够了,因为存在于对象池中的节点必然是从场景中移除的节点。在应用的过程中频繁移除和增加有性能问题。针对以上问题,我分享一下本人应用对象池的教训。 对象池的封装节点对象池import { IPool } from "./IPool";export default class CCNodePool implements IPool{ private pool: cc.NodePool; private resItem: cc.Prefab; private name: string = '' /** * * @param prefab 预制体 * @param conut 初始化个数 */ constructor(name: string, resItem: cc.Prefab, conut: number) { this.name = name this.pool = new cc.NodePool(); this.resItem = resItem; for (let i = 0; i < conut; i++) { let obj: cc.Node = this.getNode(); // 创立节点 this.pool.put(obj); // 通过 putInPool 接口放入对象池 } } getName() { return this.name } get() { let go: cc.Node = this.pool.size() > 0 ? this.pool.get() : this.getNode(); return go; } getNode() { if(this.resItem){ return cc.instantiate(this.resItem); }else{ console.error(' 预制体没有赋值 ') return null; } } size() { return this.pool.size(); } put(go: cc.Node) { this.pool.put(go); } clear() { this.pool.clear(); }}非节点对象池export default class TSObjectPool<T> { private pool:any [] = [] private className:string; constructor(className:string,type: { new(): T ;},count:number = 0){ this.className = className; for (let index = 0; index < count; index++) { this.pool.push(new type()); } } getClassName(){ return this.className; } get<T>(type: { new(): T ;} ): T { let go = this.pool.length > 0 ? this.pool.shift() : null; if(!go){ go = new type(); } return go; } put(instance:T){ this.pool.push(instance); } clear(){ this.pool = []; } }对象池管理器不论是节点对象池还是非节点对象池。我都习惯通过一个管理器封装起来应用。这样的益处就是集中管理,批改时也十分不便。 ...

July 31, 2020 · 4 min · jiezi

关于typescript:CocoscreatorTS-ccNode监听自身被添加移除

cc.Node无论是被增加、被移除,都会调用它的 setParent,咱们来看看源码: 被增加: 被移除: 被移除代码中的 child.parent 所调用的,其实就是 child.setParent ,它是在 _BaseNode (cc.Node的基类)的原型中定义的,咱们来看看: 这意味着,咱们单单重写 setParent 办法的话,只能解决被增加的状况,对被移除无能为力。那咱们只有想方法替换掉这个 parent setter 就能达到目标了。 办法是间接给cc.Node的原型定义一个属性: Object.defineProperty(cc.Node.prototype, "parent", { // getter也要定义,不然会报错,还是间接调用getParent get: function() { return this.getParent(); }, set: function(v) { /** your code */ },});但在理论场景中,咱们并不需要给所有的cc.Node增加监听,只须要给那些关注被增加/移除操作的对象增加即可: /** 自定义节点*/class CustomNode extends cc.Node { // 定义本人的解决办法。当然,持续应用setParent作为办法名也能够 mySetParent(v: cc.Node) { // 肯定要调用父类的setParent super.setParent(v); /** your code */ }}// 给自定义节点定义属性Object.defineProperty(CustomNode.prototype, "parent", { get: function() { return this.getParent(); }, set: function(v) { this.mySetParent(v) },});功败垂成! ...

July 27, 2020 · 1 min · jiezi

关于typescript:如何实现一个优雅的搜索功能

前言这星期次要实现了一个搜寻性能,,便于用户在选择项比拟多的时候疾速抉择本人想要选的那个。 防抖本着是一个暗藏的搜寻框,如果在蹦出搜寻框的同时左边有一个提交按钮的话会显得比拟突兀,也是本着让用户少点一次的准则,就想做成一个相似于搜寻提醒的成果,这里用到了防抖机制。咱们心愿当咱们在搜寻框中输出关键字时,零碎可能主动搜寻。然而零碎无奈判断咱们输出到什么时候是他想要的关键字。这是就用到了防抖,通过搜寻框内容扭转的工夫判断是否开始搜寻,当一段时间内搜寻关键字不扭转时,开始调用搜寻办法。当搜寻关键字扭转时,重置工夫。比方咱们搜寻河北工业大学在不退出防抖时,过程是这样的:“河” -- 申请1次后盾(无用功) “河北” -- 申请1次后盾(无用功) “河北工” -- 申请1次后盾(无用功) “河北工业” -- 申请1次后盾(无用功) “河北工业大学” -- 申请1次后盾(用户想要的)在咱们退出防抖时,过程是这样的:“河” -- 不申请后盾 “河北” -- 等一秒,申请1次后盾,然而搜寻后的内容还是很多,持续输出 “河北工” -- 不申请后盾 “河北工业” -- 不申请后盾 “河北工业大学” -- 一秒后,申请1次后盾(用户想要的)具体到代码中,咱们应用(input)事件判断搜寻框内容变动,当内容变动时,会调用指定办法。 <input *ngIf="this.bindCourseSearch" (input)="search()" type="text" class="form-control search" placeholder="请输出课程名称或代码" [formControl]="courseCodeOrName"/>同时c层 searchSubject: Subject<string> = new Subject<string>();ngOnInit() { ... this.searchSubject.asObservable().pipe(debounceTime( 1000)) .subscribe(() => { this.codeOrName = this.courseCodeOrName.value; });}search(): void { this.searchSubject.next(); }更多对于截流与防抖的利用请看这篇文章:Rxjs防抖与节流在我的项目中的利用 搜寻速度搜寻往往面对很多数据,如何进步搜寻速度也是重要的一环 缩小申请后盾次数每申请一次后盾,便会破费一些工夫,所以咱们要尽可能的缩小申请后盾次数。如果不波及到分页的话,咱们申请一次所有数据后,在前台保留所有数据,当前关键字变更间接调用前台所有数据,缩小申请后盾所有数据。 /** 用于查问遍历,全副的专业课 */courseClone: Array<Course>缩小遍历次数咱们在前台间接解决所有数据免不了进行遍历进行过滤,屡次遍历也会影响搜寻工夫。比如说,咱们搜寻课程有多个条件,在某一学院下名称或或者代码蕴含关键字的课程。一开始仿写写好的过滤学院条件代码去写过滤名称或代码的代码。而后先过滤一遍学院,再过滤一遍名称或代码。如果同时过滤二者须要很多判断,避免在空指针,很简单。所以进行了两次过滤。然而我没有思考到在v层显示其实也是一遍遍历。咱们能够使用v层的一遍遍历,去进行名称或代码过滤,过滤掉的加个*ngIf不显示即可。 <ng-container *ngIf="getAllByCodeOrName(_course, state.codeOrName)"> <input class="form-check-input" id="course_{{_course.id}}" type="checkbox" [checked]="state.checkedMap.get(_course.id)" (change)="change(_course.id)"> <label class="form-check-label" for="course_{{_course.id}}">{{_course.code}}&nbsp;{{_course.name}}</label></ng-container>然而波及到分页不倡议这用ngif暗藏,这样会造成一页3条数据一页4条数据的状况。 ...

July 25, 2020 · 1 min · jiezi

关于typescript:游戏开发性能优化之对象池

前言在这里,我遇到的问题是:游戏包超过4m,并且小于等于8M,不须要近程资源服务器,应用分包即可。Creator推出了2.4版本,使得全平台都有了分包治理的能力,其实这句话我说的不够谨严,对于头条来说,这个AssetBundle就有些难堪了。如果将bundle放入resource目录下打包微信小游戏报错因为咱们公布头条小游戏根本都是通过微信小游戏批改后上线,然而头条小游戏是不反对分包加载的,只能把资源都放到resource下,这岂不是难堪了吗? 我想到的计划打包头条小游戏的时候还是须要先打微信包,只是在打包之前须要将之前设置的AssetBudle目录设置为一般目录,而后将资源包放回resource中。而后打其余包的时候再将Bundle文件夹从resource目录中拿进去。 公布步骤敞开Creator,将所有bundle目录及批改后的meta文件挪动到resource目录下并删除所有bundle目录及meta文件。关上Creator 批改代码逻辑,不应用引擎的AssetBundle,这一部分通过我的sdk整合框架+我的AssetBundle应用形式很容易做到,这一步其实在切换平台的时候就实现了。应用Creator 公布微信小游戏,用头条开发者工具调试公布上线。敞开Creator 运行另脚本将resource下的所有bundle目录及批改后的meta文件再拷贝到resource外,并删除resource中的所有bundle目录及meta文件。关上Creator 公布其余渠道。要害脚本var fs = require('fs');var path = require('path');var file_util = require('./file_util')//须要被挪动到resource下的bundle。var folderList = ['lobby','decoration','outdoor']//源目录var asset_path = process.argv[2];//目标目录var export_dir = process.argv[3];//是否是bundle 1和0var opt = process.argv[4];function getBundleName(name){ for(var i = 0; i < folderList.length; i ++){ let s = folderList[i] if(name.indexOf(s) >= 0){ return s; } } return null;}function setBundle(meta_name,opt){ console.log(' meta_name',meta_name,' opt ',opt) var flag = opt == '1' ? true : false; console.log(' flag ',flag) let fileData = file_util.readFile(meta_name) if(fileData){ var obj = JSON.parse(fileData); obj['isBundle'] = flag console.log('fileData 2222 ',JSON.stringify(obj)) file_util.writeFile(meta_name,JSON.stringify(obj)) }}function exchangeFiles(asset_path,export_dir,opt) { var stat = fs.statSync(asset_path); if (!stat.isDirectory()) { return; } var subpaths = fs.readdirSync(asset_path), subpath; for (var i = 0; i < subpaths.length; ++i) { if (subpaths[i][0] === '.') { continue; } subpath = path.join(asset_path, subpaths[i]); console.log(" subpath ", subpath); var fname = getBundleName(subpath) if(fname){ stat = fs.statSync(subpath); if (stat.isDirectory()) { let dest_path = path.join(export_dir,fname) file_util.mkdir(dest_path) file_util.copyDir(subpath,dest_path) file_util.delDir(subpath) }else{ var meta_name = subpath var exportMeta_name = path.join(export_dir,fname)+'.meta' setBundle(meta_name,opt) file_util.copyFile(meta_name,exportMeta_name) file_util.deleteFile(meta_name) } } }}exchangeFiles(asset_path,export_dir,opt)结语以上是我在应用Creator2.4版本公布头条小游戏时遇到的问题及解决方案。计划可能那个有点蠢笨,然而心愿对遇到同样问题的小伙伴有所帮忙,也心愿如果哪位小伙伴有更好的解决方案能够分享进去。如需残缺脚本,请进入公众号回复 ”公布头条“ 获取网盘链接。 ...

July 25, 2020 · 1 min · jiezi

CocosCreator之分层管理的ListView

前言进入公众号回复listview即可取得demo的git地址。 之前写的一篇文章《Creator之ScrollView那些事》中提到了官网Demo中提供的ListViewCtl,只是实现了纵向滑动,没有实现横向滑动。并且倡议官网能够把性能做全而后放入组件库中供开发者应用。而后有个牛逼大神说这个ListView不ok。要我对本人的公众号内容负责。我还认为有什么重大的bug,其实是打断了合批操作。对于官网提供的ListViewCtr的操作形式必定会打断合批的 !不过对于一些简略的需要,比方我上次文章中的这个截图。这样的列表须要合批吗?我的需要就是少创立几个节点就能够了。所以我感觉ok不ok还是要看需要吧!为什么tableview呼声那么高,而Laya也在官网组件中反对了ListView,曾经是很好的阐明了。 ListView的局限首先,这个ListView是有局限的,它间接将Item放入了content中,必定会打断合批操作;如果你有一个多列多行,并且item非常复杂的需要,那么用这个ListView必定是不适合的。就好比你用一把杀鸡的刀去杀一头牛,不喜剧才怪!所以大家在看到他人分享货色的时候倡议最好不要拿来主义,而是通过剖析后决定用还是不必,我置信作为程序猿,这点判断能力还是有的! 其次 ,这个ListView不反对网格显示。如果想要多行或者多列显示,须要本人在一个Item中排列好。而后本人设置每个道具的显示与暗藏,所以对于有多列显示需要的状况还是比较复杂的。那么我先说说ListView采纳的原理,而后再说说如何改良吧。 ListView采纳的原理依据可显示区域的宽或者高计算出须要创立的道具的数量。而后多加一行或者一列,防止滑动的时候显示不天然。 滑动时,将来到可见区域的道具放到与滑动方向相同的一端重复使用。 原理其实就这么两点,目标就是少创立节点。反对网格显示的ListView——GridListView首先我为之前的ListView减少了网格显示能力,代码中通过给定的spaceX和spaceY 联合可显示区域的宽或者高计算可显示的列数或者行数 如果只是做了网格显示能力而不做分层治理其实一样有局限1。尽管比你间接把道具放入content中好很多,然而dc仍然很高。反对分层治理的ListView——GridLayerListViewGridLayerListView 是继承了GridListView,重写了设置坐标和增加节点的办法。 这里的item仍然被增加到了content中,只是此时的它曾经没有子节点了,只是用来判断是否来到显示区域而存在的。同时在增加item的时候给item自定义了一个LIST属性,用于保留子节点的援用,因为曾经不能通过item的children数组取得子节点了。为每个子节点自定义一个属性INIT_POS,保留本地坐标,更新地位的时候会用到。 为了保障所有节点显示地位的正确性,代码中间接移除节点中存在的widget组件。 当你将一个ScrollView拖到界面上时,只须要调整ScrollView和view的宽高,代码中间接删除了默认的item节点 GridLayerListView并没有应用对象池,如果的确有须要能够在getItem函数中本人通过对象池获取道具。 通过设置ScrollView的Horizontal 和 Vertical 扭转滚动方向,同时只反对一个方向滚动。 应用形式将一个ScrollView拖到界面中,挂上GridLayerListView组件 定义一个解决逻辑的组件挂到界面上,并在逻辑组件中申明好应用的变量和函数,设置好GridLayerListView的参数。(其实跟ListViiew的应用形式是一样的) 设置ScrollView 和View 的宽高,留神尤其是View的宽高,因为View大小就是可见区域,代码中会依据View的宽高判断应该显示的列数或者行数。留神列数或者行数等于宽度或者高度/(item的宽度或者高度+横向间距或者纵向间距) 应用成果为了看优化的成果,用到的两个纹理都去掉了Packable选项 不分层的GridListView dc=64在不分层治理的状况下,道具中的label是否设置为Char模式dc都是一样的。 分层+Label不为Char模式 dc=23 分层+Label为Char模式 dc=9 道具的预制体构造 道具应用状况依据后盾输入能够看出,一共有35个须要显示的道具,实际上只创立了3 x5 = 15个道具就搞定了。 dc 从64 缩小到9,仍然用上了ListView少创立,反复利用的原理,只是加上分层管,达到了这样的成果,还算过得去吧。结语以上是我在之前的ListView根底上增加了网格显示,分层治理等能力后写进去的新组件,我给它起名叫GridLayerListView,是因为它是一个反对网格显示,分层治理节点的ListView。一个既能够杀鸡也能够杀牛的刀。就是对ListView情有独钟,没方法了。我并没有说这个是最优的计划,也不保障没有bug(我还不是一个敢说本人写的货色没bug的牛人),思维仅供参考,大神能够绕道。如果你想将dc降到更低,那么你还须要做一些其余的优化。倡议浏览文弱书生陈皮皮的《Cocos Creator 性能优化:DrawCall》进入公众号回复listview即可取得demo的git地址。 欢送扫码关注公众号《微笑游戏》,浏览更多内容。如果您感觉文章还能够,点赞、在看、分享、资助都是对我最大的激励,在下将感激不尽。 欢送扫码关注公众号《微笑游戏》,浏览更多内容。

July 16, 2020 · 1 min · jiezi

CocosCreator之AssetBundle使用方案分享

前言Creator2.4 推出了AssetBundle,使得所有平台都有了分包的能力。那么该如何应用这个弱小的性能呢?上面介绍一下我集体的用法,仅供参考,程度无限,非喜勿喷。依据官网文档 指出,之前的cc.loader 须要用cc.resource替换而cc.resource 自身就是一个Bundle也就是说,2.4之前,整个引擎只有一个cc.loader 治理资源,2.4之后采纳了多个Bundle来治理资源,只是cc.resource这个Bundle负责管理resource目录下的动静资源,曾经是引擎规定好的了。而自定义的Bundle其实是与cc.resource平级的。那么只有咱们给资源管理器传入不同的Bunlde,天然就是加载不同Bundle中的内容了。 概念在介绍我的用法之前,先阐明以下几个概念 分包 AssetBundle一个Bundle 就是一个分包,cc.resource 就是引擎默认的Bundle。 模块 Module将游戏依照性能划分为不同的模块。能够多个模块共用一个分包。也能够一个模块应用一个分包。不应用分包的模块默认应用cc.resource。这个模块类跟游戏中你定义的零碎,例如,设置,大厅,战斗,背包等等并不是非要一一对应的,并不是有多少个零碎就须要创立多少个Module.这个依据你的需要而定。然而我的资源清理都是依据不同的Module来做的,划分的越多,清理就越粗疏。 import ResLoader from "../../cfw/res/ResLoader";import AudioManager from "../../cfw/audio/AudioManager";import ResHelper from "../../engine/ResHelper";import { isNull } from "../../cfw/tools/Define";export default class Module { private loader: ResLoader; protected audio: AudioManager; protected moduleName: string = '' protected projectName: string = '' constructor(projectName: string, moduleName: string) { this.projectName = projectName; this.moduleName = moduleName; //有名称的模块,须要在适当的时候加载bundle 并设置。 if (!this.moduleName) {//name != '' 的 阐明是自定义的bundle。 this.setLoader(new ResLoader(new ResHelper(cc.resources))) } } setAudio() { if (this.loader) { this.audio = new AudioManager(this.projectName, this.loader) } } hasLoader() { return !isNull(this.loader) } setLoader(loader: ResLoader) { this.loader = loader } setLoaderByBundle(bundle: cc.AssetManager.Bundle) { this.setLoader(new ResLoader(new ResHelper(bundle, this.moduleName))) } getName() { return this.moduleName; } getLoader() { return this.loader; } getAudio() { return this.audio; }}模块管理器 ModuleManager负责初始化模块,取得和设置模块。设置模块ID对应的Bundle名称,不应用分包的模块名称设置为空字符串。 ...

July 15, 2020 · 2 min · jiezi

如何创建TypeScript的React项目

前提是node和vscode还有git曾经装置好 步骤1用vscode关上新建文件夹project_demo,根目录创立src文件夹,在src目录下创立以下文件(1)index.html <!DOCTYPE html><html><head> <meta charset="UTF-8"></head><body> <div id="root"></div></body></html>(2)index.tsx import * as React from 'react'import * as ReactDOM from 'react-dom'import { APP } from './APP'ReactDOM.render(<APP/>, document.querySelector('#root')) (3)index.css .APP{ color: red}(4)APP.tsx import * as React from 'react'import './index.css'import './index.tsx'export const APP = () => <div className='APP'> hello </div>步骤2根目录下创立以下文件(1)package.json次要配置devDependencies(开发我的项目依赖的库)和dependencies(生产我的项目依赖的库)还有script { "name": "new_project", "version": "1.0.0", "description": "", "main": "index.js", //---------批改package.json 里 script--------- "scripts": { "start": "webpack-dev-server --config=webpack.config.ts", "build": "webpack --config=webpack.config.ts" }, "author": "", "license": "ISC", //---------配置devDependencies--------- "devDependencies": { "@types/lodash": "^4.14.136", "@types/react": "^16.8.23", "@types/react-dom": "^16.8.5", "clean-webpack-plugin": "^3.0.0", "css-loader": "^3.1.0", "express": "^4.17.1", "file-loader": "^4.1.0", "html-webpack-plugin": "^3.2.0", "style-loader": "^0.23.1", "ts-loader": "^6.0.4", "ts-node": "^8.3.0", "tslint": "^5.18.0", "typescript": "^3.5.3", "webpack": "^4.38.0", "webpack-cli": "^3.3.6", "webpack-dev-middleware": "^3.7.0", "webpack-dev-server": "^3.7.2", "xml-loader": "^1.2.1" }, //---------配置dependencies--------- "dependencies": { "lodash": "^4.17.15", "react": "^16.9.0", "react-dom": "^16.9.0", "react-is": "^16.9.0", "antd": "^3.22.0" }}(2)webpack.config.ts ...

July 14, 2020 · 2 min · jiezi

TypeScript-30-unknown-类型

原文地址:TypeScript 3.0: The unknown Type原文作者:Marius Schulz译文出自:掘金翻译打算本文永恒链接:https://github.com/xitu/gold-miner/blob/master/TODO1/typescript-3-0-the-unknown-type.md译者:shixi-li校对者:Usey95, smilemuffieTypeScript 3.0: unknown 类型TypeScript 3.0 引入了新的unknown 类型,它是 any 类型对应的平安类型。 unknown 和 any 的次要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,咱们必须进行某种模式的查看。而在对 any 类型的值执行操作之前,咱们不用进行任何查看。 这片文章次要关注于 unknown 类型的理论利用,以及蕴含了与 any 类型的比拟。如果须要更全面的代码示例来理解 unknown 类型的语义,能够看看 Anders Hejlsberg 的原始拉取申请。 any 类型让咱们首先看看 any 类型,这样咱们就能够更好地了解引入 unknown 类型背地的动机。 自从 TypeScript 在 2012 年公布第一个版本以来 any 类型就始终存在。它代表所有可能的 JavaScript 值 — 根本类型,对象,数组,函数,Error,Symbol,以及任何你可能定义的值。 在 TypeScript 中,任何类型都能够被归为 any 类型。这让 any 类型成为了类型零碎的 顶级类型 (也被称作 全局超级类型)。 这是一些咱们赋值给 any 类型的代码示例: let value: any;value = true; // OKvalue = 42; // OKvalue = "Hello World"; // OKvalue = []; // OKvalue = {}; // OKvalue = Math.random; // OKvalue = null; // OKvalue = undefined; // OKvalue = new TypeError(); // OKvalue = Symbol("type"); // OKany 类型实质上是类型零碎的一个逃逸舱。作为开发者,这给了咱们很大的自在:TypeScript容许咱们对 any 类型的值执行任何操作,而无需当时执行任何模式的查看。 ...

July 9, 2020 · 5 min · jiezi

Creator填色游戏的一种实现方案

前言先上一个辛苦弄出来的gif效果。写公众号时间不长,很多技巧还在慢慢跟小伙伴学习。可关注公众号,回复“绘图”或者“填色”都可获得demo的git地址。请使用Creator2.4.0运行 填色游戏种类也挺多的,我今天要说的是一种相对简单的填色。对于填色游戏的做法,我在论坛里搜到不少帖子,尤其是这个帖子的留言比较多:油漆桶填色效果怎么实现啊,找了两天都找不到资源其中有一条留言跟我的想法不谋而合,尤其是做了之前的取色,绘图等功能后,对webgl的readPixels()函数返回的数据处理起来越来越顺手。所以就用了替换数据的方式。还有一种填色游戏采用的纯Graphics的方式,各种贝塞尔曲线,矩形,直线和moveTo,实现几个区块填指定颜色的填色游戏,我感觉那种应该是借助工具的,因为生成的文件相当大。如果有知道的同学可以在下方留言,一起交流。 制作方式先上一张图这是一张我在《绘图游戏调色盘取色方法》的文章中展示的一张图,图片左侧的色盘是原始图,右侧是使用相机截屏获得的数据更新后的图。当时是为了验证截屏的正确性而做的。这次正好用来做填色游戏。 制作原理实际上更改的就是右侧这张图使用的数据。左侧的图接收触摸事件,通过触摸点的位置,更新颜色数据。所以只要将两张图片重叠,用右边的图片盖住左边的图片,然后使用处理过的颜色数据更新右边的这张图,效果也就出来了。这里需要说一嘴,为什么要用两张图,就是相机截屏获得的数据初始化出来的纹理,正好是y反转的,所以右侧的图的scaleY我给了-1.否则就是倒着的。 接收事件的处理接收事件,获得点击坐标位置,这个没什么可多说的。 更新数据更新数据的时候我是从点击的点向四周扩散,因为颜色的点太多,所以不能用递归操作,因此做了每帧更新多少个点的方式,目前我给的是5000,OPPO A9真机实测不卡。使用一个数组记录更新过的点,避免重复更新消耗时间。 坑与新玩法我没有做在更新数据时不可以更换颜色操作,所以在更新数据时如果点击了左侧的色盘,那么右侧的颜色会跟着更新,这个也可以说是坑,也可以说是玩法。色盘也是通过相机截屏获得的数据获取的,但是这种获取有缺陷,就是点击的色盘上的点不能有阴影,但是我使用的色盘刚好有,所以有的时候获取的颜色会是黑色或者灰色。 代码import TextureRenderUtils from "../TextureRenderUtils";const { ccclass, property } = cc._decorator;@ccclassexport default class FillColorV1 extends cc.Component { @property([cc.Component.EventHandler]) callback: cc.Component.EventHandler[] = []; @property(cc.Camera) camera: cc.Camera = null; @property(cc.Sprite) target: cc.Sprite = null; @property(cc.Node) renderNode: cc.Node = null; private pointList: number[] = [] private r: number; private g: number; private b: number; protected textureHelper: TextureRenderUtils = new TextureRenderUtils() private grid: number[] = [] private imgData: ArrayBufferView = null; start() { this.node.on(cc.Node.EventType.TOUCH_START, this.touchStart, this) this.init(); } getTextureInfo() { return this.textureHelper.getTextureInfo(); } getDataUrl() { return this.textureHelper.getDataUrl() } changeColor(color: cc.Color) { this.r = color.r; this.g = color.g; this.b = color.b; } init() { this.textureHelper.init(this.camera, this.renderNode) this.textureHelper.render() let data = this.textureHelper.getData() if (data.length > 0) { this.imgData = data; cc.log('FillColorV1 width ', this.renderNode.width, ' height ', this.renderNode.height) cc.log(' 实际上有多少个点 == ', data.length / 4) let count = this.renderNode.width * this.renderNode.height; cc.log(" 应该有多少个点的颜色 ", count) if (this.target) { let tTexture = this.target.spriteFrame.getTexture() tTexture.setFlipY(false) this.target.node.scaleY = -1 tTexture.initWithData(data, tTexture.getPixelFormat(), this.renderNode.width, this.renderNode.height) } } } update(dt: number) { let flag = false; let count = 0; let width = this.textureHelper.width; let r = this.r; let g = this.g; let b = this.b; //当发现有点击坐标的时候开始执行。这个5000 while (this.pointList.length >= 2 && count++ <= 5000) { flag = true; let x = this.pointList.shift(); let y = this.pointList.shift(); this.paintPoint(x, y, width, r, g, b) } if (flag) { let texture = this.target.spriteFrame.getTexture(); texture.initWithData(this.imgData, texture.getPixelFormat(), texture.width, texture.height) } } paintPoint(x: number, y: number, width: number, r: number, g: number, b: number) { let data = this.imgData; let rowW = Math.floor(width) * 4//一行的长度 x = Math.floor(x) let srow = Math.floor(y);//行开始位置 let startX = srow * rowW + x * 4;//列开始位置 if (!this.grid[startX]) { this.grid[startX] = 1 // cc.log('r g b%{} ', data[startX + 0], data[startX + 1], data[startX + 2]) if (data[startX + 0] > 100 || data[startX + 1] > 100 || data[startX + 2] > 50) { data[startX + 0] = r; data[startX + 1] = g; data[startX + 2] = b; this.pointList.push(x - 1) this.pointList.push(y); this.pointList.push(x + 1) this.pointList.push(y); this.pointList.push(x) this.pointList.push(y - 1) this.pointList.push(x) this.pointList.push(y + 1) } } } //用于打印点击的位置,无关紧要 showPointColor(x: number, y: number, width: number) { let data = this.imgData; let rowW = Math.floor(width) * 4//一行的长度 x = Math.floor(x) let srow = Math.floor(y);//行开始位置 let startX = srow * rowW + x * 4;//列开始位置 cc.log('r g b', data[startX + 0], data[startX + 1], data[startX + 2]) } touchStart(e: cc.Touch) { let pos = e.getLocation(); pos = this.node.convertToNodeSpaceAR(pos) cc.log('touchStart x ', pos.x, ' y = ', pos.y) this.pointList.length = 0; this.grid.length = 0; this.pointList.push(pos.x) this.pointList.push(pos.y) this.showPointColor(pos.x, pos.y, this.textureHelper.width) }}结语以上是我做的一种填色方案,并没有涉及到很大的图案,也没有涉及到放大缩小。我没有说我的方案是最好的,我相信方案有很多种,很多方案都有它的局限性,有它的适用范围;只要没有bug,就有参考价值;但是不要拿来主义,要根据你自己的情况,酌情考虑。 ...

July 8, 2020 · 3 min · jiezi

一个可屏蔽长短链接的网络模块

前言游戏开发中最复杂的模块,没有之一。其实我也不想写这篇文章,怎奈框架代码卖出去了,得给我的用户一个交代。网络模块都需要实现哪些功能呢?按我以往的开发经验总结如下: 消息的正常发送与接收长链接的断线重连消息发送失败与尝试长链接的心跳处理适应各种服务器定义的协议格式适应各种数据传输格式屏蔽长短链接的差异长链接支持发送协议号与接收协议号不同的情况。让短链接也可以像长链接一样更新数据。有必要屏蔽长短链接吗?这个看需求吧有没有开发过程中将长链接改成短链接的情况呢?你客户端不支持,服务器可是支持的。如果一个团队有很多开发人员,作为主程的你是否要屏蔽底层逻辑,提供统一的调用接口给其他开发人员使用呢?如果你一个人做一款游戏,你就随便来吧,随便什么样的方式只要你开心就好。 类图 Service 实现ServiceInterface接口负责屏蔽链接类型Message 负责封装发送和接收的消息。MessageHander 负责编解码处理。ServiceInfo 保存服务器信息。例如 ip 端口,协议号映射等信息。RemoteProxy 负责调用Service发送消息,接收Service返回的消息并通过事件派发给监听者。关键代码ServiceInfo ServiceInterface Service MessageHandler Message RemoteProxy 如何使用呢?定义一个类来处理链接的监听 定义一个地址和开发环境相关的数据类 实现编解码处理类 定义协议号常量类 定义一个链接 使用方式export default class LoginController extends LogicController { constructor(){ super(LoginProxy.instance()); } private static ins:LoginController; static instance():LoginController{ if(!this.ins){ this.ins = new LoginController(); } return this.ins; } //注册协议号与回调函数 getProtoList(){ return [ [NetConfig.OPEN,this.netOpen], [LoginProtocolIDs.LOGIN,this.loginRsp], ]; } netOpen(){ cc.log(' 链接成功 ') this.pushView('Prefab/LoginView','LoginView',null,ModuleManager.getLoader(),UIIndex.STACK) } //进入模块 先链接服务 当然也可以先弹出界面,再推送链接结果。 intoLayer(){ ModuleManager.setModuleID(ModuleID.LOGIN) //进入此模块,先进行链接操作,如果链接成功 会走loginRsp 函数 this.remoteProxy.connect(new ServiceInfo(NetConfig.HTTP,AddressConfig.getAdress(AddressConfig.LOGIN,0))); } // 点击登陆按钮发送请求。 loginReq(name:string){ cc.log(" loginReq ",name); this.sendMessage(LoginProtocolIDs.LOGIN,{name:name,channel:'crazy'}); } //登陆成功 loginRsp(msg:ReceiveMessage){ cc.log(" loginRsp msg ",msg); //由于服务器已经关闭,所以不会被调用,正常内容返回时会走这里。 }}结语细节代码太多了,如果都粘贴上来无法忍受。其实网络那些事论坛里已经有人说的很详细了。使用方式也很多,就好像都是用xxgl,每个引擎实现的方式都不同。我只是从框架和封装的角度整理一下具体的使用方式,其实细节的东西,你没有遇到的时候也是没办法理解的,代码里都是经验。有想法的同学留言吧。 ...

July 8, 2020 · 1 min · jiezi

游戏开发中的道具管理

前言在开发游戏的时候,我们肯定会处理道具,不论是多大的游戏都有道具。道具的管理方式也是多种多样。下面记录一下我个人在游戏开发中的道具管理方式。 道具的定义道具的属性基本分为:ID、类型、数量、图标、名称、等重要信息,还有描述、激活状态、购买此道具的消耗道具等其他信息。不过在配置和传递数据时最重要的属性还是ID,类型和数量这3个。 ID:也就是道具的唯一标识类型:用于区分道具所在的数据表道具的数量: 可以是拥有的数量,也可以是需要的数量。不论在配置数据的时候还是在传递数据的时候,都需要填写这三个信息。如果直接使用[id,type,num]的形式传递和配置数据也是没有问题的。不过我更喜欢将ID和类型合并,这样在传递和配置数据的时候只需要设置两个数值就可以 了,例如[ID,num]。而ID中是带有类型信息的,比如10001,可以用前两位代表类型,可以标识99-10 + 1 个类型。后边的三位标识道具的ID,可以标识999个道具,如果不够用自己在扩大位数即可。那么我们在配置数据的时候就可以这样填写。这里的62代表一种类型的道具,10002是我定义的视频广告。也就是说62002这个道具是需要用广告开启后才可以使用。所以道具才有激活状态这个属性,这个可以有也可以没有的东西,还是根据自己的项目而定了。 道具管理以上是定义一个道具的数据格式、配置和使用方式。那么这么多道具应该如何管理呢? . 各模块管理器 :负责管理自己的道具,比如增删改查,统一实现一个接口。 . 全局道具管理器:负责统筹管理,可以有两种方式 比如一个活动模块,可能发放各种类型的道具,这些道具属于不同的模块,这个时候全局道具管理器就会通过类型找到模块管理器处理相关逻辑,也就是为什么各模块管理器要实现一个同一的接口的原因了。各模块之间也可以通过这个全局道具管理器进行交互。这个全局道具管理器也可以不调用各模块,而是直接调用存档数据,然后让各模块自己监听数据的变化做对应的处理。建议建议只有一个,就是不要把不同类型的数据放到一个数据表中。因为大家都知道的,道具都是有些共同属性的,那索性把武将,武器,等等一些列有相同属性的道具都放到一个表中吧!这样做有几个问题: 将同一种类型的东西放到两个表中维护,增加了维护难度。将不同类型的数据放到一个表中,如果我需要配置武器数据,还要先向下拉一百或者几十行略过武将的数据,这也是在增加维护难度。程序在组织模型的时候需要从两个配置数据中获取,增加了复杂度。由于将各模块用的数据都放到了一起,那么只能是作为共有数据一起加进内存,而且不能删除。所以还是建议同种类型的数据就放到一个表中维护就可以了。 结语以上是我个人对于道具管理的一些理解,主要还是对于多人开发而语言,希望有不同意见的同学在下方留言,一起交流。 长按下方二维码,关注《微笑游戏》公众号,获取更多精彩内容。欢迎扫码关注公众号《微笑游戏》,浏览更多内容。

July 8, 2020 · 1 min · jiezi

再学-TypeScript-2-高级类型

前言在之前已经重新的去了解了一下基础类型的一些知识,现在就继续向前,进一步了解下 TypeScript 中的高级类型以及一些用法。 一、字面量类型字符串字面量类型 有时候可以直接定义字符串字面量类型简单方便的指定变量的固定值,实现类似枚举类型的字符串: type direction = 'up' | 'down' | 'left' | 'right' | 'static'function move(type: number): direction { switch(type) { case 0: return 'up' case 1: return 'down' case 2: return 'left' case 3: return 'right' default: return 'static' }}数字字面量类型 TypeScript 还具有数字字面量类型: type nums = 0 | 1 | 2二、索引类型当我们需要获取某个对象中的一些属性值时,通常会是这样: const person = { name: 'tom', age: 11}function getProps(obj: any, keys: string[]) { return keys.map(key => obj[key])}getProps(person, ['name', 'age']) // ['tom', 11]// 当指定的 key 不是对象的 key 时,编译器并没有提示错误getProps(person, ['sex']) // [undefined] 这时候,可以使用索引类型,编译器就能够检查使用了动态属性名的代码: ...

July 5, 2020 · 4 min · jiezi

游戏开发中的人工智能

前言今天非常开心,观看cocos官方直播居然在几千人中中奖,可以买彩票了。言归正传,所谓的人工智能,也就是大家常说的AI(Artificial Intelligence)。一说到AI可能就会让人觉得比较深奥,其实也就是非玩家角色思考和行为的综合。比如,在什么样的条件下,触发什么样的行为。其实我们在游戏开发中的AI要比学术理论中的AI简单很多,甚至有些行为不需要AI也能体现。比如使用剧情对话体现非玩家角色的想法。那么AI 都涉及到哪些东西呢? 控制器我理解的控制器,就是非玩家角色的大脑,是用来思考事情的。例如通过执行决策树,得到一个有效的行为。使用不一样的控制器就会有不一样的思考方式。比如玩家的控制器就是根据按键操作触发不同的行为。阿猫,阿狗的可能又不一样了。 感知器获得周围环境的情况,不如距离谁有多远,自身生命值多少,玩家生命值多少,等等。 反应也就是控制器执行决策树后产生的有效行为。比如跳跃,跑,各种攻击,防御等等。 决策树我理解为思考时的思路,比如应该在什么样的条件下执行什么样的反应。比如当我的血量低于百分之30的时候我要逃跑。具体案例体现在我的游戏《星际迷航》的第一个boss身上。 记忆就是非玩家角色可以通过存储数据,供控制器执行的时候使用,以提高非玩家角色的智商。 学习这个能力太牛逼了。实现起来也比较复杂,需要大量的数据和计算量为依托,而且在游戏开发中也并不一定实用。因此我也没用过。 如何应用到程序中呢?首先还是要定义好行为枚举,通过状态机,不同的行为实现不同的逻辑。 定义感知器特征不同的游戏感知的特征肯定是不一样的,根据游戏需求而定 实现感知类 定义决策树export default class DecisionTree { private decisionData: XlsxData; private perception: Perception; constructor(data: XlsxData) { this.decisionData = data; } setPerception(perception: Perception) { this.perception = perception; } getPerception(obj, perceptionType: PerceptionType, value: number) { return this.perception.action(obj, perceptionType, value) } //开始思考 action(obj: RoleView, decisionID: number) { let data = this.decisionData.getRowData(decisionID) let flag = false; if (data) { let perceptionType = data[Ai_dataEnum.condition]; let type = 0; let id: number[] = null; flag = this.perception.action(obj, perceptionType, data[Ai_dataEnum.cParam]) if (flag) { type = data[Ai_dataEnum.conditionYes] id = data[Ai_dataEnum.parm1] } else { type = data[Ai_dataEnum.conditionNo] id = data[Ai_dataEnum.parm2] } this.judge(obj, type, id) }else{ } return flag; } //判定感知条件 private judge(obj: RoleView, type: ThinkType, param: number[]) { if (type == ThinkType.ACTION) { this.doLogic(obj, param) } else { for (let index = 0; index < param.length; index++) { const element = param[index]; if (this.action(obj, element)) { break;//目前仅支持串行,不支持并行。如需支持并行,需要添加是否拦截字段。 } } } } // 50 30 20 : 80 根据概率选择行为 private doLogic(obj: RoleView, param: number[]) { if (param.length > 0) { let r = RandomHelper.random(0, 100); let count = param.length / 2 for (let index = 0; index < count; index++) { let behaveType: number = param[index * 2] let random: number = param[index * 2 + 1] // if (r <= random) { // 设置非玩家角色的行为。 obj.setBehaveType(behaveType) return; } } } }}定义控制器export default class EnemyController extends GameController { private perception: Perception = new Perception(); private ai: DecisionTree; constructor() { super() let ai_data: XlsxData = GameDataManager.instance().get(DataName.ai_data) this.ai = new DecisionTree(ai_data) this.ai.setPerception(this.perception) } getPerception(obj, perceptionType: PerceptionType, value: number) { return this.perception.action(obj, perceptionType, value) } action(obj: RoleView, decisionID: number) { this.ai.action(obj, decisionID) }}在非玩家角色中声明控制器和行为管理器 ...

July 2, 2020 · 2 min · jiezi

手机号-验证-ts

记录一下 手机号验证方法 /** * @description: 验证手机号正确性 * @param {string} phoneNumber 手机号 * @return {boolean} 是否验证通过 true|返回运营商-通过 false-不通过 */export const vaildPhoneNumber = (phoneNumber): string | boolean => { // 规则说明 2019-12-24 工信部核发190、192、196、197号段 // 【虚拟运营商】:170[1700/1701/1702]、162(电信),1703/1705/1706、165(移动),1704/1707/1708/1709(联通)、171、167(联通) // 【卫星通信】: 1740[0-5] (电信),1349(移动) // 【物联网网号】:10648、1440 (移动),10646、146(联通),10649、1410(电信) // 【国家工信部应急通信】:1740[6-9],1741[0-2] // 所以整理分类号段 // 手机号码: 13[0-9], 14[5,6,7,8,9], 15[0-3, 5-9], 16[2,5,6,7], 17[0-8], 18[0-9], 19[0-3, 5-9] // 移动号段: 13[4-9],147,148,15[0-2,7-9],165,170[3,5,6],172,178,18[2-4,7-8],19[5,7,8] // 联通号段: 130,131,132,145,146,155,156,166,167,170[4,7,8,9],171,175,176,185,186,196 // 电信号段: 133,149,153,162,170[0,1,2],173,174[0-5],177,180,181,189,19[0,1,3,9] // 广电号段: 192 const regMOBILE = /^(13[0-9]|14[5-9]|15[0-3,5-9]|16[2,5,6,7]|17[0-8]|18[0-9]|19[0-3,5-9])\d{8}$/; const regCNMobilE = /^((13[4-9])|(14[7-8])|(15[0-2,7-9])|(165)|(178)|(18[2-4,7-8])|(19[5,7,8]))\d{8}|(170[3,5,6])\d{7}$/; const regCNUnicom = /^((13[0-2])|(14[5,6])|(15[5-6])|(16[6-7])|(17[1,5,6])|(18[5,6])|(196))\d{8}|(170[4,7-9])\d{7}$/; const regCNTelecom = /^((133)|(149)|(153)|(162)|(17[3,7])|(18[0,1,9])|(19[0,1,3,9]))\d{8}|((170[0-2])|(174[0-5]))\d{7}$/; const regCNBroadcastingNetwork = /^((192))\\d{8}$/; if (regCNMobilE.test(phoneNumber)) return '中国移动'; if (regCNUnicom.test(phoneNumber)) return '中国联通'; if (regCNTelecom.test(phoneNumber)) return '中国电信'; if (regCNBroadcastingNetwork.test(phoneNumber)) return '中国广电'; if (regMOBILE.test(phoneNumber)) return true; return false;};

July 1, 2020 · 1 min · jiezi

Typescript-React-Umijs3中useDispatch的then报错问题

新项目开始使用umijs3.0后,其目前只有typescript版本,所以也全面尝试使用typescirpt。ts确实优势明显,变量类型清晰、优化提示、易维护,看API之类也清楚,就是项目需要写的的东西比以前翻了一倍,也难怪node内部放弃ts。回归问题如下图:使用useDisptach的then会报错,虽然使用// @ts-ignore可以屏蔽问题,但是心里面总是毛毛的,一定要解决这个问题才可以: TS2339: Property 'then' does not exist on type '{ type: string; payload: any; }'.网上找了半天都是抄来抄去,只有dispatch(action),没有说dispatch的异步处理then问题。唯一一篇在stackoverflow上的文章:https://stackoverflow.com/questions/59800913/type-safe-usedispatch-with-redux-thunk也没有解决这个问题。在官方文档的@umijs/plugin-dva处也没有关于dispatch的异步处理。后面看了半天umijs、react-redux、redux的关于useDispatch说明,最后在umijs中找到其对Dispatch的定义。 export interface Dispatch<A extends Action = AnyAction> { <T extends A>(action: T): Promise<any> | T;}因此尝试了一下对dispatch声明类型为umijs的Dispatch类型: import {Dispatch} from 'umi';const dispatch: Dispatch = useDispatch();就此解决该问题。如图: 虽然结果很简单的解决了,但是解决问题的过程中碰壁不少,也在umijs的github提了该问题但是没有任何人答复。不知道使用的人是不是都不用dispatch的异步处理。

July 1, 2020 · 1 min · jiezi

游戏开发中的道具管理

前言在开发游戏的时候,我们肯定会处理道具,不论是多大的游戏都有道具。道具的管理方式也是多种多样。下面记录一下我个人在游戏开发中的道具管理方式。 道具的定义道具的属性基本分为:ID、类型、数量、图标、名称、等重要信息,还有描述、激活状态、购买此道具的消耗道具等其他信息。不过在配置和传递数据时最重要的属性还是ID,类型和数量这3个。 ID:也就是道具的唯一标识类型:用于区分道具所在的数据表道具的数量: 可以是拥有的数量,也可以是需要的数量。不论在配置数据的时候还是在传递数据的时候,都需要填写这三个信息。如果直接使用[id,type,num]的形式传递和配置数据也是没有问题的。不过我更喜欢将ID和类型合并,这样在传递和配置数据的时候只需要设置两个数值就可以 了,例如[ID,num]。而ID中是带有类型信息的,比如10001,可以用前两位代表类型,可以标识99-10 + 1 个类型。后边的三位标识道具的ID,可以标识999个道具,如果不够用自己在扩大位数即可。那么我们在配置数据的时候就可以这样填写。这里的62代表一种类型的道具,10002是我定义的视频广告。也就是说62002这个道具是需要用广告开启后才可以使用。所以道具才有激活状态这个属性,这个可以有也可以没有的东西,还是根据自己的项目而定了。 道具管理以上是定义一个道具的数据格式、配置和使用方式。那么这么多道具应该如何管理呢? . 各模块管理器 :负责管理自己的道具,比如增删改查,统一实现一个接口。 . 全局道具管理器:负责统筹管理,可以有两种方式 比如一个活动模块,可能发放各种类型的道具,这些道具属于不同的模块,这个时候全局道具管理器就会通过类型找到模块管理器处理相关逻辑,也就是为什么各模块管理器要实现一个同一的接口的原因了。各模块之间也可以通过这个全局道具管理器进行交互。这个全局道具管理器也可以不调用各模块,而是直接调用存档数据,然后让各模块自己监听数据的变化做对应的处理。建议建议只有一个,就是不要把不同类型的数据放到一个数据表中。因为大家都知道的,道具都是有些共同属性的,那索性把武将,武器,等等一些列有相同属性的道具都放到一个表中吧!这样做有几个问题: 将同一种类型的东西放到两个表中维护,增加了维护难度。将不同类型的数据放到一个表中,如果我需要配置武器数据,还要先向下拉一百或者几十行略过武将的数据,这也是在增加维护难度。程序在组织模型的时候需要从两个配置数据中获取,增加了复杂度。由于将各模块用的数据都放到了一起,那么只能是作为共有数据一起加进内存,而且不能删除。所以还是建议同种类型的数据就放到一个表中维护就可以了。 结语以上是我个人对于道具管理的一些理解,主要还是对于多人开发而语言,希望有不同意见的同学在下方留言,一起交流。 长按下方二维码,关注《微笑游戏》公众号,获取更多精彩内容。欢迎扫码关注公众号《微笑游戏》,浏览更多内容。

July 1, 2020 · 1 min · jiezi

如何判断一个点在旋转后的矩形中

前言最近在做的一款游戏中,用到点与旋转矩形的判定来获得一个选中的物体。在此做个记录如图所示,黄色的颜料屏是旋转的,如果不做处理直接判断点是否在矩形中,那么点击红点的位置会判定为选中物体。显然这是不对的。如果物体没有旋转,判断方法就很简单了。 static isPositionInRect(point: cc.Vec2, rect: cc.Rect) { return point.x <= rect.x + rect.width/2 && point.x >= rect.x - rect.width/2 && point.y <= rect.y + rect.height /2&& point.y >= rect.y - rect.height /2; }我这个矩形锚点为为(0.5,0.5),如果锚点不是(0.5,0.5)可以自行修改。但是判断一个点在旋转后的矩形中就没有这么简单了。 怎么判断呢?首先我想到的是WebGL编程指南第92页讲到的内容。(x,y)在旋转O角度后得到(x2,y2) x2 = x * cos(O) - y * sin(O) y2 = x * sin(O) + y * cos(O)得到了这个公式,我们接下来就是要旋转触摸点了。不过这个时候我们要确定要绕哪个点旋转,是坐标系的原点吗?不是的,而是我们要碰撞的矩形的中心点,因为矩形是绕这个点旋转的。 最终我们得到一个完整的判定函数 /** * 判断点是否在旋转后的矩形中 * @param point 触摸点的坐标 * @param node 碰撞节点,锚点必须为(0.5,0.5) */ static isPosInRotationRect(point: cc.Vec2, node: cc.Node) { let hw = node.width / 2; let hh = node.height / 2 let O = node.angle; let center = node.position; let X = point.x let Y = point.y let r = -O * (Math.PI / 180) let nTempX = center.x + (X - center.x) * Math.cos(r) - (Y - center.y) * Math.sin(r); let nTempY = center.y + (X - center.x) * Math.sin(r) + (Y - center.y) * Math.cos(r); if (nTempX > center.x - hw && nTempX < center.x + hw && nTempY > center.y - hh && nTempY < center.y + hh) { return true; } return false }这里边需要注意的是角度O我们用的是反方向的。因为我们判定使用的矩形的坐标和宽高是未旋转的,也就是下图的红色框。所以我们的触摸点需要反方向旋转角度O才能使用之前的判定方法。当你点击了1的位置,经过反方向的旋转后会到达2的位置,然后与红色框的矩形判断,才会得到正确的判定结果。如果触摸点旋转的角度与矩形旋转的角度相同,那么点击1的位置就会向左移动,也会判定为选中,就会得到不正确的结果了。 ...

June 28, 2020 · 1 min · jiezi

用Creator实现一个擀面的效果

先上几张效果图 怎么实现的呢? 节点介绍1是背景图,可以忽略;2 是准备好的面团;3 是擀好的面饼先隐藏;4 是需要绘制的节点;5 是擀面杖。 制作开始 首先在view上挂一个mask,并且设置为模板模式,sprite frame 就设置成那张擀好的面饼。这样的设置可以使Mask按照擀好面饼的形状遮罩内容。 在walpaper-layer 节点上挂在了一个我写好的有关于绘制图形的脚本文件,并设置好相关参数。这个脚本主要做的就是使用Graphics绘制图形。 在graphics节点上挂上Graphics组件提供给我的脚本使用。使用擀面杖监听触摸事件,通过移动擀面杖并使用擀面杖的坐标(并不是触摸点的坐标)绘制圆形,设置绘制圆形的半径为80或者更大些,以便达到擀面饼的感觉。 怎么判断面饼擀好了呢?记录面饼九个点的坐标判断擀面杖的坐标走过的点,走过一个移除一个,都走过之后就可以设置为完成了。 最后隐藏掉绘制的图案,面团,显示出来之前设置好的面饼,这个效果就算制作完成了。长按下方二维码,关注《微笑游戏》公众号,获取更多精彩内容。 欢迎扫码关注公众号《微笑游戏》,浏览更多内容。

June 27, 2020 · 1 min · jiezi

你不知道的-TypeScript-泛型万字长文建议收藏

泛型是 TypeScript(以下简称 TS) 比较高级的功能之一,理解起来也比较困难。泛型应用场景非常广泛,很多地方都能看到它的影子。平时我们阅读开源 TS 项目源码,或者在自己的 TS 项目中使用一些第三方库(比如 React)的时候,经常会看到各种泛型定义。如果你不是特别了解泛型,那么你很可能不仅不会用,不会实现,甚至看不懂这是在干什么。 相信大家都经历过,看到过,或者正在写一些应用,这些应用充斥着各种重复类型定义, any 类型层出不穷,鼠标移到变量上面的提示只有 any,不要说类型操作了,类型能写对都是个问题。我也经历过这样的阶段,那个时候我对 TS 还比较陌生。 随着在 TS 方面学习的深入,越来越认识到 真正的 TS 高手都是在玩类型,对类型进行各种运算生成新的类型。这也好理解,毕竟 TS 提供的其实就是类型系统。你去看那些 TS 高手的代码,会各种花式使用泛型。 可以说泛型是一道坎,只有真正掌握它,你才知道原来 TS 还可以这么玩。怪不得面试的时候大家都愿意问泛型,尽管面试官很可能也不怎么懂。 只有理解事物的内在逻辑,才算真正掌握了,不然永远只是皮毛,不得其法。 本文就带你走进泛型,带你从另一个角度看看究竟什么是泛型,为什么要有它,它给 TS 带来了什么样的不同。 注意:不同语言泛型略有不同,知识迁移虽然可以,但是不能生搬硬套,本文所讲的泛型都指的是 TS 下的泛型。引言我总结了一下,学习 TS 有两个难点。第一个是TS 和 JS 中容易混淆的写法,第二个是TS中特有的一些东西。 TS 中容易引起大家的混淆的写法比如: (容易混淆的箭头函数) 再比如: (容易混淆的 interface 内的小括号) TS 中特有的一些东西比如 typeof,keyof, infer 以及本文要讲的泛型。 把这些和 JS 中容易混淆的东西分清楚,然后搞懂 TS 特有的东西,尤其是泛型(其他基本上相对简单),TS 就入门了。 泛型初体验在强类型语言中,一般而言需要给变量指定类型才能使用该变量。如下代码: const name: string = "lucifer";console.log(name);我们需要给 name 声明 string 类型,然后才能在后面使用 name 变量,当我们执行以下操作的时候会报错。 ...

June 22, 2020 · 6 min · jiezi

游戏开发中的多语言处理

前言之前我写过一遍《数据表的使用》的文章,今天继续用文本处理为例讲解一下数据表导出工具的使用和多语言的使用方式。 编写数据表我们先定义一个UI文本表,写好表头,添加几个测试用对本文,注意类型使用lang。 使用工具导出打开build.bat文件,修改数据表和项目路径,双击运行,这时候如果不出问题你的项目下就会出现对应的数据和枚举文件为了减少配置数据的大小,所有数据才用数组形式,所以生成了枚举文件,使用枚举索引对应的数值。 使用数据添加数据到数据管理器,如果不是合并的表需要给一个key,我通常使用文件名。 所有的数据都归XlsxDataManager负责管理,不过这个XlsxDataManager可以分模块,如果游戏中数据始终不清理,可以使用一个就行。 将语言文件添加到语言管理器。 语言文本管理器的处理方式与creator官方插件i18n处理方式一样,也可以处理文本替换。 定义一个UI文本管理类 定义UI文本组件 在界面中拖入一个label,挂上UI文本组件,填好我们在数据表中定义的ID 运行查看效果即可 如何切换语言?执行另一个脚本,这个脚本每次都会添加zh中新添加的字段,不会替换已有字段。 如果不出问题,导出目录会多出一个语言文本,可以拿给负责翻译的人员。为了测试自己简单翻译一下。 更改语言为en 运行看效果 结语此套方案可以用于同时支持多种语言,也可以只支持一种语言,由项目需求而定。如果需要替换文本可以直接调用语言管理器的接口使用。浏览更多内容,请关注微信公众号《微笑游戏》 框架维护购买框架的很多同学还没有收货,请尽快联系我。gitlab已建好,购买过的同学我会添加到项目中,可以免费及时更新到新内容。感谢支持我的同学们,在学习的过程中指出框架的问题,共同进步。 欢迎扫码关注公众号《微笑游戏》,浏览更多内容。

June 20, 2020 · 1 min · jiezi

一文读懂-TypeScript-泛型及应用

觉得 TypeScript 泛型有点难,想系统学习 TypeScript 泛型相关知识的小伙伴们看过来,本文从八个方面入手,全方位带你一步步学习 TypeScript 中泛型,详细的内容大纲请看下图: 一、泛型是什么软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。 在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。 设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。 为了便于大家更好地理解上述的内容,我们来举个例子,在这个例子中,我们将一步步揭示泛型的作用。首先我们来定义一个通用的 identity 函数,该函数接收一个参数并直接返回它: function identity (value) { return value;}console.log(identity(1)) // 1现在,我们将 identity 函数做适当的调整,以支持 TypeScript 的 Number 类型的参数: function identity (value: Number) : Number { return value;}console.log(identity(1)) // 1这里 identity 的问题是我们将 Number 类型分配给参数和返回类型,使该函数仅可用于该原始类型。但该函数并不是可扩展或通用的,很明显这并不是我们所希望的。 我们确实可以把 Number 换成 any,我们失去了定义应该返回哪种类型的能力,并且在这个过程中使编译器失去了类型保护的作用。我们的目标是让 identity 函数可以适用于任何特定的类型,为了实现这个目标,我们可以使用泛型来解决这个问题,具体实现方式如下: function identity <T>(value: T) : T { return value;}console.log(identity<Number>(1)) // 1对于刚接触 TypeScript 泛型的读者来说,首次看到 <T> 语法会感到陌生。但这没什么可担心的,就像传递参数一样,我们传递了我们想要用于特定函数调用的类型。 ...

June 11, 2020 · 8 min · jiezi

第40期-业务理解有偏差产品和开发如何达成共识-测一测你的TypeScript水平

HTTPS就安全了吗?会被抓包吗?我竟然回答不上来... 随着 HTTPS 建站的成本下降,现在大部分的网站都已经开始用上 HTTPS 协议。大家都知道 HTTPS 比 HTTP 安全,也听说过与 HTTPS 协议相关的概念有 SSL 、非对称加密、 CA证书等,但对于以下灵魂三拷问可能就答不上了: 为什么用了 HTTPS 就是安全的?HTTPS 的底层原理如何实现?用了 HTTPS 就一定安全吗?本文将层层深入,从原理上把 HTTPS 的安全性讲透。 图解 Promise 实现原理(二): Promise 链式调用 很多同学在学习 Promise 时,知其然却不知其所以然,对其中的用法理解不了。「本系列文章由浅入深逐步实现 Promise,并结合流程图、实例以及动画进行演示,达到深刻理解 Promise 用法的目的。」 测一测你的 TypeScript 水平 本文从最近在 Github 上比较火的仓库 typescript-exercises[2] 入手,它的中文介绍是 「富有挑战性的 TypeScript 练习集」。里面包含了 15 个 TypeScript 的练习题,我会从其中挑选出几个比较有价值的题目,一起来解答一下。 为React应用制作动画的5种方法 ReactJS应用程序中的动画是一个流行的话题,有很多方法可以创建不同类型的动画。许多开发人员只使用CSS和向HTML标记添加类来创建动画。这是一个好方法,您应该使用它,如果要创建复杂的动画,可以关注GreenSock,GreenSock是最强大的动画平台。还有很多库用于在React中创建动画的组件。 业务理解有偏差,产品和开发如何达成共识? 在考虑如何对业务模型进行抽象从而建立领域模型之前,必须解决业务与产品、开发之间“沟通”的问题。如何让业务人员和开发人员顺畅沟通,在业务流程设计中不遗漏成败攸关的业务场景?如何才能让业务沟通的过程顺畅过渡到架构设计、编码乃至测试?阿里巴巴技术专家李建结合团队的实际案例,分享了他们在使用 Event Storming(事件风暴) 进行领域建模时的经验、收获和思考。

June 10, 2020 · 1 min · jiezi

TypeScript-中的-is

前言TypeScript里有类型保护机制。要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型谓词: function isString(test: any): test is string{ return typeof test === "string";}上述写法与写一个返回值为 boolean 值函数的区别在哪里呢? function isString(test: any): boolean{ return typeof test === "string";}区别使用 is 类型保护function isString(test: any): test is string{ return typeof test === "string";}function example(foo: any){ if(isString(foo)){ console.log("it is a string" + foo); console.log(foo.length); // string function // 如下代码编译时会出错,运行时也会出错,因为 foo 是 string 不存在toExponential方法 console.log(foo.toExponential(2)); } // 编译不会出错,但是运行时出错 console.log(foo.toExponential(2));}example("hello world"); 返回值为 booleanfunction isString(test: any): boolean{ return typeof test === "string";}function example(foo: any){ if(isString(foo)){ console.log("it is a string" + foo); console.log(foo.length); // string function // foo 为 any,编译正常。但是运行时会出错,因为 foo 是 string 不存在toExponential方法 console.log(foo.toExponential(2)); }}example("hello world"); ...

June 9, 2020 · 1 min · jiezi

TypeScript-中的顶级类型any-和-unknown

作者:Dr. Axel Rauschmayer翻译:疯狂的技术宅 原文:https://2ality.com/2020/06/an... 未经允许严禁转载 在 TypeScript中,any 和 unknown 是包含所有值的类型。在本文中,我们将会研究它们是怎样工作的。 TypeScript 的两种顶级类型any and unknown are so-called top types in TypeScript. Quoting Wikipedia: any 和 unknown 在 TypeScript 中是所谓的“顶部类型”。以下文字引用自 Wikipedia: top type [...]是 通用(universal) 类型,有时也称为 通用超类型,因为在任何给定类型系统中,所有其他类型都是子类型[...]。通常,类型是包含了其相关类型系统中所有可能的[值]的类型。也就是说,当把类型看作是值的集合时,any 和 unknown 是包含所有值的集合。顺便说一句,TypeScript 还有 bottom type never,它是空集。 顶级类型 any如果一个值的类型为 any,那么我们就可以用它任何事: function func(value: any) { // 仅允许数字,但它们是 `any` 的子类型 5 * value; // 通常,`value` 的类型签名必须包含 .propName value.propName; // 通常只允许带有索引签名的数组和类型 value[123];}任何类型的值都可以赋值给 any 类型: ...

June 9, 2020 · 2 min · jiezi

Typescript-使用日志

最近这两年,有很多人都在讨论 Typescript,无论是社区还是各种文章都能看出来,整体来说正面的信息是大于负面的,这篇文章就来整理一下我所了解的 Typescript。 本文主要分为 3 个部分: Typescript 基本概念Typescript 高级用法Typescript 总结原文地址,欢迎 Star 和 订阅我的博客。Typescript 基本概念至于官网的定义,这里就不多做解释了,大家可以去官网查看。Typescript 设计目标 我理解的定义:赋予 Javascript 类型的概念,让代码可以在运行前就能发现问题。 Typescript 都有哪些类型1、Typescript 基本类型,也就是可以被直接使用的单一类型。 数字字符串布尔类型nullundefinedanyunknownvoidobject枚举never2、复合类型,包含多个单一类型的类型。 数组类型元组类型字面量类型接口类型3、如果一个类型不能满足要求怎么办? 可空类型,默认任何类型都可以被赋值成 null 或 undefined。联合类型,不确定类型是哪个,但能提供几种选择,如:type1 | type2。交叉类型,必须满足多个类型的组合,如:type1 & type2。类型都在哪里使用在 Typescript 中,类型通常在以下几种情况下使用。 变量中使用类中使用接口中使用函数中使用类型在变量中使用在变量中使用时,直接在变量后面加上类型即可。 let a: number;let b: string;let c: null;let d: undefined;let e: boolean;let obj: Ixxx = { a: 1, b: 2,};let fun: Iyyy = () => {};类型在类中使用在类中使用方式和在变量中类似,只是提供了一些专门为类设计的静态属性、静态方法、成员属性、构造函数中的类型等。 class Greeter { static name:string = 'Greeter' static log(){console.log(‘log')} greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; }}let greeter = new Greeter("world");类型在接口中使用在接口中使用也比较简单,可以理解为组合多个单一类型。 ...

June 9, 2020 · 5 min · jiezi

了不起的-TypeScript-入门教程12W字

想学习 TypeScript 的小伙伴看过来,本文将带你一步步学习 TypeScript 入门相关的十四个知识点,详细的内容大纲请看下图: 一、TypeScript 是什么TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。 TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。下图显示了 TypeScript 与 ES5、ES2015 和 ES2016 之间的关系: 1.1 TypeScript 与 JavaScript 的区别TypeScriptJavaScriptJavaScript 的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页。可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误强类型,支持静态和动态类型弱类型,没有静态类型选项最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使用支持模块、泛型和接口不支持模块,泛型或接口支持 ES3,ES4,ES5 和 ES6 等不支持编译其他 ES3,ES4,ES5 或 ES6 功能社区的支持仍在增长,而且还不是很大大量的社区支持以及大量文档和解决问题的支持1.2 获取 TypeScript命令行的 TypeScript 编译器可以使用 Node.js 包来安装。 1.安装 TypeScript $ npm install -g typescript2.编译 TypeScript 文件 $ tsc helloworld.ts# helloworld.ts => helloworld.js当然,对于刚入门 TypeScript 的小伙伴,也可以不用安装 typescript,而是直接使用线上的 TypeScript Playground 来学习新的语法或新特性。 ...

June 9, 2020 · 14 min · jiezi

游戏开发之目录划分

目录划分为一下几种先按资源类型,再按模块划分即从类型到模块。好处就是当你需要翻阅资源的时候先从类型着手。然后再定位到功能模块。高内聚低耦合的策略,有利于团队协作。 先按资源类型划分: 最外层的目录也就是代码,动态资源,静态纹理,场景文件所以主要的划分还是在动态资源目录里边,比如分为声音文件,配置文件,纹理,预制体文件,粒子效果,动画文件等。然后就是对纹理文件的划分了,主要是存放的也就是动态加载的资源,比如道具图标等,因为拼界面的纹理一般会放到静态纹理目录下,所以动态纹理只需要按纹理用途划分就可以了。静态纹理目录划分:模块独有的资源放到一个目录下,所有模块公有的资源放到一个目录下,这样当图片被打成图集一次性加入内存时也不会出现浪费的情况,比如加了一张图集,但是没几张能用到的。启动页用到的资源单独放到一个目录下,如果有与其他模块相同的资源也单独存储一份。与其他模块和共有模块的资源分离。这样做是为了加快首页显示。图片上带文字的单独一个目录,这样做虽然会打破自动合批。但是对于之后的多语言版本很有帮助。可以同时支持多种语言,也可以写一个脚本在发包的时候替换文件,另其只包含一种语言的资源,减小包体,非常灵活。背景图片单独一个目录,不要制作自动图集。资源命名资源名称由负责制作的人定,英文拼音都可,小写加下滑线命名。如果程序再更改一次名称,那么当资源需要替换的时候就是麻烦事。 代码目录划分:原则其实与静态资源划分方式是一样的。这里特别说明另一种情况。有些开发者先将代码分为vmc目录,然后再按模块划分。这种方式不推荐使用。因为在开发功能的时候你需要同时处理mvc代码,也就是需要同时打开这几个目录。那么列表会相当长,需要你下拉才可能找到对应的文件,比较繁琐。 直接按模块划分一个功能的所有类型资源放到一个目录下,这种是将预制体,图片,代码都放到一个目录下。这样可以有效的做好分包处理,小游戏用的比较多。但是不同类型资源都放到一个目录下,当需要对同一类型的文件做处理的时候就比较麻烦。比如换皮,换语言图片,这种需求是无法通过脚本来执行的,所以处理起来不如相同资源在同一目录下处理的快速。所以如何划分还是要酌情考虑了。 结语事情没有绝对,个人喜好也不尽相同,欢迎相互交流,共同进步。浏览更多内容,请关注微信公众号《微笑游戏》 欢迎扫码关注公众号《微笑游戏》,浏览更多内容。

June 8, 2020 · 1 min · jiezi

在小游戏开发中如何优雅的使用本地存档

说明h5提供了LocalStorage本地存储能力,但是如果直接使用不是很方便。所以我封装了以下几种类型,达到与其他类型几乎相同的使用方式。 BaseStorage: 存储类的基类。LocalValue :数值类型,存储float,int,string等LocalList :列表类型相当于数组LocalMap :key-value类型。StorageHelper: 用于调用各个引擎提供的LocalStorage类。使用各种形式的加密算法,对数据进行读写。类图 关键代码import StorageHelper from "./StorageHelper";export default abstract class BaseStorage { //存档的值 protected value: any; //存档的key protected key: string; //初始值 protected initValue: any; //是否有数据 protected dataFlag: boolean = true; constructor(key, initValue) { this.key = key; this.initValue = initValue; this.dataFlag = this.loadValue(); } /** * 是否已经有存档数据 */ isHaveData() { return this.dataFlag; } protected abstract loadValue(): boolean; //保存 protected saveValue() { StorageHelper.setJsonBase64(this.key, this.value); } //获取数据 protected getStorage() { return StorageHelper.getJsonBase64(this.key) } //获取值 getValue() { return this.value; } //设置值 setValue(value, save: boolean = true) { if (this.value != value) { this.value = value; if (save) { this.saveValue(); } } }}import BaseStorage from "./BaseStorage";import { isNull } from "./Define";export default class LocalList extends BaseStorage { protected value: any[]; get(index: number) { if (index >= 0 && index <= this.value.length - 1) { return this.value[index]; } else { this.set(index, this.initValue) return this.initValue; } } protected loadValue() { let localValue = this.getStorage(); if (!isNull(localValue)) { this.setValue(localValue) return true; } else { this.value = [] return false } } size() { return this.value.length; } set(index: number, value: any, save: boolean = true) { if (this.value[index] != value) { this.value[index] = value; if (save) { this.saveValue(); } } } remove(index) { if (!isNull(this.value[index])) { delete this.value[index] this.saveValue(); } }}import { isNull } from "./Define";import BaseStorage from "./BaseStorage";export default class LocalMap extends BaseStorage { protected value: Object; protected count: number = 0; protected loadValue() { let localValue = this.getStorage(); if (!isNull(localValue)) { this.setValue(localValue) for (const key in localValue) { if (localValue.hasOwnProperty(key)) { this.count++; } } return true } else { this.value = {} return false } } size() { return this.count; } get(key: any) { let data = this.value[key]; if (isNull(data)) { data = this.initValue; } this.set(key, data) return data; } has(key: any) { return !isNull(this.value[key]) } updateValue(key, value) { this.set(key, this.get(key) + value) } set(key: any, value) { if (!this.value[key]) { this.count++; } if (this.value[key] != value) { this.value[key] = value this.saveValue(); } } remove(key) { if (this.value[key]) { this.count--; delete this.value[key] this.saveValue(); } }}import BaseStorage from "./BaseStorage";import { isNull } from "./Define";export default class LocalValue extends BaseStorage { protected loadValue() { let localValue = this.getStorage(); if (isNull(localValue)) { this.setValue(this.initValue); return true } else { this.value = localValue; return false } } updateValue(value: number) { let data = this.value + value; if (data < 0) { data = 0; } this.setValue(data) }}import Base64 from "./Base64"export default class StorageHelper { static get(key) { return Laya.LocalStorage.getItem(key); } static set(key, value) { Laya.LocalStorage.setItem(key, value); } static clear() { Laya.LocalStorage.clear(); } static remove(key) { Laya.LocalStorage.removeItem(key); } static setJson(key, value) { this.set(key, JSON.stringify(value)); } static getJson(key) { let value = this.get(key); if (!value) { return null; }; return JSON.parse(value); } static getJsonBase64(key) { let localValue = this.get(key); if (!localValue) { return null; }; let string = Base64.decode(localValue); if (string) { try { let value = JSON.parse(string); return value; } catch (error) { } } return {}; } static setJsonBase64(key, value) { this.set(key, Base64.encode(JSON.stringify(value))); } static setBase64(key, value) { this.set(key, Base64.encode(value)); } static getBase64(key) { let localValue = this.get(key); if (!localValue) { return ''; }; let value = Base64.decode(localValue); return value; }}使用方式export default class Player{ //金币 private _gold: LocalValue; init(){ this._gold = new LocalValue(this.playerName + 'gold', 100) } setGold(num: number) { this._gold.setValue(Math.floor(num)) } getGold() { return this._gold.getValue(); }}如果对此内容感兴趣可关注我的公众号查看其他内容。 ...

June 7, 2020 · 3 min · jiezi

游戏开发之UI管理器跨引擎

使用UI管理器的目的使用单场景与zindex结合的方式管理UI。能够隐藏底层UI达到优化效果。很好的组织和管理UI。跨引擎使用。管理器分类根据以往经验我开发了三种类型的管理器,队列管理器,栈式管理器,单UI管理器。 单UI管理器:SingleManager负责管理如登录,loading,大厅,游戏这样的一级UI,同一时刻只有一个UI实例存在。UI之间是替换关系。栈式管理器:StackManager用于管理先进后出的UI,弹出功能UI使用。队列管理器:QueueManager用于管理先进先出的UI,用于第一次进入大厅弹出各种活动UI时候使用,关闭一个弹出另一个。类图 将UI分为五层第一层:使用单UI管理器用于管理,大厅,游戏等一级界面。第二层:使用栈式管理器 管理二级界面第三层:使用队列管理器用于管理进入游戏时弹出的各种活动面板。第四层:使用栈式管理器用于管理toast,tip等提示框。第五层:为最上层,使用栈式管理器,用于管理教学,对话界面和网络屏蔽层等。特别说明:比如将一个战斗UI分为战斗层和按钮层,这个不属于管理器范畴。 结构图 代码为了跨引擎使用,需要将各个引擎的组件抽象。export default interface LayerInterface { exit(): void; /** * 设置组件是否可见 * @param f */ setVisible(f: boolean): void; /** * 设置组件节点的zroder * @param order */ setOrder(order: number): void; /** * * @param t 管理器层级 */ setUIIndex(t: number): void; getUIIndex(): number; /** * 获得组件的node */ getNode(): any; isLoad(): boolean;}管理器的父类import LayerInterface from "./LayerInterface";export default abstract class LayerManager { //根节点 protected root: any; protected list: LayerInterface[] //管理器中的内容是否可以被删除 protected popFlag: boolean = false; protected zOrder: number = 1; constructor(zOrder: number = 1, canPop: boolean = true) { this.list = [] this.zOrder = zOrder; this.popFlag = canPop; } init(node: any) { this.root = node; } setZOrder(order: number) { this.zOrder = order; } getZOrder(): number { return this.zOrder; } canPop() { return this.popFlag; } //ui数量 count() { return this.list.length; } setVisible(flag: boolean) { for (let index = 0; index < this.list.length; index++) { const element = this.list[index]; element.setVisible(flag) } } //判断某个ui是否存在 has(layer: LayerInterface) { for (let index = 0; index < this.list.length; index++) { const element = this.list[index]; if (layer === element) { return true; } } return false; } //添加layer abstract pushView(layer: LayerInterface): void; // 移除layer abstract popView(view: LayerInterface): boolean; //删除指定ui removeView(layer: LayerInterface): boolean { // logInfo(' LayerManger removeView ') for (let index = 0; index < this.list.length; index++) { const element: LayerInterface = this.list[index]; if (layer === element) { element.exit(); this.list.splice(index, 1); return true; } } // console.warn(' removeView is not have ', layer, ' list ', this.list) return false; } //清空所有ui clear() { // logInfo(' LayerManger clear ') for (let index = 0; index < this.list.length; index++) { const element: LayerInterface = this.list[index]; element.exit(); } this.list.length = 0; }}单UI管理器import LayerManager from "./LayerManager";import LayerInterface from "./LayerInterface";export default class SingleManager extends LayerManager { pushView(view: LayerInterface) { if (this.list.length > 0) { this.removeView(this.list.shift()) } this.list.push(view); view.setOrder(this.zOrder); this.root.addChild(view.getNode()) } //不支持主动移除 popView(view: LayerInterface) { return false; }}栈结构管理器import LayerManager from "./LayerManager"import LayerInterface from "./LayerInterface"export default class StackLayerManager extends LayerManager { //添加layer pushView(view: LayerInterface) { this.list.push(view); view.setOrder(this.zOrder) this.root.addChild(view.getNode()) } // 移除layer popView(view: LayerInterface): boolean { if (this.list.length > 0) { let layerInfo = this.list.pop(); layerInfo.exit(); return true; } else { return false; } }}队列管理器import LayerManager from "./LayerManager"import LayerInterface from "./LayerInterface";export default class QueueLayerManager extends LayerManager { //添加layer pushView(view: LayerInterface) { this.list.push(view); if (this.list.length == 1) { this.show(view); } } show(view: LayerInterface) { view.setOrder(this.zOrder); this.root.addChild(view.getNode()) } // 移除layer popView(view: any): boolean { if (this.list.length > 0) { let layerInfo = this.list.shift(); layerInfo.exit(); if (this.list.length > 0) { this.show(this.list[0]); } return true; } else { return false; } }}所有管理器的整合import LayerManager from "./LayerManager"import EventDispatcher from "../event/EventDispatcher";import GlobalEvent from "../event/GlobalEvent";import LayerInterface from "./LayerInterface";export default class UIManager extends EventDispatcher { private managers: LayerManager[] = []; private root: any; private static ins: UIManager; static instance(): UIManager { if (!UIManager.ins) { UIManager.ins = new UIManager(); } return UIManager.ins; } constructor() { super(); this.managers = []; } /** * * @param uiIndex * @param flag */ setVisible(uiIndex: number, flag: boolean) { // logInfo('setVisible ', uiIndex, flag) this.managers[uiIndex].setVisible(flag) } init(node: any) { this.root = node } setManager(index: number, manager: LayerManager) { this.managers[index] = manager; this.managers[index].init(this.root) } /** * 清除UI */ clear() { for (let index = this.managers.length - 1; index >= 0; index--) { const manager = this.managers[index]; if (manager.canPop() && manager.count() > 0) { manager.clear() } } } //添加UI pushView(layer: LayerInterface) { if (layer) { let uiIndex = layer.getUIIndex() let manager = this.managers[uiIndex]; if (!manager) { console.log(' manager is null ', layer) return; } layer.setUIIndex(uiIndex) manager.pushView(layer); this.getOpenUICount(); } } /** * 此方法用于关闭界面,为了兼容Android的back键 所以layer有为null的情况。 * 如果 返回false 表明已经没有界面可以弹出,此时就可以弹出是否退出游戏提示了。 * @param layer */ popView(layer?: LayerInterface) { // console.log('popView layer is ', layer) let flag = false; if (layer) { let index: number = layer.getUIIndex() // console.log(' popView index is ', index, ' layer is ', layer) let manger = this.managers[index]; if (!manger) { // console.log(' popView layer is not found type is ', type) flag = false; } else { flag = manger.popView(layer); } } else { for (let index = this.managers.length - 1; index >= 0; index--) { const manager = this.managers[index]; if (manager.canPop() && manager.count() > 0) { if (manager.popView(null)) { flag = true; break; } } } } return flag; } //获得当前存在的ui的数量 getOpenUICount() { let count: number = 0; for (let index = this.managers.length - 1; index >= 0; index--) { const manager = this.managers[index]; if (manager.canPop()) { count += manager.count() } } return count; }}所有组件的父类import LayerInterface from "../cfw/ui/LayerInterface";import { UIIndex } from "../cfw/tools/Define";const { ccclass, property } = cc._decorator;@ccclassexport default class EngineView extends cc.Component implements LayerInterface { private uiIndex: number = 0; protected loadFlag: boolean = false; isLoad() { return this.loadFlag } start() { this.loadFlag = true; } exit() { this.node.destroy() } setVisible(f: boolean): void { this.node.active = f; } setOrder(order: number): void { this.node.zIndex = order; } setUIIndex(t: UIIndex): void { this.uiIndex = t; } getUIIndex(): UIIndex { return this.uiIndex } getNode() { return this.node; } show() { UIManager.instance().pushView(this) } hide(){ UIManager.instance().popView(this) }}使用方式定义层级枚举 ...

June 7, 2020 · 5 min · jiezi

小游戏开发之资源管理跨引擎

前言资源管理是内存优化的一部分,对于大型游戏,资源管理不明确,很容易出现内存不足而闪退的情况。说到资源也就涉及到了资源划分,这部分内容可以看另一篇文章《游戏开发之目录划分》。 资源管理器需要考虑的情况加载完成的回调加载失败后的尝试多个相同请求的处理。未加载成功之前已经删除。资源的使用情况,记数。跨引擎使用。各个引擎需要提供的辅助类需要实现的接口/** * 自定义的资源分类,对应各个引擎中相同的资源。 */export enum ResType { Texture2D, SpriteFrame, SpriteAtlas, Prefab, Json, Scene, Material, AnimationClip, Mesh, Particle2D,//粒子效果 AudioClip,}export type ResCallback = (err: any, res: any) => void/** * 是否使用引用记数 * 对于一些资源很少的小游戏不需要清理资源,所以可以设置为false。 */export let RECORD_RES_COUNT: boolean = true/** * 各个引擎需要提供资源的辅助类需要实现的接口 */export default interface ResInterface { /** * * @param url 加载资源 * @param type * @param callback */ loadRes(url: string, type: ResType, callback: ResCallback): void; /** * 清理资源 * @param url */ release(url: string): void; /** * 获取资源 * @param url * @param ResType 自定义的资源类型 */ getRes(url: string, type: ResType): any; /** * 获得资源的依赖资源 * @param url */ getDependsRecursively(url: any): any;}资源类的封装和记数处理import ResHelper from "../../engine/ResHelper";import { ResType, RECORD_RES_COUNT } from "./ResInterface";export default class ResItem { // 全局资源使用计数器。 protected static resCountMap: {} = {}; //尝试加载次数 private loadCount: number = 0; //以来资源 protected resources: {} = {}; //使用次数 protected useCount: number = 0; //资源id private url: string; //资源类型 private type: ResType; //加载是否结束 protected loadFinish: boolean = false; //资源本身 private res: any; //需要通知的函数 private callbackList: Function[] = [] constructor(url: string, type?: ResType) { this.url = url; this.type = type; } addCallback(func: Function) { this.callbackList.push(func) } //是否加载完毕 isDone() { return this.loadFinish; } getUrl() { return this.url; } getType() { return this.type; } getRes() { if (RECORD_RES_COUNT) { this.addCount(); } if (!this.res) { this.res = ResHelper.instance().getRes(this.url, this.type) } return this.res; } /** * 加载完成调用 * @param flag */ setLoadingFlag(flag: boolean) { this.loadFinish = flag; if (flag) { while (this.callbackList.length > 0) { let func = this.callbackList.shift(); func(null, this) } } } /** * 由于引擎加载机制,加载完成就已经使用, */ cacheRes(res: any) { this.res = res; if (RECORD_RES_COUNT) { let depands = ResHelper.instance().getDependsRecursively(res) for (let key of depands) { this.resources[key] = true; } //加载成功后直接加1,以免被其他模块的记载器清理掉。 this.addCount() } } //获得加载次数 getLoadCount() { return this.loadCount; } //更新加载次数 updateLoadCount() { this.loadCount++; } //获得使用次数 getUseCount() { return this.useCount; } releaseAll() { if (RECORD_RES_COUNT) { while (this.useCount > 0) { this.release(); } } } release() { if (RECORD_RES_COUNT) { if (this.useCount > 0) { this.subCount(); if (this.useCount == 0) { return true; } else { return false; } } else { return true; } } } subCount() { this.useCount --; let resources: string[] = Object.keys(this.resources); for (let index = 0; index < resources.length; index++) { const key = resources[index]; if (ResItem.resCountMap[key] > 0) { ResItem.resCountMap[key]--; if (ResItem.resCountMap[key] == 0) { ResHelper.instance().release(key) delete this.resources[key]; delete ResItem.resCountMap[key]; } } } } addCount() { this.useCount++; let resources: string[] = Object.keys(this.resources); for (let index = 0; index < resources.length; index++) { const key = resources[index]; ResItem.resCountMap[key]++; } } /** * 删除没有使用的资源 */ static removeUnUsedRes() { let resources: string[] = Object.keys(this.resCountMap); for (let index = 0; index < resources.length; index++) { const key = resources[index]; const count = this.resCountMap[key]; if (count === 1) { // cc.log("removeUnUsedRes uuid " + key + " count " + ResItem.resCountMap[key]) ResHelper.instance().release(key) delete this.resCountMap[key]; } } }}资源管理器import ResItem from "./ResItem";import ResInterface, { ResCallback, ResType } from "./ResInterface";import ResHelper from "../../engine/ResHelper";export default class ResLoader { private helper: ResInterface = null; constructor() { this.helper = ResHelper.instance(); } protected resCache = {} /** * 清理单个资源 * @param url * @param type */ releaseRes(url: string, type: ResType) { let ts = this.getKey(url, type); let item = this.resCache[ts]; if (item) { if (item.release()) { this.resCache[ts] = null; } } } /** * 删除所有资源 */ release() { console.log(' ResLoader release ================== ') let resources: string[] = Object.keys(this.resCache); for (let index = 0; index < resources.length; index++) { const key = resources[index]; const element: ResItem = this.resCache[key]; if (element) { element.releaseAll(); this.resCache[key] = null; } else { // console.warn("ResLoader release url = is error ",key) } } } private getKey(url: string, type: ResType) { let key = url + type; return key; } /** * 同时加载多个资源。 * @param list 需要加载的资源列表 * @param type 需要加载的资源类型,要求所有资源统一类型 * @param func 加载后的回调 * @param loader 资源加载管理器,默认是全局管理器。 */ loadArray(list: Array<string>, type: ResType, func: (err: string, process: number) => void) { let resCount = 0; for (let index = 0; index < list.length; index++) { const element = list[index]; this.loadRes(element, type, (err) => { // 不论是否都加载成功都返回。 if (err) { console.log(err); func(err, resCount / list.length); return; } resCount++; func(err, resCount / list.length); }); } } getItem(url: string, type: ResType) { let ts = this.getKey(url, type) if (this.resCache[ts]) { return this.resCache[ts] } else { let item = new ResItem(url, type); this.resCache[ts] = item; } } /** * 加载单个文件 * @param url * @param type * @param callback */ loadRes(url: string, type: ResType, callback: (err: string, res: ResItem) => void) { let ts = this.getKey(url, type); let item: ResItem = this.resCache[ts] // cc.log(" loadRes url ",url,' ts ',ts); if (item && item.isDone()) { callback(null, item); return; } else { if (item) { item.addCallback(callback) return; } else { item = new ResItem(url, type); this.resCache[ts] = item; } } let func: ResCallback = (err: any, res: any) => { item.updateLoadCount(); if (err) { if (item.getLoadCount() <= 3) { console.warn(" item.getLoadCount() =========== ", item.getLoadCount()) this.helper.loadRes(url, type, func); } else { console.warn(" res load fail url is " + url); this.resCache[ts] = null; callback(err, null); } } else { item.cacheRes(res); if (this.resCache[ts]) { item.setLoadingFlag(true) callback(err, item); } else { //处理加载完之前已经删除的资源 item.subCount(); } } } this.helper.loadRes(url, type, func); } /** * 获取资源的唯一方式 * @param url * @param type */ getRes(url: string, type: ResType) { let ts = this.getKey(url, type) let item = this.resCache[ts]; if (item) { return item.getRes(); } else { let res = this.helper.getRes(url, type); if (res) { // 如果其他管理器已经加载了资源,直接使用。 console.log(' 其他加载器已经加载了次资源 ', url) let item = new ResItem(url, type); item.cacheRes(item) this.resCache[ts] = item return item.getRes(); } else { console.warn('getRes url ', url, ' ts ', ts) } } return null; }}CocosCreator资源辅助类import ResInterface, { ResType, ResCallback } from "../cfw/res/ResInterface";/** * 各个引擎提供的资源辅助类。需要实现ResInterface接口 */export default class ResHelper implements ResInterface { private static ins: ResInterface; static instance() { if (!this.ins) { this.ins = new ResHelper() } return this.ins; } /** * 加载资源 * @param url * @param type * @param callback */ loadRes(url: string, type: ResType, callback: ResCallback): void { switch (type) { case ResType.Prefab: cc.loader.loadRes(url, cc.Prefab, callback) break; case ResType.Texture2D: cc.loader.loadRes(url, cc.Texture2D, callback) break; case ResType.SpriteFrame: cc.loader.loadRes(url, cc.SpriteFrame, callback) break; case ResType.Json: cc.loader.loadRes(url, cc.JsonAsset, callback) break; case ResType.SpriteAtlas: cc.loader.loadRes(url, cc.SpriteAtlas, callback) break; case ResType.Particle2D: cc.loader.loadRes(url, cc.ParticleAsset, callback) break; case ResType.AudioClip: cc.loader.loadRes(url, cc.AudioClip, callback) break; } } /** * 清理资源 * @param url */ release(url: string): void { cc.loader.release(url); } getRes(url: string, type: ResType): any { switch (type) { case ResType.Prefab: return cc.loader.getRes(url, cc.Prefab); case ResType.Texture2D: return cc.loader.getRes(url, cc.Texture2D); case ResType.SpriteFrame: return cc.loader.getRes(url, cc.SpriteFrame); case ResType.Json: return cc.loader.getRes(url, cc.JsonAsset); case ResType.SpriteAtlas: return cc.loader.getRes(url, cc.SpriteAtlas); case ResType.Particle2D: return cc.loader.getRes(url, cc.ParticleAsset) case ResType.AudioClip: return cc.loader.getRes(url, cc.AudioClip) default: console.error(' getRes error url is ', url, ' type is ', type) return null; } } getDependsRecursively(res: any): any { return cc.loader.getDependsRecursively(res) }}如何使用我一般会先定义一个模块类,管理资源//模块idexport enum ModuleID { LOGIN, LOADING, GAME, LOBBY, PUBLIC, MAX}import ResLoader from "../../cfw/res/ResLoader";import AudioManager from "../../cfw/audio/AudioManager";export default class Module { private loader: ResLoader; protected audio: AudioManager; protected name: string = '' constructor(moduleName: string) { this.name = moduleName; this.loader = new ResLoader() this.audio = new AudioManager(moduleName, this.loader) } getName() { return this.name; } getLoader() { return this.loader; } getAudio() { return this.audio; }}然后使用模块管理器管理模块import { ModuleID } from "./Config";import Module from "./Module";export default class ModuleManager { private static mgrMap: Module[] = [] private static moduleID: ModuleID = ModuleID.LOADING; static init(projectName: string) { for (let index = 0; index < ModuleID.MAX; index++) { this.mgrMap[index] = new Module(projectName + index); } } static getAudio(id: ModuleID = this.moduleID) { return this.mgrMap[id].getAudio() } static publicAudio() { return this.mgrMap[ModuleID.PUBLIC].getAudio() } static publicLoader() { return this.mgrMap[ModuleID.PUBLIC].getLoader() } static setModuleID(id: ModuleID) { this.moduleID = id; } static getLoader(id: ModuleID = this.moduleID) { return this.mgrMap[id].getLoader() }}使用 ...

June 6, 2020 · 6 min · jiezi

Typescript声明文件第三方类型扩展

对于初学Typescript的同学来说,声明文件一定是一个让人十分头疼、但是怎么也绕不开的问题。声明文件的书写和声明合并在Typescript官方文档里面已经有比较详细的介绍,这里不再陈述。我们重点讨论的是如何扩展第三方模块中的类型。 类型扩展的基本原则如何扩展第三方模块中的类型,有三条基本原则: 同模块:声明合并只能在同一个模块中进行同路径:声明的模块路径必须与目标类型(你将要扩展的类型)的原始声明文件路径保持一致同书写方式:声明书写方式必须与目标类型一致下面来详细展开 原则一:同模块声明合并只能在同一个模块中进行。意思是说,在扩展一个类型之前,你需要先引入这个类型所在的模块。 在下面这个例子中,我们需要为接口Foo扩展一个属性Bar,Foo是在moduleOfFoo中声明的,为此我们需要先引入moduleOfFoo: // 引入`Foo`所在的模块`moduleOfFoo`,这一步非常重要import 'moduleOfFoo'然后我们需要声明一个同名的模块,在模块内部进行Foo的声明合并 // 引入`Foo`所在的模块`moduleOfFoo`,这一步非常重要import 'moduleOfFoo'// 声明同名模块declare module 'moduleOfFoo' { // 在这个空间内才可以进行声明合并 interface Foo { Bar: any }}原则二:同路径声明的模块路径必须与目标类型(你将要扩展的类型)的原始声明文件路径保持一致。 我们来看一个例子: 首先我们在a.d.ts中声明了interface A // a.d.tsexport declare interface A { a: number}b.d.ts引用了A然后导出 // b.d.tsexport { A } from './a'现在我们来扩展interface A import './b'declare module './a' { interface A { test: number }}注意这里declare module './a',因为interface A就是在'./a'中定义的,必须在这个模块中才能够合并声明。 顺便说一下,这里的import './b',改成import './a'也是可以的,因为都能达到引入interface A的目的。唯有declare module './a'不可以改成declare module './b',因为'./b'不是interface A的原始声明文件。 原则三:同书写方式声明书写方式必须与目标类型一致。这里主要是说namespace嵌套关系要保持一致。 在下面这个例子中我们将要为joint.dia.CellView扩展两个方法getData和setData。 通过观察joint.d.ts,我们得知CellView嵌套了两层namespace: ...

June 4, 2020 · 1 min · jiezi

win10如何将视频弄成桌面壁纸win10专业版

默认情况下,win10系统桌面壁纸是静态的背景图片,有些追求个性化的用户想要将视频弄成桌面背景,可是却不知道要怎么设置,那么Win10怎么直接将视频设置为桌面背景呢?本教程就给大家带来详细的设置方法。 具体步骤如下: 1、首先可以百度搜索deskscpaes8,进入deskscpaes8官网。进入官网之后点击绿色的按键“Get it now”。 2、点击之后就进入下载选择页面,有三个不同权限的版本,我们只下载蓝色按键的30天试用版,如果你想永久使用,可以付费。 3、点击蓝色的按键之后,会跳出另一个界面,我们点击大大的绿色按键“Download now”。 4、点击之后跳转到另一个界面,提示你下载会在一会进行,如果没有就点击那行蓝色字体“restart the download”重新下载。这是下面就会出现保存、另存为等。保存好软件下载即可。 5、下载完成之后点击安装,安装好之后,打开软件,会有三个选项,选择试用30天,即最后一个。他会要求你输入邮箱,输入邮箱之后继续,再到邮箱里激活,就可以使用了。 6、DeskScapes 8安装好之后有三个图标,如果要设置视频的话,需要打开如下图选中的这个图标。打开之后就可以在里边选择视频或者图片作为壁纸了。如果你需要本地视频作为壁纸,同样可以添加,但要注意视频的格式,avi、wmv都可以。 上述就是Win10怎么直接将视频设置为桌面背景的详细内容,有需要的用户们可以按照上面的方法来进行设置吧。 本文来源于win10专业版,转载请注明来源与出处。

June 2, 2020 · 1 min · jiezi

Typescript-内置的模块化兼容方式

欢迎关注我的公众号睿Talk,获取我最新的文章: 一、前言前端的模块化规范包括 commonJS、AMD、CMD 和 ES6。其中 AMD 和 CMD 可以说是过渡期的产物,目前较为常见的是commonJS 和 ES6。在 TS 中这两种模块化方案的混用,往往会出现一些意想不到的问题。 二、import * as考虑到兼容性,我们一般会将代码编译为 es5 标准,于是 tsconfig.json 会有以下配置: { "compilerOptions": { "module": "commonjs", "target": "es5", }}代码编译后最终会以 commonJS 的形式输出。使用 React 的时候,这种写法 import React from "react" 会收到一个莫名其妙的报错: Module "react" has no default export这时候你只能把代码改成这样:import * as React from "react"。究其原因,React 是以 commonJS 的规范导出的,而 import React from "react" 这种写法会去找 React 模块中的 exports.default,而 React 并没有导出这个属性,于是就报了如上错误。而 import * as React 的写法会取 module.exports 中的值,这样使用起来就不会有任何问题。我们来看看 React 模块导出的代码到底是怎样的(精简过): ...

May 31, 2020 · 2 min · jiezi

vuecli3typeScript-腾讯ai接口前端鉴权做效果

前言TypeScript是一个js的超级,提供了类型系统和es6的支持,它是由微软开发,第一个版本发布于2012年10月,经多次更新,现已成为前端社区中不可忽视的力量,并且已经在微软内部广泛应用,google的angular2也使用了TypeScript的开发语言,这也就是我们为什么学习ts的背景。 我们常用的vue框架也支持了使用typeScript开发,今天我们就来学习一下如何使用vue-cli3+typeScript开发一个简单项目,最近在研究腾讯的ai接口,完成一个简单的demo,话不多少,上代码 1、创建项目1、安装vue-cli3,检查版本,创建项目,已经全局安装过vue-cli3的直接创建项目即可,跟之前一样 npm install -g @vue/clivue --versionvue create my-ts-ai2、需要注意的是第一次使用ts的,可以选择Manually select feature来自由选择功能,常用的有vuex、vue-router、CSS Pre-processors等,我们一定要把typescript勾上,就可以回车进入下一步了。PS:勾选的操作是按空格键。 需要注意,选择格式化检查的,需要选择TSlint,否则写ts会标红报错; 3、创建成功之后,执行启动命令:npm run serve;启动之后就可以访问本地项目、用ts编写代码了 2、 vue-property-decorator这是一个涵盖了vue的一些对象的集合,我们可以从这里取一些东西出来 import { Component, Prop, Vue, Watch } from 'vue-property-decorator';取出来的这几个属性,分别是 组件定义Component,父组件传递过来的参数Prop,原始vue对象Vue,数据监听对象Watch。还包括这里没有列举出来的Model,Emit,Inject,Provide,可以自己尝试下。我们看一下常用的属性prop、watch一般写法prop;@Prop() private msg!: string;watch: // watch @Watch('active') private chageActive(newVal: string, oldVal: string) { console.log(`change txt: ${oldVal} to ${newVal}`); } @Watch('sticker') private chageSticker(newVal: string, oldVal: string) { console.log(`change txt: ${oldVal} to ${newVal}`); }data: // data private num: number = 0; private appId: number = 2140682408; private appKay: string = '3gP0QJTqC3j8qP6M'; private imgBase64: string = ''; private effectImg: string = '';methods: ...

May 28, 2020 · 2 min · jiezi

在-TS-中如何减少重复代码

相信有些读者已经听说过 DRY 原则,DRY 的全称是 —— Don't Repeat Yourself ,是指编程过程中不写重复代码,将能够公共的部分抽象出来,封装成工具类或者用抽象类来抽象公共的东西,从而降低代码的耦合性,这样不仅提高代码的灵活性、健壮性以及可读性,也方便后期的维护。 接下来,本文将介绍在 TypeScript 项目开发过程中,如何参考 DRY 原则尽量减少重复代码。减少重复的最简单方法是命名类型,而不是通过以下这种方式来定义一个 distance 函数: function distance(a: {x: number, y: number}, b: {x: number, y: number}) { return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));}在上述的 distance 方法中,我们重复使用 {x: number, y: number} 来定义参数 a 和参数 b 的类型,要解决这个问题很简单,我们可以定义一个 Point2D 接口: interface Point2D { x: number; y: number;}function distance(a: Point2D, b: Point2D) { /* ... */ }然而在实际的开发过程中,重复的类型并不总是那么容易被发现。有时它们会被语法所掩盖。如果多个函数共享相同的类型签名,比如: ...

May 27, 2020 · 3 min · jiezi