首頁 > web前端 > js教程 > 主體

電影蒐集的小應用react技術棧實踐

小云云
發布: 2017-12-18 15:43:06
原創
1467 人瀏覽過
本文主要和大家分享電影蒐集的小應用react技術堆疊實踐,希望能幫助大家。

主要功能

  • 爬取豆瓣電影資訊並錄入MongoDB

  • 電影列表展示,分類、搜尋

  • 電影詳情展示及附件管理

  • 註冊、登入

  • 權限控制,一般使用者可以輸入、收藏,administrator輸入、修改、刪除

  • 用戶中心,我的收藏清單

電影蒐集的小應用react技術棧實踐

##一些總結

前端

前端使用了react,redux加redux-saga,對redux簡單總結一下,同時記錄一個前後介面呼叫有依賴關係的問題

  • redux

一句話總結redux,我覺的就是將組件之間的縱向的props傳遞和父子組件間的state愛恨糾纏給打平了,將一種縱向關係轉變成

多個元件和一個獨立出來的狀態物件直接互動,這樣之後,程式碼結構確實看上去更加清晰了。

redux的核心概念,action,reducer,和store

action就是說明我要操作一個狀態了,怎麼操作是reducer的事,而所有狀態都儲存在store中,store發出動作並交由指定的reducer來處理

redux強制規範了我們對狀態的操作,只能在action和reducer這些東西中,這樣,原本錯綜複雜的業務邏輯處理就換了個地,限制在了action和reducer中,組件看起來就很乾淨了。其實,該複雜的東西在哪裡放都複雜,只不過現在更清晰一點

使用redux不好的地方就是太繁瑣了,定義各種action,connect各種組件。 。 。 。 。現在又出來一個Mobx,不明覺厲,反正大家都說好~

  • redux-saga

redux-saga用來處理非同步呼叫啥的,借助於generator,讓非同步程式碼看起來更簡潔,常用的有

take,takeLatest,takeEvery,put,call,fork,select,使用過程中遇到一個介面呼叫有前後依賴關係的問題,比較有意思

描述一下:

  1. 有一個介面

    /api/user/checkLogin,用來判斷是否登錄,在最外層的組件的componentDidMount中觸發action來發起這個請求,並且接口返回狀態是登錄的話,還發送一個獲取用戶信息的

  2. #
    function* checkLogin() {
        const res = yield Util.fetch('/api/user/checkLogin')
        yield put(recieveCheckLogin(!res.code))
        if (!res.code) {
            //已登录
            yield put(fetchUinfo())
        }
    }
    export function* watchCheckLogin() {
        yield takeLatest(CHECK_LOAGIN, checkLogin)
    }
    登入後複製
  1. 然後我有一個電影詳情頁元件,在這個元件的

    componentDidMount中會啟動/api/movies/${id} 介面取得電影訊息,如果使用者是登入狀態的話,也會發起一個獲取電影附件資訊的介面/api/movies/${id}/attach整個步驟寫在一個generator中

  2. function* getItemMovie(id) {
        return yield Util.fetch(`/api/movies/${id}`)
    }
    
    function* getMovieAttach(id) {
        return yield Util.fetch(`/api/movies/${id}/attach`)
    }
    
    function* getMovieInfo(action) {
        const { movieId } = action
        let { login } = yield select(state => state.loginStatus)
        const res = yield call(getItemMovie, movieId)
        yield put(recieveItemMovieInfo(res.data[0]))
        if (res.data[0].attachId && login) {
            const attach = yield call(getMovieAttach, movieId)
            yield put(recieveMovieAttach(attach.data[0]))
        }
    }
    
    export function* watchLoadItemMovie() {
        yield takeLatest(LOAD_ITEM_MOVIE, getMovieInfo)
    }
    登入後複製
  1. #用戶登入了,轉到詳情,流程正常,但如果在詳情頁刷新了頁面,取得附件的介面沒觸發,原因是此時checkLogin介面還沒回傳結果,

    state.loginStatus狀態還是false,上面就沒走到if中

  2. 一開始想著怎麼控制一些generator中yield的先後順序來解決(如果用戶沒有登入的話,再發一個CHECK_LOAGIN,結果回傳了流程再繼續),但存在CHECK_LOAGIN呼叫兩次,如果登入了,還會再多一次獲取用戶資訊的介面呼叫的情況,肯定不行

  3. function* getMovieInfo(action) {
        const { movieId } = action
        let { login } = yield select(state => state.loginStatus)
        const res = yield call(getItemMovie, movieId)
        yield put(recieveItemMovieInfo(res.data[0]))
        // if (!login) {
        //     //刷新页面的时候,如果此时checklogin接口还没返回数据或还没发出,应触发一个checklogin
        //     //checklogin返回后才能得到login状态
        //     yield put({
        //         type: CHECK_LOAGIN
        //     })
        //     const ret = yield take(RECIEVE_CHECK_LOAGIN)
        //     login = ret.loginStatus
        // }
        if (res.data[0].attachId && login) {
            const attach = yield call(getMovieAttach, movieId)
            yield put(recieveMovieAttach(attach.data[0]))
        }
    }
    登入後複製
  1. #最終的辦法,分解generator的職責,componentWillUpdate中合適的觸發獲取附件的動作

  2. //将获取附件的动作从 getMovieInfo这个generator中分离出来
    function* getMovieInfo(action) {
        const { movieId } = action
        const res = yield call(getItemMovie, movieId)
        yield put(recieveItemMovieInfo(res.data[0]))
    }
    function* watchLoadItemMovie() {
        yield takeLatest(LOAD_ITEM_MOVIE, getMovieInfo)
    }
    function* watchLoadAttach() {
        while (true) {
            const { movieId } = yield take(LOAD_MOVIE_ATTACH)
            const { attachId } = yield select(state => state.detail.movieInfo)
            const attach = yield call(getMovieAttach, movieId)
            yield put(recieveMovieAttach(attach.data[0]))
        }
    }
    
    //组件中
    componentWillUpdate(nextProps) {
            if (nextProps.loginStatus && (nextProps.movieInfo!==this.props.movieInfo)) {
                //是登录状态,并且movieInfo已经返回时
                const { id } = this.props.match.params
                this.props.loadMovieAttach(id)
            }
    }
    登入後複製
  1. 總結,合理使用元件的鉤子函數,generator中不要處理太多操作,增加靈活性

