React的井字過三關(1)

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

React的井字過三關(1) 本文系React官方教程的Tutorial: Intro To React的筆記。由筆者用ES5語法改寫。 在本篇筆記中,嘗試用React構建一個 可交互的 井字棋游戲。 開始 先佈局: status反映游戲信息。九宮格採用flex佈局。右側有一處游戲信息。 再把css ...


React的井字過三關(1)

本文系React官方教程的Tutorial: Intro To React的筆記。由筆者用ES5語法改寫。

在本篇筆記中,嘗試用React構建一個可交互的井字棋游戲。


開始

先佈局:

status反映游戲信息。九宮格採用flex佈局。右側有一處游戲信息。

<div id="container">
        <div class="game">
            <div class="board">
                <div class="status">Next player: X</div>
                <div class="board-row">
                    <button class="square"></button>
                    <button class="square"></button>
                    <button class="square"></button>
                </div>
                <div class="board-row">
                    <button class="square"></button>
                    <button class="square"></button>
                    <button class="square"></button>
                </div>
                <div class="board-row">
                    <button class="square"></button>
                    <button class="square"></button>
                    <button class="square"></button>
                </div>
            </div>
            <div class="info">
                <div></div>
                <ol></ol>
            </div>
        </div>
    </div>

再把css寫一下:

/*Simple CSS-Reset*/
*{
    margin:0;
    padding:0;
}

body{
  font: 30px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ul{
    list-style: none;
}

a{
    text-decoration: none;
}



ol, ul{
  padding-left: 30px;
}

/*major*/
#container{
    width: 500px;
    margin:0 auto;
}

.game{
  display: flex;
  flex-direction: row;
}

.status{
  margin-bottom: 20px;
  text-align: center;
}

.board-row:after{
  clear: both;
  content: "";
  display: table;
}

.square{
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 36px;
  font-weight: bold;
  line-height: 100px;
  height: 100px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 100px;
}

#container .square:focus {
  background: #ddd;
  outline: none;
}

.info {
  margin-left: 30px;
  font-size:20px;
}

基本效果:

接下來只需要考慮javascript實現就可以了。

整個應用分為三個組件:

  • Square(方塊)
  • Board(九宮格面板)
  • Game(整個游戲)

接下來就是把這個結構用React寫出來。

var Game=React.createClass({
            render:function(){
                return (
                    <div className="game">
                        <Board />
                        <div className="info">
                            <div></div>
                            <ol></ol>
                        </div>
                    </div>
                );
            }
        });

        var Board=React.createClass({
            renderSquare:function(i){
                return   <Square />
            },
            render:function(){
                return (
                    <div clasName="board">
                        <div className="status">Next player: X</div>
                        <div className="board-row">
                            {this.renderSquare(0)}
                            {this.renderSquare(1)}
                            {this.renderSquare(2)}
                        </div>
                        <div className="board-row">
                            {this.renderSquare(3)}
                            {this.renderSquare(4)}
                            {this.renderSquare(5)}
                        </div>
                        <div className="board-row">
                            {this.renderSquare(6)}
                            {this.renderSquare(7)}
                            {this.renderSquare(8)}
                        </div>
                    </div>
                );
            }
        });

        var Square=React.createClass({
            render:function(){
                return (
                    <button className="square"></button>
                );
            }
        });

        ReactDOM.render(
            <Game />,
            document.getElementById('container')
        );

通過props傳遞數據

現在嘗試從Board組件中傳遞一些數據給Square組件:

