前端開發及登錄功能實現--Django播客系統(九)

前端開發及登錄功能實現–Django播客系統(九)

開發環境設置

  • 使用react-mobx-starter-master腳手架,解壓更名爲DjangoNode。

  • 在src中新增component、service、css目錄

  • 注意:沒有特別說明,js開發都在src目錄下

  • 目錄結構

DjangoNode/-.babelrc
    │-.gitignore #git的忽略文件
    │-.npmrc    #npm的服務器配置,本次使用的是阿里
    │-index.html #dev server使用的首頁
    │-jsconfig.json
    │-LICENSE-package-lock.json
    │-package.json  #項目的根目錄安裝文件,使用npm install可以構建項目
    │-README.md
    │-webpack.config.dev.js #開發用的配置文件
    │-webpack.config.prod.js #生產環境,打包用的配置文件
    └─src/- componet/ #自己組件文件夾
        │- service/  #服務程序
        │- css/  #樣式表
        │- index.html #模板頁面
        │- index.js
  1. 修改項目信息package.json文件

    {
        "name":"blog",
        "description":"blog project",
        "author":"xdd"
    }
    
  2. 修改webpack.config.dev.js

    devServer: {
            compress: true, /* gzip */
            //host:'192.168.61.109', /* 設置ip */
            port: 3000,
            publicPath: '/assets/', /* 設置bundled files瀏覽器端訪問地址 */
            hot: true,  /* 開啓HMR熱模塊替換 */
            inline: true, /* 控制瀏覽器控制檯是否顯示信息 */
            historyApiFallback: true,
            stats: {
                chunks: false
            },
            proxy: { //代理
                '/api': {
                    target: 'http://127.0.0.1:8000',
                    changeOrigin: true
                }
            }
        }
    
  3. 安裝依賴 npm install

    • npm會按照package.json中依賴的包。也可以使用新的包管理工具yarn安裝模塊
    • 使用yarn替換npm安裝,速度會更快寫,yarn是並行安裝,npm是串行安裝。
    yarn安裝
    $ npm install -g yarn
    或者,去https://yarn.bootcss.com/docs/install/
    
    相當於npm install
    $ yarn
    
    * 如果想自己構建腳手架,可以使用如下命令
    $npm install  #構建腳手架
    相當於npm install react-router #添加react-router組件
    $ yarn add react-router # 安裝路由,即項目前端web路由
    $ yarn add react-router-dom #
    
  4. 相關命令

    npm命令 Yarn命令 解釋
    npm install yarn install 安裝
    npm install [package] --save yarn add [package] 安裝運行時依賴
    npm install [package] --save-dev yarn add [package] --dev 安裝開發時依賴
    npm install [package] --global yarn global add [package] 全局安裝
    npm uninstall [package] yarn remove [package] 卸載

開發

前端路由

/* src/index.js文件 */
import React from 'react';
import ReactDom from 'react-dom';
import {Route,Link,BrowserRouter as Router,Switch} from "react-router-dom";

function App() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/users">Users</Link>
          </li>
        </ul>

        <hr />

        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/users" component={Users} />
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
    </div>
  );
}

function About() {
  return (
    <div>
      <h2>About</h2>
    </div>
  );
}

const Users = () => {
  return (
    <div>
      <h2>Users</h2>
    </div>
  )
}

ReactDom.render(<App />, document.getElementById('root'));

  • 注意:

    1. Link組件相當於a標籤
    2. Route組件不可見,用來做Router的路由定義。當網頁中的地址欄中地址發生改變,會從Route中匹配對應的路徑加載對應的組件。
  • 啓動項目yarn run start

    1. start是在配置文件dockerNode/package.json中已經定義好了,如下:
    {
        "scripts": {
            "test": "jest",
            "start": "webpack-dev-server --config webpack.config.dev.js --hot --inline",
            "build": "rimraf dist && webpack -p --config webpack.config.prod.js"
        },
    }
    

    django9_003

  • 在地址欄中輸入http://127.0.0.1:3000/http://127.0.0.1:3000/about能夠看到頁面的變化。

  • App中,使用了Router路由組件,Router是根,且它只能有一個元素,所以添加了Div

    1. 訪問http://127.0.0.1:3000/
      djnago9_001
    2. 訪問http://127.0.0.1:3000/about
      django9_002
  • 前端路由:通過一個url加載一個組件,將原來的組件替換。

Route指令

  • 它負責靜態路由,只能和Route指定的path匹配,組件就可以顯示。URL變化,將重新匹配路徑
  • component屬性設置目標組件
  • path是匹配路徑,如果匹配則顯示組件
    1. exact:布爾值
    2. strict:布爾值
  • 沒有path屬性,組件將總是顯示,例如<Route component={Always} />
  • path屬性還支持路徑數組,意思是多個路徑都可以匹配
/* 修改src/index.js文件*/
function App() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/users">Users</Link>
          </li>
        </ul>

        <hr />

        <Route exact path={["/","/index"]} component={Home} />
        <Route path="/about" component={About} />
        <Route path="/users" component={Users} />
        <Route component={Always} />
      </div>
    </Router>
  );
}

function Always(){
  return(
    <div id="footer">
      <span>Copyright 2009-2019 xdd.com</span>
    </div>
  )
}

ReactDom.render(<App />, document.getElementById('root'));

django9_004

  1. 路由配置

    • exact 只能匹配本路徑,不包含子路徑
    • strict 路徑尾部有/,則必須匹配這個/,也可以匹配子路徑|
    • exact strict 一起用,表示嚴格的等於當前指定路徑
    路徑 /about /about/ /about/123
    path="/about"
    exact path="/about"
    exact path="/about/"
    strict path="/about"
    exact strict path="/about"
    strict path="/about/"
    exact strict path="/about/"
  2. Switch指令

    • 也可以將Route組織到一個Switch中,一旦匹配Switch中的一個Route,就不再匹配其他。但是Route是匹配所有,如果匹配就會顯示組件,無path的Route始終匹配。
    • 示例:
    /* 修改src/index.js文件*/
    function App() {
    return (
        <Router>
        <div>
            <ul>
            <li>
                <Link to="/">Home</Link>
            </li>
            <li>
                <Link to="/about">About</Link>
            </li>
            <li>
                <Link to="/users">Users</Link>
            </li>
            </ul>
    
            <hr />
            <Switch>
            <Route path="/" component={Home} />
            <Route path="/about" component={About} />
            <Route path="/users" component={Users} />
            <Route component={Always} />
            </Switch>
        </div>
        </Router>
    );
    }
    
    • 注意這個時候Always組件,其實是404組件了,因爲只有Switch中其上的Route沒有匹配,才輪到它。

登錄組件

<div class="login-page">
<div class="form">
    <form class="register-form">
    <input type="text" placeholder="name"/>
    <input type="password" placeholder="password"/>
    <input type="text" placeholder="email address"/>
    <button>create</button>
    <p class="message">Already registered? <a href="#">Sign In</a></p>
    </form>
    <form class="login-form">
    <input type="text" placeholder="username"/>
    <input type="password" placeholder="password"/>
    <button>login</button>
    <p class="message">Not registered? <a href="#">Create an account</a></p>
    </form>
