我希望用户能够对待办事项列表进行排序。当用户从下拉菜单中选择一项时,它将设置sortKey
,这将创建setSortedTodos
的新版本,并依次触发useEffect
并调用setSortedTodos
。 / p>
下面的示例完全按照我想要的方式工作,但是eslint提示我将todos
添加到useEffect
依赖数组中,如果执行此操作,则会导致无限循环(如您期望的那样)。
const [todos, setTodos] = useState([]);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
实时示例:
const {useState, useCallback, useEffect} = React;
const exampleToDos = [
{title: "This", priority: "1 - high", text: "Do this"},
{title: "That", priority: "1 - high", text: "Do that"},
{title: "The Other", priority: "2 - medium", text: "Do the other"},
];
function Example() {
const [todos, setTodos] = useState(exampleToDos);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
const sortByChange = useCallback(e => {
setSortKey(e.target.value);
});
return (
<div>
Sort by:
<select onChange={sortByChange}>
<option selected={sortKey === "title"} value="title">Title</option>
<option selected={sortKey === "priority"} value="priority">Priority</option>
</select>
{todos.map(({text, title, priority}) => (
<div className="todo">
<h4>{title} <span className="priority">{priority}</span></h4>
<div>{text}</div>
</div>
))}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
body {
font-family: sans-serif;
}
.todo {
border: 1px solid #eee;
padding: 2px;
margin: 4px;
}
.todo h4 {
margin: 2px;
}
.priority {
float: right;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
我认为必须有一种更好的方法来使eslint感到高兴。
答案 0 :(得分:7)
我认为这并不理想。该函数确实取决于todos
。如果setTodos
在其他地方被调用,则必须重新计算回调函数,否则它将对陈旧数据进行操作。
为什么您仍要以状态存储已排序的数组?当键或数组更改时,可以使用useMemo
对值进行排序:
const sortedTodos = useMemo(() => {
return Array.from(todos).sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
}, [sortKey, todos]);
然后在各处引用sortedTodos
。
实时示例:
const {useState, useCallback, useMemo} = React;
const exampleToDos = [
{title: "This", priority: "1 - high", text: "Do this"},
{title: "That", priority: "1 - high", text: "Do that"},
{title: "The Other", priority: "2 - medium", text: "Do the other"},
];
function Example() {
const [sortKey, setSortKey] = useState('title');
const [todos, setTodos] = useState(exampleToDos);
const sortedTodos = useMemo(() => {
return Array.from(todos).sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
}, [sortKey, todos]);
const sortByChange = useCallback(e => {
setSortKey(e.target.value);
}, []);
return (
<div>
Sort by:
<select onChange={sortByChange}>
<option selected={sortKey === "title"} value="title">Title</option>
<option selected={sortKey === "priority"} value="priority">Priority</option>
</select>
{sortedTodos.map(({text, title, priority}) => (
<div className="todo">
<h4>{title} <span className="priority">{priority}</span></h4>
<div>{text}</div>
</div>
))}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
body {
font-family: sans-serif;
}
.todo {
border: 1px solid #eee;
padding: 2px;
margin: 4px;
}
.todo h4 {
margin: 2px;
}
.priority {
float: right;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
由于您始终可以从“基本”数组和排序键中派生/计算排序后的数组,因此无需将排序后的值存储在状态中。我认为这也使您的代码更容易理解,因为它不那么复杂。
答案 1 :(得分:3)
发生无限循环的原因是,待办事项与先前的参考不匹配,效果将重新运行。
为什么仍要为点击动作使用效果? 您可以通过以下功能运行它:
const [todos, setTodos] = useState([]);
function sortTodos(e) {
const sortKey = e.target.value;
const clonedTodos = [...todos];
const sorted = clonedTodos.sort((a, b) => {
return a[sortKey.toLowerCase()].localeCompare(b[sortKey.toLowerCase()]);
});
setTodos(sorted);
}
,然后在您的下拉菜单上执行onChange
。
<select onChange="sortTodos"> ......
顺便说一下依赖项,ESLint是正确的! 在上述情况下,您的待办事项是依赖项,应该在列表中。 选择项目的方法是错误的,因此是您的问题。
答案 2 :(得分:0)
您需要在这里使用setState
的功能形式:
const [todos, setTodos] = useState(exampleToDos);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
setTodos(currTodos => {
return currTodos.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
})
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos, todos]);
即使您为了不改变原始状态而复制状态,由于将状态设置为异步,因此仍不能保证将获得其最新值。另外,大多数方法都将返回浅表副本,因此无论如何您最终都可能会改变原始状态。
使用功能性setState
可确保您获得状态的最新值,并且不会更改原始状态值。