使用React Hooks进行反跳功能

你好!我叫Igor Shamaev,我是SmartData团队的首席开发工程师。我从事内部分析BI系统的全栈开发。在我们公司中,React被接受为构建用户界面的主要标准。像大多数React社区一样,我们在日常工作中广泛使用钩子。



持续学习是任何优秀开发人员工作不可或缺的一部分。因此,今天我想对这个过程做出不起的贡献,并为那些开始积极学习React和使用钩子的人提供一个小指南。在此过程中,为您提供一个小巧而有用的工具,以使用新的React标准。



在文章使用React Hooks进行反跳翻译中我们了解了如何在没有第三方库的情况下仅使用React的功能来创建多行代码中的钩子,以处理变量值的延迟更改。现在,我建议考虑另一个有用的钩子,该钩子将帮助我们推迟函数调用。如果该函数连续被调用多次,那么实际的调用只会在我们设置的延迟间隔之后发生。也就是说,仅适用于系列中的最后一次通话。该解决方案也非常紧凑并且易于在React中实现。如果您有兴趣,请在目录下。





React . . , React , .



...
import { useRef, useEffect } from "react";

export default function useDebouncedFunction(func, delay, cleanUp = false) {
  const timeoutRef = useRef();

  function clearTimer() {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = undefined;
    }
  }

  useEffect(() => (cleanUp ? clearTimer : undefined), [cleanUp]);

  return (...args) => {
    clearTimer();
    timeoutRef.current = setTimeout(() => func(...args), delay);
  };
}


, - , . , . Material-UI.



import React from "react";
import { makeStyles, Typography, Slider } from "@material-ui/core";
import useDebouncedFunction from "./useDebouncedFunction";
import apiRequest from "./apiRequest";

const useStyles = makeStyles({
  root: {
    width: 300
  }
});

function valuetext(value) {
  return `${value}°C`;
}

export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}




, - , . , . , - , . , , console.log():



export default function valueLogging(value) {
  console.log(`Request processed. Value: ${value}`);
}


handleChange() valueLogging() , :



  const handleChange = (event, newValue) => {
    setValue(newValue);
    valueLogging(newValue);
  };




… , . valueLogging() . . . ?



1. useDebounce value .



useDebounce debouncedValue, . useEffect valueLogging(). - :



export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);
  const [changedByUser, setChangedByUser] = React.useState(false);

  const debouncedValue = useDebounce(value, 300);

  useEffect(() => {
    if (changedByUser) {
      valueLogging(debouncedValue);
    }
  }, [debouncedValue]);

  const handleChange = (event, newValue) => {
    setValue(newValue);
    if (!changedByUser) {
        setChangedByUser(true);
    }
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}


, , ? , useEffect, , valueLogging() . , . , useEffect , valueLogging(). , React useEffect changedByUser. , .



?



2. valueLogging() handleChange().



, :



export default function RangeSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);

  const debouncedValueLogging = useDebouncedFunction(valueLogging, 300);

  const handleChange = (event, newValue) => {
    setValue(newValue);
    debouncedValueLogging(newValue);
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
      />
    </div>
  );
}


useDebouncedFunction, .



React:



import { useRef } from "react";

export default function useDebouncedFunction(func, delay) {
  const ref = useRef(null);

  return (...args) => {
    clearTimeout(ref.current);
    ref.current = setTimeout(() => func(...args), delay);
  };
}


, . , . -, useRef ( useRef). : , React. -, . current .



, useRef, , DOM-. , . .

setTimeout() . timeoutId, setTimeout(), ref.current. , useDebouncedFunction. timeoutId clearTimeout(). , . , valueLogging() 300 . .



, ... useRef? ?

let timeoutId; :



export default function useDebouncedFunction(func, delay) {
  let timeoutId;

  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
}


React. React . , , :





- , debouncedValueLogging() timeoutId. .



, :





! . , .



, . , - . . , , ?



, - . , , .



.



-, , value . , , RangeSlider .



import React, { useState } from "react";
import { ThemeProvider, createMuiTheme, Typography } from "@material-ui/core";
import RangeSlider from "./RangeSlider";

const theme = createMuiTheme({});

export default function App() {
  const [sliderShown, setSliderShown] = useState(true);

  //      
  function handleValueChange(value) {
    if (value[1] === 100) {
      setSliderShown(false);
    }
  }

  return (
    <ThemeProvider theme={theme}>
      {sliderShown ? (
        <RangeSlider onValueChange={handleValueChange} />
      ) : (
        <Typography variant="h2">Too hot!</Typography>
      )}
    </ThemeProvider>
  );
}


-, RangeSlider , , , . , - , , , . .



import React from "react";
import { makeStyles, Typography, Slider } from "@material-ui/core";
import useDebouncedFunction from "./useDebouncedFunction";
import valueLogging from "./valueLogging";
import checkIfOptimal from "./checkIfOptimal";

const useStyles = makeStyles({
  root: {
    width: 300
  }
});

function valuetext(value) {
  return `${value}°C`;
}

export default function RangeSlider(props) {
  const classes = useStyles();
  const [value, setValue] = React.useState([20, 37]);
  const [isOptimal, setIsOptimal] = React.useState(true);

  //  
  const debouncedValueLogging = useDebouncedFunction(
    newValue => valueLogging(newValue),
    300
  );

  //   
  const debouncedValueCheck = useDebouncedFunction(
    newValue => checkIfOptimal(newValue, setIsOptimal),
    300
  );

  const handleChange = async (event, newValue) => {
    setValue(newValue);
    debouncedValueLogging(newValue);
    debouncedValueCheck(newValue);
    if (props.onValueChange) {
      props.onValueChange(newValue);
    }
  };

  return (
    <div className={classes.root}>
      <Typography id="range-slider" gutterBottom>
        Temperature range
      </Typography>
      <Slider
        value={value}
        onChange={handleChange}
        valueLabelDisplay="auto"
        aria-labelledby="range-slider"
        getAriaValueText={valuetext}
        style={{ color: isOptimal ? "#4caf50" : "#f44336" }}
      />
    </div>
  );
}


-, checkIfOptimal():



//   
export default function checkIfOptimal(newValue, setIsOptimal) {
  return setIsOptimal(10 < newValue[0] && newValue[1] < 80);
}


, :





, , checkIfOptimal().





, React :



Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

in RangeSlider (at App.js:20)

? , true/false . setIsOptimal(). , 300 . . React. . ?



useDebouncedFunction: cleanUp. true, .



import { useRef, useEffect } from "react";

export default function useDebouncedFunction(func, delay, cleanUp = false) {
  const timeoutRef = useRef();

  //  
  function clearTimer() {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = undefined;
    }
  }

  //     ,  cleanUp   true
  //       
  useEffect(() => (cleanUp ? clearTimer : undefined), [cleanUp]);

  return (...args) => {
    clearTimer();
    timeoutRef.current = setTimeout(() => func(...args), delay);
  };
}


useEffect , . clearTimer() .

.



  //   
  const debouncedValueCheck = useDebouncedFunction(
    newValue => checkIfOptimal(newValue, setIsOptimal),
    300,
    true
  );


.





, . , .



?



, . checkIfOptimal() — , . checkIfOptimal() , , , .



? useDebouncedFunction , , , .



, .



. , .



, , codesandbox. :



useDebouncedFunction codesandbox




All Articles