iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 28
0
Modern Web

激戰 ReactJS 30天系列 第 28

【Day28】發現突破口!乘勝追擊 R

  • 分享至 

  • xImage
  •  

今天
本來要直接就昨天的問題解決
就在那個準備要連接這些組件的時候
發現一個天大的問題
那就是...
我還有個登入的東東阿!
(( 腦中無限東東echo ))

所以一個轉舵
從登入開始寫起
由於不牽扯後台
這個好尊重個資的登入系統必須要做到三件事情
分別是 記錄使用者名稱、 登入到首頁 和 從首頁登出
三件事情分別定義 Actions

// actions

export function setUser( user ){
    return {
        type : SET_USER,
        user : user
    }
}

export function userLogin(){
    return {
        type : LOGIN
    }
}

export function userLogout(){
    return {
        type : LOGOUT
    }
}

在寫成這樣之前
天真的我想說可以讓使用者按下按鈕的時候
在同步檢查是不是真的有名字並切換狀態
後來發現
這樣可以
但就必須破壞成就拿掉表單
成就當然要好好保留阿!
所以就以原本改變狀態的寫法
直接轉換成資料流寫法
所以就成為現在的模樣啦
另外
我把 Reducer 分開成兩個
由於登入與否和遊戲的進行互不影響
所以我就把各自的資料分開
處理器也分開
根據目的選擇使用的資料對象
避免資料汙染的狀況出現

// reducer.js
const loginData = {
    user : "",
    isLogin : false
}

const oxData = {
    playerMark : "O",
    OXgame:[
            ["1","2","3"],["4","5","6"],["7","8","9"]
           ],
    isWinner:""
}

function isLogined(state = loginData, action){
    switch(action.type){
        case SET_USER:
        // console.log(action.user.target.value);
            return Object.assign({}, state, {
                user : action.user.target.value
            });
        case LOGIN:
            if(state.user == "")
                window.alert("阿阿阿您忘記名字了阿阿阿!");
                return state;
            else
                return Object.assign({}, state, {
                    isLogin : true
                });
        case LOGOUT:
            return Object.assign({}, state, {
                user : "",
                isLogin : false
            });
        default:
            return state;
    }
}

function OXGame(state = oxData, action){
    ...
}

透過combineReducers的函式
可以讓這些 Reducer 結合成一個大Reducer
但在這個大 Reducer 內部卻是各自為政這樣
設定好這兩者之後
建立一個loginContainer作為 Container 組件
透過他來選擇會使用到的資料以及行為

const mapStateToProps = (state) => {
    return {
        user: state.isLogined.user,
        isLogin : state.isLogined.isLogin
    }
}

const mapDispatchToProps = (dispatch) =>{
    return {
        setUser: (user) => {
            dispatch( setUser(user) )
        },
        login: () => {
            dispatch( userLogin() )
        },
        logout: () => {
            dispatch( userLogout() );
        }
    }
}

藉由connect函式把它和呈現資料的組件連接
也就是我們最主要的介面 App
也因此
main.js的根組件就得要調整成這個 Container 組件啦

// main.js
ReactDOM.render(
    <Provider store = {store}>
        <LoginPage />
    </Provider>,
    document.getElementById('app')
);

其實這跟使用App當跟組件的意義相同
只是我在跟組件就需要使用資料這樣
因為把狀態都交給唯一的 Store 了
所以 App 組件也變成函式型組件囉
最後只要修改與登入功能有關的地方就完成了

// App.jsx
function App({user, isLogin, setUser, login, logout}){
    ...
    if(!isLogin){
        ...
    }else{
        return(
           <div className="animateBg">
              <div className="loginBlock">
                 <h1 style= {loginPrompt} >你好,先登入一下如何?</h1>
                 <form style={formStyle} onSubmit={login}>
                    <input id="user" type="text" placeholder="請輸入您的大名" 
                           onChange={(event) => setUser(event)}/>
                    <input type="submit" value="確認" />
                 </form>
              </div>
           </div>
        );
    }
}

由於資料及行為函式都改為透過 connect 以參數型式傳入
所以再也不會看到this.state.x這類字眼
全部都可以直接呼叫囉
這邊有讓我小小傷腦筋的是
onChange={(event) => setUser(event)}
這裡本來我只想針對輸入的值傳遞
不過沒辦法即時取出傳遞
所以我就把整個事件傳遞進去
就像 React 本身寫 handleEventChange 相同
把 event 傳遞進去直接讓 Reducer 取出 value

