Music playback SPA using react + redux + react-router

r-music

order

As a hands-on project for the react technology stack, the project uses data from Cool Dogs and NetEase Cloud.

Among them, pulling data from cool dogs is relatively easy; pulling data from NetEase cloud, refer to: https://binaryify.github.io/NeteaseCloudMusicApi/

Thank ScorpionJay Classmate, he did a lot of work in the early stage of the project.

There are many pits in the front end and many bug s in this project. Welcome to learn and communicate and climb pits together.

Catalog

Reference Documents

To develop this project, I refer to the following learning documents:

Online Experience

http://cenuon.com:8666

Effect Display

Project Description

technology stack

react + react-router + redux + webpack + ES6 + fetch + sass + flex

Project structure

r-music
│  .babelrc
│  .eslintrc.js
│  .gitignore
│  package.json
│  README.md
│  server.js                    //node startup script
│  webpack.config.js            
│  
├─config
│      webpack.dev.js           //webpack configuration file for development environment
│      webpack.hash.js          //webpack configuration file for development environment
│      webpack.prod.js          //webpack configuration file for production environment
│           
└─src
    │  api.js                   //Encapsulated fetch
    │  app.js                   
    │  config.js                //api interface profile
    │  index.hash.js
    │  index.js
    │  index.temp.hash.html
    │  index.temp.html
    │  routers.js               //Route
    │  storage.js               //Various methods of window.localStorage
    │      
    ├─components               //assembly
    │          
    ├─containers               //page
    │      account.js
    │      album.js
    │      friend.js
    │      home.js
    │      music.js
    │      play.js
    │      
    ├─images
    │      favicon.ico
    │      
    ├─json
    │      home.json
    │       
    ├─actions                 //redux -- action
    │      album.js
    │      dialog.js
    │      home.js
    │      .
    │      .     
    ├─reducers                //redux -- reducer
    │      album.js
    │      dialog.js
    │      home.js
    │      index.js
    │      login.js
    │      message.js
    │      music.js
    │      spin.js
    │      user.js
    │      
    stores                     //redux  -- store
    │      index.js
    │      
    └─sass                    //Style file
            common.scss
            home.scss
            login.scss
            main.scss
            pagination.scss
            slider.scss

Project Run

git clone https://github.com/ScorpionJay/r-music.git
cd r-music
npm install

Local Development Environment

npm run dev

This command starts a service in the scripts of package.json, namely "dev": "webpack-dev-server --config webpack.config.js --hot". If everything works, the browser opens automatically and accesses http://localhost:9999.

// Cong/webpack.dev.js Part Code
devServer: {
    contentBase: "./src",//The directory where the pages loaded by the local server are located
    historyApiFallback: true,//not taken
    inline: true,//Real-time refresh
    host: '0.0.0.0',
    port:9999,
    // Set up proxy
    proxy:{
        "/kugou": {
            target: "http://m.kugou.com",
            changeOrigin: true,
            pathRewrite: {"^/kugou" : ""}
        }
    }
}

Because config/webpack.dev.js has host:'0.0.0.0', other mobile phones or PC s on the same LAN can also be accessed via ip+port number.

Proxy, set proxy, is to solve cross-domain problems.

production environment

npm run build

This command packages all the files and places them in the dist directory.

Configure nginx, set up reverse proxy, solve cross-domain problems

Install nginx, find nginx.conf, and add the following code (equivalent to the server on the default port 80, where only the main configuration items are listed)

server {
    #Port number
    listen 8666;
    #Project Root Location
    root E:/r-music/dist
    #Access Home Page File
    location / {
        index index.html
          try_files $uri  /index.html    // Resolve Refresh Page 404 issue
   }
    #Cache static files, 30d for 30 days, resizable as needed
    location ~ ^/(images|javascript|js|css|flash|media|static)/ {
        expires 30d; 
    }
    #Set up proxy to resolve cross-domain issues
    location ^~/kugou/{
      rewrite ^/kugou/(.*)$ /$1 break;
      proxy_pass http://m.kugou.com;
    }

    location ^~/ad/{
      rewrite ^/ad/(.*)$ /$1 break;
      proxy_pass http://ads.service.kugou.com;
    }

    location ^~/musicSearch/{
      rewrite ^/musicSearch/(.*)$ /$1 break;
      proxy_pass http://mobilecdn.kugou.com;
    }
    location ^~/mobilecdn/{
        rewrite ^/mobilecdn/(.*)$ /$1 break;
        proxy_pass http://mobilecdn.kugou.com;
    }
    #Netease MV data, see https://binaryify.github.io/NeteaseCloudMusicApi/
    location ^~/NeteaseCloudMusicApi/{
        rewrite ^/NeteaseCloudMusicApi/(.*)$ /$1 break;
        proxy_pass http://www.cenuon.com:3000;
    }
}

Restart nginx

nginx -s reload

