React的井字過三關(2)

来源:http://www.cnblogs.com/djtao/archive/2016/12/23/6213473.html
-Advertisement-
Play Games

這篇筆記是官方教程的延續筆記,所有代碼基於第一篇筆記的結尾代碼。旨在解決教程後面提出的五個問題。 一 . 用(X,Y)來取代原有的數字坐標 原來的數字坐標是這樣的: 現在的要求是把原來的代碼坐標改為二維坐標系的表達形式,並且在歷史記錄面板中打出轉換後的坐標。 如果只是為了輸出好看。只需要寫一個轉換方 ...


這篇筆記是官方教程的延續筆記,所有代碼基於第一篇筆記的結尾代碼。旨在解決教程後面提出的五個問題。


一 . 用(X,Y)來取代原有的數字坐標

原來的數字坐標是這樣的:

現在的要求是把原來的代碼坐標改為二維坐標系的表達形式,並且在歷史記錄面板中打出轉換後的坐標。

如果只是為了輸出好看。只需要寫一個轉換方法,這些在頂層的Game組件中實現就夠了。而不需要修改原來的代碼核心。

很自然想到用switch語句,其實都行,怎麼喜歡怎麼寫。

convert:function(i){//i是一維坐標
                var x=Math.floor(i/3)+1;
                var y=0;
                if((i+1)%3===0){
                    y=3;
                }else{
                    y=(i+1)%3;
                }
                return [x,y];
            },

調用就直接在渲染前調用。把它存到state裡面去。

接下來問題是把這個方法怎麼獲取參數i,最直接的辦法是寫一個全局變數,然後從handleClick裡面拿到i。但是全局變數不環保。或許再設一個頂層狀態lastLocation是個不錯的選擇,渲染隊列是一個數組,姑且稱之為historyLocation

根據React的價值觀,能根據其它原有狀態計算出來的東西,就不需要設置額外的狀態。但如果思路不明確,就在這裡先寫出來。

回退步驟的本質

在上一篇筆記最後,官方文檔沒有說清楚一個問題。就是狀態中的stepNumber是什麼。現在再次遇到,需要寫明白給自己提個醒。

回退步驟

每走一步,history狀態就會在最後追加一個最新版本。

stepNumber實際上是一個指針,根據這個指針,發送history狀態的版本(可能是舊的,也可能是最新的),用它來調控渲染狀態。

點擊回退步驟,就是把指針往前挪。

通過暫存器刷新狀態

如果沒有任何其它操作直接觸發handleClick,stepNumber指針直接指向最新的版本。

如果在回退步驟上發生了handleClick,那麼將發生以下事情:

  • 根據指針生成若幹個狀態暫存器,這個暫存器是獨立且不具備任何效力的,拋開環境來看就是普通變數;
  • 追加新的狀態到暫存器;
  • 再用這個暫存器替換掉原有的狀態,在此,回退步驟列表將被刷新。

究竟有幾個狀態暫存器?在這裡就兩個:

  • 一個儲存history的當前指針版本:

