React Hooks onchange事件对于扫描仪输入而言太慢

时间:2019-08-10 00:51:40

标签: javascript reactjs react-hooks

在扫描条形码时,以下纯功能捕获扫描器输入时出现问题。我有一个keydown绑定发生在useeffect中,因为我需要注意陷阱代码。当用户输入值时,它会正确记录日志,但是扫描仪似乎正在跳过一个或两个字符。

import React, { useState, useEffect, useRef, useCallback } from 'react';
import * as propTypes from 'prop-types';
import styled from 'styled-components';

const ScannerInputWrapper = styled.input`
  visibility: hidden;
  display: none;
  width: 0%;
`;

// the current app context would be passed in as a prop to this function
function ScannerInputField({
  handleScannerInput,
  isScannerInputDisabled,
  isDisabled,
}) {
  const [dataEntered, setDataEntered] = useState('');
  const [nullarised, setNull] = useState('');
  const [scanStarted, setScanStarted] = useState(false);
  const inputRef = useRef(null);

  useEffect(() => {
    document.addEventListener('keydown', handleKeys);

    return () => {
      document.removeEventListener('keydown', handleKeys);
    };
  }, [dataEntered]);

  // focus on the text box at all points. Context awareness will need to come into this.
  useEffect(() => {
    inputRef.current.focus();
    inputRef.current.select();
  }, []);

  const handleKeys = useCallback(e => {
    e.preventDefault();

    if ((e.shiftKey || e.ctrlKey || e.altKey) && e.key.length > 1) {
      return;
    }

    if (e.key === '<') {
      setScanStarted(true);
      return;
    }

    if (e.key === '`') {
      // scan finished, lets send local data to higher function
      // handleScannerInput(dataEntered);
      console.log(dataEntered);
      setScanStarted(false);
      setDataEntered('');
      return;
    }

    if (e.key === 'Enter' && !scanStarted && dataEntered !== '') {
      // scan finished, lets send local data to higher function
      // handleScannerInput(dataEntered);
      console.log(dataEntered);
      setDataEntered('');
      return;
    }

    if (e.key.length === 1) {
      const code = e.keyCode ? e.keyCode : e.which;
      // having to do the below due to running an effect and reading from use state
      // causes you to read data incorrectly at high velocity
      const val = dataEntered.concat(String.fromCharCode(code));
      setDataEntered(val);
    }
  });

  return (
    <ScannerInputWrapper
      type="text"
      onChange={value => setNull(value)}
      value={dataEntered}
      disabled={isDisabled || isScannerInputDisabled}
      ref={inputRef}
      tabIndex={-1}
    />
  );
}

ScannerInputField.propTypes = {
  handleScannerInput: propTypes.func.isRequired,
  isScannerInputDisabled: propTypes.bool.isRequired,
  isDisabled: propTypes.bool.isRequired,
};

ScannerInputWrapper.whyDidYouRender = true;

export default ScannerInputField;

我知道不是每个人都会有扫描仪,但是如果有人看到我正在做的任何愚蠢的事情,请在此提供指导。

output of test

enter image description here

使用React v16.8.6

1 个答案:

答案 0 :(得分:0)

好的,useState太异步了。解决的办法是在函数的状态对象内改用reducer。工作了请客!

https://www.reddit.com/r/reactjs/comments/a3y76f/react_hooks_setstate_gotcha/

这是固定代码

/**
 *
 * ScannerInputField
 *
 */

import React, { useState, useEffect, useRef, useReducer } from 'react';
import * as propTypes from 'prop-types';
import styled from 'styled-components';

const ScannerInputWrapper = styled.input`
  visibility: hidden;
  display: none;
  width: 0%;
`;
const initialState = {
  barcodeInProgress: false,
  barcodeComplete: false,
  value: '',
};

const keyDownReducer = (state = initialState, e) => {
  if ((e.shiftKey || e.ctrlKey || e.altKey) && e.key.length > 1) {
    return state;
  }

  if (e.key === '<') {
    return { ...state, barcodeInProgress: true };
  }

  if (e.key === '`') {
    return { ...state, barcodeInProgress: false, barcodeComplete: true };
  }

  if (e.key === '_') {
    return {
      ...state,
      barcodeInProgress: false,
      barcodeComplete: false,
      value: '',
    };
  }

  if (e.key.length === 1) {
    return { ...state, value: state.value + e.key };
  }

  return state;
};

// the current app context would be passed in as a prop to this function
function ScannerInputField({
  handleScannerInput,
  isScannerInputDisabled,
  isDisabled,
}) {
  const inputRef = useRef(null);
  const [state, dispatch] = useReducer(keyDownReducer, initialState);
  const [nullarised, setNull] = useState('');

  useEffect(() => {
    if (state.barcodeComplete) {
      handleScannerInput(state.value);
      // startFromFresh
      dispatch(new KeyboardEvent('keypress', { key: '_' }));
    }
  }, [state.barcodeComplete]);

  useEffect(() => {
    document.addEventListener('keydown', handleKeysViaReducer);

    return () => document.removeEventListener('keydown', handleKeysViaReducer);
  }, [state]);

  // focus on the text box at all points. Context awareness will need to come into this.
  useEffect(() => {
    inputRef.current.focus();
    inputRef.current.select();
  }, []);

  const handleKeysViaReducer = e => {
    e.preventDefault();
    dispatch(e);
  };

  return (
    <ScannerInputWrapper
      type="text"
      onChange={value => setNull(value)}
      value={state.value}
      disabled={isDisabled || isScannerInputDisabled}
      ref={inputRef}
      tabIndex={-1}
    />
  );
}

ScannerInputField.propTypes = {
  handleScannerInput: propTypes.func.isRequired,
  isScannerInputDisabled: propTypes.bool.isRequired,
  isDisabled: propTypes.bool.isRequired,
};

ScannerInputWrapper.whyDidYouRender = false;

export default ScannerInputField;