Combing Knowledge

Flow Diagram

Through my own understanding, I have simply sorted out the diagram of the relationship among react, redux, react-redux, as follows:

Comb redux, react-redux through code

Note: The following code lists only the key parts of the search function, source address: https://github.com/ScorpionJay/r-music

1. Provider

The Provider component provided by react-redux allows container components to get state.

src/index.js
import configureStore from './stores'
const store = configureStore()

<Provider store={store}>
    <Router history={browserHistory} routes={routers} />
</Provider>

In the code above, the Provider makes the state available to all the subcomponents of Router.

The store whose import configureStore from'. /stores'is redux is as follows:

src/store/index.js
import reducers from '../reducers/index';

export default function(initialState) {
    let createStoreWithMiddleware

    // Determine whether the environment is logger or not
    if (process.env.NODE_ENV === 'production') {
        createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
    }else{
        //The development environment can see the real-time log of the entire status tree in console
        const logger = createLogger();
        createStoreWithMiddleware = applyMiddleware(thunk,logger)(createStore);
    }
    let store = createStoreWithMiddleware(reducers, initialState);
    return store;
};

2. react: Component

src/containers/search.js
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'

import { searchHotAPI,searchResultAPI,clearSearchResultAPI} from '../actions/search'

class Search extends Component {

  constructor(props) {
    super(props);
  }

  componentDidMount(){
    const { dispatch } = this.props
    dispatch(searchHotAPI())
  }

  searchEvt(keyword,page=1){
    const { dispatch } = this.props;
    keyword = keyword || this.refs.keyword.value
    if(keyword!=''){
      dispatch(searchResultAPI(keyword, page));
    }else{
      dispatch(clearSearchResultAPI());
    }
    this.refs.keyword.value = keyword;
  }

  render() {
    const { dispatch,controll,search } = this.props;
    return (
      <div className='root' style={{fontSize:'1.2rem'}}>

        //...

      </div>
    )
  }
}

function map(state) {
  return {
    search: state.search,
    controll: state.music.controll
  }
}

export default connect(map)(Search)

The connect method of react-redux, used to generate container components from UI components.

In the code above, connect(map)(Search) allows the component Search to get the data returned by the map through props.

dispatch(searchHotAPI()) and dispatch (clearSearchResult API ()), get data and distribute action s.

3. redux

src/actions/search.js
import Config from '../config'
import { spin,spinHidden } from './spin'
import api from '../api'

import Storage from '../storage'

//Const
export const SEARCH_HOT = 'SEARCH_HOT'
export const SEARCH_RESULT = 'SEARCH_RESULT'

//actionCreator, here is a function that returns an action object
const searchHot = (obj) => {return {type:SEARCH_HOT, obj}}
const searchResult = (obj) => {return {type:SEARCH_RESULT, obj}}

//Search for popular keywords
export function searchHotAPI(){
    return async dispatch => {
        try{
            let hots = await api( Config.searchHotAPI );
            dispatch(searchHot(hots.data.info));
        } catch(error) {
            console.log(error);
        }
    }
}

//Search by keyword
export function searchResultAPI(keyword,page){
    return async dispatch => {
        try {
            let result = await api( Config.searchResultAPI, 'get', {keyword,page} );
            //Search history stored in local Storage
            setSearchHistory(keyword);
            dispatch(searchResult(result.data.info));
        } catch(error) {
            console.log(error);
        }
    }
}

In the code above, both search Hot and search Result are Action creator s, which return one action, respectively.

An action is an object with a type keyword, such as {type:SEARCH_HOT, obj} and {type:SEARCH_RESULT, obj}.

The searchHot API and the searchResult API return an asynchronous function that retrieves data and distributes action s, respectively, and is typically called in container components.

src/reducer/search.js
import { combineReducers } from 'redux'
import { SEARCH_HOT,SEARCH_RESULT } from '../actions/search'

function hots(state = [], action){
  switch(action.type) {
    case SEARCH_HOT:
      return action.obj;
    default:
      return state;
  }
}

function result(state = [], action){
  switch(action.type) {
    case SEARCH_RESULT:
      return action.obj;
    default:
      return state;
  }
}

const Reducers = combineReducers({
  hots,result,
})

export default Reducers

In the code above, when the hots function receives an Action named SEARCH_HOT, it returns a new State as the result of a popular search.

In src/store/index.js, createLogger, redux-logger middleware, is introduced in the development environment. The results of each reducer can be observed in console browser as follows:

src/reducer/index.js
import { combineReducers } from 'redux'
//...
import  search from './search'

const reducers = combineReducers({
  //...
  search,
})

export default reducers

Reducer is a function that takes an Action and the current State as parameters, returns a new State, and the View changes. combineReducers merges multiple split reducer s.

Keywords: React Webpack github Nginx

Added by Phairest on Sun, 30 Jun 2019 20:38:10 +0300