你好!我叫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 . .
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. :