</div>
</div>
  • 使用這個HTML模板來構建組件
  • 特別注意
    1. 搬到React組件中的時候,要將class屬性改爲className.
    2. 所有標籤,需要閉合。
  1. login.js

    1. 在component目錄下新建login.js的登錄組件。
    2. 使用上面的模板的HTML中的登錄部分,挪到render函數中。
      • 修改class爲className
      • <a>標籤替換成<Link to="?">組件
      • 注意標籤閉合問題
    /* 新建文件src/component/login.js */
    import React from "react";
    import {Link} from "react-router-dom";
    
    export default class Login extends React.Component {
        render() {
            return (
                <div className="login-page">
                <div className="form">
                    <form className="login-form">
                    <input type="text" placeholder="username"/>
                    <input type="password" placeholder="password"/>
                    <button>登錄</button>
                    <p className="message">還未註冊? <Link to="#">請註冊</Link></p>
                    </form>
                </div>
                </div>
            )
        }
    }
    
  2. src/index.js路由中增加登錄組件

    /* 修改src/index.js文件內容 */
    import React from 'react';
    import ReactDom from 'react-dom';
    import {Route,Link,BrowserRouter as Router,Switch} from "react-router-dom";
    import Login from "./component/login";
    
    function App() {
    return (
        <Router>
        <div>
            <Route path="/about" component={About} />
            <Route path="/login" component={Login} />
            <Route exact path="/" component={Home} />
        </div>
        </Router>
    );
    }
    
    function Home() {
    return (
        <div>
        <h2>Home</h2>
        </div>
    );
    }
    
    function About() {
    return (
        <div>
        <h2>About</h2>
        </div>
    );
    }
    
    
    ReactDom.render(<App />, document.getElementById('root'));
    
    • 訪問http://127.0.0.1:3000/login就可以看到登錄界面了,但是沒有樣式。
      django9_005
  3. 樣式表

    • src/css中,創建login.css,放入以下內容,然後src/component/login.js中導入樣式
    /* src/component/login.js中導入樣式表 */
    import React from "react";
    import {Link} from "react-router-dom";
    import "../css/login.css";
    
    /* 新建src/css/login.css文件 */
    body {
    background: #456;
    font-family: SimSun;
    font-size: 14px;
    }
    
    .login-page {
    width: 360px;
    padding: 8% 0 0;
    margin: auto;
    }
    .form {
    font-family: "Microsoft YaHei", SimSun;
    position: relative;
    z-index: 1;
    background: #FFFFFF;
    max-width: 360px;
    margin: 0 auto 100px;
    padding: 45px;
    text-align: center;
    box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
    }
    .form input {
    outline: 0;
    background: #f2f2f2;
    width: 100%;
    border: 0;
    margin: 0 0 15px;
    padding: 15px;
    box-sizing: border-box;
    font-size: 14px;
    }
    .form button {
    text-transform: uppercase;
    outline: 0;
    background: #4CAF50;
    width: 100%;
    border: 0;
    padding: 15px;
    color: #FFFFFF;
    font-size: 14px;
    cursor: pointer;
    }
    .form button:hover,.form button:active,.form button:focus {
    background: #43A047;
    }
    .form .message {
    margin: 15px 0 0;
    color: #b3b3b3;
    font-size: 12px;
    }
    .form .message a {
    color: #4CAF50;
    text-decoration: none;
    }
    
    • 訪問http://localhost:3000/login可以看到如下界面
      django9_006

註冊組件

  • 與登錄組件編寫方式差不多,創建src/component/reg.js,使用login.css
/*新建src/component/reg.js文件 */
import React from "react";
import {Link} from "react-router-dom";
import "../css/login.css"

export default class Reg extends React.Component {
    render(){
        return(
            <div className="login-page">
            <div className="form">
                <form className="register-form">
                <input type="text" placeholder="name"/>
                <input type="text" placeholder="email" />
                <input type="password" placeholder="密碼"/>
                <input type="password" placeholder="確認密碼"/>
                <button>註冊</button>
                <p className="message">如果已經註冊 <Link to="#">請登錄</Link></p>
                </form>
            </div>
            </div>
        )
    }
}
  • src/index.js中增加一條靜態路由
/* src/index.js中修改*/
import Reg from "./component/reg";

function App() {
  return (
    <Router>
      <div>
          <Route path="/about" component={About} />
          <Route path="/login" component={Login} />
          <Route path="/reg" component={Reg} />
          <Route exact path="/" component={Home} />
      </div>
    </Router>
  );
}
  • 訪問http://localhost:3000/reg可以看到如下界面
    django9_007

導航欄鏈接

  • 在index.js中增加導航欄鏈接,方便頁面切換
/* 修改src/index.js文件*/
function App() {
  return (
    <Router>
      <div>
          <div>
            <ul>
              <li><Link to="/">主頁</Link></li>
              <li><Link to="/login">登錄</Link></li>
              <li><Link to="/reg">註冊</Link></li>
              <li><Link to="/about">關於</Link></li>
            </ul>
          </div>
          <Route path="/about" component={About} />
          <Route path="/login" component={Login} />
          <Route path="/reg" component={Reg} />
          <Route exact path="/" component={Home} />
      </div>
    </Router>
  );
}

django9_008

分層