// reducer.js
function isLogined(state = loginData, action){
    switch(action.type){
        case SET_USER:
            return Object.assign({}, state, {
                // action.user 是整個 input tag 產生的 event
                user : action.user.target.value
            });
        ...
    }
}

最後登入按鈕的點選只需要判斷是不是真的有輸入東西
並且切換登入狀態isLogin完成登入

// reducer.js
function isLogined(state = loginData, action){
    switch(action.type){
        ...
        case LOGIN:
            if(state.user == ""){
                window.alert("阿阿阿您忘記名字了阿阿阿!");
                return state;
            }else{
                return Object.assign({}, state, {
                    isLogin : true
                });
            }
        ...
    }
}

以上就是登入部分的完成過程
接著是前一天沒有解決的問題
其實在昨天晚上
有稍微想到可能可行的方法
所以今天是直接接續著嘗試實作
非常幸運的成功完成填入圖形的動作了
程式邏輯上是
利用二維陣列存放遊戲狀態
透過map函式把索引值一併傳遞進去
並且在最後表格內的 div 標籤呼叫 action

// OX.jsx
function OX({playerMark, OXgame, isWinner, putMark, isGameEnd, playAgainOOXX}){
   
   var textStyle = {
      paddingLeft:'15px'
   }
   var i = 0;
   return(
      <div className="OX">
         <div className="left">
            <table>
               <tbody>
                  {OXgame.map(
                     (OXgame, i) => <OXList game={OXgame} onClick={putMark} x={i} key={i} />
                     )}
               </tbody>
            </table>
         </div>
         <div className="right">
            <h3>規則:</h3>
            <p style={textStyle}>兩位玩家輪流點選九宮格,先連線,先勝利!</p>
            <input type="button" onClick={playAgainOOXX} value="新遊戲" />        
         </div>
      </div>      
   );

}

這是圈圈叉叉的主要組件
他負責產生遊戲的左右畫面
一樣是透過 Container 組件以參數型式傳遞資料和行為進來使用
並且透過 map 產生遊戲畫面
傳遞進去的資料有索引值和放入符號的 action
接著看內部真正產生每一格欄位的組件

// OXList.jsx
function OXList(props){
   console.log(props.onClick);
   var game = props.game;
   var clickLeft = props.onClick;
   var x = props.x;
   var divStyle = {
        width: '100%',
        height: '100%',
        cursor: 'pointer'
   }
   return(
      <tr>
         <td><div style={divStyle} onClick={() => props.onClick(x, 0)}>{game[0]}</div></td>
         <td><div style={divStyle} onClick={() => props.onClick(x, 1)}>{game[1]}</div></td>
         <td><div style={divStyle} onClick={() => props.onClick(x, 2)}>{game[2]}</div></td>
      </tr>
      );
}

一排因為有三格
所以我直接寫了三個欄位這樣
根據索引位置的不同
呼叫符號寫入的對應陣列索引位置就不同
Container 組件長這樣:

const mapStateToProps = (state) => {
    return {
        playerMark : state.OXGame.playerMark,
        OXgame : state.OXGame.OXgame,
        isWinner : state.OXGame.isWinner
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        putMark: (x, y) => {
            dispatch( putMark(x, y) )
        },
        isGameEnd: () => {
            dispatch( isGameEnd() )
        },
        playAgainOOXX: () =>{
            dispatch( playAgainOOXX() )
        }
    }
}

使用不同的 Reducer 所以跟前面登入功能的 Container 組件長得不太一樣
不過概念上相同
所以這邊就不贅述啦~

總之
今天完成了登入系統
解決了昨天的卡關問題
成果畫面:
https://ithelp.ithome.com.tw/upload/images/20180116/20107674B7aT4MOqkA.png

感覺已經開始慢慢上手了
寫出來的那一刻真的是超級震撼
覺得應該趁勝追擊
明天繼續努力!!

成就清單

https://ithelp.ithome.com.tw/upload/images/20180116/20107674FO3pcL4Cjk.png

參考資料

  1. tutorialspoint-ReactJS Tutorial
  2. React 官方文件

>>> 隊友任意門 <<<


Day28 end
by 瑞Ray ⸜(* ॑꒳ ॑* )⸝


上一篇
【Day27】 天啊 一個卡關
下一篇
【Day29】 哎呀?二度卡關T^T
系列文
激戰 ReactJS 30天31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言