Javascript React-使用受控输入将博客文章自动保存到sessionStorage()

Javascript React-使用受控输入将博客文章自动保存到sessionStorage(),javascript,reactjs,Javascript,Reactjs,我在尝试实现自动保存功能以捕获受控输入并将其保存到sessionStorage()时遇到了一个奇怪的问题 我有一个自动保存功能,每30秒运行一次。我从输入值创建一个对象,然后将它们保存到sessionStorage()中。我运行一个检查,看看我创建的输入值对象是否与当前存储的对象匹配。如果新对象不同,我将用此新对象替换sessionStorage中的当前对象。我觉得这很直截了当 现在发生的事情是,我正在观察sessionStorage一次更新一个字符,就像我使用的受控输入在通过onChange(

我在尝试实现自动保存功能以捕获受控输入并将其保存到
sessionStorage()
时遇到了一个奇怪的问题

我有一个自动保存功能,每30秒运行一次。我从输入值创建一个对象,然后将它们保存到
sessionStorage()
中。我运行一个检查,看看我创建的输入值对象是否与当前存储的对象匹配。如果新对象不同,我将用此新对象替换
sessionStorage
中的当前对象。我觉得这很直截了当

现在发生的事情是,我正在观察sessionStorage一次更新一个字符,就像我使用的受控输入在通过
onChange()
函数设置其值时的工作方式一样。一旦用我键入的内容完全更新对象,它将重置为空白

我将在代码示例下方的
sessionStorage
中显示所述问题的示例

这是我的
AddPost
组件,其中包含“添加帖子”表单和当前的自动保存功能:

import React, { useState, useEffect } from 'react';

//Styles
import {
  AddPostContainer,
  AddPostInfoInput,
  AddPostInfoLabel,
  AddPostTextArea,
  PostOptionWrapper,
  PostOptionGroup,
  AddPostBtn
} from './styles';

//Components
import LivePreview from './LivePreview/LivePreview';
import Icon from '../../../Icons/Icon';

const AddPost = props => {
  const [htmlString, setHtmlString] = useState('');
  const [title, setTitle] = useState('');
  const [postBody, setPostBody] = useState('');
  const [author, setAuthor] = useState('');
  const [tags, setTags] = useState('');
  const [featuredImage, setFeaturedImage] = useState('');

  const autoSave = async () => {
    const autoSaveObject = {
      title,
      author,
      tags,
      featuredImage,
      postBody
    };

    try {
      await window.sessionStorage.setItem(
        'add_post_auto_save',
        JSON.stringify(autoSaveObject)
      );
    } catch (e) {
      return;
    }
  };

  setInterval(() => {
    const currentSave = window.sessionStorage.getItem('add_post_auto_save');

    const autoSaveObject = {
      title,
      author,
      tags,
      featuredImage,
      postBody
    };

    if (currentSave === JSON.stringify(autoSaveObject)) {
      return;
    } else {
      autoSave();
    }
  }, 10000);

  return (
    <AddPostContainer>
      <AddPostInfoLabel htmlFor="title">Title</AddPostInfoLabel>
      <AddPostInfoInput
        type="text"
        value={title}
        onChange={e => setTitle(e.target.value)}
        placeholder="enter post title"
        id="title"
      />
      <AddPostTextArea
        inputwidth="100%"
        height="400px"
        value={postBody}
        onChange={e => {
          setHtmlString(e.target.value);
          setPostBody(e.target.value);
        }}
      />
      <AddPostInfoLabel htmlFor="postbody">Live Preview:</AddPostInfoLabel>
      <LivePreview id="postbody" htmlstring={htmlString} />
      <PostOptionWrapper>
        <PostOptionGroup width="33%">
          <AddPostInfoLabel htmlFor="author">Author:</AddPostInfoLabel>
          <AddPostInfoInput
            type="text"
            value={author}
            onChange={e => setAuthor(e.target.value)}
            placeholder="enter author's name"
            id="author"
            inputwidth="60%"
          />
        </PostOptionGroup>
        <PostOptionGroup width="33%">
          <AddPostInfoLabel htmlFor="tags">Tags:</AddPostInfoLabel>
          <AddPostInfoInput
            type="text"
            placeholder="enter tags separated by ,"
            value={tags}
            onChange={e => setTags(e.target.value)}
            id="tags"
            inputwidth="60%"
          />
        </PostOptionGroup>
        <PostOptionGroup width="33%">
          <AddPostInfoLabel htmlFor="featuredImage">
            Feat. Image:
          </AddPostInfoLabel>
          <AddPostInfoInput
            type="text"
            placeholder="enter image url"
            value={featuredImage}
            onChange={e => setFeaturedImage(e.target.value)}
            id="featuredImage"
            inputwidth="60%"
          />
        </PostOptionGroup>
      </PostOptionWrapper>
      <AddPostBtn type="button">
        <Icon icon={['far', 'plus-square']} size="lg" pSize="1em">
          <p>Add Post</p>
        </Icon>
      </AddPostBtn>
    </AddPostContainer>
  );
};

export default AddPost;
此自动保存功能每30秒运行一次,然后将会话存储中的内容替换为输入字段的当前值。我在对象上使用
JSON.stringify()
来比较它们。(注意:
会话存储
中的obj已经字符串化。)。如果该匹配返回true,则不会保存任何内容,因为当前输入值也是保存的值。否则,它会将新对象保存到会话存储中

我的想法是,我需要使
autoSave()
异步,因为更新会话和本地存储都是异步的,不会立即发生(尽管非常接近)。那没用

以下是
sessionStorage
对象试图保存时的内容:

它的质量可能较低,但您可以看到它是如何更新“title”属性的。它的行为类似于受控输入,一个字符一个字符地添加到值中


有人能指出这是怎么回事吗?在这件事上我不知所措。提前谢谢

问得好,格式也不错。您的问题之所以会发生,是因为您每次更新组件时都会创建一个新的间隔,这是因为您使用的是受控输入。我想您可以将组件更改为类组件,并在
componentDidMount
方法上创建
setInterval
。不要忘记清理组件卸载的间隔,下面是一个示例:

import ReactDOM from "react-dom";
import React from "react";

class Todo extends React.Component {
  state = {
    text1: "",
    text2: "",
    interval: null
  };

  componentDidMount() {
    this.setState({
      interval: setInterval(() => {
        const { text1, text2 } = this.state;

        const autoSaveObject = {
          text1,
          text2
        };

        console.log(JSON.stringify(autoSaveObject));
      }, 3000)
    });
  }

  componentWillUnmount() {
    clearInterval(this.state.interval);
  }

  render() {
    return (
      <div>
        <h1>TODO LIST</h1>
        <form>
          <input
            value={this.state.text1}
            onChange={e => this.setState({ text1: e.target.value })}
          />
          <input
            value={this.state.text2}
            onChange={e => this.setState({ text2: e.target.value })}
          />
        </form>
      </div>
    );
  }
}

ReactDOM.render(<Todo />, document.getElementById("root"));
从“react-dom”导入ReactDOM;
从“React”导入React;
类Todo扩展了React.Component{
状态={
文本1:“,
文本2:“,
间隔:空
};
componentDidMount(){
这是我的国家({
间隔:设置间隔(()=>{
const{text1,text2}=this.state;
常数autoSaveObject={
文本1,
文本2
};
log(JSON.stringify(autoSaveObject));
}, 3000)
});
}
组件将卸载(){
clearInterval(this.state.interval);
}
render(){
返回(
待办事项清单
this.setState({text1:e.target.value})
/>
this.setState({text2:e.target.value})
/>
);
}
}
render(,document.getElementById(“根”));

您遇到的主要问题是
setInterval
正在每个渲染中被调用,并且创建的间隔永远不会被清除

这意味着,如果在文本输入中键入10个字符,那么每10秒将触发10次间隔

为了避免这种情况,您需要使用钩子包装setInterval调用,并返回一个注销函数,该函数将在重新呈现(或卸载)时清除间隔。请参阅文档

以下是使用
useffect
的最低更新版本:

    const autoSave = (postData) => {
        try {
            window.sessionStorage.setItem(
                'add_post_auto_save',
                JSON.stringify(postData)
            );
        } catch (e) {
        }
    };

    useEffect(() => {
        const intervalId = setInterval(() => {
            const autoSaveObject = {
                title,
                author,
                tags,
                featuredImage,
                postBody
            };

            const currentSave = window.sessionStorage.getItem('add_post_auto_save');
            if (currentSave === JSON.stringify(autoSaveObject)) {
                return;
            } else {
                autoSave(autoSaveObject);
            }
        }, 10000);

        return () => {clearInterval(intervalId)};
    });

如果不希望在每次渲染时清除并重新创建间隔,则可以有条件地控制触发效果的时间。这在useEffect文档中有介绍。 最重要的是,您需要传入
useffect
的每一个依赖项,在您的情况下,它是所有状态变量

看起来是这样的-您需要确保包含
useffect
钩子中使用的每个状态变量。如果您忘记列出任何变量,那么您将设置陈旧的数据

    useEffect(() => {

        //... your behaviour here

    }, [title, author, tags, featuredImage, postBody]);

进一步阅读:

下面是Dan Abramov的一篇博文,以setInterval为例,深入探讨了钩子:


此外,如果post数据总是“捆绑”在一起是有意义的,那么您不需要有五个或六个单独的
useState
调用

您可以将post数据作为对象存储在
useState
中,而不是单独管理它们:

    const [postData, setPostData] = useState({
        htmlString: '',
        title: '',
        author: '',
        tags: '',
        featuredImage: '',
        postBody: '',
    });

    function updateData(value, key) {
        setPostData((prevData) => {
            return {
                ...prevData,
                [key]: value
            };
        });
    }

    const autoSave = (postData) => {
        try {
            window.sessionStorage.setItem(
                'add_post_auto_save',
                JSON.stringify(postData)
            );
        } catch (e) {}
    };

    useEffect(() => {
        const intervalId = setInterval(() => {
            const currentSave = window.sessionStorage.getItem('add_post_auto_save');

            if (currentSave === JSON.stringify(postData)) {
                return;
            } else {
                autoSave(postData);
            }
        }, 10000);

        return () => {
            clearInterval(intervalId)
        };
    }, [postData]);


    // jsx:
    <AddPostInfoInput
        type="text"
        value={postData.title}
        onChange={e => updateData(e.target.value, 'title')}
        placeholder="enter post title"
        id="title"
        />
const[postData,setPostData]=useState({
htmlString:“”,
标题:“”,
作者:'',
标签:“”,
特征图像:“”,
后正文:“”,
});
函数updateData(值、键){
setPostData((prevData)=>{
返回{
…以前的数据,
[键]:值
};
});
}
常量自动保存=(postData)=>{
试一试{
window.sessionStorage.setItem(
“添加后自动保存”,
JSON.stringify(postData)
);
}捕获(e){}
};
useffect(()=>{
const intervalId=setInterval(()=>{
const currentSave=window.sessionStorage.getItem('add\u post\u auto\u save');
if(currentSave==JSON.stringify(postData)){
返回;
}否则{
自动保存(postData);
}
}, 10000);
    const [postData, setPostData] = useState({
        htmlString: '',
        title: '',
        author: '',
        tags: '',
        featuredImage: '',
        postBody: '',
    });

    function updateData(value, key) {
        setPostData((prevData) => {
            return {
                ...prevData,
                [key]: value
            };
        });
    }

    const autoSave = (postData) => {
        try {
            window.sessionStorage.setItem(
                'add_post_auto_save',
                JSON.stringify(postData)
            );
        } catch (e) {}
    };

    useEffect(() => {
        const intervalId = setInterval(() => {
            const currentSave = window.sessionStorage.getItem('add_post_auto_save');

            if (currentSave === JSON.stringify(postData)) {
                return;
            } else {
                autoSave(postData);
            }
        }, 10000);

        return () => {
            clearInterval(intervalId)
        };
    }, [postData]);


    // jsx:
    <AddPostInfoInput
        type="text"
        value={postData.title}
        onChange={e => updateData(e.target.value, 'title')}
        placeholder="enter post title"
        id="title"
        />
const [state,setState]=useState({
     htmlString:"",
     title:"",
     postBody:"",
     author:"",
     tags:"",
     featuredImage:""
})


          useEffect(async () => {
               const autoSaveObject = { ...state };

               try {
                    await window.sessionStorage.setItem(
                         'add_post_auto_save',
                         JSON.stringify(autoSaveObject));
               } catch (e) {
                    return console.log(e)
               }

          }, [state])