簡體   English   中英

使用 React Hooks 卸載組件時如何訪問狀態?

[英]How to access state when component unmount with React Hooks?

使用常規的 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,可以從組件中的任何位置訪問狀態,但使用鈎子似乎只有復雜的方式,每種方式都有嚴重的缺點,或者我可能只是遺漏了一些東西。

有什么建議嗎?

useRef()來救援。

由於ref是可變的並且在組件的生命周期中存在,因此我們可以在更新時使用它來存儲當前值,並且仍然可以通過 ref 的 value .current屬性在我們的useEffect的清理函數中訪問該值。

所以會有一個額外的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/的作者。 請參考此鏈接進行深度潛水。

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>
  );
}

我想回答這個問題,以防其他人遇到這個問題。 如果您的 useEffect 卸載函數中需要多個值,請務必確保使用了正確的依賴項。 所以接受的答案很好,因為它只是一個依賴項,但開始包含更多依賴項,它變得復雜。 您需要的 useRef 數量會失控。 因此,您可以做的是一個 useRef ,它是卸載函數本身,並在卸載組件時調用它:

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'
      />
    </>
  );
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM