使用React + Redux 编写一个音乐播放器

1,458 阅读7分钟

Ahri-珊

在day32中我将总结之前写音乐播放器的经验,以实现音乐播放器的搜索功能为例子,告诉大家如何快速编写一个react+redux的项目(当然你需要以及看过react和redux的相关知识了)。

在线演示

项目源码

功能分析:

思路

1.点击搜索按钮,将搜索的值作为参数传给获取搜索结果的函数,同时通过connect将组件的dispatch方法传给这个函数。

2.使用fetch获取搜索结果,将处理后的搜索结果作为action对象的值传给reducer。

3.reducer函数根据action将搜索结果更新到store

4.搜索结果展示组件根据store的值渲染数据

项目框架

1.使用react的官方脚手架create-react-app创建一个基本的react项目框架

2.修改src中的index入口文件

修改后代码

import React from 'react';
import ReactDOM from 'react-dom';
import {Router,Route,IndexRoute,hashHistory} from 'react-router';
import { Provider } from 'react-redux'; // 引入Provider将store的数据共享给子组件

import 'bootstrap/dist/css/bootstrap.css'; 
import './index.css';
import finalCreateStore from './store/configureStore' ; // 引入增强后的store

import reducer from './reducers'  // 引入reducers集合

// 引入子组件(自定义)

import App from './App'; // 引入导航栏

import Lrc from './Lrc'; // 引入歌词展示组件

import Demo from './Demo'; // 引入卡片容器


import registerServiceWorker from './registerServiceWorker';

const store = finalCreateStore(reducer);

class Nav extends React.Component{
    render(){
        return(
		<Provider store={store}>
            <div>   
                <App/>
                {this.props.children}
            </div>   
		</Provider>           
        )
    }
}
ReactDOM.render((
    <Router history={hashHistory}>
        <Route path="/" component={Nav}>
		// 主页面

            <IndexRoute component={Demo}/> 
		// 其它页面

            <Route path="/lrc" component={Lrc}/> 
        </Route>
    </Router>
    ),document.getElementById('root')
);
registerServiceWorker();

3.补充index文件中引入的文件configureStore,这个文件用于强化store生成方法,让我们能看到store每次的变化

在src中新建文件夹store,将configureStore.js放在store文件夹下

configureStore.js代码

import thunk from 'redux-thunk' // redux-thunk 支持 dispatch function,并且可以异步调用它

import createLogger from 'redux-logger' // 利用redux-logger打印日志

import { createStore, applyMiddleware, compose } from 'redux' // 引入redux createStore、中间件及compose 

 // import DevTools from '../DevTools'引入DevTools调试组件


// 调用日志打印方法

const loggerMiddleware = createLogger

// 创建一个中间件集合

const middleware = [thunk, loggerMiddleware]

// 利用compose增强store,这个 store 与 applyMiddleware 和 redux-devtools 一起使用

const finalCreateStore = compose(
    applyMiddleware(...middleware),
    //DevTools.instrument(),

)(createStore)

export default finalCreateStore

4.补充index文件中引入的文件reducers,这个文件夹中有count.js和index.js两个文件,count用于初始化state数据并存放reducer函数,index.js用于合并reducers(我只有一个reducer所以没有用)

在src新建文件夹reducers,将count.js和index.js放在该目录下

count.js代码

// reducers/count.js

import { GETSUCCESS } from '../constants' // 引入action类型常量名


// 初始化state数据

const initialState = {
    data: [{
			id:209326,
			img:"http://p1.music.126.net/BZNpKSKkPTTv5ZnxdYAdUQ==/5850501371402948.jpg",
			lrc:"",
			name:"旅行的意义",
			singer:"陈绮贞",
			time:"4:4"
			}
			]
}

// 通过dispatch action进入


export default function update(state = initialState, action) {

    // 根据不同的action type进行state的更新

    
    switch(action.type) {
        case GETSUCCESS:
            return Object.assign({}, state, { data: action.data })
        default:
            return state
    }
}

index.js代码

// reducers/index.js

import { combineReducers } from 'redux' // 利用combineReducers 合并reducers

import update from './count' // 引入update这个reducer


export default combineReducers({
    update,
})

5.补充reducers/count.js中引入的constantes文件,这个文件用于定义action类型常量名

在src目录下新建constantes.js

constantes.js代码

// action常量

export const GETSUCCESS = 'GETSUCCESS'

6.在src目录下新建actions文件夹,这个文件夹中有一个count.js文件,这个文件用于处理组件发出的请求,将请求结果(action)传递给reducer函数

count.js代码

import { GETSUCCESS } from '../constants'  // 引入action类型名常量

import 'whatwg-fetch'  // 可以引入fetch来进行Ajax


// 这里的方法返回一个action对象


export const getSuccess = (json) => {
    return {
        type: GETSUCCESS,
        data
    }
}

好啦,到这里基本的架构就答好了,我们把其中需要的一些依赖(react-router3.0.0、redux、react-redux、bootstarp、redux-thunk、redux-logger)安装好后就可以开始写啦

功能实现

1.编写组件

搜索框:搜索想要的歌曲

组件代码

import React, { Component } from 'react';
import {Link} from 'react-router';
import { connect } from 'react-redux'; // 引入connect 
import { fetchPostsIfNeeded } from './actions/count' //引入count中的一个函数,这个函数用于获取歌曲信息
import './App.css';

