如何将React拖拽列表与Redux动态值集成并保持拖拽功能?
兄弟,你遇到的拖拽后视图不更新的问题,核心原因就是在render方法里直接赋值修改了this.state.items!React的state是不能直接碰的,必须通过setState()来更新——render是纯函数,里面不能有任何修改状态的副作用,直接赋值会让组件的内部状态和React的状态管理机制彻底不同步,导致你明明看到数组变了,但视图就是不跟着更,拖拽自然就失效了。
针对你的场景,有两种靠谱的处理方式,选哪种看你的需求:
方案1:监听props变化,用setState更新state
你的items是从Redux的selectedItem派生出来的,所以应该在selectedItem变化时,通过setState来更新state里的items,而不是在render里硬塞。用类组件的componentDidUpdate生命周期就能搞定:
import React, {Component} from 'react'; import {render} from 'react-dom'; import {SortableContainer, SortableElement, arrayMove} from 'react-sortable-hoc'; import {connect} from 'react-redux'; const SortableItem = SortableElement(({value}) => <li>{value}</li> ); const SortableList = SortableContainer(({items}) => { return ( <ul> {items.map((value, index) => ( // 划重点:别用index当key!用item的唯一值,避免排序/更新时React认不出元素 <SortableItem key={`item-${value}`} index={index} value={value} /> ))} </ul> ); }); class SelectedItem extends Component { state = { items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], }; // 监听props变化,当selectedItem更新时同步更新state componentDidUpdate(prevProps) { const {selectedItem} = this.props; // 对比前后props,避免不必要的更新 if (prevProps.selectedItem !== selectedItem) { const newItems = [ selectedItem.node.title, selectedItem.node.subtitle, selectedItem.treeIndex ]; this.setState({ items: newItems }); } } onSortEnd = ({oldIndex, newIndex}) => { this.setState({ items: arrayMove(this.state.items, oldIndex, newIndex), }); }; render() { // 这里老老实实用state里的items,再也不直接改state了 return <SortableList items={this.state.items} onSortEnd={this.onSortEnd} />; } } function mapStateToProps(state) { return { selectedItem: state.activeItem }; } export default connect(mapStateToProps)(SelectedItem);
方案2:直接用props派生的值,不用存state
如果你的items除了拖拽排序外,不需要在组件内部做其他修改,其实可以不用把items存在state里,直接在render里基于props计算,拖拽排序时再把新顺序传回Redux或者父组件(如果需要持久化的话)。不过考虑到你用了react-sortable-hoc的本地排序,方案1更适合,但也给你贴个参考:
// 其他导入和组件定义不变 class SelectedItem extends Component { // 只存排序后的结果,初始值从props取 state = { sortedItems: null, }; componentDidUpdate(prevProps) { const {selectedItem} = this.props; if (prevProps.selectedItem !== selectedItem) { // 当selectedItem变化时,重置排序后的列表 const newItems = [ selectedItem.node.title, selectedItem.node.subtitle, selectedItem.treeIndex ]; this.setState({ sortedItems: newItems }); } } onSortEnd = ({oldIndex, newIndex}) => { this.setState({ sortedItems: arrayMove(this.state.sortedItems, oldIndex, newIndex), }); }; render() { const {selectedItem} = this.props; // 如果还没排序,就用props派生的初始值 const items = this.state.sortedItems || [ selectedItem.node.title, selectedItem.node.subtitle, selectedItem.treeIndex ]; return <SortableList items={items} onSortEnd={this.onSortEnd} />; } }
你原来的代码里用item-${index}当key,这可是React开发的常见坑!当列表排序或者更新时,index会跟着变,React的diff算法会搞混元素,导致各种奇怪的渲染问题,包括拖拽失效。一定要用每个item的唯一标识当key,比如item的value(只要值是唯一的),或者从selectedItem里取唯一属性。
内容的提问来源于stack exchange,提问作者Jay J

