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.
- Source address: https://github.com/ScorpionJay/r-music
- MV data is not currently aligned with the backbone: https://github.com/li772091958/r-music
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:
- Introduction to React Example Tutorial: http://www.ruanyifeng.com/blog/2015/03/react
- React Router uses tutorials: http://www.ruanyifeng.com/blog/2016/05/react_router.html
- ECMAScript 6 Getting Started: http://es6.ruanyifeng.com/
- Chinese redux documents: http://www.redux.org.cn/
- Introduction to Redux (3) - Use of React-Redux: http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html
- Flex Layout Tutorial - Grammar Text: http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
Online Experience
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.