javascript handleClick:function(i){ //history指針版本暫存 var history=this.state.history.slice(0,this.state.stepNumber+1); var lastHistory=history[this.state.stepNumber]; var squares=lastHistory.squares.slice();

  • 一個儲存二維坐標軸版本:

javascript /**上接handleClick***/ //二維坐標數據暫存器 var historyLocation=this.state.historyLocation; historyLocation=historyLocation.slice(0,this.state.stepNumber); historyLocation.push(this.convert(i));//刷新狀態暫存器

通過狀態暫存器,既可以在指針位置重新開始,又能在屏幕上保留歷史步驟數方便查看,即實現官方文檔所謂的“時間旅行”。

在這裡意識到狀態暫存器其實應該只有一個是最好的。

judgeWinner的完善

按理來說,判斷勝負的函數judgeWinner應該是在組件的裡面,這樣比較環保,也可以更好地調用組件中的狀態。

現在就把它拿進來。直接生成渲染方法中的status。並且添加一個和棋的判斷。實現思路是調用指針版本的history狀態數組。然後遍歷這個數組對象,如果發現9個位置全部不為null,就返回和棋。

放進來之後,參數也沒有必要再寫,全部改為state相關的表達。

這樣一來就沒辦法用原來的禁著點判斷了。因為不好判斷棋局是否完結。在此根據返回的結果進行indexOf判斷,留下的坑後面填。

所以到此為止,Game組件應該是:

getInitialState:function(){
                return {
                    history:[
                        {squares:Array(9).fill(null)}
                    ],
                    turnToX:true,
                    stepNumber:0,
                    historyLocation:[]
                }
            },
            // 判斷勝負的函數,窮舉法
            judgeWinner:function(){
                var history=this.state.history;
                var lastHistory=history[this.state.stepNumber];
                var squares=lastHistory.squares;
                var win=[
                    [0,1,2],
                    [0,3,6],
                    [0,4,8],
                    [1,4,7],
                    [2,5,8],
                    [2,4,6],
                    [3,4,5],
                    [6,7,8]
                ];
                for(var i=0;i<win.length;i++){
                    var winCase=win[i];
                    if(squares[winCase[0]]&&squares[winCase[0]]===squares[winCase[1]]&&squares[winCase[1]]===squares[winCase[2]]){//三子一線
                        return ('獲勝方是:'+squares[winCase[0]]);//返回勝利一方的標識
                    }
                }

                // 定義當前棋盤上被填滿的格子數量
                var fill=lastHistory.squares.filter((item)=>item!=null).length;
                if(fill==9){
                    return '和棋!'
                }else{
                    var player=this.state.turnToX?'X':'O';
                    return ('輪到'+player+'走');
                }
            },
            // 點擊事件是把暫存器的內容存為真正的狀態。
            handleClick:function(i){
                //歷史squares暫存
                var history=this.state.history;
                history=history.slice(0,this.state.stepNumber+1);

                var lastHistory=history[this.state.stepNumber];
                var winner=this.judgeWinner();
                var squares=lastHistory.squares.slice();

                //歷史步驟暫存器
                var historyLocation=this.state.historyLocation;
                historyLocation=historyLocation.slice(0,this.state.stepNumber);
                historyLocation.push(this.convert(i));

                if((winner.indexOf('輪到')==-1)||squares[i]){
                    return false;
                    //勝負已分或是已有子則不可落子。indexOf這是一種暫時的非主流寫法
                }
                // 判斷下棋的輪換色
                squares[i]=this.state.turnToX?'X':'O';

                this.setState({
                    history:history.concat([{squares:squares}]),
                    turnToX:!this.state.turnToX,
                    stepNumber:history.length,
                    historyLocation:historyLocation
                });

            },
            // 歷史步驟跳轉是把狀態還原到某個時間點,狀態根據stepNumber呈現內容,但不會改變最終狀態。
            jumpTo:function(step){
                this.setState({
                    stepNumber:step,
                    turnToX:step%2?false:true
                });
            },
            // 坐標轉換函數
            convert:function(i){
                var x=Math.floor(i/3)+1;
                var y=0;
                if((i+1)%3===0){
                    y=3;
                }else{
                    y=(i+1)%3;
                }
                return [x,y];
            },

            render:function(){
                var history=this.state.history.slice();
                var lastHistory=history[this.state.stepNumber];//渲染方法遵照的是stepNumber而不是最後一步
                var status=this.judgeWinner();//獲勝狀態
                var arr=[];
                var location=this.state.historyLocation.slice();
                var _this=this;
                history.map(function(step,move){
                    var content='';
                    if(move!==0){
                        content='Move#'+move+':'+'('+location[move-1][0]+','+location[move-1][1]+')';
                        //console.log(location[move-1])
                    }else{
                        content='游戲開始~';
                    }
                    arr.push(<li key={move}><a onClick={()=>_this.jumpTo(move)} href="javascript:;">{content}</a></li>);
                });

                return (
                    <div className="game">
                        <Board lastHistory={lastHistory.squares} onClick={(i)=>this.handleClick(i)} />
                        <div className="info">
                            <div>{status}</div>
                            <ol>{arr}</ol>
                        </div>
                    </div>
                );
            }
        });

