admin 发布的文章 - 青蓝鱼的博客-没有bug的代码是不完美的
admin 发布的文章 - 青蓝鱼的博客-没有bug的代码是不完美的
终于找到这篇文章了,感谢作者的分享!
https://fishfive.top/index.php/archives/29/
终于找到这篇文章了,感谢作者的分享!
受益匪浅,感谢博主。
该回复疑似异常,已被系统拦截!
1
111
666
# 图片回复
666
学到了
666
hello word
首页
关于
?
归档
留言
统计
导航
更多
github
友链
推荐
百度
搜 索
1
Nginx-Quic重新编译Nginx支持HTTP3
356 阅读
2
Centos7和Centos8网卡配置
269 阅读
3
六种好看的css按钮效果
189 阅读
4
node.js简单的web服务demo
159 阅读
5
JavaScript实现静态图片局部流动效果
156 阅读
默认分类
html
css
JavaScript
React
Vue
Git
centos
node.js
php
nginx
http
登录
搜 索
https://fishfive.top
累计撰写
27
篇文章
累计收到
16
条评论
首页
栏目
默认分类
html
css
JavaScript
React
Vue
Git
centos
node.js
php
nginx
http
页面
关于
归档
留言
统计
导航
github
友链
推荐
百度
用户登录
登录
找到
27
篇与
admin
相关的结果
2022-09-26
js实现扫雷小游戏
思路流程写出基本的布局利用js生成扫雷的table表格利用随机数来做地雷在表格中的索引初始化table表格根据地雷的坐标生成地雷周围的数字点击事件分成鼠标左键点击和右键点击左键点击情况下又分为点到的是地雷和非地雷两种情况点到的是地雷情况下,则将全部地雷显示,其他样式不变,并且不能再进行任意表格内的点击事件(左键右键都不行)点到的是非地雷情况下又分为点击的数字是0和非0两种情况如果是非0,则只需要显示其数字如果是0,利用递归思想,遍历周围的表格,若为0则继续递归显示0,直到遇到非0停止接上面的6,若进行右键点击,则显示小红旗,并且剩余地雷数-1当剩余雷数为0时,判断小红旗底下是否全为地雷,若全是地雷则成功扫雷,否则扫雷失败为按钮添加功能,分别为9乘以9->10个雷、16乘以16->40个地雷、28乘以28、99个地雷,以及重新开始按钮生成游戏棋盘利用双层for循环创建设定的棋盘大小为每个单元格的dom元素创建一个属性,该属性用于保存单元格的所有信息,如x,y坐标,value,是否为雷等随机生成炸弹利用随机数,随机生成炸弹x,y坐标,并将符合该坐标信息的单元格的属性更改为雷炸弹是在用户第一次点击的时候生成,防止用户第一次点击到炸弹将生成的每个炸弹信息都保存到一个this变量中,方便后续使用遍历每个炸弹周围的非炸弹方格,每遍历一次value值+1鼠标左键点击点击的时候需要考虑该单元格是否有被标记小旗子(isFlag属性),如果有则无法点击判断是雷还是数字,雷的话则游戏结束,数字则继续判断是否等于0,等于0则使用递归显示空白区域每次打开一个单元格,需要更改该单元格的isOpen属性,表示单元格被打开鼠标右键点击点击时需要考虑该单元格的isOpen属性是否被打开,打开的话则无法点击当该单元格没有标记旗帜时标记,如果有标记旗帜则取消标记每标记一个方格,剩余炸弹数量-1,取消标记则+1游戏结束当左键点击到炸弹的时候游戏结束。失败剩余炸弹数量为0时。判断旗帜标记是否正确,正确游戏胜利,标记有误则失败html代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="css/index.css" > </head> <body> <div class="main"> <header class="header"> <button>初级</button> <button>中级</button> <button>高级</button> </header> <div class="gameBox" id="gameBox"></div> <footer class="footer">剩余雷数量:<span id="surplusMine"></span> </footer> </div> </body> <script src="js/index.js"></script> </html>css代码.main .header { text-align: center; margin: 20px auto; } .main .gameBox table { border-spacing: 1px; background-color: rgb(170, 170, 170); text-align: center; margin: 20px auto; } .main .gameBox table td.mine { /* 游戏结束时显示 */ border: none; background: url(./../img/mine.png) no-repeat; background-size: 90% 90%; background-color: #e9e6e6; background-position: 2px 0; } .main .gameBox table td.targetMine { /* 游戏结束时显示,触发雷的单元格 */ border: none; background: url(./../img/mine.png) no-repeat; background-size: 90% 90%; background-color: #ff4b4b; background-position: 2px 0; } .main .gameBox table td.targetFlag { /* 右键标记方格时显示 */ background: url(./../img/flag.png) no-repeat; background-size: 90% 90%; background-position: 2px 0; background-color: #e9e6e6; } .main .gameBox table td { /* 单元格初始样式 */ width: 20px; height: 20px; box-sizing: border-box; border: 2px solid; border-color: #eee #ccc #ccc #eee; background-color: #e9e6e6; font-size: 1px; font-weight: 800; } .gameBox table td.zero, .gameBox table td.one, .gameBox table td.two, .gameBox table td.three, .gameBox table td.four, .gameBox table td.five, .gameBox table td.six, .gameBox table td.seven, .gameBox table td.eight, .gameBox table td.nine { border: none; background-color: rgb(211, 200, 200); } .gameBox table td.zero .gameBox table td.one { color: blue; } .gameBox table td.two { color: rgb(5, 93, 5); } .gameBox table td.three { color: #008c8c; } .gameBox table td.four { color: crimson; } .gameBox table td.five { color: rgb(228, 91, 0); } .gameBox table td.six { color: darkorange; } .gameBox table td.seven { color: rgb(193, 196, 50); } .gameBox table td.eight { color: pink; } .main .footer { text-align: center; }js代码function Game(tr, td, mineNum) { this.td = td; this.tr = tr; this.mineNum = mineNum; //存储预设或设定的炸弹总数,用于后续判断是否胜利使用 this.surplusMine = 0; //剩余雷数 this.mineInfo = []; //用于接收随机生成的雷的信息 this.tdsArr = [] //存放单元格的信息 this.isPlay = false; //是否开始玩 this.openClass = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"] this.gameBox = document.getElementById("gameBox"); this.table = document.createElement("table"); //生成table标签 this.footerNum = document.getElementById("surplusMine"); //剩余炸弹数量显示框 } Game.prototype.creatDom = function() { //创建游戏区域,在玩家第一次点击游戏区域的时候执行 this.table.oncontextmenu = function() ; //清除默认右键单机事件 for (var i = 0; i < this.gameBox.children.length; i++) { //为防止重新开始游戏时,重复生成多个table,在添加之前先删除之前的 var childNod = this.gameBox.children[i]; this.gameBox.removeChild(childNod); } for (var i = 0; i < this.tr; i++) { var tr = document.createElement("tr"); this.tdsArr[i] = []; //为每一行生成一个数组 for (var j = 0; j < this.td; j++) { var td = document.createElement("td"); tr.appendChild(td); //将生成的td插入到tr中 this.tdsArr[i][j] = td; td.info = { //info属性包括了单元格的所有信息,很重要 type: "number", //格子类型,用于判断是否时炸弹 x: i, //行 y: j, //列 value: 0, //当该格子周围有炸弹时显示该数值,生成炸弹的时候会++ isOpen: false, //判断该单元格是否被打开 isFlag: false //判断是否有标记flag } } this.table.appendChild(tr); //见tr插入到table中 } this.gameBox.appendChild(this.table); } Game.prototype.creatMine = function(event, target) { //生成炸弹,该方法会在用户第一次点击棋盘的时候执行一次 var This = this; for (var i = 0; true; i++) { //随机生成炸弹,生成扎当数与设定扎当书mineNum相同时终止循环 var randomX = Math.floor(Math.random() * this.tr), //随机生成炸弹的行数 randomY = Math.floor(Math.random() * this.td); //随机生成炸弹的列数 // console.log(randomX + " " + randomY) if (target.info.x != randomX || target.info.y != randomY) { //保证第一次点击的时候不是炸弹 if (this.tdsArr[randomX][randomY].info.type != "mine") { //保证每次生成的雷的位置不重复 this.tdsArr[randomX][randomY].info.type = "mine"; //单元格更改属性为雷 this.surplusMine++; //生成雷的数量+1 this.mineInfo.push(this.tdsArr[randomX][randomY]); //将生成的雷的信息存放到this变量中,方便后续使用 } if (this.surplusMine >= this.mineNum) { //当生成的炸弹数量等于设定的数量后跳出循环 break; } } } //为每个炸弹周围的方格添加数字 for (var i = 0; i < this.mineInfo.length; i++) { var around = this.getAround(this.mineInfo[i], This); //获取每个炸弹的周围方格 // console.log(this.getAround(this.mineInfo[i], This)) for (var j = 0; j < around.length; j++) { //将周围每个方格的value++ around[j].info.value += 1; } } } Game.prototype.getAround = function(thisCell, This) { //获取某个方格的周围非炸弹方格,需要传递一个单元格dom元素,Game的this var x = thisCell.info.x, //行 y = thisCell.info.y, //列 result = []; // x-1,y-1 x-1,y x-1,y+1 // x,y-1 x,y x,y+1 // x+1,y-1 x+1y x+1,y+1 for (var j = x - 1; j <= x + 1; j++) { for (var k = y - 1; k <= y + 1; k++) { if ( //游戏区域的边界,行数x和列数y不能为负数,且不能超过设定的行数和列数 j < 0 || k < 0 || j > (This.tr - 1) || k > (This.td - 1) || //同时跳过自身和周边是雷的方格 This.tdsArr[j][k].info.type == "mine" || (j == x && k == y) ) { continue; //满足上述条件是则跳过当此循环; } else { result.push(This.tdsArr[j][k]) //将符合的单元格push到result中返回 } } } return result; } Game.prototype.lifeMouse = function(event, target) { //左键点击事件 var This = this; //用变量的方式将Game的this传递到函数中 var noOpen = 0; //没有被打开的格子数量 if (!target.info.isFlag) { //表示该必须没有被右键标记才能鼠标左击 if (target.info.type == "number") { //是数字时,则可视化 function getAllZero(target, This) { //递归函数 // console.log(target.info) if (target.info.isFlag) { //当这个单元格之前有被标记过flag时,则将剩余炸弹数+1 This.surplusMine += 1; target.info.isFlag = false; //单元格被打开后初始化flag } if (target.info.value == 0) { //等于格子的value等于0的时候 target.className = This.openClass[target.info.value]; //可视化 target.info.isOpen = true; //表示该单元格被打开 var thisAround = This.getAround(target, This); //获取该单元格周围的格子信息 for (var i = 0; i < thisAround.length; i++) { // console.log(thisAround[i].info.isOpen) if (!thisAround[i].info.isOpen) { //递归的条件,当格子的open为true时不执行 getAllZero(thisAround[i], This) //执行递归 } } } else { target.innerHTML = target.info.value; target.className = This.openClass[target.info.value]; //可视化 target.info.isOpen = true; //表示单元格被打开 target.info.isFlag = false; //单元格被打开后初始化flag } } getAllZero(target, This); //首次执行 //每次鼠标左键点击的时候都需要检查一下没有被打开的方格数量,每有一个则noOpen++ for (var i = 0; i < this.tr; i++) { for (var j = 0; j < this.tr; j++) { if (this.tdsArr[i][j].info.isOpen == false) { noOpen++; } } } //当noOpen的数量与炸弹数量相同时,说明剩余的方格全是雷,游戏通过 if (noOpen == this.mineNum) { console.log(noOpen) this.gameWin(); } } else { //点击到了炸弹,游戏结束 this.gameOver(target) } } } Game.prototype.rightMouse = function(target) { //鼠标右键点击执行 if (!target.info.isOpen) { if (!target.info.isFlag) { //标记 target.className = "targetFlag"; //显示旗帜 target.info.isFlag = true; //表示该方格已经被标记 this.surplusMine -= 1; //每标记一个方格,剩余炸弹数量-=1 // console.log(this.surplusMine) } else { //取消标记 target.className = ""; //去掉旗帜 target.info.isFlag = false; this.surplusMine += 1; // console.log(this.surplusMine) } var isWin = true; if (this.surplusMine == 0) { //标记完所有flag时,遍历所有单元格 // console.log(this.mineInfo.length) for (var i = 0; i < this.mineInfo.length; i++) { console.log(this.mineInfo[i].info.isFlag) if (!this.mineInfo[i].info.isFlag) { //检查每个雷的isFlag属性是否被标记,只要有一个为false则输掉游戏 isWin = false; this.gameOver(target, 1); break; } } isWin ? this.gameWin(1) : 0; //三目运算符号 } // if (this.surplusMine == 0) { //标记完所有flag时,遍历所有单元格 // for (var i; i < this.tr; i++) { // for (var j; j < this.td; j++) { // if() // } // } // } } } Game.prototype.gameOver = function(target, code) { //游戏结束,code为触发代码,当旗用完了时为1,点击到炸弹为0 // console.log(this.mineInfo) var mineInfoLen = this.mineInfo.length; for (var i = 0; i < mineInfoLen; i++) { //显示每个雷的位置 this.mineInfo[i].className = "mine"; } this.table.onmousedown = false; //取消鼠标事件 if (code) { alert("旗帜用完了,没有排除所有雷,游戏结束") } else { target.className = "targetMine"; //触发雷标红色 alert("你被炸弹炸死了,游戏结束") } } Game.prototype.gameWin = function(code) { //游戏胜利 if (code) { alert("你成功标记所有地雷,游戏通过") } else { alert("你找到了所有安全区域,游戏通过") } this.table.onmousedown = false; } Game.prototype.play = function() { var This = this; //需要将this传递到事件函数中使用 this.table.onmousedown = function(event) { event = event || window.event; //兼容IE target = event.target || event.srcElement //兼容IE if (!this.isPlay) { //首次点击初始化棋盘,随机生成炸弹 this.isPlay = true; This.creatMine(event, target); } if (event.button == 0) { //鼠标左键点击时执行 This.lifeMouse(event, target); } else if (event.button == 2) { //右键点击执行 This.rightMouse(target) } This.footerNum.innerHTML = This.surplusMine; //每次点击右键,刷新页面下方的剩余雷数 } } Game.prototype.tablePos = function() { //将table居中显示 var width = this.table.offsetWidth, height = this.table.offsetHeight; // console.log(this.table.offsetWidth) this.table.style.width = width + "px "; this.table.style.height = height + "px " } function addEvent(elem, type, handle) { //添加事件函数 if (elem.addEventListener) { //w3c标准 elem.addEventListener(type, handle, false); } else if (elem.attachEvent) { //IE9及以下 elem.attachEvent("on" + type, function() { handle.call(elem); }) } else { //其他情况 elem["on" + type] = handle; } } Game.prototype.setDegree = function() { //调整难度 var button = document.getElementsByTagName("button"); addEvent(button[0], "click", function() { //简单 var game = new Game(10, 10, 10); game.creatDom(); game.play(); game.tablePos(); }); addEvent(button[1], "click", function() { //一般 var game = new Game(16, 16, 50); game.creatDom(); game.play(); game.tablePos(); }); addEvent(button[2], "click", function() { //困难 var game = new Game(30, 30, 125); game.creatDom(); game.play(); game.tablePos(); }); } // 默认棋盘 var game = new Game(10, 10, 10); game.creatDom(); game.play(); game.tablePos(); game.setDegree()扫雷源码
2022年09月26日
61 阅读
3 评论
0 点赞
2022-09-08
Crontab 定时任务
cron介绍我们经常使用的是crontab命令是cron table的简写,它是cron的配置文件,也可以叫它作业列表,我们可以在以下文件夹内找到相关配置文件。/var/spool/cron/ 目录下存放的是每个用户包括root的crontab任务,每个任务以创建者的名字命名/etc/crontab 这个文件负责调度各种管理和维护任务。/etc/cron.d/ 这个目录用来存放任何要执行的crontab文件或脚本。我们还可以把脚本放在/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly目录中,让它每小时/天/星期、月执行一次。crontab的使用我们常用的命令如下:crontab [-u username] //省略用户表表示操作当前用户的crontab -e (编辑工作表) -l (列出工作表里的命令) -r (删除工作作)我们用crontab -e进入当前用户的工作表编辑,是常见的vim界面。每行是一条命令。crontab的命令构成为 时间+动作,其时间有分、时、日、月、周五种,操作符有* 取值范围内的所有数字/ 每过多少个数字-从X到Z,散列数字Crontab在Linux上的结构 从左到右依次为:[分钟] [小时] [每月的某一天] [每年的某一月] [每周的某一天] [执行的命令]Crontab使用实例1.每天02:00执行任务0 2 * * * /bin/sh backup.sh2.每天5:00和17:00执行任务0 5,17 * * * /scripts/script.sh3.每分钟执行一次任务 * * * * * /scripts/script.sh4.每周日17:00执行任务0 17 * * sun /scripts/script.sh5.每10min执行一次任务*/10 * * * * /scripts/monitor.sh6.在特定的某几个月执行任务 * * * jan,may,aug * /script/script.sh7.在特定的某几天执行任务,在每周五、周日的17点执行任务0 17 * * sun,fri /script/scripy.sh8.在某个月的第一个周日执行任务0 2 * * sun [ $(date +%d) -le 07 ] && /script/script.sh9.每四个小时执行一个任务0 */4 * * * /scripts/script.sh10.每周一、周日执行任务0 4,17 * * sun,mon /scripts/script.sh11.每个30秒执行一次任务,我们没有办法直接通过上诉类似的例子去执行,因为最小的是1min。但是我们可以通过如下的方法。 * * * * * /scripts/script.sh * * * * * sleep 30; /scripts/script.sh12.多个任务在一条命令中配置 * * * * * /scripts/script.sh; /scripts/scrit2.sh13.每年执行一次任务,@yearly 类似于“0 0 1 1 *”。它会在每年的第一分钟内执行,通常我们可以用这个发送新年的问候。@yearly /scripts/script.sh14.每月执行一次任务@yearly /scripts/script.sh15.每周执行一次任务@yearly /scripts/script.sh16.每天执行一次任务@yearly /scripts/script.sh17.每分钟执行一次任务@yearly /scripts/script.sh18.系统重启时执行@reboot /scripts/script.sh19.将Cron结果重定向的特定的账户默认情况下,cron只会将结果详情发送给cron被制定的用户。如果需要发送给其他用户,可以通过如下的方式: # crontab -l MAIL=bob 0 2 * * * /script/backup.sh20.将所有的cron命令备份到文本文件当中这是一个当我们丢失了cron命令后方便快速的一个恢复方式。下面是利用这个方式恢复cron的一个小例子。(看看就行~)首先:检查当前的cron# crontab -l MAIL=rahul 0 2 * * * /script/backup.sh然后:备份cron到文件中# crontab -l > cron-backup.txt # cat cron-backup.txt MAIL=rahul 0 2 * * * /script/backup.sh接着:移除当前的cron# crontab -r # crontab -l no crontab for root恢复:从text file中恢复# crontab cron-backup.txt # crontab -l MAIL=rahul 0 2 * * * /script/backup.sh21.在Crontab中使用PHP执行脚本就像在Crontab中调用普通的shell脚本一样(具体Crontab用法),使用PHP程序来调用PHP脚本,每一小时执行 myscript.php ,/usr/local/bin/php为PHP程序的路径。如下:# crontab -e 00 * * * * /usr/local/bin/php /home/john/myscript.php22.在Crontab中使用URL执行脚本如果你的PHP脚本可以通过URL触发,你可以使用 lynx 或 curl 或 wget 来配置你的Crontab.下面的例子是使用Lynx文本浏览器访问URL来每小时执行PHP脚本。Lynx文本浏览器默认使用对话方式打开URL。但是,像下面的,我们在lynx命令行中使用-dump选项来把URL的输出转换来标准输出。00 * * * * lynx -dump http://www.sf.net/myscript.php下面的例子是使用 CURL 访问URL来每5分执行PHP脚本。Curl默认在标准输出显示输出。使用 "curl -o" 选项,你也可以把脚本的输出转储到临时文件temp.txt。***/5 * * * * /usr/bin/curl -o temp.txt http://www.sf.net/myscript.php**下面的例子是使用WGET访问URL来每10分执行PHP脚本。-q 选项表示安静模式。"-O temp.txt" 表示输出会发送到临时文件。*/10 * * * * /usr/bin/wget -q -O temp.txt http://www.sf.net/myscript.php
2022年09月08日
84 阅读
0 评论
0 点赞
2022-09-07
git-flow 的工作流程
什么是 git-flow?一旦安装安装 git-flow,你将会拥有一些扩展命令。这些命令会在一个预定义的顺序下自动执行多个操作。是的,这就是我们的工作流程!git-flow 并不是要替代 Git,它仅仅是非常聪明有效地把标准的 Git 命令用脚本组合了起来。严格来讲,你并不需要安装什么特别的东西就可以使用 git-flow 工作流程。你只需要了解,哪些工作流程是由哪些单独的任务所组成的,并且附带上正确的参数,以及在一个正确的顺序下简单执行那些对应的 Git 命令就可以了。当然,如果你使用 git-flow 脚本就会更加方便了,你就不需要把这些命令和顺序都记在脑子里。Master分支Master分支作为唯一一个正式对外发布的分支,是所有分支里最稳定的。这是因为,只有经过了严格审核和测试,并且在当前发布计划里的特性,才会被合并到master分支。当某个版本发布的时候,我们通常还会为master分支加上带有相应版本号的tag。Develop分支Develop分支是根据master分支创建出来的,它作为一种集成分支(Integration Branch),是专门用来集成开发完成的各种特性的。Develop分支通常具有更加详细和完整的提交历史,包括一些很细节的提交记录。而master分支则因为是面向版本发布的,所以它的提交历史会略去这些细节,显得比较精简。Feature分支Feature分支是根据develop分支创建出来的,Gitflow工作流里的每个新特性都有自己的feature分支,这一点和特性分支工作流是一样的。这些分支除了在开发人员的本地存在以外,也可以被推送到共享的远程Git库,作为工作备份,以及与其他人协同工作的基础。当特性开发结束以后,这些分支上的工作会被合并到develop分支。但feature分支从来不会直接和master分支打交道。Release分支当积累了足够多的已完成特性,或者预定的系统发布周期临近的时候,我们就会从develop分支创建出一个release分支,专门用来做和当前版本发布有关的工作。Release分支一旦开出来以后,就不允许再有新的特性被加入到这个分支了,只有bug修复或者文档编辑之类的工作才允许进入该分支。Release分支上的内容最终会被合并到master分支,等版本发布的时候,我们通常还会为master分支加上带有相应版本号的tag。同时,release分支也会被合并到develop分支。在release分支活跃其间,develop分支也一直处于Open状态。Release分支上的内容代表当前版本在发布之前的准备工作,develop分支上的内容则代表下一个版本的开发工作,两者是可以并行展开的。Hotfix分支Hotfix分支不从是develop分支创建出来的,而是直接根据master分支创建得到的,其目的是为了给运行在生产环境中的系统快速提供补丁,同时确保不会给正在其他上分支进行的工作造成影响。当hotfix分支上的工作完成以后,可以合并到master分支和develop分支,以及当前的release分支。如果有版本的更新,也可以为master分支打上相应的tag。上面提到的所有分支,从稳定性的角度来说,每一种分支都处在各自不同的层次。如果当前分支的代码达到了更加稳定的水平,那它就可以向更稳定的分支进行合并了。如何工作?Vincent Driessen不仅定义了Gitflow的工作流程,还提供了一个相应的命令行工具git-flow,可以简化我们在执行Gitflow工作流时,对每一个分支的各种繁琐的操作。它实际上是对Git命令行的封装。接下来,我们就利用这个工具演示一下整个Gitflow工作流。如果你使用的是Hello Git的实验环境,那么git-flow已经提前安装好了,否则需要自己手动安装。第一步,在服务器上建立一个远程Git库,供所有人使用:$ ssh git@my-git-remote git> create test-gitflow Initialized empty Git repository in /home/git/test-gitflow.git/ git> exit Connection to my-git-remote closed.第二步,William把远程库克隆到本地:$ git clone git@my-git-remote:~/test-gitflow.git Cloning into 'test-gitflow'... warning: You appear to have cloned an empty repository. cd test-gitflow/并生成项目的初始提交:$ touch README $ git add README $ git commit -m 'Initial commit' [master (root-commit) db64af2] Initial commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README然后把它推送到远程:$ git push Counting objects: 3, done. Writing objects: 100% (3/3), 208 bytes | 17.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0) To my-git-remote:~/test-gitflow.git * [new branch] master -> master第三步,William在他本地的工作目录下执行git flow init命令,对本地库进行初始化:$ git flow init Which branch should be used for bringing forth production releases? - master Branch name for production releases: [master] Branch name for "next release" development: [develop] How to name your supporting branch prefixes? Feature branches? [feature/] Bugfix branches? [bugfix/] Release branches? [release/] Hotfix branches? [hotfix/] Support branches? [support/] Version tag prefix? [] Hooks and filters directory? [/root/test-gitflow/.git/hooks] git-flow的初始化过程包含一系列问答环节,主要涉及各个分支的名称以及名称前缀的配置。如果我们选择默认配置,就可以一直按回车键直到命令执行完毕。这个时候,我们会发现本地除了master分支以外,还会多出一个develop分支。并且,git-flow已经替我们把当前分支切换到了develop分支:$ git branch * develop master第四步,William开始新特性xyz的开发,执行git flow feature start命令,并传入特性的名称:$ git flow feature start xyz Switched to a new branch 'feature/xyz' Summary of actions: - A new branch 'feature/xyz' was created, based on 'develop' - You are now on branch 'feature/xyz' Now, start committing on your feature. When done, use: git flow feature finish xyzgit-flow会自动为我们从develop分支创建出一个名叫feature/xyz的分支,并把当前分支切换到该分支。现在,William可以在feature/xyz分支上进行新特性的开发,并生成相应的提交记录了:$ vi README $ cat README Git workflows * Gitflow workflow $ git commit -am 'Working on feature/xyz' [feature/xyz 48d92a9] Working on feature/xyz 1 file changed, 2 insertions(+)然后再把提交记录推送到远程,可以作为当前工作的备份,也可以让其他人访问到他的工作,还可以通过Pull Request发起讨论:$ git push origin feature/xyz Counting objects: 3, done. Writing objects: 100% (3/3), 269 bytes | 24.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0) To my-git-remote:~/test-gitflow.git * [new branch] feature/xyz -> feature/xyz第五步,Nicole接到了一个hotfix的任务,她和William一样,也把远程库克隆到本地,并利用git flow init命令对本地库进行了初始化,然后执行git flow hotfix start命令,并传入hotfix的名称:$ git flow hotfix start 123 Switched to a new branch 'hotfix/123' Summary of actions: - A new branch 'hotfix/123' was created, based on 'master' - You are now on branch 'hotfix/123' Follow-up actions: - Start committing your hot fixes - Bump the version number now! - When done, run: git flow hotfix finish '123'git-flow会自动为我们直接从master分支创建出一个名叫hotfix/123的分支,并把当前分支切换到该分支。现在,Nicole可以在hotfix/123分支上进行hotfix的开发,并生成相应的提交记录了:$ touch LICENSE $ git add LICENSE $ git commit -m 'Working on hotfix/123' [hotfix/123 1a70748] Working on hotfix/123 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 LICENSE然后再把提交记录推送到远程,可以作为当前工作的备份,也可以让其他人访问到她的工作,还可以通过Pull Request发起讨论:$ git push origin hotfix/123 Counting objects: 2, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (2/2), 245 bytes | 27.00 KiB/s, done. Total 2 (delta 0), reused 0 (delta 0) To my-git-remote:~/test-gitflow.git * [new branch] hotfix/123 -> hotfix/123当hotfix的开发工作结束以后,再执行git flow hotfix finish命令,并传入hotfix的名称:$ git flow hotfix finish 123 Switched to branch 'master' Your branch is up to date with 'origin/master'. Merge made by the 'recursive' strategy. LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 LICENSE Switched to branch 'develop' Merge made by the 'recursive' strategy. LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 LICENSE To my-git-remote:~/test-gitflow.git - [deleted] hotfix/123 Deleted branch hotfix/123 (was 1a70748). Summary of actions: - Hotfix branch 'hotfix/123' has been merged into 'master' - The hotfix was tagged '123' - Hotfix tag '123' has been back-merged into 'develop' - Hotfix branch 'hotfix/123' has been locally deleted; it has been remotely deleted from 'origin' - You are now on branch 'develop'git-flow会自动为我们把hotfix/123分支上的工作合并到master分支和develop分支,并给master分支加上hotfix的tag,然后再把hotfix分支分别从本地库和远程库里删掉,最后再把当前分支切换到develop。第六步,William的新特性开发工作完成了,执行git flow feature finish命令,并传入特性的名称:$ git flow feature finish xyz Switched to branch 'develop' Updating db64af2..48d92a9 Fast-forward README | 2 ++ 1 file changed, 2 insertions(+) To my-git-remote:~/test-gitflow.git - [deleted] feature/xyz Deleted branch feature/xyz (was 48d92a9). Summary of actions: - The feature branch 'feature/xyz' was merged into 'develop' - Feature branch 'feature/xyz' has been locally deleted; it has been remotely deleted from 'origin' - You are now on branch 'develop'git-flow会自动为我们把分支feature/xyz上的工作合并到develop分支,并把该分支分别从本地库和远程库里删掉,然后再把当前分支切换到develop。第七步,当前版本abc即将要发布了,William执行git flow release start命令,并传入版本号:$ git flow release start abc Switched to a new branch 'release/abc' Summary of actions: - A new branch 'release/abc' was created, based on 'develop' - You are now on branch 'release/abc' Follow-up actions: - Bump the version number now! - Start committing last-minute fixes in preparing your release - When done, run: git flow release finish 'abc'git-flow会根据develop分支自动为我们创建出一个名叫release/abc的分支,专门用于发布准备,然后把当前分支切换到该分支。release/abc分支一旦创建,就不能再往里面添加新特性了,但可以继续做一些bug修复的工作:$ vi README $ cat README Git workflows * Gitflow workflow * ...生成相应的提交记录:$ git commit -am 'Working on release/abc' [release/abc 79f1e5e] Working on release/abc 1 file changed, 1 insertion(+)然后推送到远程:$ git push origin release/abc Counting objects: 6, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (6/6), 510 bytes | 19.00 KiB/s, done. Total 6 (delta 0), reused 0 (delta 0) To my-git-remote:~/test-gitflow.git * [new branch] release/abc -> release/abc最后,发布工作结束,执行git flow release命令,并传入版本号: $ git flow release finish abc Switched to branch 'master' Your branch is up to date with 'origin/master'. Merge made by the 'recursive' strategy. README | 3 +++ 1 file changed, 3 insertions(+) Already on 'master' Your branch is ahead of 'origin/master' by 3 commits. (use "git push" to publish your local commits) Switched to branch 'develop' Merge made by the 'recursive' strategy. README | 1 + 1 file changed, 1 insertion(+) To my-git-remote:~/test-gitflow.git - [deleted] release/abc Deleted branch release/abc (was 79f1e5e). Summary of actions: - Release branch 'release/abc' has been merged into 'master' - The release was tagged 'abc' - Release tag 'abc' has been back-merged into 'develop' - Release branch 'release/abc' has been locally deleted; it has been remotely deleted from 'origin' - You are now on branch 'develop'git-flow会自动为我们把release/abc分支上的工作合并到master分支和develop分支,并在master分支上为当前版本加上tag。然后把release分支分别从本地库和远程库里删掉,最后再切换回develop分支,一个版本发布周期就结束了。
2022年09月07日
63 阅读
0 评论
0 点赞
2022-08-21
ref 和 React中的 DOM 操作
setState在 React.js 当中你基本不需要和 DOM 直接打交道。React.js 提供了一系列的 on* 方法帮助我们进行事件监听,所以 React.js 当中不需要直接调用 addEventListener 的 DOM API;以前我们通过手动 DOM 操作进行页面更新(例如借助 jQuery),而在 React.js 当中可以直接通过 setState 的方式重新渲染组件,渲染的时候可以把新的 props 传递给子组件,从而达到页面更新的效果。jQueryReact.js 这种重新渲染的机制帮助我们免除了绝大部分的 DOM 更新操作,也让类似于 jQuery 这种以封装 DOM 操作为主的第三方的库从我们的开发工具链中删除。ref但是 React.js 并不能完全满足所有 DOM 操作需求,有些时候我们还是需要和 DOM 打交道。比如说你想进入页面以后自动 focus 到某个输入框,你需要调用 input.focus() 的 DOM API,比如说你想动态获取某个 DOM 元素的尺寸来做后续的动画,等等。React.js 当中提供了 ref 属性来帮助我们获取已经挂载的元素的 DOM 节点,你可以给某个 JSX 元素加上 ref属性:class AutoFocusInput extends Component { componentDidMount () { this.input.focus() } render () { return ( <input ref= /> ) } } ReactDOM.render( <AutoFocusInput />, document.getElementById('root') )可以看到我们给 input 元素加了一个 ref 属性,这个属性值是一个函数。当 input 元素在页面上挂载完成以后,React.js 就会调用这个函数,并且把这个挂载以后的 DOM 节点传给这个函数。在函数中我们把这个 DOM 元素设置为组件实例的一个属性,这样以后我们就可以通过 this.input 获取到这个 DOM 元素。然后我们就可以在 componentDidMount 中使用这个 DOM 元素,并且调用 this.input.focus() 的 DOM API。整体就达到了页面加载完成就自动 focus 到输入框的功能(大家可以注意到我们用上了 componentDidMount 这个组件生命周期)。我们可以给任意代表 HTML 元素标签加上 ref 从而获取到它 DOM 元素然后调用 DOM API。但是记住一个原则:能不用 ref 就不用。特别是要避免用 ref 来做 React.js 本来就可以帮助你做到的页面自动更新的操作和事件监听。多余的 DOM 操作其实是代码里面的“噪音”,不利于我们理解和维护。顺带一提的是,其实可以给组件标签也加上 ref ,例如:<Clock ref= />这样你获取到的是这个 Clock 组件在 React.js 内部初始化的实例。但这并不是什么常用的做法,而且也并不建议这么做,所以这里就简单提及,有兴趣的朋友可以自己学习探索。
2022年08月21日
36 阅读
0 评论
0 点赞
2022-08-21
React中使用 JSX 描述 UI 信息
先通过一个简单的例子讲解 React.js 描述页面 UI 的方式。把 src/index.js 中的代码改成:import React, from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( <div> <h1>标题1</h1> </div> ) } } ReactDOM.render( <Header />, document.getElementById('root') )我们在文件头部从 react 的包当中引入了 React 和 React.js 的组件父类 Component。记住,只要你要写 React.js 组件,那么就必须要引入这两个东西。ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。你可以发现它是从 react-dom 中引入的,而不是从 react 引入。有些朋友可能会疑惑,为什么不把这些东西都包含在 react 包当中呢?我们稍后会回答这个问题。接下来的代码你看起来会比较熟悉,但又会有点陌生。你看其实它跟我们前几节里面讲的内容其实很类似,一个组件继承 Component 类,有一个 render 方法,并且把这个组件的 HTML 结构返回;这里 return 的东西就比较奇怪了,它并不是一个字符串,看起来像是纯 HTML 代码写在 JavaScript 代码里面。你也许会说,这不就有语法错误了么?这完全不是合法的 JavaScript 代码。这种看起来“在 JavaScript 写的标签的”语法叫 JSX。JSX 原理为了让大家深刻理解 JSX 的含义。有必要简单介绍了一下 JSX 稍微底层的运作原理,这样大家可以更加深刻理解 JSX 到底是什么东西,为什么要有这种语法,它是经过怎么样的转化变成页面的元素的。思考一个问题:如何用 JavaScript 对象来表现一个 DOM 元素的结构,举个例子:<div class='box' id='content'> <div class='title'>Hello</div> <button>Click</button> </div>每个 DOM 元素的结构都可以用 JavaScript 的对象来表示。你会发现一个 DOM 元素包含的信息其实只有三个:标签名,属性,子元素。所以其实上面这个 HTML 所有的信息我们都可以用合法的 JavaScript 对象来表示:{ tag: 'div', attrs: , children: [ { tag: 'div', arrts: , children: ['Hello'] }, { tag: 'button', attrs: null, children: ['Click'] } ] }你会发现,HTML 的信息和 JavaScript 所包含的结构和信息其实是一样的,我们可以用 JavaScript 对象来描述所有能用 HTML 表示的 UI 信息。但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。上面的代码:import React, from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( <div> <h1 className='title'>标题1</h1> </div> ) } } ReactDOM.render( <Header />, document.getElementById('root') )经过编译以后会变成:import React, from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( React.createElement( "div", null, React.createElement( "h1", , "标题1" ) ) ) } } ReactDOM.render( React.createElement(Header, null), document.getElementById('root') );React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等。这样的代码就是合法的 JavaScript 代码了。所以使用 React 和 JSX 的时候一定要经过编译的过程。这里再重复一遍:所谓的 JSX 其实就是 JavaScript 对象。每当在 JavaScript 代码中看到这种 JSX 结构的时候,脑子里面就可以自动做转化,这样对你理解 React.js 的组件写法很有好处。有了这个表示 HTML 结构和信息的对象以后,就可以拿去构造真正的 DOM 元素,然后把这个 DOM 元素塞到页面上。这也是我们最后一段代码中 ReactDOM.render 所干的事情:ReactDOM.render( <Header />, document.getElementById('root') )ReactDOM.render 功能就是把组件渲染并且构造 DOM 树,然后插入到页面上某个特定的元素上(在这里是 id 为 root 的 div 元素)。所以可以总结一下从 JSX 到页面到底经过了什么样的过程: 有些同学可能会问,为什么不直接从 JSX 直接渲染构造 DOM 结构,而是要经过中间这么一层呢?第一个原因是,当我们拿到一个表示 UI 的结构和信息的对象以后,不一定会把元素渲染到浏览器的普通页面上,我们有可能把这个结构渲染到 canvas 上,或者是手机 App 上。所以这也是为什么会要把 react-dom 单独抽离出来的原因,可以想象有一个叫 react-canvas 可以帮我们把 UI 渲染到 canvas 上,或者是有一个叫 react-app 可以帮我们把它转换成原生的 App(实际上这玩意叫 ReactNative)。第二个原因是,有了这样一个对象。当数据变化,需要更新组件的时候,就可以用比较快的算法操作这个 JavaScript 对象,而不用直接操作页面上的 DOM,这样可以尽量少的减少浏览器重排,极大地优化性能。总结JSX 是 JavaScript 语言的一种语法扩展,长得像 HTML,但并不是 HTML。React.js 可以用 JSX 来描述你的组件长什么样的。JSX 在编译的时候会变成相应的 JavaScript 对象描述。react-dom 负责把这个用来描述 UI 信息的 JavaScript 对象变成 DOM 元素,并且渲染到页面上。
2022年08月21日
48 阅读
0 评论
0 点赞
1
2
3
4
...
6