【问题标题】:How to debounce a controlled input?如何消除受控输入的抖动?
【发布时间】:2020-04-10 12:12:22
【问题描述】:

我目前正在努力应对反应输入和 lodash 的去抖动。 大多数时候,当我有一个表单时,我也有一个编辑选项,所以我需要一个受控组件来使用value={state["targetValue"]} 填写输入,以便我可以填写和编辑该字段。

但是,如果组件受到控制,则去抖动不起作用。 我在 CodeSandbox 上做了一个简单的例子:https://codesandbox.io/embed/icy-cloud-ydzj2?fontsize=14&hidenavigation=1&theme=dark

代码:

import React, { Component } from "react";
import ReactDOM from "react-dom";
import { debounce } from "lodash";

import "./styles.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "",
      title: "",
      editMode: false
    };
    this.debouncedEvent = React.createRef();
  }

  debounceEvent(_fn, timer = 500, options = null) {
    this.debouncedEvent.current = debounce(_fn, timer, options);
    return e => {
      e.persist();
      return this.debouncedEvent.current(e);
    };
  }

  componentWillUnmount() {
    this.debouncedEvent.current.cancel();
  }

  onChangeValue = event => {
    const { name, value } = event.target;

    this.setState(() => {
      return { [name]: value };
    });
  };

  onRequestEdit = () => {
    this.setState({ name: "Abla blabla bla", editMode: true });
  };

  onCancelEdit = () => {
    if (this.state.editMode) this.setState({ name: "", editMode: false });
  };

  onSubmit = event => {
    event.preventDefault();
    console.log("Submiting", this.state.name);
  };

  render() {
    const { name, editMode } = this.state;
    const isSubmitOrEditLabel = editMode ? `Edit` : "Submit";
    console.log("rendering", name);
    return (
      <div className="App">
        <h1> How to debounce controlled input ?</h1>
        <button type="button" onClick={this.onRequestEdit}>
          Fill with dummy data
        </button>
        <button type="button" onClick={this.onCancelEdit}>
          Cancel Edit Mode
        </button>
        <div style={{ marginTop: "25px" }}>
          <label>
            Controlled / Can be used for editing but not with debounce
          </label>
          <form onSubmit={this.onSubmit}>
            <input
              required
              type="text"
              name="name"
              value={name}
              placeholder="type something"
              // onChange={this.onChangeValue}
              onChange={this.debounceEvent(this.onChangeValue)}
            />
            <button type="submit">{isSubmitOrEditLabel}</button>
          </form>
        </div>
        <div style={{ marginTop: "25px" }}>
          <label> Uncontrolled / Can't be used for editing </label>
          <form onSubmit={this.onSubmit}>
            <input
              required
              type="text"
              name="name"
              placeholder="type something"
              onChange={this.debounceEvent(this.onChangeValue)}
            />
            <button type="submit">{isSubmitOrEditLabel}</button>
          </form>
        </div>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

【问题讨论】:

  • 你到底想消除什么?
  • 我正在尝试消除我的输入字段的抖动,以便在用户完成输入之前它们不会触发 setState 上的重新渲染。在我发布的示例中,第二个输入按预期工作,但不能在编辑模式下使用。

标签: javascript reactjs lodash


【解决方案1】:

起初看起来不可能,但有一个简单的方法可以做到。 只需在 react 组件之外创建 debounce 函数表达式即可。

这是一个伪示例,用于现代 React with Hooks:

import React, { useState, useEffect } from "react";
import { debounce } from "lodash";
...


const getSearchResults = debounce((value, dispatch) => {
  dispatch(getDataFromAPI(value));
}, 800);



const SearchData = () => {
  const [inputValue, setInputValue] = useState("");

  ...

  useEffect(() => {
    getSearchResults(inputValue, dispatch);
  }, [inputValue]);

 ...

 return (
   <>
      <input
        type="text"
        placeholder="Search..."
        value={inputValue}
        onChange={e => setInputValue(e.target.value)}
      />
      ...
   </>
 );
};

export default SearchData;

【讨论】:

    【解决方案2】:

    你可以试试这个。

    import React, { useState, useCallback, useRef, useEffect } from 'react';
    import _ from 'lodash';
    
    function usePrevious(value: any) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      });
      return ref.current;
    }
    
    const DeboucnedInput = React.memo(({ value, onChange }) => {
      const [localValue, setLocalValue] = useState('');
      const prevValue = usePrevious(value);
      const ref = useRef();
      ref.current = _.debounce(onChange, 500);
    
      useEffect(() => {
        if (!_.isNil(value) && prevValue !== value && localValue !== value) {
          setLocalValue(value);
        }
      }, [value]);
    
      const debounceChange = useCallback(
        _.debounce(nextValue => {
          onChange(nextValue);
        }, 1000),
        []
      );
    
      const handleSearch = useCallback(
        nextValue => {
          if (nextValue !== localValue) {
            setLocalValue(nextValue);
            debounceChange(nextValue);
          }
        },
        [localValue, debounceChange]
      );
    
    
      return (
        <input
          type="text"
          value={localValue}
          onChange={handleSearch}
        />
      );
    });
    

    【讨论】:

      【解决方案3】:

      所以...显然,没有解决方案。输入从状态中获取值。而 debounce 阻止状态触发。

      我使用 ReactDOM 做了一个解决方法。

      import ReactDOM from "react-dom";
      
      export const setFormDefaultValue = (obj, ref) => {
        if (ref && !ref.current) return;
      
        if (!obj || !obj instanceof Object) return;
      
        const _this = [
          ...ReactDOM.findDOMNode(ref.current).getElementsByClassName("form-control")
        ];
      
        if (_this.length > 0) {
          _this.forEach(el => {
            if (el.name in obj) el.value = obj[el.name];
            else console.error(`Object value for ${el.name} is missing...`);
          });
        }
      };
      

      然后使用:

      this.refForm = React.createRef();
      setFormDefaultValue(this.state, refForm)
      

      这样我可以用状态默认值填写我的表单并继续使用 debounce。

      【讨论】:

        【解决方案4】:

        看看这个库:https://www.npmjs.com/package/use-debounce

        这是一个如何使用它的示例:

        import React, { useState } from 'react';
        import { useDebounce } from 'use-debounce';
        
        export default function Input() {
          const [text, setText] = useState('Hello');
          const [value] = useDebounce(text, 1000);
        
          return (
            <div>
              <input
                defaultValue={'Hello'}
                onChange={(e) => {
                  setText(e.target.value);
                }}
              />
              <p>Actual value: {text}</p>
              <p>Debounce value: {value}</p>
            </div>
          );
        }
        

        【讨论】:

        • 您好,谢谢您的回复,很遗憾,我目前无法使用钩子。此外,由于我们正在编辑text 而不是value,因此每次我们在输入中写入时仍会触发重新渲染。
        • 这不是您问题的解决方案...它是 debounce 行为的一个示例...它用于比较两个流程..
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-22
        • 1970-01-01
        • 1970-01-01
        • 2019-07-09
        • 2017-04-10
        • 1970-01-01
        相关资源
        最近更新 更多