二. 對右方的被選中的當前記錄進行加粗顯示

樣式這種東西,就交給CSS來實現吧!

.back-active{
    font-weight: bolder;
    color: #EE9611;
}

簡單實現

思路就是加個class。操作方法是jumpTo。

問題在於,當前的jumpTo已經給定了參數。為了拿到e.target還得在改改。

jumpTo在這個問題中實際上要完成兩件事,刪除所有a的class中可能.back-active;給當前對象加個.back-active

有了e.target,就能用DOM找到該有的內容。比方說e.target.parentNode.parentNode.childNode就代表所有點擊對象上層的所有li集合

然而這個集合不是一個數組啊,不能map。只能用for迴圈。根據查到的性能資料,for迴圈還真的比其它迭代方法高。

jumpTo:function(e,step){
                // console.log(e.target)
                var aList=e.target.parentNode.parentNode.childNodes;
                for(var i=0;i<aList.length;i++){
                    var item=aList[i];
                    if(item.childNodes[0].classList.contains('back-active')){
                        item.childNodes[0].classList.remove('back-active');
                    }
                }

                e.target.classList.add('back-active');
                this.setState({
                    stepNumber:step,
                    turnToX:step%2?false:true
                });
            },

這個問題就算解決了。

點擊實現高亮當前的步驟

其實就個人理解來說,不應該再對handleClick再加什麼高亮當前步驟的操作了。當前步驟明擺著就是最後一個。縱觀就當前的代碼實現,用戶體驗已經很好了,進程不會亂七八糟,用戶還可以很清晰地知道指針指向的還原點。還高亮什麼?

但是假設老闆就要求點擊按鈕時最後一步也高亮,那也只能照做。

顯然,這個應該放渲染前判斷:如果這是狀態最後一步(是this.state.history.length-1,不是this.state.stepNumber),那麼就高亮。反正樣式也不要錢,就多寫一個樣式給它。

.process-active{
    font-weight: bolder;
    color: green;
}/*寫在.back-active之後,方便覆蓋*/

這樣,渲染前的處理里還得多加一個判斷:是最後一個就加.process-active——這段獲取歷史步驟的方法已經變得太長了。為了閱讀方便把它放一個getMoveList函數里吧。

...
    getMoveList:function(){
                var history=this.state.history.slice();
                var arr=[];
                var location=this.state.historyLocation.slice();
                var _this=this;
                history.map(function(step,move){
                    var content='';
                    if(move!==0){
                        content='Move#'+move+':'+'('+location[move-1][0]+','+location[move-1][1]+')';
                        //console.log(location[move-1])
                    }else{
                        content='游戲開始~';
                    }
                    //console.log(_this.state.stepNumber)
                    if(arr.length==_this.state.history.length-1){
                        arr.push(<li key={move}><a className="process-active" onClick={(e)=>_this.jumpTo(e,move)} href="javascript:;">{content}</a></li>);
                    }else{
                        arr.push(<li key={move}><a onClick={(e)=>_this.jumpTo(e,move)} href="javascript:;">{content}</a></li>);
                    }
                });
                return arr;
            },
              ...

這樣,第二個問題就解決了。


三. 用兩個迴圈重寫Board組件,替代掉原來生硬的代碼結構

因為只有9宮格,復用也毫無意義。所以寫死也問題不大。

想到的處理方法就是這樣了。

