該項目做爲react技術棧的練手項目,使用了酷狗和網易雲的數據。javascript
其中酷狗的數據拉取,相對容易;網易雲數據的拉取,參照: https://binaryify.github.io/NeteaseCloudMusicApi/css
感謝ScorpionJay同窗,該項目前期的工做,大量都由他完成。html
前端坑多,該項目還有不少bug,歡迎一塊兒學習交流,共同爬坑。前端
開發這個項目,我參閱的學習文檔以下:java
react + react-router + redux + webpack + ES6 + fetch + sass + flexreact
r-music │ .babelrc │ .eslintrc.js │ .gitignore │ package.json │ README.md │ server.js //node啓動腳本 │ webpack.config.js │ ├─config │ webpack.dev.js //開發環境的webpack配置文件 │ webpack.hash.js //開發環境的webpack配置文件 │ webpack.prod.js //生產環境的webpack配置文件 │ └─src │ api.js //封裝的fetch │ app.js │ config.js //api接口配置文件 │ index.hash.js │ index.js │ index.temp.hash.html │ index.temp.html │ routers.js //路由 │ storage.js //window.localStorage的各類方法 │ ├─components //組件 │ ├─containers //頁面 │ 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 //樣式文件 common.scss home.scss login.scss main.scss pagination.scss slider.scss
git clone https://github.com/ScorpionJay/r-music.git cd r-music npm install
npm run dev
該命令在package.json的scripts中,即"dev": "webpack-dev-server --config webpack.config.js --hot"
,啓動一個服務。 若是一切正常,會自動打開瀏覽器並訪問http://localhost:9999
。webpack
// config/webpack.dev.js部分代碼 devServer: { contentBase: "./src",//本地服務器所加載的頁面所在的目錄 historyApiFallback: true,//不跳轉 inline: true,//實時刷新 host: '0.0.0.0', port:9999, // 設置代理 proxy:{ "/kugou": { target: "http://m.kugou.com", changeOrigin: true, pathRewrite: {"^/kugou" : ""} } } }
由於在 config/webpack.dev.js
設置了host:'0.0.0.0',因此同局域網的其餘手機或PC也能夠經過ip+端口號訪問。nginx
proxy,設置代理,是爲了解決跨域的問題。git
npm run build
該命令會將全部文件打包,並放在dist目錄下。
安裝好nginx,找到nginx.conf,並添加以下代碼(與默認80端口的server同級,這裏只列出了主要的配置項)
server { #端口號 listen 8666; #項目根目錄位置 root E:/r-music/dist #訪問首頁文件 location / { index index.html try_files $uri /index.html // 解決刷新頁面404問題 } #緩存靜態文件,30d表示30天,可按需調整大小 location ~ ^/(images|javascript|js|css|flash|media|static)/ { expires 30d; } #設置代理,解決跨域 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; } #網易MV的數據,詳見https://binaryify.github.io/NeteaseCloudMusicApi/ location ^~/NeteaseCloudMusicApi/{ rewrite ^/NeteaseCloudMusicApi/(.*)$ /$1 break; proxy_pass http://www.cenuon.com:3000; } }
重啓nginx便可
nginx -s reload
經過我本身的理解方式,簡單地整理了react、redux、react-redux三者之間的關係圖,以下:
注:下面代碼只列出搜索功能的關鍵部分,源碼地址:https://github.com/ScorpionJay/r-music
react-redux提供的Provider
組件,可讓容器組件取得state
。
import configureStore from './stores' const store = configureStore() <Provider store={store}> <Router history={browserHistory} routes={routers} /> </Provider>
上面代碼中,Provider
使得Router
的全部子組件能夠取得state
。
import configureStore from './stores'
爲redux的store,以下:
import reducers from '../reducers/index'; export default function(initialState) { let createStoreWithMiddleware // 判斷環境是否logger if (process.env.NODE_ENV === 'production') { createStoreWithMiddleware = applyMiddleware(thunk)(createStore); }else{ //開發環境在console能夠看到整個狀態樹的實時日誌 const logger = createLogger(); createStoreWithMiddleware = applyMiddleware(thunk,logger)(createStore); } let store = createStoreWithMiddleware(reducers, initialState); return store; };
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)
react-redux的connect
方法,用於從 UI 組件生成容器組件。
上面代碼中,connect(map)(Search)
使得組件Search
能夠經過props
取得map
返回的數據。
dispatch(searchHotAPI())
和dispatch(clearSearchResultAPI())
,獲取數據並分發action。
import Config from '../config' import { spin,spinHidden } from './spin' import api from '../api' import Storage from '../storage' //定義常量 export const SEARCH_HOT = 'SEARCH_HOT' export const SEARCH_RESULT = 'SEARCH_RESULT' //actionCreator,這裏是一個函數,返回action對象 const searchHot = (obj) => {return {type:SEARCH_HOT, obj}} const searchResult = (obj) => {return {type:SEARCH_RESULT, obj}} //搜索熱門關鍵字 export function searchHotAPI(){ return async dispatch => { try{ let hots = await api( Config.searchHotAPI ); dispatch(searchHot(hots.data.info)); } catch(error) { console.log(error); } } } //經過關鍵字搜索 export function searchResultAPI(keyword,page){ return async dispatch => { try { let result = await api( Config.searchResultAPI, 'get', {keyword,page} ); //搜索歷史存到localStorage setSearchHistory(keyword); dispatch(searchResult(result.data.info)); } catch(error) { console.log(error); } } }
上面代碼中,searchHot
和searchResult
都是Action creator,即分別返回一個action。
action是一個帶有type關鍵字的對象,如{type:SEARCH_HOT, obj}
和{type:SEARCH_RESULT, obj}
。
searchHotAPI
和searchResultAPI
分別返回一個獲取數據並分發action的異步函數,通常在容器組件裏會調用。
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
上面代碼中,hots
函數收到名爲SEARCH_HOT
的 Action 之後,就返回一個新的 State,做爲熱門搜索的結果。
在src/store/index.js
中,開發環境下,引入了中間件redux-logger
的createLogger
,在瀏覽器console能夠觀察到每次reducer的結果,以下:
import { combineReducers } from 'redux' //... import search from './search' const reducers = combineReducers({ //... search, }) export default reducers
Reducer 是一個函數,它接受 Action 和當前 State 做爲參數,返回一個新的 State,而後View發生變化。 combineReducers
將多個拆分的reducer合併。