使用React向播放列表添加/删除曲目

时间:2017-11-03 17:59:32

标签: javascript jquery reactjs jsx

我正在尝试实施一个将搜索结果曲目列表中的歌曲添加或删除到用户自定义播放列表的过程。

我向App.js添加了名为addTrackremoveTrack的方法,用于将歌曲添加/删除到播放列表状态。应用程序将该方法通过一系列组件传递给Track。用户可以通过单击搜索结果列表中的+或 - 符号来触发.addTrack().removeTrack()方法。

我的问题是我不知道如何连接锚点标签,我有+和 - 实际添加或删除自定义播放列表中的歌曲!

您可以给予的任何帮助非常感谢。请参阅我的App.js,SearchResults.js代码和下面的跟踪:

App.js

import React, { Component } from 'react';
import {Playlist} from '../Playlist/Playlist.js';
import './App.css';
import {SearchBar} from '../SearchBar/SearchBar.js';
import {SearchResults} from '../SearchResults/SearchResults.js';



  const playlistName ="new playlist";
  const playlistTracks = [{name:'biggie',artist:'biggiesmalls',album:'reaady to die'},{name:'nas',artist:'nases',album:'illmatic'},{name:'eminem',artist:'em',album:'marshall mathers'}];


class App extends Component {

  constructor(props){
    super(props);
    this.state.searchResults = [{name:'biggiesearch',artist:'biggiesmallssearch',album:'reaady to diesearch'},{name:'nassearch',artist:'nasessearch',album:'illmaticsearch'},{name:'eminemsearch',artist:'emsearch',album:'marshall matherssearch'}]
    this.addTrack = this.addTrack.bind(this);
    this.removeTrack = this.removeTrack.bind(this);
  }

  addTrack(track){
    if(track.id !== this.state.playlistTracks){
    this.state.playlistTracks = this.state.playlistTracks.push(track);
    }
  }

  removeTrack(track){
    this.state.playlistTracks = this.state.playlistTracks.filter(track => track.id);
    //this may be wrong! (point 49 in checklist)
  }

  render() {
    return (
      <div>
        <h1>Ja<span className="highlight">mmm</span>ing</h1>
          <div className="App">
          <SearchBar onAdd={this.addTrack}/>
            <div className="App-playlist">
          <SearchResults searchResults={this.state.searchResults}/>
          <Playlist 
              playlistName={this.state.playlistName} 
              playlistTracks={this.state.playlistTracks} 
              onRemove={this.state.removeTrack}/>
            </div>
          </div>
      </div>
    );
  }
}

export default App;

Track.js

import React, { Component } from 'react';
import './Track.css';

export class Track extends Component {

  constructor(props){
    super(props);
    this.addTrack = this.addTrack.bind(this);
    this.removeTrack = this.removeTrack.bind(this);
  }

  renderAction(){
    //className = track-action
    if(Track.isRemoval === true){
      return '+'
    }
    else{
      return '-'
    }
  }


  render() {
    return (
      <div className="Track">
          <div className="Track-information">
                <h3>{this.state.track.name}</h3>
                <p>{this.state.track.artist} | {this.state.track.album}</p>
          </div>
        <a className="Track-action" >{Track.renderAction}</a>
      </div>
    );
  }
}

export default Track;

SearchResults.js

import React, { Component } from 'react';
import './SearchResults.css';
import {TrackList} from '../TrackList/TrackList.js';

export class SearchResults extends Component {
  render() {
    return (
      <div className="SearchResults">
        <h2>Results</h2>
        <TrackList tracks={this.props.searchResults} onAdd={this.props.onAdd}/>
      </div>
    );
  }
}

export default SearchResults;

曲目

import React, { Component } from 'react';
import './TrackList.css';
import {Track} from '../Track/Track.js';
//  const tracks = ['firstTrack','secondTrack','thirdTrack'];

export class TrackList extends Component {

  render() {
    return (
      <div className="TrackList">
        <Track 
           track={this.props.track} 
           onAdd={this.props.onAdd(this.props.track)} 
           onRemove={this.props.onRemove(this.props.track)}/>


             const trackItems = {this.props.tracks}.map((track) =>
                <li key={this.props.track.id}>
                  track
                    {this.props.track.name};
                    {this.props.track.artist};
                    {this.props.track.album};
                </li>
              );
     </div>
    );
  }
}