層次 作用 路徑
視圖層 負責數據呈現,負責用戶交互界面 src/component/xxx.js
服務層 負責業務邏輯處理 src/service/xxx.js
Model層 數據持久化

登錄功能實現

  • view層,登錄組件和用戶交互。相當於button點擊觸發onclick,調用事件響應函數handleClick,handleClick中調用服務service層login函數。
  • service層,負責業務邏輯處理。調用Model層數據操作函數
  • 新建src/service/user.js處理調用邏輯
/* 新建src/service/user.js邏輯 */
export default class UserService {
    login (email,password) {
        //Tood
    }
}
  • 修改src/component/login.js文件
/* 修改`src/component/login.js`文件 */
import React from "react";
import {Link} from "react-router-dom";
import "../css/login.css";

export default class Login extends React.Component {
    handleClick(event){
        console.log(event.target)
    }

    render() {
        return (
            <div className="login-page">
            <div className="form">
                <form className="login-form">
                <input type="text" placeholder="email"/>
                <input type="password" placeholder="password"/>
                <button onClick={this.handleClick.bind(this)}>登錄</button>
                <p className="message">還未註冊? <Link to="#">請註冊</Link></p>
                </form>
            </div>
            </div>
        )
    }
}
  1. 問題:
    • 頁面提交
      1. 這次發現有一些問題,按鈕點擊會提交,導致頁面刷新了。要阻止頁面刷新,其實就是阻止提交。使用event.preventDefault()。
    • 如何拿到郵箱和密碼?
      1. event.target.form返回暗流所在表單,可以看做一個數組。
      2. fm[0].valuefm[1].value就是文本框的值。
    • 如何在Login組件中使用UserService實例呢?
      1. 使用全局變量,雖然可以,但不好。
      2. 可以在Login的構造器中通過屬性注入。
      3. 也可以在外部使用props注入。使用這種方式。
  • 修改,保證在login組件中使用UserService,使用屬性注入
/* 修改src/component/login.js文件 */
import React from "react";
import {Link} from "react-router-dom";
import "../css/login.css";
import UserService from "../service/user";

const serviec = new UserService();

export default class Login extends React.Component{
    render(){
        return <_Login service={serviec} />;
    }
}

class _Login extends React.Component {
    handleClick(event){
        event.preventDefault(); /* 阻止from表單提交 */
        let fm = event.target.form;
        this.props.service.login(fm[0].value,fm[1].value)
    }

    render() {
        return (
            <div className="login-page">
            <div className="form">
                <form className="login-form">
                <input type="text" placeholder="email"/>
                <input type="password" placeholder="password"/>
                <button onClick={this.handleClick.bind(this)}>登錄</button>
                <p className="message">還未註冊? <Link to="#">請註冊</Link></p>
                </form>
            </div>
            </div>
        )
    }
}

UserService的login方法實現

  1. 代理配置

    • 修改webpack.config.dev.jsw文件中proxy部分,保證proxy的target是後臺服務的地址和端口,且要開啓後臺服務。
    • 注意:修改這個配置,需要重啓dev server
    /* 修改webpack.config.dev.jsw文件 */
    devServer: {
        compress: true, /* gzip */
        //host:'192.168.61.109', /* 設置ip */
        port: 3000,
        publicPath: '/assets/', /* 設置bundled files瀏覽器端訪問地址 */
        hot: true,  /* 開啓HMR熱模塊替換 */
        inline: true, /* 控制瀏覽器控制檯是否顯示信息 */
        historyApiFallback: true,
        stats: {
            chunks: false
        },
        proxy: { //代理
            '/api': {
                target: 'http://127.0.0.1:8000',
                changeOrigin: true
            }
        }
    }
    
  2. axios異步庫

    • axios是一個基於Promise的HTTP異步庫,可以用在瀏覽器或nodejs中。
    • 使用axios發起異步調用,完成POST、GET方法的數據提交。可以查照官網的例子。中文說明https://www.kancloud.cn/yunye/axios/234845
    1. 安裝npm $ npm install axiosyarn add axios

      • 注意:如果使用yarn安裝,就不要再使用npm安裝包了,以免出現問題。
    2. 導入 import axios from 'axios';

      • 修改service/user.js如下
      import axios from "axios";
      
      export default class UserService {
          login (email,password) {
              console.log(email,password);
      
              axios.post("/api/users/login",{
                  email:email,
                  password:password
              }) /* dev server會代理 */
              .then( /* 成功後返回執行函數 */
                  function (response){
                      console.log(response);
                      console.log(response.data)
                      console.log("response.status: " + response.status);
                      console.log(response.statusText);
                      console.log(response.headers);
                      console.log(response.config);
                  }
              ).catch(/* 出錯後執行函數 */
                  function(error){
                      console.log(error);
                  }
              )
          }
      }
      
    3. 問題:

      1. 404 填入郵箱,密碼,點擊登錄,返回404,查看發現訪問的地址是http://127.0.0.1:3000/api/users/login,也就是多了/api
        django9_009

      2. 解決:

        1. 修改blog server的代碼的路由匹配規則(不建議這麼做,影響比較大)
        2. rewrite,類似httpd,nginx等的rewrite功能。本次測試使用的是dev server,去官方看看。https://webpack.js.org/configuration/dev-server/#devserver-proxy可以看到pathRewrite可以完成路由重寫。
      3. 修改webpack.config.dev.js文件

        /* 修改webpack.config.dev.js文件中對應內容*/
        devServer: {
            compress: true, /* gzip */
            //host:'192.168.61.109', /* 設置ip */
            port: 3000,
            publicPath: '/assets/', /* 設置bundled files瀏覽器端訪問地址 */
            hot: true,  /* 開啓HMR熱模塊替換 */
            inline: true, /* 控制瀏覽器控制檯是否顯示信息 */
            historyApiFallback: true,
            stats: {
                chunks: false
            },
            proxy: { //代理
                '/api': {
                    target: 'http://127.0.0.1:8000',
                    pathRewrite: {"^/api" : ""}, //將所有代理親戚中已/api開頭的請求中對應字符替換成空
                    changeOrigin: true
                }
            }
        }
        
        • 重啓dev server.使用正確的郵箱,密碼登錄,返回了json數據,在response.data中可以看到token、user。
          django9_010

token持久化–LocalStorage

  • 使用LocalStorage來存儲token。

  • LocalStorage是HTML5標準增加的技術,是瀏覽器端持久化方案之一。

  • LocalStorage是爲了存儲瀏覽器得到的數據,例如JSON。

  • 數據存儲時鍵值對。數據會存儲在不同的域名下面。

  • 不同瀏覽器對單個域名下存儲的數據的長度支持不同,有的最多支持2MB。

  • 在Charmo瀏覽器中查看,如下
    django9_011

  • SessionStorage和LocalStorage功能差不多,只不過SessionStorage是會話級的,瀏覽器關閉,會話結束,數據清除。而LocalStorage可以持久保存。

  • indexedDB

    1. 一個域一個datatable
    2. key-value檢索方式
    3. 建立在關係型的數據模型之上,具有索引表、遊標、事務等概念
  • store.js

    1. store.js是一個兼容所有瀏覽器的LocalStorage包裝器,不需要藉助Cookie或者Flash。
    2. store.js會根據瀏覽器自動選擇使用localStorage、globalStorage或者userData來實現本地存儲功能。
  • 安裝npm i storeyarn add store

  • 測試代碼

    1. 編寫一個test.js,使用node exec插件按F8執行

      let store = require("store");
      
      store.set("user","xdd");
      console.log(store.get("user"));
      
      store.remove("user");
      console.log(store.get("user")); // undefined
      console.log(store.get("user","a")); //a
      
      store.set("user",{name:"xdd",age:30});
      console.log(store.get("user").name);
      
      store.set("school",{name:"magedu"});
      
      // 遍歷所有鍵值對
      store.each(function(value,key){ //注意這裏key,value是反的
          console.log(key,"-->",value)
      })
      
      //清除所有鍵值對
      store.clearAll()
      
      console.log(store.get("user"));// undefined
      

      django9_012

    2. 安裝store的同時,也安裝了expire過期插件,可以在把kv對存儲到LS中的時候順便加入過期時長。

      let store = require("store");
      //一定要加載插件,否則key不會過期
      store.addPlugin(require("store/plugins/expire"));
      
      let d = new Date();
      store.set("user","xdd",(new Date()).getTime() + (5 * 1000)); //注意時間單位
      
      setInterval(() => console.log(store.get("user","abc")),1000);
      
      

      django9_013

    3. 下面是準備寫在service中的代碼

      import store from "store";
      import expire from "store/plugins/expire";
      
      store.addPlugin(expire)
      //存儲token
      store.set("token",res.data.token,(new Date()).getTime() + (8*3600*1000));
      
      /* 修改對應的src/service/user.js文件 */
      import axios from "axios";
      import store from "store";
      import expire from "store/plugins/expire";
      
      // 過期插件
      store.addPlugin(expire)
      
      export default class UserService {
          login (email,password) {
              console.log(email,password);
      
              axios.post("/api/users/login",{
                  email:email,
                  password:password
              }) /* dev server會代理 */
              .then( /* 成功後返回執行函數 */
                  function (response){
                      console.log(response.data)
                      console.log("response.status: " + response.status);
                      // 存儲token,注意需要重開一次chrome的調試窗口才能看到
                      store.set("token",response.data.token,(new Date()).getTime() + (8*3600*1000));
                  }
              ).catch(/* 出錯後執行函數 */
                  function(error){
                      console.log(error);
                  }
              )
          }
      }
      
      