class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
			value:'陈绮贞',
			class: 'collapse navbar-collapse',
			class1: 'navbar-form navbar-right',
			liked: true
        }
    }
    //获取搜索框的值
    
	handleChange=(event)=> 
	{
    	this.setState({value: event.target.value});
	}
	//控制导航栏的显示与隐藏
	
	hand=(event)=>
	{
		if(this.state.liked)
			this.setState({liked: false,class:'',class1:'nav navbar-nav search'});
		else
			this.setState({liked: true,class:'collapse navbar-collapse',class1:'navbar-form navbar-right'});
	}

 	render() {
		const { fetchPostsIfNeeded } = this.props;
		var value = this.state.value;
		return (
		  <nav className="navbar navbar-default App">
			<div className="container-fluid">
			<div className="navbar-header">
				<a className="navbar-brand" href="">Ahri-珊
				</a>
				<img src='favicon.ico' className="App-logo" alt="logo" />
				<button className="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse" onClick={this.hand}>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
                <span className="icon-bar"></span>
            </button>
			</div>
			<div className={this.state.class}>
				<ul className="nav navbar-nav">
					<li className="dropdown"><Link to="/" onClick={this.hand}>Demo</Link></li>
					<li className="dropdown"><Link to="/lrc" onClick={this.hand}>Lrc</Link></li>
				</ul>
				<div className={this.state.class1}>
					<span><input type="text" value={value} onChange={this.handleChange} /></span>
					<Link to="/"><button onClick={() => fetchPostsIfNeeded(this.state.value)}><span  onClick={this.hand}>搜索</span></button></Link>
				</div>
			</div>
			</div>
			<div>{this.props.children}</div>
		  </nav>
		);
 	 }
}

//从store中拿到data数据(我并没有用,只是为了举个又拿数据,又发请求的例子)

const getList = state => {
    return {
        lists: state.update.data
    }
}
  
//将data数据绑定到组件的this.props,并让组件可以使用fetchPostsIfNeeded函数

export default connect(
    getList, 
    {fetchPostsIfNeeded}
)(App)

歌曲信息展示卡片:显示歌曲封面、歌名、歌手名、歌曲时长,还有播放按钮。

组件代码

    class Card extends React.Component{
	constructor(props) {
    super(props);
    this.state = {};
	}
    render(){
        return(
				<div className="col-sm-3 col-xs-6 card" >
					<div className="card_box">
					  <Link to="/lrc"><img src={this.props.img} alt={this.props.name} /></Link>
					  <div  className="card_content">
						  <p>{this.props.singer}</p>
						  <Link to="/lrc"><h3>{this.props.name}</h3></Link>
						  <div>
						  <Link to="/lrc"><span className="glyphicon glyphicon-play" ></span></Link>
							<span className="time">{this.props.time}</span>
							<span className="glyphicon glyphicon-share"></span>
						  </div>
					  </div>
				  	</div>
				</div>
        )
    }
}
    

卡片容器:装卡片的大div,还要给卡片提供信息

实现思路

这个容器要给卡片提供歌曲信息,歌曲信息需要从store拿。

组件代码

import React from 'react';
import Card from './Card';
import { connect } from 'react-redux'; // 引入connect 

class Demo extends React.Component {
	 constructor(props) {
        super(props)
        this.state = {}
    }
 	 render() {
		const { data } = this.props;
		return (
		  <div className="box">
		  		{data.map((key,index) => 
				<Card key={index.toString()} url={key.url} name={key.name} time={key.time} lrc={key.lrc} img={key.img} id={key.id} singer={key.singer} />
				)}
		  </div>
		);
 	 }
	}
	
// 从store拿到data数据
const getData = state => {
    return {
        data: state.update.data
    }
}

//将拿到的数据传给组件的this.props
export default connect(
    getData
)(Demo)
    

2.在constants.js中定义请求获取搜索歌曲的action变量名

export const GETSUCCESS = 'GETSUCCESS'

3.在actions/count.js中引入这个action类型名常量并编写这个action和搜索框所用到的fetchPostsIfNeeded函数

count.js代码

import { GETSUCCESS } from '../constants'  // 引入action类型名常量

import 'whatwg-fetch'  // 可以引入fetch来进行Ajax


// 这里的方法返回一个action对象


//处理歌曲信息

export const getSuccess = (json) => {
    return {
        type: GETSUCCESS,
        data
    }
}

//获取歌曲信息

function fetchPosts(keyword) {
	//服务器搜索方法地址

	var url = 'http://youxinyu.me:3000/search?keywords='+keyword;
    return dispatch => {
        return fetch(url, {
    method: 'GET',
    mode: 'cors',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
    	},
	})
            .then((res) => { console.log(res.status); return res.json() })
            .then((data) => {
				var action = getSuccess(data);
                dispatch(action)
            })
            .catch((e) => { console.log(e.message) })
        }
}

//搜索歌曲

export function fetchPostsIfNeeded(keyword) {
    return (dispatch, getState) => {
        return dispatch(fetchPosts(keyword))
    }
}

4.在reducers/count.js文件中的初始化state,给它一个变量data存放我们搜索到的数据,并在update函数里更新state的值

count.js代码

// reducers/count.js

import { GETSUCCESS } from '../constants' // 引入action类型常量名


// 初始化state数据


const initialState = {
    data: [{
			id:209326,
			img:"http://p1.music.126.net/BZNpKSKkPTTv5ZnxdYAdUQ==/5850501371402948.jpg",
			lrc:"",
			name:"旅行的意义",
			singer:"陈绮贞",
			time:"4:4"
		}]
}

// 通过dispatch action进入

export default function update(state = initialState, action) {

    // 根据不同的action type进行state的更新

    switch(action.type) {
        case GETSUCCESS:
            return Object.assign({}, state, { data: action.data })
        default:
            return state
    }
}

好啦,到这里,我们就写好搜索功能啦,再加功能就再做一下功能实现这些步骤就可以了~

查看完整代码与示例

看代码点这里~

玩游戏点这里~

End