export default TrackList;

2 个答案:

答案 0 :(得分:1)

您可以通过至少两种方式解决您的用例:

  1. 子组件,其中子组件将触发父组件的回调。 (这是你的片段方法。)见下面的演示或小提琴。
  2. Redux管理播放列表作为应用程序状态的一部分。
  3. 至1.父/子组件

    就像你在代码中尝试过一样。我认为代码中唯一的问题是你如何处理状态和它背后的数据结构。切勿直接修改状态。 <删除> e.g。 this.state.playlist.push(...)不正确。

    您可以编写this.state = { }的唯一位置是构造函数。稍后,您必须始终创建一个新的状态对象,并对先前的状态进行一些更改,并使用this.setState({...})

    使用这种方法,您应该拥有在父组件状态下修改的播放列表,并且回调将根据您传递的参数创建新状态。

    回调用于将子组件与父组件连接。其中的上下文/范围设置为App组件 - 因此this.setState(...)将修改App组件的状态。它是在构造函数中使用.bind(this)完成的。

    至2. Redux

    因此,我将向您提供一些使用Redux的演示代码的信息(请参阅下面的演示或小提琴)。此外,请查看文档(Redux&amp; React-Redux)了解更多详情,因为我只能在此为您提供概述:

    引导您的应用

    ReactDOM.render(
       <Provider store={store}>
          <AppContainer />
       </Provider>
       , document.getElementById('root'));
    

    我们正在使用Provider组件将我们的应用程序作为子项包装到其中,因此我们不必手动传递商店。这是由React-Redux为我们处理的。商店在提供商内部的每个组件中都可用作上下文 - 甚至是嵌套组件。

    您通常不会直接使用上下文,因为React-Redux有一个您可以并且应该使用的帮助程序。它被称为connect。有了它,因为它已经说你可以将你的组件连接到Redux商店,并映射你的状态&amp;组件属性的方法。

    React-Redux的连接为你做了很多事情,例如:订阅播放列表道具以观察商店更改并触发组件在数据更改时重新呈现。您可以尝试添加相同的行为,无需连接,以便您可以了解更多相关信息 - 请参阅演示中的注释部分。

    根减速机

    要开始使用减速器,可以从一个减速器开始。稍后您可以组合多个减少器,例如你可以创建一个playlistReducer

    什么是reducer做什么?它是一个纯粹的javascript函数,它正在获取一个动作,并将根据动作和之前的状态返回下一个状态。永远记住要创建一个新状态,不要直接修改状态。 该操作通常包含一个类型和一些有效负载。

    纯函数意味着如果您将相同的数据传递给它并且初始状态相同,它将始终创建相同的状态。因此,请避免在reducer中使用ajax请求。减速剂需要没有副作用。

    创建Redux商店

    rootReducer传递给Redux的createStore。它将返回您传递给上述提供者的商店对象。

    创建AppContainer组件

    需要将Redux状态和调度程序连接到您的应用程序组件的道具。

    最后的想法/建议

    我会使用Redux作为播放列表管理可以很快变得更复杂,例如多个列表等。如果您只有一个播放列表,那么使用父/子方法也可以这样做。

    请查看下面的演示或以下小提琴:

    家长/儿童演示代码:

    &#13;
    &#13;
    const log = (val) => JSON.stringify(val, null, 2);
    
    const Track = (props) => {
    
    	const displayIcon = (type) => {
      	const adding = type === 'add';
      	return (
      		<span title={adding? 'add track' : 'remove track'}>{adding ? '+' : '-'}</span>
      	)
      };
      
    	return (
      	<span onClick={() => props.clickHandler(props.track)}>
          {displayIcon(props.type)} {props.track.title ||props.track.name} - {props.track.artist}
        </span>
      )
    }
    
    const Tracklist = (props) => {
    	const listType = props.listType;
    	return (
      	<div>
          {props.playlist.length === 0 ?
            ( <strong>No tracks in list yet.</strong> ) :
            ( <ul>
                
              { props.playlist.map((track) => 
              	(
                  <li key={track.id} >
                    { listType === 'playlist' ? 
                      <Track 
                        clickHandler={props.clickHandler} 
                        type="remove" 
                        track={track} /> : 
                      <Track 
                        clickHandler={props.clickHandler} 
                        type="add" 
                        track={track} /> }
                    <span>{props.isInList && props.isInList(track) ? 
                    ' - added' : null
                    }
                    </span>
                  </li>
                )
              )}
            </ul> )
          }
        </div>
      )
    }
    
    const initialState = {
    	playlist: [{
      	id: 0,
        title:'Black or White',
        artist: 'Michael Jackson'
      },
      {
      	id: 1,
        title:'Bad',
        artist: 'Michael Jackson'
      },
      ]
    };
    
    const searchData = {
        	playlist: [
          	{id: 's1', name:'biggiesearch',artist:'biggiesmallssearch',album:'reaady to diesearch'},				
            {id: 's2', name:'nassearch',artist:'nasessearch',album:'illmaticsearch'},		
          	{id: 's3', name:'eminemsearch',artist:'emsearch',album:'marshall matherssearch'}
          ]
    };
    
    class App extends React.Component {
    	constructor(props) {
      	super(props);
        this.state = initialState;
        
        this.add = this.add.bind(this);
        this.remove = this.remove.bind(this);
        this.isInList = this.isInList.bind(this);
      }
      
    	render () {
      	return (
        	<div>
           <h1>Playlist:</h1>
        	  <Tracklist 
        	    listType="playlist" 
        	    playlist={this.state.playlist} 
        	    clickHandler={this.remove}
              />
            <h1>Search result:</h1>
            <Tracklist 
              playlist={searchData.playlist}
              clickHandler={this.add} 
              isInList={this.isInList}
              />
              <pre>{log(this.state)}</pre>
           </div>
        )
      }
      
      add (newTrack) {
      	console.log('Add track', newTrack);
        if (this.state.playlist.filter(track => track.id === newTrack.id).length === 0) {
          this.setState({
            ...this.state,
    				playlist: [
            	...this.state.playlist,
              newTrack
            ]
          });
        }
      }
      
      isInList (track) {
      	// used for displayling if search result track is already in playlist
      	return this.state.playlist.filter(playlistTrack => 
        	playlistTrack.id === track.id).length > 0;
      }
      
      remove (trackToRemove) {
      	console.log('remove', trackToRemove);
        this.setState({
        	...this.state,
          playlist: this.state.playlist.filter(track => track.id !== trackToRemove.id)
        });
      }
    }
    
    ReactDOM.render(
    	<App/>,
      document.getElementById('root')
    )
    &#13;
    * {
      font-family: sans-serif;
    }
    
    h1 {
      font-size: 1.2em;
    }
    
    li {
      list-style-type: none;
    }
    &#13;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.development.js"></script>
    <div id="root"></div>
    &#13;
    &#13;
    &#13;

    Redux演示

    &#13;
    &#13;
    const TrackList = (props) => (
    	<div>
    	  {props.tracks.map((track) => (
        	<div key={track.id}>{track.title || track.name} - {track.artist} 
          {props.onAddClick ? 
          	( <button onClick={() => props.onAddClick(track)}>Add</button> ) : null }
          {props.onRemoveClick ?
          	( <button onClick={() => props.onRemoveClick(track.id)}>Remove</button> ): null
          }
          </div>
        ))}
        
        {props.tracks.length === 0 ? 
        	( <strong>No tracks.</strong> ) : null
        }
    	</div>
    )
    /*
    const App = ({playlist, add, remove}) => (
    	<div>
    	  <h2>Current playlist</h2>
        <TrackList tracks={playlist} onRemoveClick={remove}></TrackList>
        <SearchResult onAddClick={add}></SearchResult>
    	</div>
    )*/
    
    class App extends React.Component {
    	
      render () {
      	/*
        // the following code would be required if connect from react-redux is not used
        // -> subscribe to state change and update component
        // So always use React-redux's connect to simplify code
        
        const store = this.context.store;
        console.log(this.props);
        
        const select = (state) => state.playlist;
        let playlist = select(store.getState());
        
        function handleChange() {
          let previousValue = playlist
          playlist = select(store.getState())
    
          if (previousValue !== playlist) {
            console.log('playlist changed');
            // re-render
            this.forceUpdate();
          }
        }
        
        this.unsubscribe = store.subscribe(handleChange.bind(this));
        // --> also unsubscribing in unmount would be required
        */
        
        console.log('playlist render', this.props);
      	return (
        	<div>
    	  <h2>Current playlist</h2>
        <TrackList tracks={this.props.playlist} onRemoveClick={this.props.remove}></TrackList>
        <SearchResult onAddClick={this.props.add}></SearchResult>
          
          <hr/>
          <pre>
            debugging
            {JSON.stringify(store.getState(), null, 2)}  
          </pre>
    	</div>
        )
      }
    }
    
    console.log('react', PropTypes.object);
    App.contextTypes = {
    	store: PropTypes.object
    }
    
    class SearchResult extends React.Component {
    	
      constructor(props) {
      	super(props);
        
        this.searchResults = // this will be a result of a search later
        	[
          	{id: 's1', name:'biggiesearch',artist:'biggiesmallssearch',album:'reaady to diesearch'},				
            {id: 's2', name:'nassearch',artist:'nasessearch',album:'illmaticsearch'},		
          	{id: 's3', name:'eminemsearch',artist:'emsearch',album:'marshall matherssearch'}
          ];
      }
      
      render () {
     		return (
        	<div>
        	  <h2>Search results: </h2>
        	  <TrackList tracks={this.searchResults} onAddClick={this.props.onAddClick}>
        	  </TrackList>
          </div>
        ) 	
      }
    }
    
    const initialState = {
    	playlist: [{
      	id: 0,
        title:'Michal Jackson',
        artist: 'Black or White'
      },
      {
      	id: 1,
        title:'Michal Jackson',
        artist: 'Bad'
      },
      ]
    }
    const rootReducer = (state = initialState, action) => {
    	const newTrack = action.track;
    	switch(action.type) {
      	case 'ADD':
        	// only add once
          if (state.playlist.filter(track => action.track.id === track.id).length > 0) {
          	return state; // do nothing --> already in list 
          }
          
      		return {
          	...state,
            playlist: [
              ...state.playlist,
              {
                id: state.playlist.length,
                ...newTrack
              }
            ]
          };
        case 'REMOVE':
        	 console.log('remove', action.id)
           return {
              ...state,
              playlist: state.playlist.filter((track) => track.id !== action.id)
           };
        default:
        	return state;
      }
    }
    
    const store = Redux.createStore(rootReducer)
    
    const Provider = ReactRedux.Provider
    
    // actions
    const addToList = (track) => {
    	return {
      	type: 'ADD',
      	track
      };
    }
    
    const removeFromList = id => {
    	return {
      	type: 'REMOVE',
      	id
      };
    }
    
    const AppContainer = ReactRedux.connect(
    	// null // --> set to null if you're not using mapStateToProps --> manually handling state changes required (see comment in App)
      (state) => { return { playlist: state.playlist } } // mapStateToProps
      ,
      (dispatch) => {
      	return {
          add: track => dispatch(addToList(track)),  // mapDispatchToProps
          remove: id => dispatch(removeFromList(id)) 
      	}
      }
    )(App)
    
    console.log(Provider);
    
    ReactDOM.render(
    	<Provider store={store}>
        <AppContainer />
      </Provider>
    , document.getElementById('root'));
    &#13;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/prop-types/prop-types.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js"></script>
    
    <div id="root">
    
    </div>
    &#13;
    &#13;
    &#13;

答案 1 :(得分:0)

您需要使用this.setState()addTrack()功能中添加曲目。

addTrack(track){
    let tracks = this.state.playlistTracks;
    let trackExists = tracks.filter(t=>{return t.id==track.id}); //Check if already exists in array
    if(trackExists.length === 0){
        this.setState({
            playlistTracks: this.state.playlistTracks.push(track)
        });
    }
}