reactjs 使用React Hooks卸载组件时如何访问状态?

vof42yt1  于 2023-03-01  发布在  React
关注(0)|答案(4)|浏览(285)

使用常规React,可能会出现以下情况:

class NoteEditor extends React.PureComponent {

    constructor() {
        super();

        this.state = {
            noteId: 123,
        };
    }

    componentWillUnmount() {
        logger('This note has been closed: ' + this.state.noteId);
    }

    // ... more code to load and save note

}

在React Hooks中,我们可以这样写:

function NoteEditor {
    const [noteId, setNoteId] = useState(123);

    useEffect(() => {
        return () => {
            logger('This note has been closed: ' + noteId); // bug!!
        }
    }, [])

    return '...';
}

useEffect返回的内容只会在组件卸载之前执行一次,但是状态(如上面的代码所示)将是陈旧的。
一个解决方案是将noteId作为依赖项传递,但这样效果会在每次渲染时运行,而不是只运行一次;或者使用引用,但这很难维护。
那么,有没有推荐的模式来使用React Hook实现这一点呢?
使用常规React,可以从组件中的任何地方访问状态,但使用钩子似乎只有一些复杂的方法,每种方法都有严重的缺点,或者我只是遗漏了一些东西。
有什么建议吗?

nkoocmlb

nkoocmlb1#

**使用Ref()**进行救援。

由于ref是可变的,并且在组件的生命周期内存在,因此我们可以使用它来存储每次更新的当前值,并且仍然可以通过ref的value**.current属性在useEffect的cleanup函数中访问该值。
因此,将有一个额外的
useEffect()**来保持ref的值在状态改变时更新。
样本片段

const [value, setValue] = useState();
const valueRef = useRef();

useEffect(() => {
  valueRef.current = value;
}, [value]);

useEffect(() => {
  return function cleanup() {
    console.log(valueRef.current);
  };
}, []);

感谢https://www.timveletta.com/blog/2020-07-14-accessing-react-state-in-your-component-cleanup-with-hooks/的作者。请参考此链接进行深入研究。

iibxawm4

iibxawm42#

useState()useReducer()的一种特殊形式,因此您可以用一个完整的reducer来替代它,以获得当前状态并解决闭包问题。

注解编辑器

import React, { useEffect, useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "set":
      return action.payload;
    case "unMount":
      console.log("This note has been closed: " + state); // This note has been closed: 201
      break;
    default:
      throw new Error();
  }
}

function NoteEditor({ initialNoteId }) {
  const [noteId, dispatch] = useReducer(reducer, initialNoteId);

  useEffect(function logBeforeUnMount() {
    return () => dispatch({ type: "unMount" });
  }, []);

  useEffect(function changeIdSideEffect() {
    setTimeout(() => {
      dispatch({ type: "set", payload: noteId + 1 });
    }, 1000);
  }, []);

  return <div>{noteId}</div>;
}
export default NoteEditor;

应用程序

import React, { useState, useEffect } from "react";
import "./styles.css";
import NoteEditor from "./note-editor";

export default function App() {
  const [notes, setNotes] = useState([100, 200, 300]);

  useEffect(function removeNote() {
    setTimeout(() => {
      setNotes([100, 300]);
    }, 2000);
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      {notes.map(note => (
        <NoteEditor key={`Note${note}`} initialNoteId={note} />
      ))}
    </div>
  );
}
67up9zun

67up9zun3#

我想给出一个答案,以防其他人遇到这个问题。如果你需要在useEffect卸载函数中使用多个值,那么确保使用正确的依赖关系是很重要的。所以,接受的答案很好,因为它只是一个依赖关系,但是开始包括更多的依赖关系,它就变得复杂了。你需要的useRef的数量超出了控制。所以,你可以做的是一个useRef,它是unmount函数本身,当你卸载组件时调用它:

import React, { useRef, useState, useContext, useCallback, useEffect } from 'react';
import { Heading, Input } from '../components';
import { AppContext } from '../../AppContext';

export const TitleSection: React.FC = ({ thing }) => {
  const { updateThing } = useContext(AppContext);
  const [name, setName] = useState(thing.name);
  const timer = useRef(null);
  const onUnmount = useRef();

  const handleChangeName = useCallback((event) => {
    setName(event.target.value);

    timer.current !== null && clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      updateThing({
        name: name || ''
      });
      timer.current = null;
    }, 1000);
  }, [name, updateThing]);

  useEffect(() => {
    onUnmount.current = () => {
      if (thing?.name !== name) {
        timer.current !== null && clearTimeout(timer.current);
        updateThing({
          name: name || '',
        });
        timer.current = null;
      }
    };
  }, [thing?.name, name, updateThing]);

  useEffect(() => {
    return () => {
      onUnmount.current?.();
    };
  }, []);

  return (
    <>
      <Heading as="h1" fontSize="md" style={{ marginBottom: 5 }}>
        Name
      </Heading>
      <Input
        placeholder='Grab eggs from the store...'
        value={name}
        onChange={handleChangeName}
        variant='white'
      />
    </>
  );
};
0yycz8jy

0yycz8jy4#

对于ref中有多个项的情况,你可以把ref变成一个对象,然后给它添加你想要的键值:

const [value1, setValue1] = useState();
const [value2, setValue2] = useState();
const valueRef = useRef();

useEffect(() => {
  valueRef.current = { value1, value2 };
}, [value1, value2]);

useEffect(() => {
  return function cleanup() {
    console.log(`retained both ${valueRef.current.value1} and ${valueRef.current.value2}`);
  };
}, []);

相关问题