0

我已经为此奋斗了太久,有人可以指出我正确的方向吗?问题:当我创建一个列表时,我可以更新/删除它。我还可以向其中添加项目并更新/删除这些项目。当我添加另一个列表时,前者的项目会转移到后者,然后我无法编辑这些项目。如果我删除列表并且不刷新浏览器,则项目仍在列表中。我需要一种方法将两者联系在一起,以使列表只知道其项目在此先感谢您的帮助。

/actions/lists.js

export const CREATE_LIST = 'CREATE_LIST'
export function createList(list) {
  return {
    type: CREATE_LIST,
    id: uuid.v4(),
    items: list.items || [],
    ...list
  }
}

export const CONNECT_TO_LIST = 'CONNECT_TO_LIST'
export function connectToList(listId, itemId) {
  return {
    type: CONNECT_TO_LIST,
    listId,
    itemId
  }
}

export const DISCONNECT_FROM_LIST = 'DISCONNECT_FROM_LIST'
export function disconnectFromList(listId, itemId) {
  return {
    type: DISCONNECT_FROM_LIST,
    listId,
    itemId
  }
}

/actions/items.js

export const CREATE_ITEM = 'CREATE_ITEM'
export function createItem(item) {
  return {
    type: CREATE_ITEM,
    item: {
      id: uuid.v4(),
      ...item
    }
  }
}

export const UPDATE_ITEM = 'UPDATE_ITEM'
export function updateItem(updatedItem) {
  return {
    type: UPDATE_ITEM,
    ...updatedItem
  }
}

/reducers/lists.js

import * as types from '../actions/lists'

const initialState = []

export default function lists(state = initialState, action) {
  switch (action.type) {
    case types.CREATE_LIST:
      return [
        ...state,
        {
          id: action.id,
          title: action.title,
          items: action.items || []
        }
      ]

    case types.UPDATE_LIST:
      return state.map((list) => {
        if(list.id === action.id) {
          return Object.assign({}, list, action)
        }

        return list
      })

    case types.CONNECT_TO_LIST:
      const listId = action.listId
      const itemId = action.itemId

      return state.map((list) => {
        const index = list.items.indexOf(itemId)

        if(index >= 0) {
          return Object.assign({}, list, {
            items: list.items.length > 1 ? list.items.slice(0, index).concat(
              list.items.slice(index + 1)): []
          })
        }
        if(list.id === listId) {
          return Object.assign({}, list, {
            items: [...list.items, itemId]
          })
        }

        return list
      })

    case types.DISCONNECT_FROM_LIST:
      return state.map((list) => {
        if(list.id === action.listId) {
          return Object.assign({}, list, {
            items: list.items.filter((id) => id !== action.itemId)
          })
        }

        return list
      })

    default:
      return state
  }
}

/reducers/items.js

import * as types from '../actions/items'

const initialState = []

export default function items(state = initialState, action) {
  switch (action.type) {
    case types.CREATE_ITEM:
      return [ ...state, action.item ]

    case types.UPDATE_ITEM:
      return state.map((item) => {
        if(item.id === action.id) {
          return Object.assign({}, item, action)
        }

        return item
      })

    case types.DELETE_ITEM:
      return state.filter((item) => item.id !== action.id )

    default:
      return state
  }
}

/components/List.jsx

import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Items from './Items'
import Editor from './Editor'
import * as listActionCreators from '../actions/lists'
import * as itemActionCreators from '../actions/items'

export default class List extends React.Component {
  render() {
    const { list, updateList, ...props } = this.props
    const listId = list.id

    return (
      <div {...props}>
        <div className="list-header"
          onClick={() => props.listActions.updateList({id: listId, isEditing: true})}
        >
          <div className="list-add-item">
            <button onClick={this.addItem.bind(this, listId)}>+</button>
          </div>
          <Editor 
            className="list-title"
            isEditing={list.isEditing}
            value={list.title}
            onEdit={title => props.listActions.updateList({id: listId, title, isEditing: false})} 
          />
          <div className="list-delete">
            <button onClick={this.deleteList.bind(this, listId)}>x</button>
          </div>
        </div>
        <Items
          items={this.listItems}
          onValueClick={id => props.itemActions.updateItem({id, isEditing: true})}
          onEdit={(id, text) => props.itemActions.updateItem({id, text, isEditing: false})}
          onDelete={itemId => this.deleteItem(listId, itemId)}
        />
      </div>
    )
  }

