无标题文章

2017-06-10  本文已影响0人  子木_3558



r-music

该项目作为react技术栈的练手项目,使用了酷狗和网易云的数据。

其中酷狗的数据拉取,相对容易;网易云数据的拉取,参照:https://binaryify.github.io/NeteaseCloudMusicApi/

感谢ScorpionJay同学,该项目前期的工作,大量都由他完成。

源码地址:https://github.com/ScorpionJay/r-music

MV数据暂未合到主干:https://github.com/li772091958/r-music

前端坑多,该项目还有很多bug,欢迎一起学习交流,共同爬坑。

目录

参考文档

在线体验

效果展示

      项目说明

      技术栈

      项目结构

      项目运行

知识梳理

参考文档

开发这个项目,我参阅的学习文档如下:

React 入门实例教程:http://www.ruanyifeng.com/blog/2015/03/react

React Router 使用教程:http://www.ruanyifeng.com/blog/2016/05/react_router.html

ECMAScript 6 入门:http://es6.ruanyifeng.com/

redux中文文档:http://www.redux.org.cn/

Redux 入门教程(三)——React-Redux 的用法:http://www.ruanyifeng.com/blog/2016/09/reduxtutorialpartthreereact-redux.html

Flex 布局教程——语法篇:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

在线体验

http://cenuon.com:8666

扫码体验

效果展示

项目说明

技术栈

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

项目结构

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。

// 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+端口号访问。

proxy,设置代理,是为了解决跨域的问题。

生产环境

npm run build

该命令会将所有文件打包,并放在dist目录下。

配置nginx,设置反向代理,解决跨域问题

安装好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三者之间的关系图,如下:

通过代码,梳理redux、react-redux

注:下面代码只列出搜索功能的关键部分,源码地址:https://github.com/ScorpionJay/r-music

1. Provider

react-redux提供的Provider组件,可以让容器组件取得state。

src/index.js

import configureStore from './stores'

const store = configureStore()

上面代码中,Provider使得Router的所有子组件可以取得state。

import configureStore from './stores'为redux的store,如下:

src/store/index.js

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;

};

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 (

//...

)

}

}

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。

3. redux

src/actions/search.js

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的异步函数,一般在容器组件里会调用。

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

上面代码中,hots函数收到名为SEARCH_HOT的 Action 以后,就返回一个新的 State,作为热门搜索的结果。

在src/store/index.js中,开发环境下,引入了中间件redux-logger的createLogger,在浏览器console可以观察到每次reducer的结果,如下:

src/reducer/index.js

import { combineReducers } from 'redux'

//...

import  search from './search'

const reducers = combineReducers({

//...

search,

})

export default reducers

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State,然后View发生变化。combineReducers将多个拆分的reducer合并。

上一篇下一篇

猜你喜欢

热点阅读