学习并写了个基于graphql的全栈小项目,可以注册登录,简易的文章增删改查投票~
Code:
https://github.com/kacoro/React-Graphql-Demo
window的docker终于变好用了。使用wsl2,
顺便在这个项目里面使用Dockerfile、docker-compose来部署。感觉特别好~
只是一个很基础的项目。不过想根据这个再来发展一个新项目。一些比较固定的技术,比如react,typeorm,express,graphql都差不多了吧~
apollo-client感觉还用得不是很好。
父亲生日,所以回了一趟老家。
我还是很深切地关心老家的发展的。晚上的时候约了几个朋友在家里吃起卤水火锅。了解了一下老家的羊毛产业,虽然今年大家都普遍入场拼多多,总之看起来还是难。还在努力考虑自己是否适合去做这种事情了。
跟朋友们也有9个月不见了。随着自己对这个世界的看法开始不同,发现我的世界观与价值观与他们的偏差越来越大了。确实没什么好聊的,在老家待了两天就回了深圳。
潮汕地区出现过不少首富,为什么潮汕发展如此缓慢~未来几年的发展趋势。潮汕话与闽南话的异同,汕头的发展之类的。以前看是宗族,地理位置,政策问题,信任危机也过去了20几年了。还是文化出了问题。
寻找我们文化的根源,虽然说不能说是起于韩愈吧,从秦汉开始,前人已经打好基础,让他有条件得以丰富和发展了,确实是一个很重要的节点。唐宋八大家之首。
然后找了些韩愈的视频来看,看到了南怀瑾的《韩愈与侄子韩湘子的故事》。主要讲韩愈被贬的事情,《谏佛骨表》,反对过于推崇佛教,而无人学儒。宪宗皇帝,龙颜大怒,将他贬于潮州。
八仙过海当然是小时候最常听到的故事。韩湘子原来是韩愈的侄子这个我倒是忘记了。不过这是我开始了南怀瑾开始有了一些联结吧。
看了《世界两种学问不要去碰》原标题是《南怀瑾先生谈学佛》,有大智慧,无读过书的人可以碰,而我们这种庸才就别学了,学了就成了废人。《佛学》和《易经》。易经是中国文化的根根。
佛教、佛法、佛法、学佛,释迦摩尼佛
看了一点《南禅七日》。没找到很完整的,也许油管有吧。确实没时间看了。抽空再继续看。看得比较零散。
倒是把【追思南怀瑾老师】先看完了。
脱掉宗教的外衣,科学地研究各种学问。
成住坏空。
缘起性空,性空缘起,无主宰,非自然。
世界,时间是世,界是空间,一个太阳系是一个世界。累计1千个世界,为小千世界;累计1千个小千世界为中千世界,累计一千个中千世界为大千世界。宇宙是三千大千世界。
贪嗔痴慢疑悔,教育是要帮助我们改进这些问题。
这个社会是有问题的,皇帝是钱。
十恶业道
身、口、意
身:杀盗淫
口:妄语 恶口 绮语 两舌
意:贪嗔痴
十善业道。
人生而无命。理解生命。什么是命呢。一个人一辈子叩首一件事。
需要去寻找这么一件事。
生命的意义:自求多福,一切唯心
南怀瑾是在26岁发了大愿。要延续中华文化,度人化人。70年的牺牲奉献。
任务系统 quest system ( task system )
稍微看了一下莫需要写的 通用任务系统,下了他的项目,嗯,交互按钮是R,项目有几个bug,应该是还没有实现的功能。不过能跑起来了。借鉴借鉴。
在youtube看了一些,并没有找到合适的资料~都太过于简单了~不过代码比较优雅。
事件委托。
任务系统还需要配合对话系统与道具系统,所以,还是先完善一下道具系统吧。
12月14日
目前找到唐老湿的视频教程,讲得目前找到的比较系统的。实现了一个小框架 Project Base。运用导出功能,可以在其他项目中导入,以达到通用性。
项目中运用了泛型单例模式,缓存池,UI管理,资源管理,场景管理,音乐管理,输入管理等。
https://luoluo.blog.csdn.net/article/details/106180451
https://luoluo.blog.csdn.net/article/details/106180496#comments_13793720
大概了解了一下xLua ab包热更新 c#调用xlua xlua调用c# xLua 热补丁
unity Asset Bundel Brower
关于unity开发告一段段落。
今天试了一下Universal RP 光照系统,调色板什么的都不需要了。哈哈,白瞎了我研究很久的调色板。
最后动画还是要自己代码封装。感觉基本的功能似乎可以了。主要是为了直接读取图片,来生成动画。
using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\npublic class SpriteAnimator\n{\n SpriteRenderer spriteRenderer;\n float framRate;\n float timer;\n bool loop;\n\n int currentFrame;\n List<Sprite> frames = new List<Sprite>();\n public SpriteAnimator(List<Sprite> frames, SpriteRenderer spriteRenderer, float frameRate = 0.16f, bool loop = true)\n {\n this.frames = frames;\n this.spriteRenderer = spriteRenderer;\n this.framRate = frameRate;\n this.loop = loop;\n }\n\n public int CurrentFrame\n {\n get { return currentFrame; }\n }\n\n public void Start()\n {\n currentFrame = 0;\n timer = 0f;\n spriteRenderer.sprite = frames[0];\n }\n\n public void HandleUpdate()\n {\n timer += Time.deltaTime;\n if (timer > framRate)\n {\n if (loop)\n {\n currentFrame = (currentFrame + 1) % frames.Count;\n spriteRenderer.sprite = frames[currentFrame];\n timer -= framRate;\n }\n else\n {\n if (currentFrame < frames.Count - 1)\n {\n currentFrame += 1;\n spriteRenderer.sprite = frames[currentFrame];\n timer -= framRate;\n }\n }\n\n }\n }\n\n public List<Sprite> Frames\n {\n get { return frames; }\n }\n\n}\n
using System.Linq;\nusing System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\npublic class CharacterController : MonoBehaviour\n{\n [SerializeField] string path;\n // Start is called before the first frame update\n public float MoveX { get; set; }\n public float MoveY { get; set; }\n public bool IsMoving { get; set; }\n public bool IsRunning {get;set;}\n public bool IsSkill {get;set;}\n public bool IsHurt {get;set;}\n public bool IsFaint {get;set;}\n public bool IsAttack {get;set;}\n public Vector2 Direction {get;set;}\n //states\n public SpriteAnimator currentAnim {get;set;}\n\n Dictionary<AnimatorID, SpriteAnimator> animators;\n\n List<Sprite> frames = new List<Sprite>();\n\n SpriteRenderer spriteRenderer;\n\n \n public List<SpriteAnimator> AttackDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> runDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> skillDirections = new List<SpriteAnimator>();\n\n public List<SpriteAnimator> helloDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> faintDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> hurtDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> defanceDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> cryDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> angryDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> sitDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> idleDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> nodDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> walkDirections = new List<SpriteAnimator>();\n public List<SpriteAnimator> victoryDirections = new List<SpriteAnimator>();\n\n int lastDireaction;\n\n private SpriteAnimator wasPrevAnim =null;\n\n private void Awake()\n {\n frames = Resources.LoadAll<Sprite>(path).ToList();\n }\n private void Start()\n {\n lastDireaction = 6;\n spriteRenderer = GetComponent<SpriteRenderer>();\n // nw 39 \n for (int i = 0; i < 8; i++)\n {\n var index = i * 47;\n AttackDirections.Add(new SpriteAnimator(frames.GetRange(index + 0, 8), spriteRenderer,0.16f,false));\n runDirections.Add(new SpriteAnimator(frames.GetRange(index + 8, 6), spriteRenderer));\n helloDirections.Add(new SpriteAnimator(frames.GetRange(index + 14, 2), spriteRenderer));\n faintDirections.Add(new SpriteAnimator(frames.GetRange(index + 16, 6), spriteRenderer));\n hurtDirections.Add(new SpriteAnimator(frames.GetRange(index + 22, 2), spriteRenderer));\n defanceDirections.Add(new SpriteAnimator(frames.GetRange(index + 24, 1), spriteRenderer));\n cryDirections.Add(new SpriteAnimator(frames.GetRange(index + 25, 2), spriteRenderer));\n skillDirections.Add(new SpriteAnimator(frames.GetRange(index + 27, 3), spriteRenderer,0.16f,false));\n angryDirections.Add(new SpriteAnimator(frames.GetRange(index + 30, 2), spriteRenderer));\n sitDirections.Add(new SpriteAnimator(frames.GetRange(index + 32, 1), spriteRenderer));\n idleDirections.Add(new SpriteAnimator(frames.GetRange(index + 33, 4), spriteRenderer, 1.394f / 4));\n nodDirections.Add(new SpriteAnimator(frames.GetRange(index + 37, 2), spriteRenderer));\n walkDirections.Add(new SpriteAnimator(frames.GetRange(index + 39, 6), spriteRenderer));\n victoryDirections.Add(new SpriteAnimator(frames.GetRange(index + 45, 2), spriteRenderer));\n }\n\n // spriteRenderer.sprite = frames.GetRange(368,6)[0];\n currentAnim = idleDirections[lastDireaction];\n }\n\n private void Update()\n {\n var prevAnim = currentAnim;\n SetDirection();\n if(IsMoving){\n currentAnim = walkDirections[lastDireaction];\n }else if(IsSkill){\n currentAnim = skillDirections[lastDireaction];\n }else if(IsRunning){\n currentAnim = runDirections[lastDireaction];\n }else if(IsAttack){\n currentAnim = AttackDirections[lastDireaction];\n }else{\n currentAnim = idleDirections[lastDireaction];\n }\n if (prevAnim != currentAnim )\n currentAnim.Start();\n \n currentAnim.HandleUpdate();\n }\n\n public int LastDireaction{\n get {return lastDireaction;}\n set {lastDireaction = value;}\n }\n\n public bool IsFinshed(){\n return currentAnim.CurrentFrame == currentAnim.Frames.Count-1;\n }\n\n public void SetDirection()\n {\n \n if (Direction.magnitude < 0.01) //MARKER character is static. \n {\n // IsMoving = false;\n // directionArray = idleDirections;\n }\n else\n {\n // IsMoving = true;\n // directionArray = walkDirections;\n lastDireaction = DirectionToIndex();\n }\n // currentAnim = directionArray[lastDireaction];\n }\n\n private int DirectionToIndex()\n {\n Vector2 norDir = Direction.normalized;\n float step = 360 / 8;\n float offset = step / 2;\n float angle = -Vector2.SignedAngle(new Vector2(-1,1), norDir);\n \n angle += offset;\n if (angle < 0)\n {\n angle += 360;\n }\n float stepCount = angle / step;\n //NOW NW N NE E SE S SW W\n //BEFORE N NW W SW S SE E NE\n lastDireaction = Mathf.FloorToInt(stepCount);\n return lastDireaction;\n }\n}\n\n
using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\npublic class NpcController : MonoBehaviour\n{\n private float moveSpeed = 4f;\n private Vector2 move;\n private Rigidbody2D rb;\n public float moveH, moveV;\n private CharacterController animator;\n bool isMoving;\n // Start is called before the first frame update\n IEnumerator Start()\n {\n animator = GetComponent<CharacterController>();\n rb = GetComponent<Rigidbody2D>();\n // Move(new Vector2(2,2));\n var orginVec = new Vector2(transform.position.x,transform.position.y);\n yield return StartCoroutine(Run(new Vector2(1,2)));\n\n yield return StartCoroutine(Skill());\n\n yield return StartCoroutine(Attack());\n\n yield return StartCoroutine(Run(orginVec));\n \n animator.LastDireaction = 0;\n\n }\n\n // Update is called once per frame\n void Update()\n {\n }\n IEnumerator Skill(){\n animator.IsSkill = true;\n Debug.Log(\"start skill\");\n yield return new WaitForSeconds(0.16f*5*2);\n Debug.Log(\"end skill\");\n animator.IsSkill = false;\n }\n IEnumerator Attack(){\n animator.IsAttack = true;\n Debug.Log(\"start IsAttack\");\n yield return new WaitForSeconds(0.16f*8+1f);\n Debug.Log(\"end IsAttack\");\n animator.IsAttack = false;\n }\n IEnumerator Walk(Vector2 targetPos)\n { //固定移动\n animator.IsMoving = true;\n animator.Direction = targetPos - new Vector2(transform.position.x,transform.position.y);\n while ((targetPos - new Vector2(transform.position.x,transform.position.y)).sqrMagnitude > Mathf.Epsilon)\n {\n transform.position = Vector2.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);\n yield return null;\n }\n transform.position = targetPos;\n animator.Direction = targetPos - new Vector2(transform.position.x,transform.position.y);\n animator.IsMoving = false;\n }\n IEnumerator Run(Vector2 targetPos)\n { //固定移动\n animator.IsRunning = true;\n animator.Direction = targetPos - new Vector2(transform.position.x,transform.position.y);\n while ((targetPos - new Vector2(transform.position.x,transform.position.y)).sqrMagnitude > Mathf.Epsilon)\n {\n transform.position = Vector2.MoveTowards(transform.position, targetPos, moveSpeed * 1.5f * Time.deltaTime);\n yield return null;\n }\n transform.position = targetPos;\n animator.Direction = targetPos - new Vector2(transform.position.x,transform.position.y);\n animator.IsRunning = false;\n }\n}\n
动画,可以通过设置条件。预先设置好条件变量。
新建new Blend Tree。通过代码来控制。
又发现一个好东西Scriptable Objects。
使用这个来存储宠物和物品信息是很不错的方法。
这两天都在跟着 《Make A Game Like Pokemon in Unity 》做。
目前到第18章,stat boosting moves 使用字典,确实很绕。。为了提前存储计算行动值,教程中真的很绕~看了四五遍。主要是他的口音真的听不惯~
主要都是为了计算增益或减益效果,包括物理攻击、特殊攻击、状态。
https://www.youtube.com/watch?v=dOsk0r12ziI&list=PLLf84Zj7U26kfPQ00JVI2nIoozuPkykDX&index=18
随机遇敌已经实现了。整个游戏流程算是可以了。不过目前还是宠物小精灵模式的。还得改~
有多种行动选择,死亡判断,换宠。包括记录了宠物的等级 hp等。
目前我见过最完整的一个教程。还有13章。里面有很多技巧都挺有用的。
目前唯一使用的一个插件
原本是想停下来了。觉得技术储备已经足够,想等空闲时间再摸起来。不过自己确实还没有完成一整个游戏系统的流程开发。所以,还是接着做吧。
对了这个项目我放在github上面了。不过估计会非常大,酌情。
上一篇主要完成了 地图绘制,碰撞和遮罩。人物移动,人物动画,以及镜头跟随。
接下来做了场景切换。和一个简单的对战系统。目前是宠物小精灵模式,还需要很多调整的。关键词 trun base rpg
简单也算是一个流程吧。
然后做了系统保存和读取,目前只支持玩家的。
接着更换了InputSystem。这个可能到时候会有坑。比如unity ui无法点击,不过通过报错,确实解决了。场景下的Event System,点击一下repalce和重新绑定键位。还有原本的input代码都需要调整。
简单说一下使用方式,新建一个action asset。然后呢在EventSystem里面使用它。
在代码中
private PlayerInputActions controls;\nprivate Vector2 move;\nprivate void Awake() {\n controls = new PlayerInputActions();\n controls.GamePlay.Ok.started += ctx => OpenTreasureBox();\n controls.GamePlay.Move.performed += ctx => move = ctx.ReadValue<Vector2>();\n controls.GamePlay.Move.canceled += ctx => move = Vector2.zero;\n}\nprivate void OnEnable() {\n controls.GamePlay.Enable();\n}\n \nprivate void OnDisable() {\n controls.GamePlay.Disable();\n}\n
做了主菜单和暂停菜单。暂停菜单支持了保存和读取和退出的功能。
今天打算做一个对话框和打开物品。
然后还需要有随机遇敌,物品管理,武器装备。人物状态管理。剧情管理,完善对战系统。整个游戏的数据管理(这个要使用数据库还是~~~sqlite)
好多。
还有一个就是编辑器,之前没有打算深入体验unity,没有怎么配置vscode,即使安装了c#和unity的扩展也没有用。完全没有智能提示。在配置完成后,终于有提示了。
对话框和宝箱开启都完成了。为了做开启宝箱的效果,还自己画了几帧动画。这个魔力宝贝原本是没有的功能。
宝箱开启后,我希望它永久保存。但是还得记录状态。所以~保存系统还需要完善。已经有思路了。
关于图档问题。之前用网友的程序导出的图片,默认的调色版是08的黄昏色。特别是在出生点灵堂,看着很不舒服。
由于C++的能力不足,我又不知道怎么改他的。最关键的默认读取08号cpg。读了很久的源代码始终不知道怎么改~
之前也不理解为什么他要全部拼成json之后一次性导出全部图片,那是相当的花时间。
主要参考的两篇文章
https://blog.csdn.net/qq_37543025/article/details/88377553
https://www.cnblogs.com/CG7/archive/2004/01/13/12237395.html
项目地址是:
https://github.com/mversace/CrossGateRemastered
导出的一些信息tilemap丢失了。实际上都是有作用的。
于时我居然自己想写一个。node版本的。。
有点后悔,要在js里面处理大量的二进制 十六进制 位运算,确实很头疼。node buffer又是存的 Uint8Array
Uint8Array
硬着头皮干。
首先还得理解,各种调色板的含义。从二进制文件读取图片信息,原始图片信息,解密算法,填色,再导出。
graph_info.bin 和 graph.bin的理解。
目前已经可以读取到图片信息和原始图片信息。根据图片信息可以找到图片的地址,得到原始图片数据。再进行解密。
解压,解压算法好像不太对。。
我解出来的图片明显太大了。肯定是哪里出了问题。
还差读取调色板和填色的步骤。。
不过如果能够实现的话,
那么就可以直接运算魔力宝贝bin文件了。这样的话,其实可以畅想的就很多了,虽然我已经不想再复刻魔力了。不过,里面的游戏系统知识还是有用的。包括可怕的二进制文件。
折腾了一下c++,通过修改debug的目标路径,也算是可以调试了。到时候再监听对比一下数据应该就可以了。
最后直接把08的替换掉,想要的信息只要稍微改动一下源代码就好了。把想要的调色板改成08就行了。。
调色板来控制白天00() 傍晚01(发橙色) 黑夜02(发蓝色) 凌晨03(有点发青色)
我觉得黑夜的颜色最好看。灵堂似乎永远都采用黑夜。
11月25日
终于解决了。
经过调试发现,原来调色板是用了unordered_map无序存储的。默认begin就是08。
在define.h,添加默认读取的调色板
__declspec(selectany) std::string defaultCgp = \"palet_03.cgp\";\n
将getCGImage.cpp中 改成:
unsigned char *pCgp = _uMapCgp.find(defaultCgp)->second.data();\nstrCgpName = _uMapCgp.find(defaultCgp)->first;\n
终于完美解决了~
node 方面 感觉看到了胜利的曙光。。
终于!!!成功了。不过颜色不大对
11月26日 14:22
泪流满面,终于~~
其实透明好像不太好。在有一些调色板下,一些像素会丢失。这也是个问题。也就是还得保留黑色,之后再去除黑色背景?
后面只要再完善一下就行了。
11月28日更新
有些图还是有问题,程序目前还有bug~
先放一个 目前的效果 demo
可以通过手柄或者方向键控制人物行走哦
1、官方教程 https://docs.unity3d.com/Manual/Tilemap-Isometric-SpritesImport.html
遮挡问题
https://blog.csdn.net/tianguisu/article/details/105631579
像素问题:
https://blog.csdn.net/yye4520/article/details/81208664
https://www.cnblogs.com/vvjiang/p/10344007.html
注意导入的素材需要设置每单位像素数(PPU)
比如单位宽度为64.就应该改为64.默认值是100.如果不修改。地图会出现问题。
以64*47为例。
Gird 和tile palette, cell size 设置为 :x:1 y:47/64 z:1
tile map层 设置为 x:0.5 y:0.5 z:0.5
edit->project settings->Graphics->Camera settings
transparecy Sort Mode设置为 Custom Axis,Transparency Sort Axis设置为:x:0,y:1,z:0
需要为物品设置正确的偏移 Pivot 设置为custom,Pivot Unit设置为pixels(默认都是百分比,是为了做适配吗?)
碰撞问题,可以为每个物品单独设置
动画
动画的导入,有多少方式。图集的需要选择multiple,之后在Sprite Editor里面Slice。
设置Samples帧率。默认没有打开
目前进度 高清化呀
目前玩家与物品的遮挡问题还是存在~ 通过z as y 的方式好像也不能解决。可能还需要再看看资料。
不过这样还是无法解决与不规则占多格的物品。难道还是得用常规的做法~
unity相对于ue4来说,确实更加适合。tilmap的pallet很接近我想要的效果。
通过一周多的时间研究,我发现,unreal对isometric的支持真的很不友好~
一、贴图
把一张图片导入后,先右键Sprite操作。应用paper2D纹理设置。之后就可以进行创建sprite,或者提取,瓦片集的操作。
二、动画,图片序列视图
图片提取之后,就可以对多张sprite创建图片序列视图。这就是动画了。的确很方便。
三、自定义game_mode
四、角色 继承paperCharacter
控制人物行根据当前状态,来控制对应的动画。
五、tilemap的使用
unreal 的tilemap真心不好用。主要是在45度 isometric 里面比较难处理。只能用层,没有对象层的概念。这可就为难了。想要动态加载需要自己脑补很多办法。碰撞可以运用它里面提供的3d碰撞。但是遮挡就麻烦了。
也不能直接引用
六、遮挡问题。
在unreal里,可以设置半透明排序优先级,来控制遮挡。所用的材质必须要设置为TranslucentUnlitSpriteMaterial。才有效。
主要是建立一个actor 继承PaperSprite,然后设置一个自定义的sprite变量来设置对象。再设定一个sortPoint变量,根据sortPoint,来设置半透明排序优先级。这两个变量都要可见,和生成时公开,以便后面动态生成时使用。
角色类在移动时根据坐标实时设置半透明排序优先级。
动态生成
我设置在了关卡蓝图,目前只是简单支持,墙壁。实在不想一个个拖进去。如何动态获取图片的宽高,都难以解决。最后估计还是靠数据表格来解决吧。
由于有些是长方体的。遮挡的ac面还是比较难以控制。暂时,我是把其切割成两半,这样就行了。
又得重新自己建立坐标系。
大概从tilemap editor里面的坐标转换是这样的。
var tileWith = 64;\nvar tileHeight = 47;\nfunction getPinouter(x,y,width,height){\n var n = (x) / tileHeight \n var m = (y) /tileHeight\n return {\n x:(n-m)*32 , \n y:-(m+n)*23.5 + height/2-23.5\n }\n}\n
七、CVS和JSON的使用
蓝图里面只有结构和枚举。在编辑器里,数据量大时非常不好修改。很浪费时间。
数据表格,第一行必须大写Name,不然Json里面的第一行字段可能会丢失。
对比一下建立的结构,数据表格以及导出json、csv文件。
八、其他一些小问题。由于是在2d,可以取消掉重力,不然,在没有地面时,人物就会一直往下掉。
九、摄像机的设定需要使用正交模式。垂直对着玩家。正面。
我这又时何苦呢?
先说结论 ,ahook是更好的选择
继之前的debounce,和throttle的js实现,然后看了loadash和underscroe的源码,改成typescript并整合到自己的utils库里。
yarn add @kacoro/utils\n
然后接着用react中使用,
import {throttle,debounce} from '@kacoro/utils'\n\n<input type=\"text\" onChange={(e)=>{\ne.persist() // react默认会清除所有的默认属性,所以需要添加这段,保留参数的属性\ndebounce(()=>{\n console.log(e.target.value\n) },500)()\n}} />\n
在hook来实现。参考:https://www.freecodecamp.org/news/debounce-and-throttle-in-react-with-hooks/
通过使用useRef、和useCallback来实现。
使用useCallback实现bebounce
在向子组件传递回调时,useCallback通常用于性能优化。但我们可以使用它的约束记忆一个回调函数,
,以确保该debouncedSave引用同去抖功能跨越渲染。
debouncedSave
const [value, setValue] = useState('');\n const [dbValue, saveToDb] = useState(''); // would be an API call normally\n \n const debouncedSaveByCallBack:Function = useCallback(debounce((nextValue: string) => saveToDb(nextValue), 1000),[],); // will be created only once initially\n \n const handleDbChange = (event: { target: { value: any; }; }) => {\n const { value: nextValue } = event.target;\n setValue(nextValue);\n debouncedSaveByCallBack(nextValue);\n };\n
使用useRef实现throttle
useRef为我们提供了一个可变对象,其current属性引用传递的初始值。 如果我们不手动更改它,则该值将在组件的整个生命周期内保持不变 这类似于类实例的属性
useRef
current
const [value2, setValue2] = useState('');\n const [thValue, saveToTh] = useState(''); // would be an API call normally\n const throttledSaveByRef:Function = useRef(throttle((nextValue: string) => saveToTh(nextValue), 1000))\n .current;\n \n const handleThChange = (event: { target: { value: any; }; }) => {\n const { value: nextValue } = event.target;\n setValue2(nextValue);\n throttledSaveByRef(nextValue);\n };\n
自定义hooks
interface CurrentRef {\n fn: Function;\n timer?: ReturnType<typeof setTimeout> | null\n}\n\nfunction useDebounce(fn: Function, delay: number = 300, dep = []) {\n const { current } = useRef<CurrentRef>({ fn, timer: null });\n useEffect(function () {\n current.fn = fn; // eslint-disable-next-line \n }, [fn]);\n\n return useCallback(function f(...args: any[]) {\n if (current.timer) {\n clearTimeout(current.timer);\n }\n current.timer = setTimeout(() => {\n current.fn.call(useDebounce, ...args);\n }, delay); // eslint-disable-next-line \n }, dep)\n}\n\nfunction useThrottle(fn: Function, delay: number = 300, dep = []) {\n const { current } = useRef<CurrentRef>({ fn, timer: null });\n useEffect(function () {\n current.fn = fn; // eslint-disable-next-line\n }, [fn]);\n\n return useCallback(function f(...args) {\n if (!current.timer) {\n current.timer = setTimeout(() => {\n delete current.timer; // eslint-disable-next-line\n }, delay);\n current.fn.call(useThrottle, ...args);\n }// eslint-disable-next-line\n }, dep);\n}\n
const [counter, setCounter] = useState(0);\n const handleDebounceClick = useDebounce(function () {\n setCounter(counter + 1)\n }, 300)\n const handleThrottleClick = useThrottle(function () {\n setCounter(counter + 1)\n }, 300)\n
完整代码
import React, { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {throttle, debounce } from '@kacoro/utils'\n\ninterface CurrentRef {\n fn: Function;\n timer?: ReturnType<typeof setTimeout> | null\n}\n\nfunction useDebounce(fn: Function, delay: number = 300, dep = []) {\n const { current } = useRef<CurrentRef>({ fn, timer: null });\n useEffect(function () {\n current.fn = fn; // eslint-disable-next-line \n }, [fn]);\n\n return useCallback(function f(...args: any[]) {\n if (current.timer) {\n clearTimeout(current.timer);\n }\n current.timer = setTimeout(() => {\n current.fn.call(useDebounce, ...args);\n }, delay); // eslint-disable-next-line \n }, dep)\n}\n\nfunction useThrottle(fn: Function, delay: number = 300, dep = []) {\n const { current } = useRef<CurrentRef>({ fn, timer: null });\n useEffect(function () {\n current.fn = fn; // eslint-disable-next-line\n }, [fn]);\n\n return useCallback(function f(...args) {\n if (!current.timer) {\n current.timer = setTimeout(() => {\n delete current.timer; // eslint-disable-next-line\n }, delay);\n current.fn.call(useThrottle, ...args);\n }// eslint-disable-next-line\n }, dep);\n}\nfunction DebonceThrottle() {\n const [counter, setCounter] = useState(0);\n const handleDebounceClick = useDebounce(function () {\n setCounter(counter + 1)\n }, 300)\n const handleThrottleClick = useThrottle(function () {\n setCounter(counter + 1)\n }, 300)\n\n const [value, setValue] = useState('');\n const [dbValue, saveToDb] = useState(''); // would be an API call normally\n \n const debouncedSaveByCallBack:Function = useCallback(debounce((nextValue: string) => saveToDb(nextValue), 1000),[],); // will be created only once initially\n \n const handleDbChange = (event: { target: { value: any; }; }) => {\n const { value: nextValue } = event.target;\n setValue(nextValue);\n debouncedSaveByCallBack(nextValue);\n };\n \n const [value2, setValue2] = useState('');\n const [thValue, saveToTh] = useState(''); // would be an API call normally\n const throttledSaveByRef:Function = useRef(throttle((nextValue: string) => saveToTh(nextValue), 1000))\n .current;\n \n const handleThChange = (event: { target: { value: any; }; }) => {\n const { value: nextValue } = event.target;\n setValue2(nextValue);\n throttledSaveByRef(nextValue);\n };\n \n return (\n <div>\n <textarea value={value} onChange={handleDbChange} rows={5} cols={50} />\n <section >\n <div>\n <b>Editor (Client):</b>\n {value}\n </div>\n <div>\n <b>Saved (Debounce):</b>\n {dbValue}\n </div>\n </section>\n <textarea value={value2} onChange={handleThChange} rows={5} cols={50} />\n <section >\n <div>\n <b>Editor (Client):</b>\n {value2}\n </div>\n <div>\n <b>Saved (Throttle):</b>\n {thValue}\n </div>\n </section>\n <section>\n <button\n onClick={handleDebounceClick}\n >click debounce</button> <button\n onClick={handleThrottleClick}\n >click throttle</button>\n <div>{counter}</div>\n </section>\n </div>\n );\n}\n\nexport default DebonceThrottle;\n