  listItems() {
    props.list.items.map(id => state.items[
      state.items.findIndex(item => item.id === id)
    ]).filter(item => item)
  }

  deleteList(listId, e) {
    e.stopPropagation()

    this.props.listActions.deleteList(listId)
  } 

  addItem(listId, event) {
    event.stopPropagation()

    const item = this.props.itemActions.createItem({
      text: 'New Shopping Item'
    })

    this.props.listActions.connectToList(listId, item.id)
  }

  deleteItem(listId, itemId) {
    this.props.listActions.disconnectFromList(listId, itemId)
    this.props.itemActions.deleteItem(itemId)
  }
}

function mapStateToProps(state) {
  return {
    lists: state.lists,
    items: state.items
  }
}

function mapDispatchToProps(dispatch) {
  return {
    listActions: bindActionCreators(listActionCreators, dispatch),
    itemActions: bindActionCreators(itemActionCreators, dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(List)

/components/List.jsx

import React from 'react'
import List from './List.jsx'

export default ({lists}) => {
  return (
    <div className="lists">{lists.map((list) =>
      <List className="list" key={list.id} list={list} id={list.id} />
    )}</div>
  )
}

/components/Items.jsx

import React from 'react'
import { connect } from 'react-redux'
import Editor from './Editor'
import Item from './Item'

export default class Items extends React.Component {
  render () {
    const {items, onEdit, onDelete, onValueClick, isEditing} = this.props

    return (
      <ul className="items">{items.map(item =>
        <Item 
          className="item"
          key={item.id}
          id={item.id}
          isEditing={item.isEditing}>
          <Editor
            isEditing={item.isEditing}
            value={item.text}
            onValueClick={onValueClick.bind(null, item.id)}
            onEdit={onEdit.bind(null, item.id)}
            onDelete={onDelete.bind(null, item.id)}
          />
        </Item>
      )}</ul>
    )
  }
}

export default connect(
  state => ({
    items: state.items
  })
)(Items)

/components/Item.jsx

import React from 'react'

export default class Item extends React.Component {
  render() {
    const { id, isEditing, ...props } = this.props

    return (
      <li {...props}>{props.children}</li>
    )
  }
}

/components/App.jsx

class App extends React.Component {
  handleClick = () => {
    this.props.dispatch(createList({title: "New Shopping List"}))
  }

  render() {
    const lists = this.props.lists

    return (
      <div>
        <button 
          className="add-list"
          onClick={this.handleClick}>Add Shopping List</button>
        <Lists lists={lists}/>
      </div>
    )
  } 
}

export default connect(state => ({ lists: state.lists }))(App)
4

2 回答 2

1

假设这一切都是为了一次对单个数组进行操作,这部分看起来很可疑:

case types.CREATE_LIST:
  return [
    ...state,
    {
      id: action.id,
      title: action.title,
      items: action.items || []
    }
  ]

...state就是将现有的任何数组扩展到您要返回的新数组中,这听起来不像您真正想要的行为。我的第一个猜测是,当您创建一个新列表时,您可能只想返回其中的一个新项目,而不是整个旧列表内容加上该新项目。

您的其他一些不可变样式更新代码也看起来有点复杂。我知道“更新数组中间的东西”并不总是那么容易处理。您可能想看看这篇关于更新不可变数据的 SO 帖子,其中列出了几种处理方法。我还有一个链接存储库,它对与 Redux 相关的库进行分类,它有一个不可变数据管理库的列表,可能会让你的事情变得更容易。

于 2016-04-03T03:59:47.723 回答
0

尽管@markerikson 的提示是我遇到的问题的一部分,但它并没有完全解决它。我必须解决mapStateToProps这个问题

function mapStateToProps(state, props) {
  return {
    lists: state.lists,
    listItems: props.list.items.map(id => state.items[
        state.items.findIndex(item => item.id === id)
      ]).filter(item => item)
  }
}

并从以前的实现中删除我的项目中单独的连接Items.jsx

于 2016-04-04T20:35:56.863 回答