javascript - React/Recoil:一旦在主应用程序中设置状态,就不能在组件内再次设置?
问题描述
从 App.js 文件中的可加载项设置状态后:
import React from 'react';
import { useRecoilState, useSetRecoilState, useRecoilValueLoadable } from 'recoil';
import './App.css';
import { Point } from './components/Point';
import { FocusState } from './context/FocusState';
import { ItemListState } from './context/ItemListState';
import { RootState } from './context/RootState';
import { DataState } from './context/DataState';
function App() {
const setFocusState = useSetRecoilState(FocusState);
const setItemListState = useSetRecoilState(ItemListState);
const [rootState, setRootState] = useRecoilState(RootState);
const dataStateLoadable = useRecoilValueLoadable(DataState);
switch (dataStateLoadable.state) {
case 'hasValue':
let dataState = dataStateLoadable.contents;
let {root, focus, items} = dataState;
setFocusState(focus);
setItemListState(items);
setRootState(root);
return (
<div className="App">
<Point key={rootState} id={rootState} depth={0} />
</div>
)
case 'loading':
return (
<div className="App">
<p>Loading...</p>
</div>
)
case 'hasError':
throw dataStateLoadable.contents;
default:
return (
<div className="App">
<p>Loading...</p>
</div>
)
}
}
export default App;
setFocusState
从组件内调用函数Point
似乎不起作用:
export const Point: React.FC<{id: string, depth: number}> = ({id, depth}) => {
const pointRef = useRef<HTMLDivElement>(null);
const [focusState, setFocusState] = useRecoilState(FocusState);
const [itemState, setItemState] = useRecoilState(SingleItemStateFamily(id))
const parentPoint = useRecoilValue(SingleItemStateFamily(itemState.parent));
const grandparentPoint = useRecoilValue(SingleItemStateFamily(parentPoint.parent));
const setCursor = () => {
// mignt be null
const node = pointRef.current;
let range = document.createRange();
let sel = window.getSelection();
if (node !== null && node.childNodes.length > 0 && focusState.id === id) {
console.log(node, node.childNodes, focusState)
// select a range first
range.setStart(node.childNodes[0], focusState.cursorPosition);
range.setEnd(node.childNodes[0], focusState.cursorPosition);
// perform selection
sel?.removeAllRanges();
sel?.addRange(range);
node.focus();
}
}
const handleChange = (evt) => {
let newState = {...itemState};
newState.content = evt.currentTarget.innerHTML;
setItemState(newState);
}
const handleKeyEvent = (evt) => {
switch (evt.key) {
case "ArrowUp":
evt.preventDefault();
console.log("Shift focus to item above", parentPoint.children, itemState.id, parentPoint.children.indexOf(itemState.id));
// if it is the first child of a parent, shift focus to the parent
if (parentPoint.children.indexOf(itemState.id) === 0) {
console.log("Shift focus to parent")
setFocusState({id: parentPoint.id, cursorPosition: focusState.cursorPosition});
console.log(focusState);
}
// else, go to the next highest sibling
// the cursorPosition should be min(focusState.curpos, newPoint.content.length)
else {
console.log("Shift focus to previous sibling")
setFocusState({
id: parentPoint.children[parentPoint.children.indexOf(itemState.id)-1],
cursorPosition: focusState.cursorPosition
});
console.log(focusState);
}
break;
case "ArrowDown":
evt.preventDefault();
console.log("Shift focus to item below", parentPoint.children, itemState.id, parentPoint.children.indexOf(itemState.id));
// if it is the last child of a parent, shift focus to the parent's next sibling
if (parentPoint.children.indexOf(itemState.id) === parentPoint.children.length - 1) {
console.log("Shift focus to parent's next sibling")
setFocusState({
id: grandparentPoint.children[grandparentPoint.children.indexOf(parentPoint.id) + 1],
cursorPosition: focusState.cursorPosition
})
}
// else if it has any children, shift focus to the first child
else if (itemState.children.length > 0) {
console.log("Shift focus to first child")
setFocusState({
id: itemState.children[0],
cursorPosition: focusState.cursorPosition
})
}
// else, go to the next lowest sibling
else {
console.log("Shift focus to next sibling")
setFocusState({
id: parentPoint.children[parentPoint.children.indexOf(itemState.id)+1],
cursorPosition: focusState.cursorPosition
});
}
break;
case "Tab":
evt.preventDefault();
if (evt.shiftKey) {
console.log("Dedent item");
} else {
console.log("Indent item");
}
break;
default:
break;
}
}
const handleBlur = (evt) => {
let sel = window.getSelection();
let offset = sel?.anchorOffset as number;
setFocusState({
id: focusState.id,
cursorPosition: offset
})
}
useEffect(() => {
setCursor();
// eslint-disable-next-line
}, [])
return (
<ul className="point-item">
<li>
<ContentEditable onChange={handleChange}
html={itemState.content}
onKeyDown={handleKeyEvent}
innerRef={pointRef}
onBlur={handleBlur}
/>
</li>
{itemState.children.map((child_id: string) => (
<Point key={child_id} id={child_id} depth={depth+1}/>
))}
</ul>
)
}
当我console.log(focusState)
在 function 中添加 switch 语句的相关部分时handleKeyEvent
,它表明每次setFocusState
从 Point 组件中调用时,没有任何变化,并且值focusState
保持与初始设置相同。我猜这就是为什么setCursor
不通过useEffect调用的原因。
有人可以建议这里出了什么问题吗?需要改变什么
setFocusState
实际修改从组件focusState
内调用时的值Point
- 为此,通过实际修改光标位置
setCursor
解决方案
首先:如果您focusState
在设置后立即记录,您甚至不会记录新值,因为:
组件以' 旧值
Point
呈现focusState
(我们称其为 #1 值)您添加一个调用
setCursor
的效果,该效果具有被调用但不会再次调用的空数组依赖项您将
focusState
(设置为#2 值)设置为处理程序。在上下文中,您记录它希望它包含新值,但是......因为您将
focusState
组件重新渲染设置为使用新focusState
值进行渲染(#2)你的效果不会调用
setCursor
,因为它不依赖于focusState
我认为这是你的问题
你// eslint-disable-next-line
是为了问这个问题而添加的,还是因为你想避免setCursor
每次引用setCursor
都是新的(在每次渲染时)都调用?如果是后者,请考虑将其重构为
useEffect(() => {
// mignt be null
const node = pointRef.current;
let range = document.createRange();
let sel = window.getSelection();
if (node !== null && node.childNodes.length > 0 && focusState.id === id) {
console.log(node, node.childNodes, focusState)
// select a range first
range.setStart(node.childNodes[0], focusState.cursorPosition);
range.setEnd(node.childNodes[0], focusState.cursorPosition);
// perform selection
sel?.removeAllRanges();
sel?.addRange(range);
node.focus();
}
}, [id, focusState])
并完全删除该setCursor
功能。
请注意:此答案可能不完整,请使用可播放的 CodeSandbox 更新问题以明确解决您的问题。
推荐阅读
- c# - C# Hashing Base64 编码的字符串长度
- python - 用 1s 填充 scipy.sparse.coo_matrix,得到一个 2
- java - Java枚举反向查找
- c# - C# 将通用列表添加到字典
- java - 如何包含 java src 代码,以便我可以在 Android Studio 中使用它
- javascript - 函数使用 JavaScript 从循环中获取正确的元素 ID 时出错
- javascript - JavaScript 和 CSS 未按预期工作
- javascript - 使用 Panolens.js 不加载图像
- jenkins - 如何使用选择参数通过 Jenkins 在特定浏览器上执行测试?
- c# - 无法在 C# 中读取值 XML 属性