Mobx狀態管理

  • Redux和Mobx
    1. 社區提供的狀態管理庫,有Redux和Mobx。
    2. Redux代碼優秀,使用嚴格的函數式編程思想,學習曲線陡峭,小項目使用的優劣不明顯。
    3. Mobx,非常優秀穩定的庫,簡單方便,適合中小項目使用。使用面向對象的方式,容易學習和接受。現在在中小項目中使用也非常廣泛。Mobx和React也是一對強力組合。
  • 觀察者模式
    1. 觀察者模式,也稱爲發佈訂閱模式。觀察者觀察某個目標,目標對象(Obserable)狀態發生了變化,會通知自己內部註冊了的觀察者Observer。
  • 狀態管理
    1. 需求:

      • 一個組件的onClick觸發事件響應函數,此函數會調用後臺服務。但是後臺服務比較耗時,等處理完,需要引起組件的渲染操作。
      • 要組件渲染,就需要改變組件的props或state。
    2. 同步調用

      • 同步調用中,實際上就是等着耗時的函數返回
    3. 異步調用

      • 思路一,使用setTimeout問題

        1. 無法向內部的等待執行函數傳入參數,比如Root實例。
        2. 延時執行的函數的返回值無法取到,所以無法通知Root
      • 思路二、Promise異步執行

        1. Promise異步執行,如果成功,將調用回調。
        2. 不管render中是否顯示state的值,只要state改變,都會觸發render執行
        /* 可以在src/index.js中修改測試代碼如下 */
        import React from 'react';
        import ReactDom from 'react-dom';
        
        class Service{
            handle(obj){
                //Promise
                new Promise((resolve,reject) => {
                    //定時器5秒後返回ok
                    setTimeout(() => resolve("ok"), 5000);
                }).then(value => { //成功後執行
                //使用obj
                obj.setState({ret:(Math.random()*1000)});
                }
                )
            }
        }
        
        class Root extends React.Component{
            state = {ret:null}
            handleClick(event){
                //異步不能直接使用返回值
                this.props.service.handle(this);
            }
        
            render(){
                console.log("*****************")
                return (
                <div>
                    <button onClick={this.handleClick.bind(this)}>觸發handleClick函數</button>
                    <span style={{color:"red"}}> {new Date().getTime()} Service中修改state的值是{this.state.ret}</span>
                </div>
                )
            }
        }
        
        ReactDom.render(<Root service={new Service()} />, document.getElementById('root'));
        

        django9_014

    4. Mobx實現

      • observable裝飾器:設置被觀察者
      • observer裝飾器:設置觀察者,將React組件轉換爲響應式組件
      /* 可以在src/index.js中修改測試代碼如下 */
      import React from 'react';
      import ReactDom from 'react-dom';
      import {observable} from 'mobx';
      import {observer} from "mobx-react"
      
      class Service{
          @observable ret = -100;
      
          handle(obj){
              //Promise
              new Promise((resolve,reject) => {
                  //定時器5秒後返回ok
                  setTimeout(() => resolve("ok"), 5000);
              }).then(value => { //成功後執行
                  this.ret = Math.random()*1000;
              }
              )
          }
      }
      
      @observer //將react組件轉換爲響應式組件
      class Root extends React.Component{
          // state = {ret:null} //不使用state了
          handleClick(event){
              //異步不能直接使用返回值
              this.props.service.handle(this);
          }
      
          render(){
              console.log("*****************")
              return (
              <div>
                  <button onClick={this.handleClick.bind(this)}>觸發handleClick函數</button>
                  <span style={{color:"red"}}> {new Date().getTime()} Service中修改state的值是{this.props.service.ret /* 如果不使用,當值改變render就不會被調用 */}</span>
              </div>
              )
          }
      }
      
      ReactDom.render(<Root service={new Service()} />, document.getElementById('root'));
      
      • Service中被觀察者ret變化,導致了觀察者調用render函數。
      • 被觀察者變化不引起渲染的情況:
        1. 將root中的rander中{this.props.service.ret}註釋{/* this.props.service.ret */}。可以看到,如果render中不使用這個被觀察者,render函數就不會調用。
      • 注意:在觀察者render函數中,一定要使用這個被觀察對象。