var Board=React.createClass({
            renderSquare:function(i){
                return <Square key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
            },
            getSquare:function(rows){
                var index=rows*3;
                var arr=[];
                for(var i=index;i<index+3;i++){
                    arr.push(this.renderSquare(i));
                }
                return arr;
            },
            getBoardRow:function(){
                var arr=[];
                for(var i=0;i<3;i++){
                    arr.push(<div key={i} className="board-row">{this.getSquare(i)}</div>)
                }
                return arr;
            },
            render:function(){
                return (
                    <div clasName="board">
                        <div className="status"></div>
                        {this.getBoardRow()}
                    </div>
                );
            }
        });

四. 對你的歷史記錄進行升降序排列

接下來又回到Game組件上面來了。在渲染結構中加一個按鈕。點擊,觸發事件。大概就是這樣。

<input type="button" value={this.state.isAscending} onClick={this.sortToggle} />

因為預設就是降序,因此這個toggleSort只做一件事:切換開關。至於是升序還是降序,又要多設置一個開關狀態(isAscending,初始為降序排列)。

根據這個狀態,getMoveList方法決定生成數組後是直接return還是return arr.reverse()

sortToggle:function(){
    this.setState(function(prevState){
        var sort=prevState.isAscending;
        var content='';
        if(sort=='升序排列'){
            content='降序排列';
        }else{
            content='升序排列'
        }
        return {
            isAscending:content
        }
    })
},

然後再到getMoveList方法的最後,加一個判斷:

.....
                if(this.state.isAscending=='降序排列'){
                    return arr;
                }else{
                    return arr.reverse();
                }
}

嗯,第四個問題解決。


五. 高亮顯示獲勝的結果

擴展judgeWinner的功能

judgeWinner判斷函數已經被納入到了組件中,而且只是返回一個status,現在要擴展它的功能,把勝負情況反應出來。

在原來的判斷勝負函數裡面加個console就可以知道勝負手了。

for(var i=0;i<win.length;i++){
                    var winCase=win[i];
                    if(squares[winCase[0]]&&squares[winCase[0]]===squares[winCase[1]]&&squares[winCase[1]]===squares[winCase[2]]){//三子一線
                        console.log(winCase)//這裡的winCase就是勝負手
                        return ('獲勝方是:'+squares[winCase[0]]);//返回勝利一方的標識
                    }
                }

既然是擴展功能,再來大改就沒必要了。可以考慮把return一個字元串改為return一個數組。第0項放status,第1項放winCase或null

有了這個方法,handleClick中那種非主流寫法就可以刪掉了。

                var winner=this.judgeWinner();
                if(winner[1]||squares[i]){
                    return false;
                    //勝負已分或已有子:則不可落子。
                }

傳遞勝負手

再寫一個 CSS

.win-case{
    color: red;
}

現在可以通過winner[1]拿到勝負手了。它是一個數組。現在就得在Game組件render方法裡面在var一個數據。通過props傳下去,傳到Board組件之後,做一個判斷,看看參數是否符合點位條件,是的話就繼續把class名傳下去。