var Board=React.createClass({
            renderSquare:function(i){
                return   <Square value={i} />
            },
            ...

Square內部:

var Square=React.createClass({
            render:function(){
                return (
                    <button className="square">{this.props.value}</button>
                );
            }
        });

數字就被打上去了。


交互的組件

當點擊方塊時,打出“X”。

先把Square設置初始的state.value為null。當點擊小方框,觸發一個changeState方法。把當下的State改為X.

然後把渲染方法改為:

var Square=React.createClass({
    getInitialState:function(){
        return {
            value:null
        }
    },
    changeState:function(){
        this.setState({
            value:'X'
        })
    },
    render:function(){
        return (
            <button className="square" onClick={this.changeState}>{this.state.value}</button>
        );
    }
});

基本效果:

無論何時,this.setState只要被調用,組件將馬上更新並根據狀態渲染它的後代。


通過開發者工具看組件樹

插播一個廣告:React為開發者提供了適用於火狐及Chrome的擴展工具。有了它可以很方便看到你構建的組件庫。

當然Google商店現在得FQ才行。在安裝之後,勾選“允許訪問本地網址”,便可激活。


解除狀態

現在,井字棋已經有了個雛形。但是State被鎖定在每個單獨小的方塊中。

為了讓游戲能夠正常進行,還需要做一些事情:

  • 判斷勝負
  • XO的交替

為了判斷勝負,我們需要將9個方塊的value放到一塊。

你可能會想,為什麼Board組件為什麼不查詢每個組件的狀態併進行計算?——這在理論上是可行的。但是React不鼓勵這樣做——這樣導致代碼難讀,脆弱,變得難以重構。

相反,最好的解決方案就是把state放到Board組件上,而不是每個方塊里。Board組件可以告訴每個小方塊要顯示什麼——通過之前加索引值的方法。

當你先從各種各樣的子代中把它們的數據統一起來,那就把state放到它們的父級組件上吧!然後通過props把數據全部傳下去。子組件就會根據這些props同步地展示內容。

在React里,組件做不下去的時候,把state向上放是很常見的處理辦法。正好藉此機會來試一下:設置Board組件的狀態——為一個9個元素的數組(全部是null),以此對應九個方塊:

var Board=React.createClass({
    getInitialState:function(){
        return (
            squares:Array(9).fill(null),
        )
    },
  ...

到了後期,這個狀態可以指代一個棋局,比如這樣:

[
  'O', null, 'X',
  'X', 'X', 'O',
  'O', null, null,
]

然後把這個狀態數組分配到每個小方塊中(還記得renderSquare方法嗎?):

renderSquare:function(i){
    return   <Square value={this.state.squares[i]} />
},

再次把Square的組件改為{this.props.value}。現在需要改變點擊事件的方法。當點擊小方塊,通過回調props傳入到Square中,直接把Board組件state相應的值給改了:

return <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />

這裡的onClick不是一個事件。而是方塊組件的一個props。現在方塊組件Square接受到這個props方法,就把它綁定到真正的onClick上面:

<button className="square" onClick={() => this.props.onClick()}>{this.props.value}</button>

補白:ES6的箭頭函數

x => x * x

以上的意思是:

function (x) {
    return x * x;
}

箭頭函數相當於匿名函數,並且簡化了函數定義。

React在此引入箭頭函數處理的是this的問題。

如果不用箭頭函數寫是:

renderSquare:function(i){
    var _this=this;
    return   <Square onClick={function(){return _this.handleClick(i)}} value={this.state.squares[i]} />
},

選擇自己喜歡的就好。

現在根據就差定義面板組件中handleClick函數了。顯然點擊一下就刷新Board的狀態。以下兩種方法都可以。

handleClick:function(i){
    this.setState(function(prev){
        //console.log(prev.squares)
        var arr=prev.squares;
        arr.squares[i]='X';
        return {
            squares:prev.arr
        };
    })
},
handleClick:function(i){
    var squares=this.state.squares.slice();
    squares[i]='X';
    this.setState({
        squares:squares
    })
},          

把狀態往上放,使得每個小方框不再擁有自己的狀態。面板組件會分配props給他們。只要狀態改變,下麵的組件就會更新。


為什麼不突變的數據很重要(Why Immutability Is Important)

在handleClick裡面,用了一個slice()方法把原來的數組克隆出來。有效防止了數組被破壞。

“不突變的對象”這是一個重要的概念,值得React文檔重開一章來強調。

有兩種改變數據的辦法,一個是直接改變(突變,mutate),一種是存到一個變數裡面。二者的結果是相同,但是後者有額外的好處。

跟蹤變化

查找一個突變對象(mutate)的數據變化是非常麻煩的。 這就要求比較當前對象之前的副本,還要遍歷整個對象樹,比較每個變數和價值。 這個過程變得越來越複雜。

而確定一個不突變的對象的數據變化相當容易。 如果這個對象和之前相比不同,那麼數據就已改變了。就這麼簡單。

決定React何時重新渲染

最大的好處:在構建簡單純粹的組件時, 因為不突變的數據可以更容易地確定是否更改了,也有助於確定一個組件是否需要被重新渲染。


功能組件

回到之前的項目,現在你不再需要Square組件中的構造函數了。 事實上,對於一個簡單而無狀態的功能性組件類型,比如Square,一個渲染方法足夠了,它只乾一件事:根據上面傳下來的props來決定渲染什麼,怎麼渲染,完全沒必要再開一個擴展組件。

var Square=React.createClass({
    render:function(){
        return (
            <button className="square" onClick={() => this.props.onClick()}>{this.props.value}</button>
        );
    }
});

可以說這個square組件做到這裡就完結了。不用再理他了。


決定次序

目前這個App最大的問題就是整個游戲竟然只有X玩家,簡直不能忍,還能不能好好的OOXX了?

對這個App來說誰先走肯定是狀態。這個狀態決定handleClick渲染的是X還是O:

首先,我們定義X玩家先走。

var Board=React.createClass({
    getInitialState:function(){
        return {
            squares:Array(9).fill(null),
            turnToX:true//為ture時輪到X走
        }
    },
  ...

每點擊一次,將造成這個開關的輪換。

handleClick:function(i){
    var squares=this.state.squares.slice();
    squares[i]=this.state.turnToX?'X':'O';
    this.setState({
        squares:squares,
        turnToX:!this.state.turnToX
    })
},

現在棋是走起來了。


判斷勝負

鑒於井字棋很簡單,獲勝的最終畫面只有8個。所以判斷勝負用窮舉法就可以了。也就是說,當squares數組出現8個情況,就宣告勝者並終止游戲。這裡妨自己寫寫判斷勝負的引擎:

function judgeWinner(square){
    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[1]]&&squares[winCase[1]]==squares[winCase[2]]){//三子一線
            return squares(winCase[0]);//返回勝利一方的標識
        }
    }
    return false;
}

這個方法在Board渲染前執行就可以了。

...
render:function(){
    var winner=judgeWinner(this.state.squares);//每次渲染都判斷獲勝者
    var status='';
    if(winner!==null){
        status='獲勝方是:'+winner
    }else{
        var player=this.state.turnToX?'X':'O';
        status='輪到'+player+'走'
    }
    return (
        <div clasName="board">
            <div className="status">{status}</div>
      ...

好啦!現在已經把這游戲給做出來了。你可以在電腦上自己跟自己下井字棋,一個React新手,走到這一步已是winner。來看看效果吧~

什麼,真要完了嗎?還有一半的內容。


儲存歷史步數

現在我們嘗試做一個歷史步數管理。方便你悔棋或復盤(井字棋還得復盤?!)

每走一步,就刷新一次狀態,那就把這個狀態存到一個數組對象(比如history)中。調用這個歷史對象的是Game組件,要做這一步,就得把狀態進一步往上放(滿滿的都是套路啊)。

在Game當中設置狀態也是一個大工程。但是基本上和在Board里寫狀態差不多。

  • 首先,用一個history狀態存放每一步生成的squares數組。turnToX也提到Game組件中。
  • 找出最新的狀態history[history.length-1]lastHistory
  • 在handleClick方法中添加落子判斷:勝負已分或是已經落子則不響應。
  • 在Game渲染函數中寫好status,然後放到指定位置。
  • 把handleClick函數傳到Board組件去!
var Game=React.createClass({
            getInitialState:function(){
                return {
                    history:[
                        {squares:Array(9).fill(null)}
                    ],
                    turnToX:true
                }
            },
            handleClick:function(i){//這裡的i是棋盤的點位。
                var history=this.state.history;
                var lastHistory=history[history.length-1];
                var winner=judgeWinner(lastHistory.squares);
                var squares=lastHistory.squares.slice();

                if(winner||squares[i]){//如果勝負已分,或者該位置已經落子,則不會響應!
                    return false;
                }
                squares[i]=this.state.turnToX?'X':'O';//決定該位置是X還是O

                this.setState({
                    history:history.concat([{squares:squares}]),
                    turnToX:!this.state.turnToX
                });//然後把修改後的squares橋接到狀態中去
            },
            render:function(){
                var history=this.state.history;
                var lastHistory=history[history.length-1];
                var winner=judgeWinner(lastHistory.squares);

                var status='';
                if(winner){
                    status='獲勝方是'+winner;
                }else{
                    var player=this.state.turnToX?'X':'O';
                    status='輪到'+player+'走';
                }

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

那麼Board組件裡面的各種狀態完全不需要了,只保留render和renderSquare函數足矣。

var Board=React.createClass({
    renderSquare:function(i){
        return <Square value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
    },
    render:function(){
        return (
            <div clasName="board">
                <div className="status"></div>
                <div className="board-row">
                    {this.renderSquare(0)}
                    {this.renderSquare(1)}
                    {this.renderSquare(2)}
                </div>
                <div className="board-row">
                    {this.renderSquare(3)}
                    {this.renderSquare(4)}
                    {this.renderSquare(5)}
                </div>
                <div className="board-row">
                    {this.renderSquare(6)}
                    {this.renderSquare(7)}
                    {this.renderSquare(8)}
                </div>
            </div>
        );
    }
});

展示歷史步數

在之前入門學習中已經有了深刻體會:渲染一串React元素,最好用的方法是數組。

恰好我們的history也是一個數組。而且Game的架構設計中還有一個ol——那麼會做了吧?

                ...
                var arr=[];
                var _this=this;
                history.map(function(step,move){
                    var content='';
                    if(move){
                        content='Move#'+move;
                    }else{
                        content='游戲開始~';
                    }
                    arr.push(<li key={move}><a onClick={()=>_this.jumpTo(move)} href="javascript:;">{content}</a></li>);
                });
                ...

在這個a標記里,還加了個this.jumpToMove。當點擊之後j將把該索引值的舊狀態作為最後一個狀態。

好了,現在話分兩頭,插播一段關於Key值的論述。


論Key值的重要性

任何一個數組,都必須有key值。

當你渲染一串組件,React總是會把一些信息安置到每個單獨組件裡頭去。比如你渲染一串涉及state的組件時,這個state是得存起來的。不管你如何實現你的組件。React都會在背後存一個參照。

你對這些組件增刪改查。React通過這些參照信息得知哪些數據需要發生變動。

...
<li>蘇三說:xxx</li>
<li>李四說:ooo</li>
..

如上你想修改li的內容,React無法判斷哪個li是蘇三的,哪個li是李四的。這時就要一個key值(字元串)。對於同輩元素,key是唯一的。

<li key="蘇三">蘇三說:xxx</li>
<li key="李四">李四說:OOO</li>

key值是React保留的一個特殊屬性,它擁有比ref更先進的特性。當創建一個元素,React直接把一個key值傳到被return的元素中去。儘管看起來也是props之一,但是this.props.key這樣的查詢是無效的。

重新渲染一串組件,React通過key來查找需要渲染的匹配元素。可以這麼說,key被添加到數組,那這個組件就創建了;key被移除,組件就被銷毀。key就是每個組件的身份標誌,在重新渲染的時候就可以保持狀態。倘若你改變一個組件的key,它將完全銷毀,並重新創建一個新的狀態。

因此:強制要求你插入到頁面的數組元素有key,如果你不方便插入,那麼一定是你的設計出了問題。


來場說走就走的時間旅行

由於添加了悔棋這一設定,而悔棋是不可預測的。所以井字棋組件初始需要多一個狀態:stepNumber:0。另一方面,悔棋導致turnToX需要重新設定。

jumpTo:function(step){
                this.setState({
                    stepNumber:step,
                    turnToX:step%2?false:true
                })
            },

留意到this.state.stepNumber其實可以取代history.length-1——那就在render方法和handleClick方法中全部把它替換了。

最後一個問題還是出在handleClick,雖然可以回退,但是狀態最終不能實時更新。用history=history.slice(0,this.state.stepNumber+1);把它剪切一下就行了。

那麼全部功能就完成了。嗯,應該是完成了。

var Game=React.createClass({
            getInitialState:function(){
                return {
                    history:[
                        {squares:Array(9).fill(null)}
                    ],
                    turnToX:true,
                    stepNumber:0
                }
            },
            handleClick:function(i){
                var history=this.state.history;
                history=history.slice(0,this.state.stepNumber+1);
                var lastHistory=history[this.state.stepNumber];
                var winner=judgeWinner(lastHistory.squares);
                var squares=lastHistory.squares.slice();

                if(winner||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
                });
                console.log(this.state.history)
            },
            jumpTo:function(step){
                this.setState({
                    stepNumber:step,
                    turnToX:step%2?false:true
                });
            },
            render:function(){
                var history=this.state.history;
                var lastHistory=history[this.state.stepNumber];
                var winner=judgeWinner(lastHistory.squares);

                var status='';
                if(winner){
                    status='獲勝方是'+winner;
                }else{
                    var player=this.state.turnToX?'X':'O';
                    status='輪到'+player+'走';
                }

                var arr=[];
                var _this=this;
                history.map(function(step,move){
                    var content='';
                    if(move){
                        content='Move#'+move;
                    }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>
                );
            }
        });

        var Board=React.createClass({
            renderSquare:function(i){
                return <Square value={this.props.lastHistory[i]} onClick={() => this.props.onClick(i)} />
            },
            render:function(){
                return (
                    <div clasName="board">
                        <div className="status"></div>
                        <div className="board-row">
                            {this.renderSquare(0)}
      

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • s1=set([11,22,33,44,'Tom','tony',11,77,2.5,])返回的是{11,22,33,44,‘Tom’,‘tony’,77,2.5}(註意:返回的並不是一個字典,只是告訴你這個集合中含有這些元素,所以每次返回的結果元素的順序可能是不一樣的) s2=set([11,22 ...
  • 1 項目中程式調用發信息的介面時候,直接調用可能出現錯誤,影響主程式的流程; 解決:(1)mq發送消息 (2)發消息的介面自身捕獲處理異常,不能拋出到主程式中 現在針對第二種解決方案進行實現:因為以前各個發消息的節點都是拋出異常,如果針對每個方法都加上異常處理會很麻煩,現在用spring提供的@As ...
  • -------說明-------- IBatis 版本2.0 配置一對多 namespace = testDao ------------------ /** *班級的resultMap *ClassBean 對應的bean 例如org.test.ClassBean *id 為唯一的標識 */ //... ...
  • 前言:最近需要做一套CMS系統,由於功能比較單一,而且要求靈活,所以放棄了WP這樣的成熟系統,自己做一套相對簡單一點的。文章的詳情頁URL想要做成url偽靜態的格式即xxx.html 其中xxx考慮過直接用自增主鍵,但是感覺這樣有點暴露文章數量,有同學說可以把初始值設高一點,可是還是可以通過ID差算 ...
  • 本文地址 分享提綱: 1.概述 2. 原理 3. 安裝 4. 使用 5. 參考文檔 1. 概述 1.1)【常見文件系統】 Google了一下,流行的開源分散式文件系統有很多,介紹如下: -- mogileFS:Key-Value型元文件系統,不支持FUSE,應用程式訪問它時需要API,主要用在web ...
  • Windos環境用Nginx配置反向代理和負載均衡 引言:在前後端分離架構下,難免會遇到跨域問題。目前的解決方案大致有JSONP,反向代理,CORS這三種方式。JSONP相容性良好,最大的缺點是只支持GET方式請求。反向代理方式簡單徹底,基本只需要伺服器配置即可完成。CORS由服務提供程式主動聲明自 ...
  • RabbitMq應用一 RabbitMQ的具體概念,百度百科一下,我這裡說一下我的理解,如果有少或者不對的地方,歡迎糾正和補充。 一個項目架構,小的時候,一般都是傳統的單一網站系統,或者項目,三層架構,到現在的MVC架構。隨著用戶訪問量越來越多,系統業務越來越多,會出現以下問題: 1.修改完大量代碼 ...
  • --From : JAVA程式性能優化 (葛一鳴,清華大學出版社,2012/10第一版) 1. java性能調優概述 1.1 性能概述 程式性能: 執行速度,記憶體分配,啟動時間, 負載承受能力。 性能指標: 執行時間,CPU時間,記憶體分配,磁碟吞吐量,網路吞吐量,響應時間。 優化策略: 木桶原理,優 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...