【问题标题】:React/Recoil: once state is set in the main app, cannot be set again inside a component?React/Recoil:一旦在主应用程序中设置了状态,就不能在组件内再次设置?
【发布时间】:2020-12-04 00:12:08
【问题描述】:

从 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;

Point 组件中调用setFocusState 函数似乎不起作用:

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) 添加到函数handleKeyEvent 内的switch 语句的相关部分时,它表明每次从Point 组件中调用setFocusState 时,没有任何变化,focusState 的值仍然是与初始设置相同的值。我猜这就是为什么 setCursor 没有被 useEffect 调用的原因。

谁能告诉我这里出了什么问题?需要改变什么

  1. setFocusStatePoint 组件内调用时实际修改focusState 的值
  2. 通过setCursor 实际修改光标位置

【问题讨论】:

    标签: javascript reactjs typescript recoiljs


    【解决方案1】:

    首先:如果您在设置后立即记录focusState,您甚至不会记录新值,因为:

    1. Point 组件使用 focusState' 旧值呈现(我们称其为 #1 值)

    2. 您添加了一个调用setCursor 的效果,该效果具有空数组依赖项,它被调用但不会再次被调用

    3. 您将focusState(设置为#2 值)设置到处理程序中。根据上下文,您记录它希望它包含新值,但是...

    4. 由于您设置了focusState,组件重新渲染以使用新的focusState 值(#2)进行渲染

    5. 您的效果不会调用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 更新问题以明确解决您的问题。

    【讨论】:

      猜你喜欢
      • 2021-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-26
      • 2019-02-17
      • 1970-01-01
      • 2018-12-25
      相关资源
      最近更新 更多