/********<Game/>*******/
render:function{
    var winCase=this.judgeWinner()[1];//獲勝狀態
    return (
        <div className="game">
        <Board winCase={winCase} lastHistory={lastHistory.squares} onClick={(i)=>this.handleClick(i)} />
...
/**********<Board/>***********/
renderSquare:function(i){
                if(this.props.winCase){
                    for(var j=0;j<this.props.winCase.length;j++){
                        if(this.props.winCase[j]==i){
                            return <Square winCase="win-case" key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
                        }
                    }
                }
                return <Square key={i} value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
            },
...
/************<square/>******************/
  var Square=React.createClass({
              render:function(){
                  if(this.props.winCase){
                      return (
                          <button className={"square "+this.props.winCase} onClick={() => this.props.onClick()}>{this.props.value}</button>
                      );
                  }else{
                      return (
                          <button className="square" onClick={() => this.props.onClick()}>{this.props.value}</button>
                      );
                  }
              }
        });
。。。。。。

那麼第五個問題就完成了。


結束

現在,功能已經完備。思路已經理清。再看之前留下的大坑:historyLocation

之前提到過,historyLocation是可以和history相互計算得出的。historyLocation只用於展示步數。組件的判斷引擎是用相容history的一維數組實現的,為了後期實現AI書寫方便,也顯然是history更好。還是刪掉這個historyLocation。

不好之處在於每次都要多一點計算,相比React每次動輒重新渲染,這點計算也不是很多。

寫一個根據history獲取坐標的方法,拿到坐標之後在轉換為二維坐標,這本質上是一件事,所以convert方法也可以刪掉了。

getRectangular:function(){
                var arr=[];
                var mainArr=this.state.history.slice();
                for(var i=0;i<mainArr.length;i++){
                    if(i<mainArr.length-1){
                        for(var j=0;j<9;j++){
                            //比較mainArr[i].squares和mainArr[i+1].squares[j])不同,拿到坐標值
                            if(mainArr[i].squares[j]!==mainArr[i+1].squares[j]){
                                arr.push(j);
                            }
                        }
                    }
                }
                var result=[]
                for(var i=0;i<arr.length;i++){
                    var x=Math.floor(arr[i]/3)+1;
                    var y=(arr[i]+1)%3===0?3:(arr[i]+1)%3;
                    result.push([x,y])
                }
                return result;
            },

可以再自己優化下演算法和css,或者加個重置button之類的。把不必要的變數刪掉。

效果:

下一篇筆記將解決最大的一個坑。


附錄:組件代碼

var Game=React.createClass({
            getInitialState:function(){
                return {
                    history:[
                        {squares:Array(9).fill(null)}
                    ],
                    turnToX:true,
                    stepNumber:0,
                    isAscending:'降序排列'
                }
            },
            // 判斷勝負的函數,窮舉法,返回一個數組,如果勝負已定,第二個元素就是勝負手
            judgeWinner:function(){
                var lastHistory=this.state.history[this.state.stepNumber];//獲取指針版本
                var squares=lastHistory.squares;
                var win=[
                    [0,1,2],
                    [0,3,6],
                    [0,4,8],
                    [1,4,7],
                    [2,5,8],
                    [2,4,6],
                    [3,4,5],
                    [6,7,8]
                ];
                for(var i=0;i<win.length;i++){
                    var winCase=win[i];
                    if(squares[winCase[0]]
                      &&squares[winCase[0]]===squares[winCase[1]]
                      &&squares[winCase[1]]===squares[winCase[2]]){//三子一線
                        return [('獲勝方是:'+squares[winCase[0]]),winCase];//返回一個status和勝負情況
                    }
                }
                // 獲取當前棋盤上被填滿的格子數量
                var fill=lastHistory.squares.filter((item)=>item!=null).length;
                if(fill==9){
                    return ['和棋!',null];
                }else{
                    var player=this.state.turnToX?'X':'O';
                    return [('輪到'+player+'走'),null];
                }
            },

            // 點擊事件是把暫存器的內容存為真正的狀態。
            handleClick:function(i){
                //history指針版本暫存
                var history=this.state.history.slice(0,this.state.stepNumber+1);
                var lastHistory=history[this.state.stepNumber];
                var squares=lastHistory.squares.slice();


                var winner=this.judgeWinner();
                if(winner[1]||squares[i]){
                    return false;
                    //勝負已分或是已有子則不可落子。
                }
                // 判斷下棋的輪換色
                squares[i]=this.state.turnToX?'X':'O';
                //覆蓋掉原來的狀態!
                this.setState({
                    history:history.concat([{squares:squares}]),
                    turnToX:!this.state.turnToX,
                    stepNumber:history.length
                });

            },
            // 轉化history狀態為各個版本的平面直角坐標
            getRectangular:function(){
                var arr=[];
                var mainArr=this.state.history.slice();
                for(var i=0;i<mainArr.length;i++){
                    if(i<mainArr.length-1){
                        for(var j=0;j<9;j++){
                            //比較mainArr[i].squares和mainArr[i+1].squares[j])不同,拿到坐標值
                            if(mainArr[i].squares[j]!==mainArr[i+1].squares[j]){
                                arr.push(j);
                            }
                        }
                    }
                }
                var result=[]
                for(var i=0;i<arr.length;i++){
                    var x=Math.floor(arr[i]/3)+1;
                    var y=(arr[i]+1)%3===0?3:(arr[i]+1)%3;
                    result.push([x,y])
                }
                return result;
            },
            // 歷史步驟跳轉是把狀態還原到某個時間點,狀態根據stepNumber呈現內容,但不會改變最終狀態。
            jumpTo:function(e,step){
                var aList=e.target.parentNode.parentNode.childNodes;
                for(var i=0;i<aList.length;i++){
                    var item=aList[i];
                    if(item.childNodes[0].classList.contains('back-active')){
                        item.childNodes[0].classList.remove('back-active');
                    }
                }
                
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Asp.Net MVC FilterAttribute特性、讀取xml反序列化、NLog實戰系列文章 首先新建一個MVC project。 一、NLog的配置。 作者:Jarosław Kowalski <[email protected]> 翻譯:CrazyCoder 原文:http://www ...
  • 背景:博主本是一位Windows桌面應用程式開發工程師,對網路通信一知半解。一日老婆逛完某寶,問:“為什麼他們知道我的地址呢,他們是怎麼獲取我的地址的呢?” 順著這個問題我們的探秘開始: 第一步:簡單的服務搭建 思路,通過HttpListener在本地搭建一個簡易的伺服器,開發程式為控制台介面,核心 ...
  • 筆者最近要負責有個項目工程網站的安裝進度過程,實現的效果要求大概如下圖所示 由於筆者沒有參與到資料庫的製作,得知他們這個項目設計工序的時候就一個開始日期的和完成日期,連整個項目的安裝結束時間都沒有簡直了。這裡公開一下我的資料庫 有點,總之就是說不出話的感覺。 之前筆者寫前臺table表綁定的時候一般 ...
  • 異常: 在可以調用 OLE 之前,必須將當前線程設置為單線程單元(STA)模式。請確保您的 Main 函數帶有 STAThreadAttribute 標記。 只有將調試器附加到該進程才會引發此異常。 方法1: 方法2: ...
  • 在我們編寫程式中,往往需要一些存儲過程,在LINQ to SQL中怎麼使用呢?也許比原來的更簡單些。下麵我們以NORTHWND.MDF資料庫中自帶的幾個存儲過程來理解一下。 1.標量返回 在資料庫中,有名為Customers Count By Region的存儲過程。該存儲過程返回顧客所在"WA"區 ...
  • 對象載入 延遲載入 在查詢某對象時,實際上你只查詢該對象。不會同時自動獲取這個對象。這就是延遲載入。 例如,您可能需要查看客戶數據和訂單數據。你最初不一定需要檢索與每個客戶有關的所有訂單數據。其優點是你可以使用延遲載入將額外信息的檢索操作延遲到你確實需要檢索它們時再進行。請看下麵的示例:檢索出來Cu ...
  • lpStatuss是一個 的指針類型實例,並包含SensorDust欄位 ...
  • 前言 馬上2016年就要過去了,時間可是真快啊。 上次寫完 Identity 系列之後,反響還不錯,所以本來打算寫一個 ASP.NET Core 中間件系列的,但是中間遇到了很多事情。首先是 NPOI 的移植工作,移植過後還有一些Bug需要修複,然後一個事情是一個有關於分散式架構中消息一致性的一個中 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...