一.redux
1.下载安装
cnpm i redux --save
2.核心
-
Store
-
createStore
-
import { createStore } from 'redux'; //得到store对象 const store = createStore(fn);
-
-
State
-
const state = [] /得到状态数据 const state = store.getState();
-
-
Action
-
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,
-
const action = {type:'传递的类型',data:传递的数据} action是由view来触发的
-
-
store.dispatch()
-
在view层进行触发store.dispatch() eg: const action = {type:'del',data:index} store.dispatch(action)
-
-
Reducer函数
-
Reducer 1.是一个函数, 2.接受 Action 和当前 State 作为参数, 3.返回一个新的 State。
-
-
store.subscribe()
-
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
-
案例
import React, { Component } from 'react'
import store from '../utils/store'
export default class Brand extends Component {
action = {};
constructor(){
super()
this.state = {
name:'',
current:{},//存放编辑的数据
}
}
// 改变name的值
change(e){
this.setState({name:e.target.value})
}
// 做name提交方法
submit(e){
if(e.keyCode === 13){
let name = e.target.value.trim();
if(name === ''){
return alert('请输入品牌名称')
}
if(this.state.current.id){
this.action = {type:'brand_update',data:[this.state.current,name]};
this.setState({current:{}});
}else{
this.action = {type:'brand_add',data:name};
}
store.dispatch(this.action)
this.setState({name:''})
}
}
edit(row){
this.setState({name:row.name});
this.setState({current:row});
}
del(index){
store.dispatch({type:'brand_del',data:index})
}
render() {
// console.log(store.getState());
const {BrandList} = store.getState();
return (
<div className="container">
<h2 style={{textAlign:'center'}}>品牌管理</h2>
<div className="well">
<input type="text" value={this.state.name} onKeyUp={(e)=>this.submit(e)} onChange={(e)=>this.change(e)} placeholder="请输入品牌名称" className="form-control"/>
</div>
<table className="table table-bordered table-hover">
<thead>
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{BrandList.map((item,index)=>(
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.name}</td>
<td>
<button className="btn btn-success" onClick={()=>this.edit(item)}>编辑</button>
<button className="btn btn-danger" onClick={()=>this.del(index)}>删除</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
reducer.js
// 合并reducer函数
import {combineReducers} from 'redux'
// 定义状态数据
const state = [
{
id:2,
name:'特斯拉',
},
{
id:1,
name:'宝马',
}
]
// 声明一个reducer函数,函数是自定义的
//须有两个参数:state action
/**
* 参数一: init 是上一次更新的数据
* 参数二: action:{type:'类型',data:数据}
*/
function BrandList(init=state,action){
switch(action.type){
case 'brand_add':
// 添加品牌
// state状态数据不能修改
const list = [...init];
let id = list.length > 0 ? list[0].id+1 : 1;
let name = action.data
let params = {
id,
name
}
list.unshift(params);
console.log(list);
return list
case 'brand_update':
// 修改品牌
const list1 = [...init];
let newList = list1.map(item=>{
if(item.id === action.data[0].id){
return {
...item,
name:action.data[1]
}
}else{
return item
}
})
return newList
case 'brand_del':
// 删除品牌
const list2 = [...init];
list2.splice(action.data,1)
return list2
default:
return state
}
}
export default combineReducers({BrandList:BrandList})
store.js
import {createStore} from 'redux';
// 导入reducer函数
import reducer from './reducer'
// console.log(reducer);
// 生成store对象
// 参数: reducer函数
const store = createStore(reducer);
// 导出
export default store
二.优音乐项目
1.启动服务端
npm i
npm start
服务端运行在: http://localhost:4000
2.创建项目
create-react-app music
cnpm i axios antd @ant-design/icons react-router-dom --save
3.修改端口
{
"scripts":
"start": "set port=5001&node scripts/start.js",
}
npm start
4.引入样式文件
// 引入重置样式
import './assets/css/reset.css'
// 引入antd
import 'antd/dist/antd.css';
5.Layout.jsx
import React, { Component } from 'react'
import {HashRouter as Router,NavLink,Switch,Route,Redirect} from 'react-router-dom'
import { PageHeader,Button } from 'antd';
import './layout.css'
import asyncComponents from '../../utils/asyncComponents'
const Recommend = asyncComponents(()=>import('../Recommend/Recommend'));
const Hot = asyncComponents(()=>import('../Hot/Hot'));
const Search = asyncComponents(()=>import('../Search/Search'));
const Play = asyncComponents(()=>import('../Play/Play'));
export default class Layout extends Component {
render() {
return (
<Router>
<div className="layout">
{/* 页头 */}
<div className="header">
<PageHeader
className="site-page-header"
title="优音乐"
/>
<Button>下载App</Button>
</div>
{/* 导航 */}
<div className="nav">
<NavLink to="/recommend" activeClassName='select'>推荐</NavLink>
<NavLink to="/hot" activeClassName='select'>热歌</NavLink>
<NavLink to="/search" activeClassName='select'>搜索</NavLink>
</div>
{/* 二级路由出口 */}
<Switch>
<Route path="/recommend" component={Recommend}></Route>
<Route path="/hot" component={Hot}></Route>
<Route path="/search" component={Search}></Route>
<Route path="/play/:ids" component={Play}></Route>
<Redirect to="/recommend"></Redirect>
</Switch>
</div>
</Router>
)
}
}
layout.css
/* 页头样式 */
.header{
background: #FFA500;
position: relative;
}
.header .ant-page-header-heading-title{
color: white;
}
.header .ant-btn{
width: 80px;
height: 30px;
position: absolute;
right:10px;
top:50%;
transform: translateY(-50%);
border-radius: 20px;
background: white;
}
.header .ant-btn span{
color:#FFA500;
}
/* 页头样式结束 */
/* 导航开始 */
.layout .nav{
display: flex;
}
.layout .nav a{
flex:1;
text-align: center;
line-height: 60px;
color:#333;
font-size:16px;
}
.layout .nav .select{
border-bottom:3px solid #FFA500;
}
/* 导航结束 */
6.recommend.jsx
import React, { Component } from 'react'
import { Carousel,Row, Col,List } from 'antd';
import {PlayCircleOutlined} from '@ant-design/icons'
import './recommend.css'
const contentStyle = {
height: '160px',
color: '#fff',
lineHeight: '160px',
textAlign: 'center',
background: '#364d79',
};
const style = { background: '#0092ff', padding: '8px 0' };
const data = [
'Racing car sprays ',
'Japanese princess',
'Australian walks ',
'Man charged over',
'Los Angeles battles',
];
export default class Recommend extends Component {
constructor(){
super()
this.state = {
bannerList:[],//轮播图列表
recommend:[],//推荐歌单
newMusic:[],//最新音乐
}
}
render() {
const {bannerList,recommend,newMusic} = this.state;
return (
<div className="recommend">
{/* 轮播图 */}
<div className="banner">
<Carousel autoplay>
{bannerList.map((item,index)=>(
<div key={index}>
<img style={{width:'100%'}} src={item.imageUrl} alt=""/>
</div>
))}
</Carousel>
</div>
{/* 推荐音乐 */}
<div className="section">
<h3>推荐音乐</h3>
<Row gutter={16}>
{recommend.map(item=>(
<Col key={item.id} className="gutter-row" span={8}>
<div>
<img style={{width:'100%'}} src={item.picUrl} alt=""/>
<h3 style={{textAlign:'center'}}>{item.name.slice(0,10)}</h3>
</div>
</Col>
))}
</Row>
</div>
{/* 最新歌单 */}
<div className="section">
<h3>最新歌单</h3>
<List
dataSource={newMusic}
renderItem={item => (
<List.Item
onClick={()=>this.props.history.push(`/play/${item.id}`)}
key={item.id}
actions={[<PlayCircleOutlined style={{fontSize:'24px'}} />]}
>
{item.name}
</List.Item>
)}
/>
</div>
</div>
)
}
// 获取轮播图
requestBanner(){
this.$axios.get('/banner').then(res=>{
if(res.data.code === 200){
this.setState({bannerList:res.data.banners})
}
})
}
// 获取推荐歌单
requestCommend(){
this.$axios.get('/personalized').then(res=>{
if(res.data.code === 200){
this.setState({recommend:res.data.result})
}
})
}
// 获取最新音乐
requestNewMusic(){
this.$axios.get('/personalized/newsong').then(res=>{
if(res.data.code === 200){
this.setState({newMusic:res.data.result})
}
})
}
// 组件挂载完成
componentDidMount(){
// 1.发起轮播图请求
this.requestBanner()
// 2.发起推荐歌单请求
this.requestCommend()
// 3.发起最新音乐请求
this.requestNewMusic()
}
}
recommend.css
.recommend .section{
padding: 5px 10px;
}
.recommend .section > h3{
border-left: 3px solid #FFA500;
padding-left: 10px;
}
request.js
import React from 'react'
import axios from 'axios'
// 配置代理
axios.defaults.baseURL = "http://localhost:4000";
// 设置响应拦截
axios.interceptors.response.use(res=>{
console.group('本次响应地址为:'+res.config.url);
console.log(res);
return res;
})
React.Component.prototype.$axios = axios;
export default axios;
7.hot.jsx
import React, { Component } from 'react'
import {List} from 'antd'
import {PlayCircleOutlined} from '@ant-design/icons'
import './hot.css'
const data = [
'Racing car sprays ',
'Japanese princess',
'Australian walks ',
'Man charged over',
'Los Angeles battles',
];
export default class Hot extends Component {
constructor(){
super()
this.state = {
hotList:[],//热歌
coverImg:'',//封面图
}
}
render() {
const {hotList,coverImg} = this.state;
return (
<div className="hot">
<div className="img">
<img style={{width:'100%',height:'100%'}} src={coverImg} alt="" />
</div>
<List
dataSource={hotList}
renderItem={item => (
<List.Item
key={item.id}
onClick={()=>this.props.history.push(`/play/${item.id}`)}
actions={[<PlayCircleOutlined style={{fontSize:'24px'}} />]}
>
{item.name}
</List.Item>
)}
/>
</div>
)
}
requestHot(){
this.$axios.get('/top/list',{params:{idx:1}}).then(res=>{
if(res.data.code === 200){
this.setState({hotList:res.data.playlist.tracks,coverImg:res.data.playlist.coverImgUrl})
}
})
}
componentDidMount(){
// 发起热歌请求
this.requestHot()
}
}
hot.css
.hot .img{
width:100vw;
height:300px;
}
.hot .ant-list-items{
margin:0 15px;
}
8.search.jsx
import React, { Component } from 'react'
import { Input,Divider,Button,Space,List } from 'antd';
import {SearchOutlined,PlayCircleOutlined} from '@ant-design/icons'
import './search.css'
const data = [
'Racing car sprays ',
'Japanese princess',
'Australian walks ',
'Man charged over',
'Los Angeles battles',
];
export default class Search extends Component {
constructor(){
super()
this.state = {
keywords:'',//关键字
songs:[],//歌曲列表
hotSearch:[],//热门搜索
}
}
change(e){
this.setState({keywords:e.target.value})
}
submit(e){
if(e.keyCode === 13){
let keywords = e.target.value.trim();
if(keywords === ''){
return alert('请输入关键字')
}
// 根据关键字发起歌曲列表请求
this.getSongs(keywords)
}
}
render() {
const {keywords,songs,hotSearch} = this.state
return (
<div className="search">
<div className="sear">
<Input size="large" onKeyUp={(e)=>this.submit(e)} value={keywords} onChange={(e)=>this.change(e)} placeholder="请输入关键字" prefix={<SearchOutlined />} />
</div>
<Divider />
<Space>
{hotSearch.map((item,index)=>(
<Button onClick={()=>this.getKeyWords(item.first)} key={index} shape="round" size="middle">{item.first}</Button>
))}
</Space>
<List
dataSource={songs}
renderItem={item => (
<List.Item
onClick={()=>this.props.history.push(`/play/${item.id}`)}
key={item.id}
actions={[<PlayCircleOutlined style={{fontSize:'24px'}} />]}
>
{item.name}
</List.Item>
)}
/>
</div>
)
}
// 发起获取歌曲列表请求根据关键字
getSongs(keywords){
this.$axios.get('/search',{params:{keywords}}).then(res=>{
this.setState({songs:res.data.result.songs})
})
}
getKeyWords(keywords){
this.setState({keywords})
this.getSongs(keywords)
}
requestHotSearch(){
this.$axios.get('/search/hot').then(res=>{
this.setState({hotSearch:res.data.result.hots})
})
}
componentDidMount(){
// 发起热门搜索请求
this.requestHotSearch()
}
}
search.css
.search .sear{
margin:10px 10px;
}
.search .sear .ant-input-affix-wrapper{
border-radius: 20px;
}
.search .ant-space-align-center{
display: flex;
flex-wrap: wrap;
}
.search .ant-space-item{
margin:10px;
}
.search .ant-list-items{
margin:0 15px;
}
9.play.jsx
import React, { Component } from 'react'
import './play.css'
export default class Play extends Component {
constructor(){
super()
this.state = {
// 播放按钮的状态 false:为播放 true:正在播放
play:false,
// 控件旋转角度
rotate:-15,
detail:{},//歌曲详情
lyric:[],//歌词
}
}
toggle(){
this.setState({play:!this.state.play,rotate:this.state.rotate===0?-15:0})
}
render() {
const {play,rotate,detail,lyric} = this.state;
const styleObj ={ transform:`rotate(${rotate}deg)`}
return (
<div className="play">
{/* 播放控件 */}
<div className="tools" style={styleObj}></div>
<div className="box">
<img src={detail.picUrl} alt="" />
<div onClick={()=>this.toggle()} className={['btn',play ? 'btn-play' : 'btn-stopPlay'].join(' ')}></div>
</div>
<div className="section">
<h3>{detail.name}</h3>
{lyric.map((item,index)=>(
<div key={index}>{item}</div>
))}
</div>
</div>
)
}
// 获取详情
requestDetail(ids){
this.$axios.get('/song/detail',{params:{ids}}).then(res=>{
this.setState({detail:res.data.songs[0].al})
})
}
// 获取歌词
getLyric(id){
this.$axios.get('/lyric',{params:{id}}).then(res=>{
let arr1 = this.lyricFmt( res.data.lrc.lyric)
this.setState({lyric:arr1})
})
}
//处理歌词
lyricFmt(str){
let reg = /(\[.*\])(.*)/g
let arr = [];
str.replace(reg,function(a,b,c){
arr.push(c)
})
return arr
}
componentDidMount(){
// 获取详情
const ids = this.props.match.params.ids;
this.requestDetail(ids)
// 获取歌词
this.getLyric(ids)
}
}
play.css
.play{
background: #7F7F7F;
overflow: hidden;
position: relative;
}
.play .box{
width:300px;
height: 300px;
/* border: 1px solid red; */
background: url('../../assets/imgs/disc-ip6.png') no-repeat center center;
background-size: 100%;
border-radius: 50%;
margin:60px auto;
position: relative;
}
.play .box img{
width:200px;
height: 200px;
border-radius: 50%;
position: absolute;
left:50%;
top:50%;
transform: translate(-50%,-50%);
}
/* 播放按钮开始 */
.play .box .btn{
width:40px;
height: 40px;
background: url('../../assets/imgs/list_sprite.png') no-repeat;
background-size: 100%;
position: absolute;
left:50%;
top:50%;
transform: translate(-50%,-50%);
z-index:1;
}
.play .box .btn-stopPlay{
background-position: left top;
}
.play .box .btn-play{
background-position: left -41px;
}
/* 播放按钮结束 */
/* 播放控件 */
.play .tools{
width:100px;
height: 140px;
background: url('../../assets/imgs/needle-ip6.png');
background-size: 100%;
position: absolute;
left:50%;
top:0;
z-index:1;
/* transform: rotate(0deg); */
transform-origin: left top;
}
.play .section{
text-align: center;
color: white;
}
.play .section h3{
color: white;
}