跳轉

  • 如果service中ret發生了變化,觀察者Login就會被通知到。一般來說,就會跳轉到用戶界面,需要使用Redirect組件。
// 導入Redirect
import {Link,Redirect} from 'react-router-dom';

//render函數中return
return <Redirect to="/" />; //to表示跳轉到哪裏

Login登錄功能代碼實現

  1. src/service/user.js文件內容

    /*`src/service/user.js`文件內容*/
    import axios from "axios";
    import store from "store";
    import expire from "store/plugins/expire";
    import {observable} from "mobx";
    
    // 過期插件
    store.addPlugin(expire)
    
    export default class UserService {
        @observable loggedin = false; //被觀察者
    
        login (email,password) {
            console.log(email,password);
    
            axios.post("/api/users/login",{
                email:email,
                password:password
            }) /* dev server會代理 */
            .then( /* 成功後返回執行函數 */
                (function (response){
                    console.log(response.data)
                    console.log("response.status: " + response.status);
                    // 存儲token,注意需要重開一次chrome的調試窗口才能看到
                    store.set("token",response.data.token,(new Date()).getTime() + (8*3600*1000));
                    this.loggedin = true; // 修改被觀察者
                }).bind(this) /*注意綁定this,如果不想可以使用箭頭函數*/
            ).catch(/* 出錯後執行函數 */
                function(error){
                    console.log(error);
                }
            )
        }
    }
    
  2. src/component/login.js文件內容

    /* src/component/login.js文件內容 */
    import React from "react";
    import {Link,Redirect} from "react-router-dom";
    import "../css/login.css";
    import UserService from "../service/user";
    import {observer} from "mobx-react";
    
    const userService = new UserService();
    
    export default class Login extends React.Component{
        render(){
            return <_Login service={userService} />;
        }
    }
    
    @observer //將react組件轉換爲響應式組件
    class _Login extends React.Component {
        handleClick(event){
            event.preventDefault(); /* 阻止from表單提交 */
            let fm = event.target.form;
            this.props.service.login(fm[0].value,fm[1].value)
        }
    
        render() {
            console.log(this.props.service.loggedin)
            if (this.props.service.loggedin) {
                return <Redirect to="/" />; //已經登錄,直接跳轉
            }
            return (
                <div className="login-page">
                <div className="form">
                    <form className="login-form">
                    <input type="text" placeholder="email"/>
                    <input type="password" placeholder="password"/>
                    <button onClick={this.handleClick.bind(this)}>登錄</button>
                    <p className="message">還未註冊? <Link to="#">請註冊</Link></p>
                    </form>
                </div>
                </div>
            )
        }
    }
    
    • 注意:測試時,開啓Django編寫的後臺服務程序
    • 測試成功,成功登錄,寫入Localstorage,也實現了跳轉
      django9_015
      django9_016
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章