redux的简单介绍、优音乐项目

一.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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值