後端

後端採用express和mongodb,也用到了redis,主要技術點有

使用pm2來管理node應用程式及部署程式碼,mongodb中開啟身分認證,使用token+ redis來做身分認證、在node中寫了寫單元測試,還是值得記錄的

  • #使用jwt + redis 來做基於token的使用者身分認證

#基於token的認證流程

  1. 客戶端發起登入請求

  2. #服務端驗證使用者名稱密碼

  3. 驗證成功服務端產生一個token,回應給客戶端

  4. #客戶端之後的每次請求header中都帶上這個token

  5. 服務端對需要認證的介面要驗證token,驗證成功接收請求

#這裡採用jsonwebtoken來產生token,

jwt.sign(payload, secretOrPrivateKey, [options, callback])
登入後複製
使用express-jwt驗證token(驗證成功會把token資訊放在request.user中)

express_jwt({
        secret: SECRET,
        getToken: (req)=> {
        if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
            return req.headers.authorization.split(' ')[1];
        } else if (req.query && req.query.token) {
            return req.query.token;
        }
        return null;
    }
    }
登入後複製
為什麼要使用redis

**採用jsonwebtoken產生token時可以指定token的有效期,並且jsonwebtoken的verify方法也提供了選項來更新token的有效期,
但這裡使用了express_jwt中間件,而express_jwt不提供方法來刷新token **

想法:

  1. 客戶端請求登入成功,產生token

  2. 將此token保存在redis中,設定redis的有效期限(例如1h)

  3. 新的請求過來,先express_jwt驗證token,驗證成功, 再驗證token是否在redis中存在,存在說明有效

  4. 有效期內客戶端新的請求過來,提取token,更新此token在redis中的有效期

  5. 客戶端退出登入請求,刪除redis中此token

具體程式碼

  • 使用mocha + supertest + should 來寫單元測試

測試覆蓋了所有接口,在開發中,因為沒什麼進度要求就慢慢寫了,寫完一個接口就去寫一個測試,測試寫也還算詳細,等測試通過了再前端調接口,整個過程還是挺有意思的

mocha 是一個node單元測試框架,類似前端的jasmine,語法也相近

supertest 用來測試node介面的函式庫

#should nodejs斷言函式庫,可讀性很高

測試的一個例子,篇幅太長,就不放在這了

#相關推薦:

React 內部機制探針

React中元件的寫法有哪些

react.js給標識ref,取得內容


以上是電影蒐集的小應用react技術棧實踐的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板