Practice of react technology stack (a movie collection application from front to back)

Time:2021-4-20

Previously, I made a good small application for movie collection. The front end uses react, and the back end uses express + mongodb. Recently, I changed the state management between components to Redux, and added Redux saga to manage asynchronous operations, recording some summaries

Online addressMobile mode

Source code

major function

  • Crawl Douban movie information and input it into mongodb
  • Movie list display, classification, search
  • Film details display and accessories management
  • Register, login
  • Authority control, ordinary users can enter, collect, administrator input, modify, delete
  • User center, my favorites list

Practice of react technology stack (a movie collection application from front to back)

Some summary

front end

The front end uses react, Redux and Redux saga to summarize Redux briefly, and record a problem of dependency between front and back interface calls

  • redux

In a word, to sum up Redux, what I think is to level off the vertical props passing between components and the entanglement of state love and hate between parent-child components, and turn a vertical relationship intoMultiple components interact directly with an independent state objectAfter that, the code structure really looks clearer.

The core concept of Redux, action , reducer , And store

Action means that I want to operate a state. How to operate is a matter of the reducer. All the states are stored in the store. The store sends out actions and the specified reducer handles them

In this way, the original complex business logic processing is changed and limited to action and reducer, and the components look very clean. As a matter of fact, it’s complicated where to put the complicated things, but now it’s clearer

The disadvantage of using Redux is that it is too cumbersome to define various actions and connect various components….. Now there’s another mobx. I don’t know why. Anyway, everyone agreed~

  • redux-saga

Redux saga is used to deal with asynchronous calls. With the help of generator, it makes asynchronous code look more concisetake,takeLatest,takeEvery,put,call,fork,selectIn the process of using, we encountered a problem that the interface call has a dependency relationship , It’s interesting

Describe it :

  1. There is an interface/api/user/checkLogin, It is used to determine whether to log in or not < App ></ App > If the action is triggered in the componentdidmount of the component to initiate the request and the interface returns the login status, a request to obtain the user information is also sent
function* checkLogin() {
    const res = yield Util.fetch('/api/user/checkLogin')
    yield put(recieveCheckLogin(!res.code))
    if (!res.code) {
        // Signed in
        yield put(fetchUinfo())
    }
}
export function* watchCheckLogin() {
    yield takeLatest(CHECK_LOAGIN, checkLogin)
}
  1. Then I have a movie details page component in thecomponentDidMountInitiated by China Society/api/movies/${id}Interface to get movie information, if the user is in login status, it will also initiate an interface to get movie attachment information/api/movies/${id}/attachThe whole procedure is written in a generator
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. When the user logs in and goes to the details page, the process is normal. However, if the page is refreshed on the details page, the interface to get the attachment is not triggered because the checklogin interface has not returned the result,state.loginStatusIf the status is still false, it doesn’t go to if
  2. At the beginning, I thought about how to control the sequence of yield in some generators (if the user does not log in, send another check)_ The result is returned to the process and continues), but check exists_ Login is called twice. If you log in, you will call the interface to get the user information again. This is definitely not good
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) {
    //// when refreshing the page, if the checklogin interface has not returned data or sent data, a checklogin should be triggered
    //// only when checklogin is returned can the login status be obtained
    //     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. The ultimate solution , Decompose the responsibility of the generator, and trigger the action of getting attachment appropriately in componentwillupdate
// Separate the action of getting the attachment from the generator getmovieinfo
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]))
    }
}

//In component
componentWillUpdate(nextProps) {
        if (nextProps.loginStatus && (nextProps.movieInfo!==this.props.movieInfo)) {
            //Is the login status, and movieinfo has returned
            const { id } = this.props.match.params
            this.props.loadMovieAttach(id)
        }
}
  1. In summary, the hook function of components should be used reasonably, and not too many operations should be handled in generator to increase flexibility

back-end

The back end uses express and mongodb, as well as redis , The main technical points are as followsUse PM2 to manage node application and deployment codeOpening identity authentication in mongodb, using token + redis for identity authentication, and writing unit tests in node are worth recording

  • Using JWT + redis to do token based user authentication

Authentication process based on token

  1. Client initiates login request
  2. Server authentication user name and password
  3. After verification, the server generates a token and responds to the client
  4. This token is carried in the header of every request after the client
  5. The server should verify the token of the interface to be authenticated, and verify that the request is received successfully

Here we usejsonwebtokenTo generate the token,

jwt.sign(payload, secretOrPrivateKey, [options, callback])

useexpress-jwtVerify the token (if the verification is successful, the token information will be put in the request.user (middle)

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;
    }
    }

Why use redis

** When using JSON webtoken to generate a token, you can specify the validity period of the token, and the verify method of JSON webtoken also provides options to update the validity period of the token,
But express is used here_ JWT middleware, express_ JWT does not provide a method to refresh the token**

Thinking:

  1. The client requests login successfully and generates token
  2. Save this token in redis and set the validity period of redis (for example, 1H)
  3. New request, express first_ JWT verifies the token, and the verification is successful. Then verify whether the token exists in redis, which indicates that it is valid
  4. During the validity period, new requests from the client come over, extract the token, and update the validity period of the token in redis
  5. The client exits the login request and deletes the token in redis

Specific code

  • Use Mocha + Supertest + should to write unit tests

The test covers all interfaces. In the development process, because there is no progress requirement, I write a test slowly. After writing an interface, I write a test, and the test writing is fairly detailed. When the test is passed, I call the front-end interface again. The whole process is very interesting

Mocha is a node unit testing framework, similar to jasmine on the front end, with similar syntax

The library used by Supertest to test the node interface

Should nodejs assertion library with high readability

An example of a test is too long to be testedPut it hereIt’s over

last

If you like, you canfollowNext, in case of Welfare…..